package tlv8

import (
	"bytes"
	"encoding/base64"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"math"
	"reflect"
	"strconv"
)

type errReader struct {
	err error
}

func (e *errReader) Read([]byte) (int, error) {
	return 0, e.err
}

func MarshalBase64(v any) (string, error) {
	b, err := Marshal(v)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(b), nil
}

func MarshalReader(v any) io.Reader {
	b, err := Marshal(v)
	if err != nil {
		return &errReader{err: err}
	}
	return bytes.NewReader(b)
}

func Marshal(v any) ([]byte, error) {
	value := reflect.ValueOf(v)
	kind := value.Type().Kind()

	if kind == reflect.Pointer {
		value = value.Elem()
		kind = value.Type().Kind()
	}

	switch kind {
	case reflect.Slice:
		return appendSlice(nil, value)
	case reflect.Struct:
		return appendStruct(nil, value)
	}

	return nil, errors.New("tlv8: not implemented: " + kind.String())
}

// separator the most confusing meaning in the documentation.
// It can have a value of 0x00 or 0xFF or even 0x05.
const separator = 0xFF

func appendSlice(b []byte, value reflect.Value) ([]byte, error) {
	for i := 0; i < value.Len(); i++ {
		if i > 0 {
			b = append(b, separator, 0)
		}
		var err error
		if b, err = appendStruct(b, value.Index(i)); err != nil {
			return nil, err
		}
	}
	return b, nil
}

func appendStruct(b []byte, value reflect.Value) ([]byte, error) {
	valueType := value.Type()

	for i := 0; i < value.NumField(); i++ {
		refField := value.Field(i)
		s, ok := valueType.Field(i).Tag.Lookup("tlv8")
		if !ok {
			continue
		}

		tag, err := strconv.Atoi(s)
		if err != nil {
			return nil, err
		}

		b, err = appendValue(b, byte(tag), refField)
		if err != nil {
			return nil, err
		}
	}

	return b, nil
}

func appendValue(b []byte, tag byte, value reflect.Value) ([]byte, error) {
	var err error

	switch value.Kind() {
	case reflect.Uint8:
		v := value.Uint()
		return append(b, tag, 1, byte(v)), nil

	case reflect.Uint16:
		v := value.Uint()
		return append(b, tag, 2, byte(v), byte(v>>8)), nil

	case reflect.Uint32:
		v := value.Uint()
		return append(b, tag, 4, byte(v), byte(v>>8), byte(v>>16), byte(v>>24)), nil

	case reflect.Uint64:
		v := value.Uint()
		return binary.LittleEndian.AppendUint64(append(b, tag, 8), v), nil

	case reflect.Float32:
		v := math.Float32bits(float32(value.Float()))
		return append(b, tag, 4, byte(v), byte(v>>8), byte(v>>16), byte(v>>24)), nil

	case reflect.String:
		v := value.String()
		l := len(v) // support "big" string
		for ; l > 255; l -= 255 {
			b = append(b, tag, 255)
			b = append(b, v[:255]...)
			v = v[255:]
		}
		b = append(b, tag, byte(l))
		return append(b, v...), nil

	case reflect.Array:
		if value.Type().Elem().Kind() == reflect.Uint8 {
			n := value.Len()
			b = append(b, tag, byte(n))
			for i := 0; i < n; i++ {
				b = append(b, byte(value.Index(i).Uint()))
			}
			return b, nil
		}

	case reflect.Slice:
		for i := 0; i < value.Len(); i++ {
			if i > 0 {
				b = append(b, separator, 0)
			}
			if b, err = appendValue(b, tag, value.Index(i)); err != nil {
				return nil, err
			}
		}
		return b, nil

	case reflect.Struct:
		b = append(b, tag, 0)
		i := len(b)
		if b, err = appendStruct(b, value); err != nil {
			return nil, err
		}
		b[i-1] = byte(len(b) - i) // set struct size
		return b, nil
	}

	return nil, errors.New("tlv8: not implemented: " + value.Kind().String())
}

func UnmarshalBase64(in any, out any) error {
	s, _ := in.(string) // protect from in == nil
	data, err := base64.StdEncoding.DecodeString(s)
	if err != nil {
		return err
	}
	return Unmarshal(data, out)
}

func UnmarshalReader(r io.Reader, n int64, v any) error {
	var data []byte
	var err error

	if n > 0 {
		data = make([]byte, n)
		_, err = io.ReadFull(r, data)
	} else {
		data, err = io.ReadAll(r)
	}
	if err != nil {
		return err
	}

	return Unmarshal(data, v)
}

