package bin

import (
	"errors"
	"fmt"
	"io"
	"math"
)

const (
	varInt64SignFlag = 0b01000000
	maxReadAttempts  = 5
)

var (
	ErrNoSpace         = errors.New("no space")
	ErrIncompleteWrite = errors.New("incomplete write")

	ErrReadOverflow = errors.New("bin: reader returned 'n' > bufsize")
	// ErrNegativeReadCount shows 100% bug in the source reader
	ErrNegativeReadCount = errors.New("bin: reader returned negative 'n'")
)

// READ

func ReadUint16(src io.Reader) (num uint16, err error) {
	var (
		q   = 2
		arr = make([]byte, q)
	)
	n, err := src.Read(arr)
	if err != nil {
		return
	}
	if n != q {
		return 0, fmt.Errorf("read %d bytes only", n)
	}
	return GetUint16(arr), nil
}

func ReadUint24AsInt(src io.Reader) (num int, err error) {
	var (
		q   = 3
		arr = make([]byte, q)
	)
	n, err := src.Read(arr)
	if err != nil {
		return
	}
	if n != q {
		return 0, fmt.Errorf("read %d bytes only", n)
	}
	return GetUint24AsInt(arr), nil
}

func ReadUint32(src io.Reader) (num uint32, err error) {
	var (
		q   = 4
		arr = make([]byte, q)
	)
	n, err := src.Read(arr)
	if err != nil {
		return
	}
	if n != q {
		return 0, fmt.Errorf("read %d bytes only", n)
	}
	return GetUint32(arr), nil
}

func ReadUint64(src io.Reader) (num uint64, err error) {
	var (
		q   = 8
		arr = make([]byte, q)
	)
	n, err := src.Read(arr)
	if err != nil {
		return
	}
	if n != q {
		return 0, fmt.Errorf("read %d bytes only", n)
	}
	return GetUint64(arr), nil
}

func ReadFloat64(src io.Reader) (num float64, err error) {
	var (
		q   = 8
		arr = make([]byte, q)
	)
	n, err := src.Read(arr)
	if err != nil {
		return
	}
	if n != q {
		return 0, fmt.Errorf("read %d bytes only", n)
	}
	return GetFloat64(arr), nil
}

func ReadUnixtime(src io.Reader) (num int64, err error) {
	var (
		q   = 8
		arr = make([]byte, q)
	)
	n, err := src.Read(arr)
	if err != nil {
		return
	}
	if n != q {
		return 0, fmt.Errorf("read %d bytes only", n)
	}
	return int64(GetUint64(arr)), nil
}

// READ VAR

func ReadVarUint64(src io.Reader) (num uint64, n int, err error) {
	var (
		p = make([]byte, 1)
		b byte
	)
	for i := range 8 {
		_, err = src.Read(p)
		if err != nil {
			return
		}
		n++
		b = p[0]
		if b >= 128 {
			num |= uint64(b&127) << uint(i*7)
			return
		}
		num |= uint64(b) << uint(i*7)
	}
	_, err = src.Read(p)
	if err != nil {
		return
	}
	n++
	num |= uint64(p[0]) << 56
	return
}

// GET

func GetUint16(arr []byte) uint16 {
	return uint16(arr[0]) | (uint16(arr[1]) << 8)
}

func GetUint16AsInt(arr []byte) int {
	return int(arr[0]) | (int(arr[1]) << 8)
}

func GetUint24AsInt(arr []byte) int {
	return int(arr[0]) | (int(arr[1]) << 8) | (int(arr[2]) << 16)
}

func GetUint32(arr []byte) uint32 {
	return uint32(arr[0]) | (uint32(arr[1]) << 8) |
		(uint32(arr[2]) << 16) | (uint32(arr[3]) << 24)
}

func GetUint32AsInt64(arr []byte) int64 {
	u32 := uint32(arr[0]) | (uint32(arr[1]) << 8) |
		(uint32(arr[2]) << 16) | (uint32(arr[3]) << 24)
	return int64(u32)
}

