package tlv

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

const (
	TLV_FLOAT64 = 1
	TLV_FLOAT32 = 2
	TLV_INT8    = 3
	TLV_INT16   = 4
	TLV_INT32   = 5
	TLV_INT64   = 6
	TLV_UINT8   = 7
	TLV_UINT16  = 8
	TLV_UINT32  = 9
	TLV_UINT64  = 10
	TLV_BYTES   = 11
	TLV_STRING  = 12
	TLV_BOOL    = 13
)

type TLV struct {
	Tag   uint16
	Value []byte
}

func Uint16ToByte(value uint16) []byte {
	buf := bytes.NewBuffer([]byte{})
	binary.Write(buf, binary.BigEndian, value)

	return buf.Bytes()
}

func ByteToUint16(buf []byte) uint16 {
	tmpBuf := bytes.NewBuffer(buf)
	var value uint16
	binary.Read(tmpBuf, binary.BigEndian, &value)

	return value
}

func (tlv *TLV) ToBinary() []byte {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.BigEndian, &tlv.Tag)
	binary.Write(buf, binary.BigEndian, &tlv.Value)

	return buf.Bytes()
}

func (tlv *TLV) Length() int {
	length := int(0)
	switch tlv.Tag {
	case TLV_FLOAT64:
		length = 8
	case TLV_INT64:
		length = 8
	case TLV_UINT64:
		length = 8
	case TLV_FLOAT32:
		length = 4
	case TLV_INT32:
		length = 4
	case TLV_UINT32:
		length = 4
	case TLV_INT16:
		length = 2
	case TLV_UINT16:
		length = 2
	case TLV_INT8:
		length = 1
	case TLV_UINT8:
		length = 1
	case TLV_BYTES:
		length = int(ByteToUint16(tlv.Value[0:2]))
		length += 2
	case TLV_STRING:
		length = int(ByteToUint16(tlv.Value[0:2]))
		length += 2
	default:
		length = 0
	}

	length += 2

	return length
}

func (tlv *TLV) FromBinary(r io.Reader) error {
	binary.Read(r, binary.BigEndian, &tlv.Tag)
	length := uint16(0)
	switch tlv.Tag {
	case TLV_FLOAT64:
		length = 8
		tlv.Value = make([]byte, length)
		binary.Read(r, binary.BigEndian, &tlv.Value)
	case TLV_INT64:
		length = 8
		tlv.Value = make([]byte, length)
		binary.Read(r, binary.BigEndian, &tlv.Value)
	case TLV_UINT64:
		length = 8
		tlv.Value = make([]byte, length)
		binary.Read(r, binary.BigEndian, &tlv.Value)
	case TLV_FLOAT32:
		length = 4
		tlv.Value = make([]byte, length)
		binary.Read(r, binary.BigEndian, &tlv.Value)
	case TLV_INT32:
		length = 4
		tlv.Value = make([]byte, length)
		binary.Read(r, binary.BigEndian, &tlv.Value)
	case TLV_UINT32:
		length = 4
		tlv.Value = make([]byte, length)
		binary.Read(r, binary.BigEndian, &tlv.Value)
	case TLV_INT16:
		length = 2
		tlv.Value = make([]byte, length)
		binary.Read(r, binary.BigEndian, &tlv.Value)
	case TLV_UINT16:
		length = 2
		tlv.Value = make([]byte, length)
		binary.Read(r, binary.BigEndian, &tlv.Value)
	case TLV_INT8:
		length = 1
		tlv.Value = make([]byte, length)
		binary.Read(r, binary.BigEndian, &tlv.Value)
	case TLV_UINT8:
		length = 1
		tlv.Value = make([]byte, length)
		binary.Read(r, binary.BigEndian, &tlv.Value)
	case TLV_BYTES:
		binary.Read(r, binary.BigEndian, &length)
		tlv.Value = make([]byte, length+2)
		copy(tlv.Value[0:2], Uint16ToByte(length))
		binary.Read(r, binary.BigEndian, tlv.Value[2:])
	case TLV_STRING:
		binary.Read(r, binary.BigEndian, &length)
		tlv.Value = make([]byte, length+2)
		copy(tlv.Value[0:2], Uint16ToByte(length))
		binary.Read(r, binary.BigEndian, tlv.Value[2:])
	default:
		return errors.New(fmt.Sprintf("unsuport value: %d", tlv.Tag))
	}

	return nil
}

