package scanproto

import (
	"bufio"
	"encoding/binary"
	"io"

	"github.com/golang/protobuf/proto"
)

func NewEventSetScanner(r io.Reader) *bufio.Scanner {
	// The field number of the "event" field in the EventSet message
	const eventField = 1

	return newScanner(r, eventField)
}

func newScanner(r io.Reader, field uint64) *bufio.Scanner {
	sc := bufio.NewScanner(r)
	sc.Split(protoSplit{field: field}.split)
	return sc
}

type protoSplit struct {
	field uint64
}

func (ps protoSplit) split(data []byte, atEOF bool) (advance int, token []byte, err error) {
	// https://developers.google.com/protocol-buffers/docs/encoding

	const (
		wireVarint      = 0
		wire64bit       = 1
		wireLengthDelim = 2
		wire32bit       = 5
	)

	pb := &protoBuffer{data: data}

	// Decode field header
	x, err := pb.DecodeVarint()
	if err != nil {
		if err == io.ErrUnexpectedEOF {
			// Ask for more bytes.
			return 0, nil, nil
		}
		return 0, nil, err
	}

	field, wire := x>>3, x&0x07
	switch field {
	case ps.field:
		switch wire {
		case wireLengthDelim:
			_, err := pb.DecodeRawBytes(false)
			if err != nil {
				if err == io.ErrUnexpectedEOF {
					// Ask for more bytes.
					return 0, nil, nil
				}
				return 0, nil, err
			}

			// We've found what we're looking for.  Give it to the caller.
			advance = len(data) - len(pb.data)
			return advance, data[:advance], nil

		default:
			// We expect a length-delimited field here.
			return 0, nil, &proto.ParseError{}
		}

	default:
		// skip unknown fields

		var err error
		switch wire {
		case wire32bit:
			_, err = pb.DecodeFixed32()
		case wire64bit:
			_, err = pb.DecodeFixed64()
		case wireVarint:
			_, err = pb.DecodeVarint()
		case wireLengthDelim:
			_, err = pb.DecodeRawBytes(false)
		default:
			err = &proto.ParseError{}
		}
		if err != nil {
			if err == io.ErrUnexpectedEOF {
				// Ask for more bytes.
				return 0, nil, nil
			}
			return 0, nil, err
		}

		// Discard the bytes and move on.
		return len(data) - len(pb.data), nil, nil
	}
}

type protoBuffer struct {
	data []byte
}

func (pb *protoBuffer) DecodeFixed32() (x uint64, err error) {
	const l = 4
	if len(pb.data) < l {
		return 0, io.ErrUnexpectedEOF
	}
	var buf []byte
	buf, pb.data = pb.data[:l], pb.data[l:]
	return uint64(binary.LittleEndian.Uint32(buf)), nil
}

func (pb *protoBuffer) DecodeFixed64() (x uint64, err error) {
	const l = 8
	if len(pb.data) < l {
		return 0, io.ErrUnexpectedEOF
	}
	var buf []byte
	buf, pb.data = pb.data[:l], pb.data[l:]
	return binary.LittleEndian.Uint64(buf), nil
}

func (pb *protoBuffer) DecodeRawBytes(alloc bool) (buf []byte, err error) {
	x, err := pb.DecodeVarint()
	if err != nil {
		return nil, err
	}
	if uint64(int(x)) != x {
		return nil, &proto.ParseError{}
	}
	if int(x) > len(pb.data) {
		return nil, io.ErrUnexpectedEOF
	}

	buf, pb.data = pb.data[:int(x)], pb.data[int(x):]
	if alloc {
		buf = append(make([]byte, 0, len(buf)), buf...)
	}
	return buf, nil
}

func (pb *protoBuffer) DecodeVarint() (x uint64, err error) {
	x, n := proto.DecodeVarint(pb.data)
	if n == 0 {
		if len(pb.data) >= 10 {
			// Varints are up to 64 bits.  Ten bytes is enough to store that,
			// we must have gotten a bogus message.
			return 0, &proto.ParseError{}
		}
		return 0, io.ErrUnexpectedEOF
	}
	pb.data = pb.data[n:]
	return x, nil
}