func GetUint40(arr []byte) uint64 {
	return uint64(arr[0]) | (uint64(arr[1]) << 8) |
		(uint64(arr[2]) << 16) | (uint64(arr[3]) << 24) |
		(uint64(arr[4]) << 32)
}

func GetUint48(arr []byte) uint64 {
	return uint64(arr[0]) | (uint64(arr[1]) << 8) |
		(uint64(arr[2]) << 16) | (uint64(arr[3]) << 24) |
		(uint64(arr[4]) << 32) | (uint64(arr[5]) << 40)
}

func GetUint64(arr []byte) uint64 {
	return uint64(arr[0]) | (uint64(arr[1]) << 8) |
		(uint64(arr[2]) << 16) | (uint64(arr[3]) << 24) |
		(uint64(arr[4]) << 32) | (uint64(arr[5]) << 40) |
		(uint64(arr[6]) << 48) | (uint64(arr[7]) << 56)
}

func GetFloat32(arr []byte) float32 {
	return math.Float32frombits(GetUint32(arr))
}

func GetFloat64(arr []byte) float64 {
	return math.Float64frombits(GetUint64(arr))
}

func GetUnixtime(arr []byte) int64 {
	u32 := uint32(arr[0]) | (uint32(arr[1]) << 8) |
		(uint32(arr[2]) << 16) | (uint32(arr[3]) << 24)
	return int64(u32)
}

func GetVarUint64(arr []byte) (num uint64, n int, err error) {
	var b byte
	for i := range 8 {
		if i >= len(arr) {
			return 0, 0, io.EOF
		}
		b = arr[i]
		if b >= 128 {
			num |= uint64(b&127) << uint(i*7)
			return num, i + 1, nil
		}
		num |= uint64(b) << uint(i*7)
	}
	if len(arr) < 9 {
		return 0, 0, io.EOF
	}
	return num | uint64(arr[8])<<56, 9, nil
}

func ReverseGetVarUint64(arr []byte) (num uint64, n int, err error) {
	var (
		b byte
		j = len(arr) - 1
	)
	for i := range 8 {
		if j < 0 {
			return 0, 0, io.EOF
		}
		b = arr[j]
		if b >= 128 {
			num |= uint64(b&127) << uint(i*7)
			return num, i + 1, nil
		}
		num |= uint64(b) << uint(i*7)
		j--
	}
	if j < 0 {
		return 0, 0, io.EOF
	}
	return num | uint64(arr[j])<<56, 9, nil
}

func GetVarInt64(arr []byte) (num int64, n int, err error) {
	u64, n, err := GetVarUint64(arr)
	if err != nil {
		return
	}
	return DecodeZigZag(u64), n, nil
}

func ReverseGetVarInt64(arr []byte) (num int64, n int, err error) {
	u64, n, err := ReverseGetVarUint64(arr)
	if err != nil {
		return
	}
	return DecodeZigZag(u64), n, nil
}

// PUT

func PutUint16(arr []byte, num uint16) {
	arr[0] = byte(num)
	arr[1] = byte(num >> 8)
}

func PutIntAsUint16(arr []byte, num int) {
	arr[0] = byte(num)
	arr[1] = byte(num >> 8)
}

func PutIntAsUint24(arr []byte, num int) {
	arr[0] = byte(num)
	arr[1] = byte(num >> 8)
	arr[2] = byte(num >> 16)
}

func PutUint32(arr []byte, num uint32) {
	arr[0] = byte(num)
	arr[1] = byte(num >> 8)
	arr[2] = byte(num >> 16)
	arr[3] = byte(num >> 24)
}

func PutInt64AsUint32(arr []byte, num int64) {
	arr[0] = byte(num)
	arr[1] = byte(num >> 8)
	arr[2] = byte(num >> 16)
	arr[3] = byte(num >> 24)
}

