package encoding

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"math"
)

func write1(dst []byte, code byte, n uint8) []byte {
	return append(dst, code, n)
}

func write2(dst []byte, code byte, n uint16) []byte {
	return append(dst, code, byte(n>>8), byte(n))
}

func write4(dst []byte, code byte, n uint32) []byte {
	return append(
		dst,
		code,
		byte(n>>24),
		byte(n>>16),
		byte(n>>8),
		byte(n),
	)
}

func write8(dst []byte, code byte, n uint64) []byte {
	return append(
		dst,
		code,
		byte(n>>56),
		byte(n>>48),
		byte(n>>40),
		byte(n>>32),
		byte(n>>24),
		byte(n>>16),
		byte(n>>8),
		byte(n),
	)
}

func Skip(b []byte) int {
	if b[0] >= IntSmallValue && b[0] < Uint8Value {
		return 1
	}

	switch b[0] {
	case NullValue, FalseValue, TrueValue, DESC_NullValue, DESC_FalseValue, DESC_TrueValue:
		return 1
	case Int8Value, Uint8Value, DESC_Int8Value, DESC_Uint8Value:
		return 2
	case Int16Value, Uint16Value, DESC_Int16Value, DESC_Uint16Value:
		return 3
	case Int32Value, Uint32Value, DESC_Int32Value, DESC_Uint32Value:
		return 5
	case Int64Value, Uint64Value, Float64Value, DESC_Int64Value, DESC_Uint64Value, DESC_Float64Value:
		return 9
	case TextValue, ByteaValue, DESC_TextValue, DESC_ByteaValue:
		l, n := binary.Uvarint(b[1:])
		return n + int(l) + 1
	case ArrayValue, DESC_ArrayValue:
		return 1 + SkipArray(b[1:])
	case ObjectValue, DESC_ObjectValue:
		return 1 + SkipObject(b[1:])
	}

	return 0
}

func SkipArray(b []byte) int {
	l, n := binary.Uvarint(b)

	for i := 0; i < int(l); i++ {
		n += Skip(b[n:])
	}

	return n
}

func SkipObject(b []byte) int {
	l, n := binary.Uvarint(b)

	for i := 0; i < int(l); i++ {
		// skip field
		n += Skip(b[n:])

		// skip value
		n += Skip(b[n:])
	}

	return n
}

func Equal(a, b []byte) bool {
	return bytes.Equal(a, b)
}

func Compare(a, b []byte) int {
	var n, cmp int

	for {
		if n == len(a) {
			if len(b) == n {
				return 0
			}
			return -1
		} else if n == len(b) {
			return 1
		}

		a = a[n:]
		b = b[n:]

		cmp, n = compareNextValue(a, b)
		if cmp != 0 {
			return cmp
		}
	}
}

func compareNextValue(a, b []byte) (cmp int, n int) {
	if len(a) == 0 || len(b) == 0 {
		if len(a) == 0 && len(b) == 0 {
			return 0, 0
		}

		if len(a) == 0 {
			return -1, 0
		}

		return 1, 0
	}

	// compare the type first
	cmp = int(a[0]) - int(b[0])
	if cmp != 0 {
		return cmp, 1
	}

	if a[0] >= IntSmallValue && a[0] < Uint8Value {
		return 0, 1
	}
	if a[0] <= DESC_IntSmallValue && a[0] > DESC_Uint8Value {
		return 0, 1
	}

	// then compare values
	switch a[0] {
	case TombstoneValue, NullValue, FalseValue, TrueValue,
		DESC_NullValue, DESC_FalseValue, DESC_TrueValue:
		return 0, 1
	}

	// deal with empty values
	if len(a) == 1 || len(b) == 1 {
		if len(a) == 1 && len(b) > 1 {
			return -1, 1
		}

		if len(a) > 1 && len(b) == 1 {
			return 1, 1
		}

		return 0, 1
	}

	// compare non empty values

	t := a[0]
	// if DESC_*, invert the comparison
	if a[0] > 128 {
		t = 255 - t
	}

	cmp, n = compareNonEmptyValues(t, a, b)
	if a[0] > 128 {
		return -cmp, n
	}

	return cmp, n
}

