package quic

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"

	"github.com/quic-go/quic-go/quicvarint"
)

// The Header represents a QUIC header.
type Header struct {
	Type             uint8
	Version          uint32
	SrcConnectionID  []byte
	DestConnectionID []byte
	Length           int64
	Token            []byte
}

// ParseInitialHeader parses the initial packet of a QUIC connection,
// return the initial header and number of bytes read so far.
func ParseInitialHeader(data []byte) (*Header, int64, error) {
	br := bytes.NewReader(data)
	hdr, err := parseLongHeader(br)
	if err != nil {
		return nil, 0, err
	}
	n := int64(len(data) - br.Len())
	return hdr, n, nil
}

func parseLongHeader(b *bytes.Reader) (*Header, error) {
	typeByte, err := b.ReadByte()
	if err != nil {
		return nil, err
	}
	h := &Header{}
	ver, err := beUint32(b)
	if err != nil {
		return nil, err
	}
	h.Version = ver
	if h.Version != 0 && typeByte&0x40 == 0 {
		return nil, errors.New("not a QUIC packet")
	}
	destConnIDLen, err := b.ReadByte()
	if err != nil {
		return nil, err
	}
	h.DestConnectionID = make([]byte, int(destConnIDLen))
	if err := readConnectionID(b, h.DestConnectionID); err != nil {
		return nil, err
	}
	srcConnIDLen, err := b.ReadByte()
	if err != nil {
		return nil, err
	}
	h.SrcConnectionID = make([]byte, int(srcConnIDLen))
	if err := readConnectionID(b, h.SrcConnectionID); err != nil {
		return nil, err
	}

	initialPacketType := byte(0b00)
	if h.Version == V2 {
		initialPacketType = 0b01
	}
	if (typeByte >> 4 & 0b11) == initialPacketType {
		tokenLen, err := quicvarint.Read(b)
		if err != nil {
			return nil, err
		}
		if tokenLen > uint64(b.Len()) {
			return nil, io.EOF
		}
		h.Token = make([]byte, tokenLen)
		if _, err := io.ReadFull(b, h.Token); err != nil {
			return nil, err
		}
	}

	pl, err := quicvarint.Read(b)
	if err != nil {
		return nil, err
	}
	h.Length = int64(pl)
	return h, err
}

func readConnectionID(r io.Reader, cid []byte) error {
	_, err := io.ReadFull(r, cid)
	if err == io.ErrUnexpectedEOF {
		return io.EOF
	}
	return nil
}

func beUint32(r io.Reader) (uint32, error) {
	b := make([]byte, 4)
	if _, err := io.ReadFull(r, b); err != nil {
		return 0, err
	}
	return binary.BigEndian.Uint32(b), nil
}
