package pktline

import (
	"errors"
	"io"
)

// Scanner provides a convenient interface for reading the payloads of a
// series of pkt-lines.  It takes an io.Reader providing the source,
// which then can be tokenized through repeated calls to the Scan
// method.
//
// After each Scan call, the Bytes method returns the payload of the
// corresponding pkt-line as a slice into the Scanner's internal buffer.
// This buffer is overwritten on the next call to Scan, so callers must
// process or copy the data before the next Scan. For a string copy, use
// Text.
//
// Special pkt-lines ([Flush], [Delim], [ResponseEnd]) return a nil slice
// from Bytes; Len returns the pkt-line length, which equals the
// corresponding constant (Flush=0, Delim=1, ResponseEnd=2).
//
// Scanning stops at EOF or the first I/O error.
type Scanner struct {
	r   io.Reader     // The reader provided by the client
	err error         // Sticky error
	buf [MaxSize]byte // Buffer used to read the pktlines
	n   int           // Number of bytes read in the last read
}

// NewScanner returns a new Scanner to read from r.
func NewScanner(r io.Reader) *Scanner {
	return &Scanner{
		r: r,
	}
}

// Err returns the first error encountered by the Scanner.
func (s *Scanner) Err() error {
	return s.err
}

// Scan advances the Scanner to the next pkt-line, whose payload will
// then be available through the Bytes method.  Scanning stops at EOF
// or the first I/O error.  After Scan returns false, the Err method
// will return any error that occurred during scanning, except that if
// it was io.EOF, Err will return nil.
func (s *Scanner) Scan() bool {
	if s.r == nil {
		return false
	}
	s.n, s.err = Read(s.r, s.buf[:])
	if errors.Is(s.err, io.EOF) {
		s.err = nil
		return false
	}
	return s.err == nil
}

// Bytes returns the payload of the most recent pkt-line as a slice
// into the Scanner's internal buffer. The slice is valid only until
// the next call to Scan, which overwrites the buffer. Use [Text] or
// copy the data when the payload must outlive the next Scan.
//
// Bytes does no allocation. It returns nil for special pkt-lines
// ([Flush], [Delim], [ResponseEnd]); use [Len] to distinguish them.
func (s *Scanner) Bytes() []byte {
	if s.n >= LenSize {
		return s.buf[LenSize:s.n]
	}
	return nil
}

// Text returns the most recent packet generated by a call to Scan.
func (s *Scanner) Text() string {
	return string(s.Bytes())
}

// Len returns the pkt-line length of the most recent pkt-line. For data
// lines this is the length of the entire pkt-line including the 4-byte
// length prefix. For special pkt-lines, Len returns the corresponding
// constant: [Flush] (0), [Delim] (1), or [ResponseEnd] (2).
func (s *Scanner) Len() int {
	return s.n
}