func PutUint40(arr []byte, num uint64) {
	arr[0] = byte(num)
	arr[1] = byte(num >> 8)
	arr[2] = byte(num >> 16)
	arr[3] = byte(num >> 24)
	arr[4] = byte(num >> 32)
}

func PutUint48(arr []byte, num uint64) {
	arr[0] = byte(num)
	arr[1] = byte(num >> 8)
	arr[2] = byte(num >> 16)
	arr[3] = byte(num >> 24)
	arr[4] = byte(num >> 32)
	arr[5] = byte(num >> 40)
}

func PutUint64(arr []byte, num uint64) {
	arr[0] = byte(num)
	arr[1] = byte(num >> 8)
	arr[2] = byte(num >> 16)
	arr[3] = byte(num >> 24)
	arr[4] = byte(num >> 32)
	arr[5] = byte(num >> 40)
	arr[6] = byte(num >> 48)
	arr[7] = byte(num >> 56)
}

func PutFloat32(arr []byte, num float32) {
	PutUint32(arr, math.Float32bits(num))
}

func PutFloat64(arr []byte, num float64) {
	PutUint64(arr, math.Float64bits(num))
}

// WRITE

func WriteUint16(dst io.Writer, num uint16) error {
	arr := []byte{
		byte(num),
		byte(num >> 8),
	}
	n, err := dst.Write(arr)
	if err != nil {
		return err
	}
	if n != 2 {
		return ErrIncompleteWrite
	}
	return err
}

func WriteUint32(dst io.Writer, num uint32) error {
	arr := []byte{
		byte(num),
		byte(num >> 8),
		byte(num >> 16),
		byte(num >> 24),
	}
	n, err := dst.Write(arr)
	if err != nil {
		return err
	}
	if n != 4 {
		return ErrIncompleteWrite
	}
	return err
}

func WriteFloat64(dst io.Writer, num float64) error {
	arr := make([]byte, 8)
	PutUint64(arr, math.Float64bits(num))
	n, err := dst.Write(arr)
	if err != nil {
		return err
	}
	if n != 2 {
		return ErrIncompleteWrite
	}
	return err
}

// WRITE VAR

func WriteVarUint64(dst io.Writer, num uint64) (int, error) {
	arr := make([]byte, 9)
	for i := range 8 {
		arr[i] = byte(num & 127)
		num >>= 7
		if num == 0 {
			arr[i] |= 128
			size := i + 1
			n, err := dst.Write(arr[:size])
			if err != nil {
				return n, err
			}
			if n != size {
				return n, ErrIncompleteWrite
			}
			return size, nil
		}
	}
	arr[8] = byte(num)
	n, err := dst.Write(arr)
	if err != nil {
		return n, err
	}
	if n != 9 {
		return n, ErrIncompleteWrite
	}
	return 9, nil
}

func PutVarUint64(arr []byte, num uint64) (int, error) {
	for i := range 8 {
		if i >= len(arr) {
			return 0, ErrNoSpace
		}
		arr[i] = byte(num & 127)
		num >>= 7
		if num == 0 {
			arr[i] |= 128
			return i + 1, nil
		}
	}
	if len(arr) < 9 {
		return 0, ErrNoSpace
	}
	arr[8] = byte(num)
	return 9, nil
}

func ReversePutVarUint64(arr []byte, num uint64) (int, error) {
	var tmp [9]byte
	for i := range 8 {
		tmp[i] = byte(num & 127)
		num >>= 7
		if num == 0 {
			tmp[i] |= 128
			n := i + 1
			if len(arr) < n {
				return 0, ErrNoSpace
			}
			for j := i; j >= 0; j-- {
				arr[i-j] = tmp[j]
			}
			return n, nil
		}
	}
	tmp[8] = byte(num)
	n := 9
	if len(arr) < n {
		return 0, ErrNoSpace
	}
	for j := 8; j >= 0; j-- {
		arr[8-j] = tmp[j]
	}
	return n, nil
}

