// Read a generic "framed" packet consisting of a header and a
// This is used for both TLS Records and TLS Handshake Messages
package mint

type framing interface {
	headerLen() int
	defaultReadLen() int
	frameLen(hdr []byte) (int, error)
}

const (
	kFrameReaderHdr  = 0
	kFrameReaderBody = 1
)

type frameNextAction func(f *frameReader) error

type frameReader struct {
	details     framing
	state       uint8
	header      []byte
	body        []byte
	working     []byte
	writeOffset int
	remainder   []byte
}

func newFrameReader(d framing) *frameReader {
	hdr := make([]byte, d.headerLen())
	return &frameReader{
		d,
		kFrameReaderHdr,
		hdr,
		nil,
		hdr,
		0,
		nil,
	}
}

func dup(a []byte) []byte {
	r := make([]byte, len(a))
	copy(r, a)
	return r
}

func (f *frameReader) needed() int {
	tmp := (len(f.working) - f.writeOffset) - len(f.remainder)
	if tmp < 0 {
		return 0
	}
	return tmp
}

func (f *frameReader) addChunk(in []byte) {
	// Append to the buffer.
	logf(logTypeFrameReader, "Appending %v", len(in))
	f.remainder = append(f.remainder, in...)
}

func (f *frameReader) process() (hdr []byte, body []byte, err error) {
	for f.needed() == 0 {
		logf(logTypeFrameReader, "%v bytes needed for next block", len(f.working)-f.writeOffset)
		// Fill out our working block
		copied := copy(f.working[f.writeOffset:], f.remainder)
		f.remainder = f.remainder[copied:]
		f.writeOffset += copied
		if f.writeOffset < len(f.working) {
			logf(logTypeVerbose, "Read would have blocked 1")
			return nil, nil, AlertWouldBlock
		}
		// Reset the write offset, because we are now full.
		f.writeOffset = 0

		// We have read a full frame
		if f.state == kFrameReaderBody {
			logf(logTypeFrameReader, "Returning frame hdr=%#x len=%d buffered=%d", f.header, len(f.body), len(f.remainder))
			f.state = kFrameReaderHdr
			f.working = f.header
			return dup(f.header), dup(f.body), nil
		}

		// We have read the header
		bodyLen, err := f.details.frameLen(f.header)
		if err != nil {
			return nil, nil, err
		}
		logf(logTypeFrameReader, "Processed header, body len = %v", bodyLen)

		f.body = make([]byte, bodyLen)
		f.working = f.body
		f.writeOffset = 0
		f.state = kFrameReaderBody
	}

	logf(logTypeVerbose, "Read would have blocked 2")
	return nil, nil, AlertWouldBlock
}
