// Package blockchain provides the tools for encoding
// data primitives in blockchain structures
package blockchain

import (
	"encoding/binary"
	"errors"
	"io"
	"math"
	"sync"

	"github.com/chainmint/encoding/bufpool"
)

var bufPool = sync.Pool{New: func() interface{} { return new([9]byte) }}

var ErrRange = errors.New("value out of range")

// Reader wraps a buffer and provides utilities for decoding
// data primitives in blockchain structures. Its various read
// calls may return a slice of the underlying buffer.
type Reader struct {
	buf []byte
}

// NewReader constructs a new reader with the provided bytes. It
// does not create a copy of the bytes, so the caller is responsible
// for copying the bytes if necessary.
func NewReader(b []byte) *Reader {
	return &Reader{buf: b}
}

// Len returns the number of unread bytes.
func (r *Reader) Len() int {
	return len(r.buf)
}

// ReadByte reads and returns the next byte from the input.
//
// It implements the io.ByteReader interface.
func (r *Reader) ReadByte() (byte, error) {
	if len(r.buf) == 0 {
		return 0, io.EOF
	}

	b := r.buf[0]
	r.buf = r.buf[1:]
	return b, nil
}

// Read reads up to len(p) bytes into p. It implements
// the io.Reader interface.
func (r *Reader) Read(p []byte) (n int, err error) {
	n = copy(p, r.buf)
	r.buf = r.buf[n:]
	if len(r.buf) == 0 {
		err = io.EOF
	}
	return
}

func ReadVarint31(r *Reader) (uint32, error) {
	val, err := binary.ReadUvarint(r)
	if err != nil {
		return 0, err
	}
	if val > math.MaxInt32 {
		return 0, ErrRange
	}
	return uint32(val), nil
}

func ReadVarint63(r *Reader) (uint64, error) {
	val, err := binary.ReadUvarint(r)
	if err != nil {
		return 0, err
	}
	if val > math.MaxInt64 {
		return 0, ErrRange
	}
	return val, nil
}

func ReadVarstr31(r *Reader) ([]byte, error) {
	l, err := ReadVarint31(r)
	if err != nil {
		return nil, err
	}
	if l == 0 {
		return nil, nil
	}
	if int(l) > len(r.buf) {
		return nil, io.ErrUnexpectedEOF
	}
	str := r.buf[:l]
	r.buf = r.buf[l:]
	return str, nil
}

// ReadVarstrList reads a varint31 length prefix followed by
// that many varstrs.
func ReadVarstrList(r *Reader) (result [][]byte, err error) {
	nelts, err := ReadVarint31(r)
	if err != nil {
		return nil, err
	}
	if nelts == 0 {
		return nil, nil
	}

	for ; nelts > 0 && err == nil; nelts-- {
		var s []byte
		s, err = ReadVarstr31(r)
		result = append(result, s)
	}
	if len(result) < int(nelts) {
		err = io.ErrUnexpectedEOF
	}
	return result, err
}

// ReadExtensibleString reads a varint31 length prefix and that many
// bytes from r. It then calls the given function to consume those
// bytes, returning any unconsumed suffix.
func ReadExtensibleString(r *Reader, f func(*Reader) error) (suffix []byte, err error) {
	s, err := ReadVarstr31(r)
	if err != nil {
		return nil, err
	}

	sr := NewReader(s)
	err = f(sr)
	if err != nil {
		return nil, err
	}
	return sr.buf, nil
}

func WriteVarint31(w io.Writer, val uint64) (int, error) {
	if val > math.MaxInt32 {
		return 0, ErrRange
	}
	buf := bufPool.Get().(*[9]byte)
	n := binary.PutUvarint(buf[:], val)
	b, err := w.Write(buf[:n])
	bufPool.Put(buf)
	return b, err
}

func WriteVarint63(w io.Writer, val uint64) (int, error) {
	if val > math.MaxInt64 {
		return 0, ErrRange
	}
	buf := bufPool.Get().(*[9]byte)
	n := binary.PutUvarint(buf[:], val)
	b, err := w.Write(buf[:n])
	bufPool.Put(buf)
	return b, err
}

func WriteVarstr31(w io.Writer, str []byte) (int, error) {
	n, err := WriteVarint31(w, uint64(len(str)))
	if err != nil {
		return n, err
	}
	n2, err := w.Write(str)
	return n + n2, err
}

// WriteVarstrList writes a varint31 length prefix followed by the
// elements of l as varstrs.
func WriteVarstrList(w io.Writer, l [][]byte) (int, error) {
	n, err := WriteVarint31(w, uint64(len(l)))
	if err != nil {
		return n, err
	}
	for _, s := range l {
		n2, err := WriteVarstr31(w, s)
		n += n2
		if err != nil {
			return n, err
		}
	}
	return n, err
}

// WriteExtensibleString sends the output of the given function, plus
// the given suffix, to w, together with a varint31 length prefix.
func WriteExtensibleString(w io.Writer, suffix []byte, f func(io.Writer) error) (int, error) {
	buf := bufpool.Get()
	defer bufpool.Put(buf)
	err := f(buf)
	if err != nil {
		return 0, err
	}
	if len(suffix) > 0 {
		_, err := buf.Write(suffix)
		if err != nil {
			return 0, err
		}
	}
	return WriteVarstr31(w, buf.Bytes())
}