func MakeTLV(a interface{}) (*TLV, error) {
	var tag uint16
	var length uint16
	buf := new(bytes.Buffer)
	switch a.(type) {
	case float64:
		tag = TLV_FLOAT64
		length = 8
		binary.Write(buf, binary.BigEndian, a.(float64))
	case float32:
		tag = TLV_FLOAT32
		length = 4
		binary.Write(buf, binary.BigEndian, a.(float32))
	case int8:
		tag = TLV_INT8
		length = 1
		binary.Write(buf, binary.BigEndian, a.(int8))
	case int16:
		tag = TLV_INT16
		length = 2
		binary.Write(buf, binary.BigEndian, a.(int16))
	case int32:
		tag = TLV_INT32
		length = 4
		binary.Write(buf, binary.BigEndian, a.(int32))
	case int64:
		tag = TLV_INT64
		length = 8
		binary.Write(buf, binary.BigEndian, a.(int64))
	case uint8:
		tag = TLV_UINT8
		length = 1
		binary.Write(buf, binary.BigEndian, a.(uint8))
	case uint16:
		tag = TLV_UINT16
		length = 2
		binary.Write(buf, binary.BigEndian, a.(uint16))
	case uint32:
		tag = TLV_UINT32
		length = 4
		binary.Write(buf, binary.BigEndian, a.(uint32))
	case uint64:
		tag = TLV_UINT64
		length = 8
		binary.Write(buf, binary.BigEndian, a.(uint64))
	case []byte:
		tag = TLV_BYTES
		length = uint16(len(a.([]byte)))
		binary.Write(buf, binary.BigEndian, length)
		binary.Write(buf, binary.BigEndian, a.([]byte))
	case string:
		tag = TLV_STRING
		length = uint16(len(a.(string)))
		binary.Write(buf, binary.BigEndian, length)
		binary.Write(buf, binary.BigEndian, []byte(a.(string)))
	default:
		return nil, errors.New(fmt.Sprintf("unsuport value: %v", a))
	}

	tlv := TLV{
		Tag:   tag,
		Value: buf.Bytes(),
	}

	if length == 0 {
		tlv.Value = []byte{}
	}

	return &tlv, nil
}

func ReadTLV(tlv *TLV) (interface{}, error) {
	tag := tlv.Tag
	length := uint16(0)
	value := tlv.Value

	buffer := bytes.NewReader(value)
	switch tag {
	case TLV_FLOAT64:
		retvar := float64(0.0)
		err := binary.Read(buffer, binary.BigEndian, &retvar)
		return retvar, err
	case TLV_FLOAT32:
		retvar := float32(0.0)
		err := binary.Read(buffer, binary.BigEndian, &retvar)
		return retvar, err
	case TLV_INT8:
		retvar := int8(0)
		err := binary.Read(buffer, binary.BigEndian, &retvar)
		return retvar, err
	case TLV_INT16:
		retvar := int16(0)
		err := binary.Read(buffer, binary.BigEndian, &retvar)
		return retvar, err
	case TLV_INT32:
		retvar := int32(0)
		err := binary.Read(buffer, binary.BigEndian, &retvar)
		return retvar, err
	case TLV_INT64:
		retvar := int64(0)
		err := binary.Read(buffer, binary.BigEndian, &retvar)
		return retvar, err
	case TLV_UINT8:
		retvar := uint8(0)
		err := binary.Read(buffer, binary.BigEndian, &retvar)
		return retvar, err
	case TLV_UINT16:
		retvar := uint16(0)
		err := binary.Read(buffer, binary.BigEndian, &retvar)
		return retvar, err
	case TLV_UINT32:
		retvar := uint32(0)
		err := binary.Read(buffer, binary.BigEndian, &retvar)
		return retvar, err
	case TLV_UINT64:
		retvar := uint64(0)
		err := binary.Read(buffer, binary.BigEndian, &retvar)
		return retvar, err
	case TLV_BYTES:
		err := binary.Read(buffer, binary.BigEndian, &length)
		if err != nil {
			return []byte{}, err
		}
		retvar := make([]byte, length)
		err = binary.Read(buffer, binary.BigEndian, &retvar)
		return retvar, err
	case TLV_STRING:
		err := binary.Read(buffer, binary.BigEndian, &length)
		if err != nil {
			return string([]byte{}), err
		}
		retvar := make([]byte, length)
		err = binary.Read(buffer, binary.BigEndian, &retvar)
		return string(retvar), err
	default:
		return nil, errors.New("Reading TLV error ,Unkown TLV type: " + string(tag))
	}
}

func MakeTLVs(a []interface{}) ([]TLV, error) {
	tlvs := []TLV{}
	for _, one := range a {
		tlv, err := MakeTLV(one)
		if err != nil {
			return nil, err
		}
		tlvs = append(tlvs, *tlv)
	}
	return tlvs, nil
}

func ReadTLVs(tlvs []TLV) ([]interface{}, error) {
	values := []interface{}{}
	for _, tlv := range tlvs {
		one, err := ReadTLV(&tlv)
		if err != nil {
			return values, err
		}
		values = append(values, one)
	}
	return values, nil
}

func CastTLV(value interface{}, valueType int32) interface{} {
	switch valueType {
	case TLV_FLOAT64:
		return float64(value.(float64))
	case TLV_FLOAT32:
		return float32(value.(float64))
	case TLV_INT8:
		return int8(value.(float64))
	case TLV_INT16:
		return int16(value.(float64))
	case TLV_INT32:
		return int32(value.(float64))
	case TLV_INT64:
		return int64(value.(float64))
	case TLV_UINT8:
		return uint8(value.(float64))
	case TLV_UINT16:
		return uint16(value.(float64))
	case TLV_UINT32:
		return uint32(value.(float64))
	case TLV_UINT64:
		return uint64(value.(float64))
	case TLV_BYTES:
		return []byte(value.(string))
	case TLV_STRING:
		return value.(string)
	default:
		return nil
	}
}
