package h2mux

import (
	"bytes"
	"io"
	"sync"
)

type ReadWriteLengther interface {
	io.ReadWriter
	Reset()
	Len() int
}

type ReadWriteClosedCloser interface {
	io.ReadWriteCloser
	Closed() bool
}

type MuxedStream struct {
	Headers []Header

	streamID uint32

	responseHeadersReceived chan struct{}

	readBuffer    ReadWriteClosedCloser
	receiveWindow uint32
	// current window size limit. Exponentially increase it when it's exhausted
	receiveWindowCurrentMax uint32
	// limit set in http2 spec. 2^31-1
	receiveWindowMax uint32

	// nonzero if a WINDOW_UPDATE frame for a stream needs to be sent
	windowUpdate uint32

	writeLock sync.Mutex
	// The zero value for Buffer is an empty buffer ready to use.
	writeBuffer ReadWriteLengther

	sendWindow uint32

	readyList    *ReadyList
	headersSent  bool
	writeHeaders []Header
	// true if the write end of this stream has been closed
	writeEOF bool
	// true if we have sent EOF to the peer
	sentEOF bool
	// true if the peer sent us an EOF
	receivedEOF bool

	// dictionary that was used to compress the stream
	receivedUseDict bool
	method          string
	contentType     string
	path            string
	dictionaries    h2Dictionaries
	readBufferLock  sync.RWMutex
}

func (s *MuxedStream) Read(p []byte) (n int, err error) {
	if s.dictionaries.read != nil {
		s.readBufferLock.RLock()
		b := s.readBuffer
		s.readBufferLock.RUnlock()
		return b.Read(p)
	}
	return s.readBuffer.Read(p)
}

func (s *MuxedStream) Write(p []byte) (n int, err error) {
	ok := assignDictToStream(s, p)
	if !ok {
		s.writeLock.Lock()
	}
	defer s.writeLock.Unlock()
	if s.writeEOF {
		return 0, io.EOF
	}
	n, err = s.writeBuffer.Write(p)
	if n != len(p) || err != nil {
		return n, err
	}
	s.writeNotify()
	return n, nil
}

func (s *MuxedStream) Close() error {
	// TUN-115: Close the write buffer before the read buffer.
	// In the case of shutdown, read will not get new data, but the write buffer can still receive
	// new data. Closing read before write allows application to race between a failed read and a
	// successful write, even though this close should appear to be atomic.
	// This can't happen the other way because reads may succeed after a failed write; if we read
	// past EOF the application will block until we close the buffer.
	err := s.CloseWrite()
	if err != nil {
		if s.CloseRead() == nil {
			// don't bother the caller with errors if at least one close succeeded
			return nil
		}
		return err
	}
	return s.CloseRead()
}

func (s *MuxedStream) CloseRead() error {
	return s.readBuffer.Close()
}

func (s *MuxedStream) CloseWrite() error {
	s.writeLock.Lock()
	defer s.writeLock.Unlock()
	if s.writeEOF {
		return io.EOF
	}
	s.writeEOF = true
	if c, ok := s.writeBuffer.(io.Closer); ok {
		c.Close()
	}
	s.writeNotify()
	return nil
}

func (s *MuxedStream) WriteHeaders(headers []Header) error {
	s.writeLock.Lock()
	defer s.writeLock.Unlock()
	if s.writeHeaders != nil {
		return ErrStreamHeadersSent
	}

	if s.dictionaries.write != nil {
		dictWriter := s.dictionaries.write.getDictWriter(s, headers)
		if dictWriter != nil {
			s.writeBuffer = dictWriter
		}

	}

	s.writeHeaders = headers
	s.headersSent = false
	s.writeNotify()
	return nil
}

func (s *MuxedStream) getReceiveWindow() uint32 {
	s.writeLock.Lock()
	defer s.writeLock.Unlock()
	return s.receiveWindow
}

func (s *MuxedStream) getSendWindow() uint32 {
	s.writeLock.Lock()
	defer s.writeLock.Unlock()
	return s.sendWindow
}

// writeNotify must happen while holding writeLock.
func (s *MuxedStream) writeNotify() {
	s.readyList.Signal(s.streamID)
}