func Unmarshal(data []byte, v any) error {
	if len(data) == 0 {
		return errors.New("tlv8: unmarshal zero data")
	}

	value := reflect.ValueOf(v)
	kind := value.Kind()

	if kind != reflect.Pointer {
		return errors.New("tlv8: value should be pointer: " + kind.String())
	}

	value = value.Elem()
	kind = value.Kind()

	if kind == reflect.Interface {
		value = value.Elem()
		kind = value.Kind()
	}

	switch kind {
	case reflect.Slice:
		return unmarshalSlice(data, value)
	case reflect.Struct:
		return unmarshalStruct(data, value)
	}

	return errors.New("tlv8: not implemented: " + kind.String())
}

// unmarshalTLV can return two types of errors:
// - critical and then the value of []byte will be nil
// - not critical and then []byte will contain the value
func unmarshalTLV(b []byte, value reflect.Value) ([]byte, error) {
	if len(b) < 2 {
		return nil, errors.New("tlv8: wrong size: " + value.Type().Name())
	}

	t := b[0]
	l := int(b[1])

	// array item divider (t == 0x00 || t == 0xFF)
	if l == 0 {
		return b[2:], errors.New("tlv8: zero item")
	}

	var v []byte

	for {
		if len(b) < 2+l {
			return nil, errors.New("tlv8: wrong size: " + value.Type().Name())
		}

		v = append(v, b[2:2+l]...)
		b = b[2+l:]

		// if size == 255 and same tag - continue read big payload
		if l < 255 || len(b) < 2 || b[0] != t {
			break
		}

		l = int(b[1])
	}

	tag := strconv.Itoa(int(t))

	valueField, ok := getStructField(value, tag)
	if !ok {
		return b, fmt.Errorf("tlv8: can't find T=%d,L=%d,V=%x for: %s", t, l, v, value.Type().Name())
	}

	if err := unmarshalValue(v, valueField); err != nil {
		return nil, err
	}

	return b, nil
}

func unmarshalSlice(b []byte, value reflect.Value) error {
	valueIndex := value.Index(growSlice(value))
	for len(b) > 0 {
		var err error
		if b, err = unmarshalTLV(b, valueIndex); err != nil {
			if b != nil {
				valueIndex = value.Index(growSlice(value))
				continue
			}
			return err
		}
	}
	return nil
}

func unmarshalStruct(b []byte, value reflect.Value) error {
	for len(b) > 0 {
		var err error
		if b, err = unmarshalTLV(b, value); b == nil && err != nil {
			return err
		}
	}
	return nil
}

func unmarshalValue(v []byte, value reflect.Value) error {
	switch value.Kind() {
	case reflect.Uint8:
		if len(v) != 1 {
			return errors.New("tlv8: wrong size: " + value.Type().Name())
		}
		value.SetUint(uint64(v[0]))

	case reflect.Uint16:
		if len(v) != 2 {
			return errors.New("tlv8: wrong size: " + value.Type().Name())
		}
		value.SetUint(uint64(v[0]) | uint64(v[1])<<8)

	case reflect.Uint32:
		if len(v) != 4 {
			return errors.New("tlv8: wrong size: " + value.Type().Name())
		}
		value.SetUint(uint64(v[0]) | uint64(v[1])<<8 | uint64(v[2])<<16 | uint64(v[3])<<24)

	case reflect.Uint64:
		if len(v) != 8 {
			return errors.New("tlv8: wrong size: " + value.Type().Name())
		}
		value.SetUint(binary.LittleEndian.Uint64(v))

	case reflect.Float32:
		f := math.Float32frombits(binary.LittleEndian.Uint32(v))
		value.SetFloat(float64(f))

	case reflect.String:
		value.SetString(string(v))

	case reflect.Array:
		if kind := value.Type().Elem().Kind(); kind != reflect.Uint8 {
			return errors.New("tlv8: unsupported array: " + kind.String())
		}

		for i, b := range v {
			value.Index(i).SetUint(uint64(b))
		}
		return nil

	case reflect.Slice:
		i := growSlice(value)
		return unmarshalValue(v, value.Index(i))

	case reflect.Struct:
		return unmarshalStruct(v, value)

	default:
		return errors.New("tlv8: not implemented: " + value.Kind().String())
	}

	return nil
}

func getStructField(value reflect.Value, tag string) (reflect.Value, bool) {
	valueType := value.Type()

	for i := 0; i < value.NumField(); i++ {
		valueField := value.Field(i)

		if s, ok := valueType.Field(i).Tag.Lookup("tlv8"); ok && s == tag {
			return valueField, true
		}
	}

	return reflect.Value{}, false
}

func growSlice(value reflect.Value) int {
	size := value.Len()

	if size >= value.Cap() {
		newcap := value.Cap() + value.Cap()/2
		if newcap < 4 {
			newcap = 4
		}
		newValue := reflect.MakeSlice(value.Type(), value.Len(), newcap)
		reflect.Copy(newValue, value)
		value.Set(newValue)
	}

	if size >= value.Len() {
		value.SetLen(size + 1)
	}

	return size
}