func PutVarUint64AtEnd(arr []byte, num uint64) (int, error) {
	var (
		tmp [9]byte
		n   int
	)
	for i := range 8 {
		tmp[i] = byte(num & 127)
		num >>= 7
		if num == 0 {
			tmp[i] |= 128
			n = i + 1
			break
		}
	}
	if n == 0 {
		tmp[8] = byte(num)
		n = 9
	}
	if len(arr) < n {
		return 0, ErrNoSpace
	}
	j := len(arr) - n
	for i := range n {
		arr[j] = tmp[i]
		j++
	}
	return n, nil
}

func PutVarInt64(arr []byte, x int64) (int, error) {
	return PutVarUint64(arr, EncodeZigZag(x))
}

func PutVarInt64AtEnd(arr []byte, x int64) (int, error) {
	return PutVarUint64AtEnd(arr, EncodeZigZag(x))
}

func ReversePutVarInt64(arr []byte, x int64) (int, error) {
	return ReversePutVarUint64(arr, EncodeZigZag(x))
}

type KeyComparator interface {
	CompareTo(int) int
}

func BinarySearch(qty int, keyComparator KeyComparator) (elemIdx int, isFound bool) {
	if qty == 0 {
		return
	}
	a := 0
	b := qty - 1
	for {
		var (
			elemIdx = (b-a)/2 + a
			code    = keyComparator.CompareTo(elemIdx)
		)

		if code == 1 {
			a = elemIdx + 1
			if a > b {
				return elemIdx + 1, false
			}
		} else if code == -1 {
			b = elemIdx - 1
			if b < a {
				return elemIdx, false
			}
		} else {
			return elemIdx, true
		}
	}
}

func DeleteReverseArrElem(arr []byte, qty int, elemSize int, idx int) {
	dstIdx := len(arr) - idx*elemSize - 1
	srcIdx := dstIdx - elemSize

	end := len(arr) - qty*elemSize

	for ; srcIdx >= end; srcIdx-- {
		arr[dstIdx] = arr[srcIdx]
		dstIdx--
	}

	for i := end; i < end+elemSize; i++ {
		arr[i] = 0
	}
}

// ZigZag

// ZigZag encoding: int64 -> uint64
func EncodeZigZag(x int64) uint64 {
	return uint64(x<<1) ^ uint64(x>>63)
}

// ZigZag decoding: uint64 -> int64
func DecodeZigZag(u uint64) int64 {
	return int64(u>>1) ^ -(int64(u & 1))
}

func ReadN(r io.Reader, n int) (_ []byte, err error) {
	if n < 0 {
		err = fmt.Errorf("wrong n=%d", n)
		return
	}
	buf := make([]byte, n)
	err = ReadNInto(r, buf)
	if err != nil {
		return
	}
	return buf, nil
}

func ReadNInto(r io.Reader, buf []byte) (err error) {
	if len(buf) == 0 {
		return
	}

	var q, total, readAttempts int

	for readAttempts < maxReadAttempts {
		bufsize := len(buf) - total
		q, err = r.Read(buf[total:])
		if q == bufsize {
			return nil
		}
		if err != nil {
			return
		}
		if q > bufsize {
			err = ErrReadOverflow
			return
		}
		if q < 0 {
			err = ErrNegativeReadCount
			return
		}
		if q == 0 {
			readAttempts++
		} else {
			total += q
		}
	}
	err = io.ErrNoProgress
	return
}

func CalcVarUint64Length(num uint64) int {
	for i := range 8 {
		num >>= 7
		if num == 0 {
			return i + 1
		}
	}
	return 9
}

func CalcVarInt64Length(num int64) int {
	u64 := EncodeZigZag(num)
	for i := range 8 {
		u64 >>= 7
		if u64 == 0 {
			return i + 1
		}
	}
	return 9
}