// Call by muxreader when it gets a WindowUpdateFrame. This is an update of the peer's
// receive window (how much data we can send).
func (s *MuxedStream) replenishSendWindow(bytes uint32) {
	s.writeLock.Lock()
	s.sendWindow += bytes
	s.writeNotify()
	s.writeLock.Unlock()
}

// Call by muxreader when it receives a data frame
func (s *MuxedStream) consumeReceiveWindow(bytes uint32) bool {
	s.writeLock.Lock()
	defer s.writeLock.Unlock()
	// received data size is greater than receive window/buffer
	if s.receiveWindow < bytes {
		return false
	}
	s.receiveWindow -= bytes
	if s.receiveWindow < s.receiveWindowCurrentMax/2 {
		// exhausting client send window (how much data client can send)
		if s.receiveWindowCurrentMax < s.receiveWindowMax {
			s.receiveWindowCurrentMax <<= 1
		}
		s.windowUpdate += s.receiveWindowCurrentMax - s.receiveWindow
		s.writeNotify()
	}
	return true
}

// receiveEOF should be called when the peer indicates no more data will be sent.
// Returns true if the socket is now closed (i.e. the write side is already closed).
func (s *MuxedStream) receiveEOF() (closed bool) {
	s.writeLock.Lock()
	defer s.writeLock.Unlock()
	s.receivedEOF = true
	s.CloseRead()
	return s.writeEOF && s.writeBuffer.Len() == 0
}

func (s *MuxedStream) gotReceiveEOF() bool {
	s.writeLock.Lock()
	defer s.writeLock.Unlock()
	return s.receivedEOF
}

// MuxedStreamReader implements io.ReadCloser for the read end of the stream.
// This is useful for passing to functions that close the object after it is done reading,
// but you still want to be able to write data afterwards (e.g. http.Client).
type MuxedStreamReader struct {
	*MuxedStream
}

func (s MuxedStreamReader) Read(p []byte) (n int, err error) {
	return s.MuxedStream.Read(p)
}

func (s MuxedStreamReader) Close() error {
	return s.MuxedStream.CloseRead()
}

// streamChunk represents a chunk of data to be written.
type streamChunk struct {
	streamID uint32
	// true if a HEADERS frame should be sent
	sendHeaders bool
	headers     []Header
	// nonzero if a WINDOW_UPDATE frame should be sent
	windowUpdate uint32
	// true if data frames should be sent
	sendData bool
	eof      bool
	buffer   bytes.Buffer
}

// getChunk atomically extracts a chunk of data to be written by MuxWriter.
// The data returned will not exceed the send window for this stream.
func (s *MuxedStream) getChunk() *streamChunk {
	s.writeLock.Lock()
	defer s.writeLock.Unlock()

	chunk := &streamChunk{
		streamID:     s.streamID,
		sendHeaders:  !s.headersSent,
		headers:      s.writeHeaders,
		windowUpdate: s.windowUpdate,
		sendData:     !s.sentEOF,
		eof:          s.writeEOF && uint32(s.writeBuffer.Len()) <= s.sendWindow,
	}

	// Copies at most s.sendWindow bytes
	writeLen, _ := io.CopyN(&chunk.buffer, s.writeBuffer, int64(s.sendWindow))
	s.sendWindow -= uint32(writeLen)
	s.receiveWindow += s.windowUpdate
	s.windowUpdate = 0
	s.headersSent = true

	// if this chunk contains the end of the stream, close the stream now
	if chunk.sendData && chunk.eof {
		s.sentEOF = true
	}

	return chunk
}

func (c *streamChunk) sendHeadersFrame() bool {
	return c.sendHeaders
}

func (c *streamChunk) sendWindowUpdateFrame() bool {
	return c.windowUpdate > 0
}

func (c *streamChunk) sendDataFrame() bool {
	return c.sendData
}

func (c *streamChunk) nextDataFrame(frameSize int) (payload []byte, endStream bool) {
	payload = c.buffer.Next(frameSize)
	if c.buffer.Len() == 0 {
		// this is the last data frame in this chunk
		c.sendData = false
		if c.eof {
			endStream = true
		}
	}
	return
}
