// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package osp

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

const (
	maxInt6  = 1<<5 - 1
	maxInt14 = 1<<13 - 1
	maxInt30 = 1<<29 - 1
	maxInt62 = 1<<63 - 1
)

// https://tools.ietf.org/html/draft-ietf-quic-transport-16#section-16
func ReadVaruint(r io.Reader) (uint64, error) {
	b0, err := readByte(r)
	if err != nil {
		return 0, err
	}
	var a [8]byte
	b := a[:]
	e := binary.BigEndian
	b[0] = last6Bits(b0)
	switch first2Bits(b0) {
	case 0:
		return uint64(b[0]), nil
	case 1:
		err = readBytes(r, b[1:2])
		return uint64(e.Uint16(b)), err
	case 2:
		err = readBytes(r, b[1:4])
		return uint64(e.Uint32(b)), err
	case 3:
		err = readBytes(r, b[1:8])
		return uint64(e.Uint64(b)), err
	}
	return 0, nil
}

// https://tools.ietf.org/html/draft-ietf-quic-transport-16#section-16
func WriteVaruint(v uint64, w io.Writer) error {
	var a [8]byte
	b := a[:]
	e := binary.BigEndian
	if v <= maxInt6 {
		b[0] = byte(v)
		setFirst2Bits(0, b)
		return writeBytes(b[:1], w)
	} else if v <= maxInt14 {
		e.PutUint16(b, uint16(v))
		setFirst2Bits(1, b)
		return writeBytes(b[:2], w)
	} else if v <= maxInt30 {
		e.PutUint32(b, uint32(v))
		setFirst2Bits(2, b)
		return writeBytes(b[:4], w)
	} else if v <= maxInt62 {
		e.PutUint64(b, v)
		setFirst2Bits(3, b)
		return writeBytes(b[:8], w)
	}
	return errors.New("Too big")
}

func first2Bits(b byte) byte {
	return b >> 6
}

func last6Bits(b byte) byte {
	return b & 0x3f // 0b00111111
}

func setFirst2Bits(first byte, b []byte) {
	b[0] = (first << 6) | b[0]
}

func readByte(r io.Reader) (byte, error) {
	var b [1]byte
	err := readBytes(r, b[:])
	return b[0], err
}

// Read len(b) bytes from r into b
// If you hit an error (of the end), propogate the error from r.Read
func readBytes(r io.Reader, b []byte) error {
	start := 0
	end := len(b)
	for start < end {
		n, err := r.Read(b[start:end])
		if err != nil {
			return err
		}
		start += n
	}
	return nil
}

// Write len(b) bytes from b to w
// If you hit an error, propogate the error from w.Write
func writeBytes(b []byte, w io.Writer) error {
	start := 0
	end := len(b)
	for start < end {
		n, err := w.Write(b[start:end])
		if err != nil {
			return err
		}
		start += n
	}
	return nil
}