func compareNonEmptyValues(t byte, a, b []byte) (cmp int, n int) {
	// compare non empty values
	switch t {
	case Int64Value, Uint64Value, Float64Value:
		return bytes.Compare(a[1:9], b[1:9]), 9
	case Int32Value, Uint32Value:
		return bytes.Compare(a[1:5], b[1:5]), 5
	case Int16Value, Uint16Value:
		return bytes.Compare(a[1:3], b[1:3]), 3
	case Int8Value, Uint8Value:
		return bytes.Compare(a[1:2], b[1:2]), 2
	case TextValue, ByteaValue:
		l, n := binary.Uvarint(a[1:])
		n++
		enda := n + int(l)
		l, n = binary.Uvarint(b[1:])
		n++
		endb := n + int(l)
		return bytes.Compare(a[n:enda], b[n:endb]), enda
	case ArrayValue:
		la, na := binary.Uvarint(a[1:])
		lb, nb := binary.Uvarint(b[1:])
		na++
		nb++
		minl := la
		if lb < minl {
			minl = lb
		}
		for i := 0; i < int(minl); i++ {
			cmp, nn := compareNextValue(a[na:], b[nb:])
			na += nn
			nb += nn
			if cmp != 0 {
				return cmp, na
			}
		}
		if la < lb {
			return -1, na
		}
		if la > lb {
			return 1, na
		}

		return 0, na
	case ObjectValue:
		la, na := binary.Uvarint(a[1:])
		lb, nb := binary.Uvarint(b[1:])
		na++
		nb++
		minl := la
		if lb < minl {
			minl = lb
		}
		for i := 0; i < int(minl); i++ {
			// compare field
			cmp, nn := compareNextValue(a[na:], b[nb:])
			na += nn
			nb += nn
			if cmp != 0 {
				return cmp, na
			}

			// compare value
			cmp, nn = compareNextValue(a[na:], b[nb:])
			na += nn
			nb += nn
			if cmp != 0 {
				return cmp, na
			}
		}
		if la < lb {
			return -1, na
		}
		if la > lb {
			return 1, na
		}

		return 0, na
	}

	panic(fmt.Sprintf("unsupported value type: %d", a[0]))
}

// AbbreviatedKey returns a shortened version that is used for
// comparing keys during indexed batch comparisons.
// The key is not guaranteed to be unique, but it respects the
// same ordering as the original key.
// If two abbreviated keys are equal, Pebble will call the
// Equal function to determine if the original keys are equal.
// The key is constructed as follows:
// - 12 bits: the namespace, from 0 to 4096. If bigger than 4096, returns math.MaxUint64.
// - 4 bits: the Chai type of the first value.
// - 48 bits: a representation of the first value of the key, depending on its type.
func AbbreviatedKey(key []byte) uint64 {
	if len(key) == 0 {
		return 0
	}

	var abbv uint64

	// get the namespace
	namespace, n := DecodeInt(key)
	key = key[n:]
	if namespace >= 1<<16 {
		return math.MaxUint16 << 48
	}

	// First 16 bits are the namespace. (64 - 16 = 48)
	abbv |= uint64(namespace) << 48

	if len(key) == 0 {
		return abbv
	}

	// Get a sorted int value from the key.
	// The type is encoded on 8 bits
	tn := key[0]

	// Set the type. (48 - 8 = 40)
	abbv |= uint64(tn) << 40

	abbv |= abbreviatedValue(key)
	return abbv
}

// return the abbreviated value of the first value on max 5 bytes.
func abbreviatedValue(key []byte) uint64 {
	if len(key) == 0 {
		return 0
	}

	if key[0] >= IntSmallValue && key[0] < Uint8Value {
		return 0
	}

	if len(key) == 1 {
		return 0
	}

	switch key[0] {
	case Uint8Value, Int8Value:
		x := DecodeUint8(key[1:])
		return uint64(x)
	case Uint16Value, Int16Value:
		if len(key) < 3 {
			return 0
		}
		x := DecodeUint16(key[1:])
		return uint64(x)
	case Uint32Value, Int32Value:
		if len(key) < 5 {
			return 0
		}
		x := DecodeUint32(key[1:])
		return uint64(x)
	case Uint64Value, Int64Value, Float64Value:
		if len(key) < 9 {
			return 0
		}
		x := DecodeUint64(key[1:])
		return uint64(x) >> 24
	case TextValue, ByteaValue:
		var abbv uint64
		l, n := binary.Uvarint(key[1:])
		n++
		key = key[n:]
		ll := int(l)
		// put the first 5 bytes of the value
		for i := 0; i < 5 && i < ll; i++ {
			abbv |= uint64(key[i]) << (32 - uint64(i)*8)
		}
		return abbv
	case ArrayValue, ObjectValue:
		key = key[1:]
		l, n := binary.Uvarint(key)
		key = key[n:]
		if l > 0 {
			switch key[0] {
			case ArrayValue, ObjectValue:
				return uint64(key[0]) << 32
			default:
				abbv := uint64(key[0]) << 32
				x := abbreviatedValue(key) >> 8
				return abbv | x
			}
		}

		return 0
	}

	return 0
}

func Separator(dst, a, b []byte) []byte {
	if len(b) == 0 {
		return append(dst, a...)
	}

	var n, cmp int
	var idx int
	var aa []byte = a

	for {
		if n == len(a) {
			return a
		}

		a = a[n:]
		b = b[n:]
		idx += n

		cmp, n = compareNextValue(a, b)
		if cmp != 0 {
			idx += n
			dst = append(dst, aa[:idx]...)
			dst = append(dst, 0xFF)
			return dst
		}
	}
}

// Split returns the length of the prefix (namespace) that should be used as
// the user key prefix for pebble. Our keys encode a namespace as the first
// integer value. Use DecodeInt to find its size.
func Split(k []byte) int {
	if len(k) == 0 {
		return 0
	}

	// Our keys encode a namespace as the first integer value. Use DecodeInt
	// to find its encoded size and return that as the prefix length.
	_, n := DecodeInt(k)
	return n
}
