/*
NAME
  bytescanner.go

AUTHOR
  Dan Kortschak <dan@ausocean.org>

LICENSE
  This is Copyright (C) 2017 the Australian Ocean Lab (AusOcean)

  It is free software: you can redistribute it and/or modify them
  under the terms of the GNU General Public License as published by the
  Free Software Foundation, either version 3 of the License, or (at your
  option) any later version.

  It is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  for more details.

  You should have received a copy of the GNU General Public License
  in gpl.txt. If not, see http://www.gnu.org/licenses.
*/

// Package bytescan implements a byte-level scanner.
package codecutil

import "io"

// ByteScanner is a byte scanner.
type ByteScanner struct {
	buf []byte
	off int

	// r is the source of data for the scanner.
	r io.Reader
}

// NewByteScanner returns a scanner initialised with an io.Reader and a read buffer.
func NewByteScanner(r io.Reader, buf []byte) *ByteScanner {
	return &ByteScanner{r: r, buf: buf[:0]}
}

// ScanUntil scans the scanner's underlying io.Reader until a delim byte
// has been read, appending all read bytes to dst. The resulting appended data,
// the last read byte and whether the last read byte was the delimiter.
func (c *ByteScanner) ScanUntil(dst []byte, delim byte) (res []byte, b byte, err error) {
outer:
	for {
		var i int
		for i, b = range c.buf[c.off:] {
			if b != delim {
				continue
			}
			dst = append(dst, c.buf[c.off:c.off+i+1]...)
			c.off += i + 1
			break outer
		}
		dst = append(dst, c.buf[c.off:]...)
		err = c.reload()
		if err != nil {
			break
		}
	}
	return dst, b, err
}

// ReadByte is an unexported ReadByte.
func (c *ByteScanner) ReadByte() (byte, error) {
	if c.off >= len(c.buf) {
		err := c.reload()
		if err != nil {
			return 0, err
		}
	}
	b := c.buf[c.off]
	c.off++
	return b, nil
}

// reload re-fills the scanner's buffer.
func (c *ByteScanner) reload() error {
	n, err := c.r.Read(c.buf[:cap(c.buf)])
	c.buf = c.buf[:n]
	if err != nil {
		if err != io.EOF {
			return err
		}
		if n == 0 {
			return io.EOF
		}
	}
	c.off = 0
	return nil
}