package n9m

import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"github.com/icza/bitio"
	"log"
	"time"
)

// Read package
func (e *Package) ReadPackage() bool {
	if len(e.Accum) < 12 {
		return false
	}

	r := bitio.NewReader(bytes.NewBuffer(e.Accum))
	e.Version = uint8(r.TryReadBits(2))
	e.EncryptionFlag = r.TryReadBool()
	e.CompressFlag = r.TryReadBool()
	e.CSRCCount = uint8(r.TryReadBits(4))
	e.PayloadType = PayloadType(r.TryReadBits(8))
	e.SSRC = SpecialPayloadType((r.TryReadBits(8) | (r.TryReadBits(8) << 8)))

	if e.EncryptionFlag && e.CompressFlag {
		// TODO: get snippet, that use this code
		r.TryReadBits(8 * 4)
		e.payloadLen = r.TryReadBits(8)
		r.TryReadBits(3 * 8)

		if uint64(len(e.Accum)) < e.payloadLen+12 {
			return false
		}
	} else {
		e.payloadLen = r.TryReadBits(32)

		// WTF: e.CC is useless
		for i := uint64(0); i < 1; i++ {
			e.CSRC[i] = r.TryReadBits(32)
		}
	}

	if e.payloadLen > 1e6 {
		log.Printf("%v\n", e)
		log.Panicln("CORRUPTED PACKAGE")
	}

	numOfBytes := 0

	if e.payloadLen != 0 {
		e.RawPayload = make([]byte, e.payloadLen)
		numOfBytes = r.TryRead(e.RawPayload)
	} else {
		e.RawPayload = []byte{}
	}

	if numOfBytes != int(e.payloadLen) {
		return false
	}

	e.Accum = e.Accum[12+e.payloadLen:]

	switch e.PayloadType {
	case PayloadTypeData:
		if err := json.Unmarshal(e.RawPayload, &e.Payload); err != nil {
			log.Printf("Error parsing JSON payload: %v", err)
			return false
		}
	case PayloadTypeSpecial:
		switch e.SSRC {
		case SpecialPayloadTypeGPS:
			e.GPS.GPSStatus = e.RawPayload[0]
			e.GPS.Expand = e.RawPayload[1]
			e.GPS.Real = e.RawPayload[2]

			e.GPS.Longitude = float64(binary.BigEndian.Uint32(e.RawPayload[4:8])) / 1e6
			e.GPS.Latitude = float64(binary.BigEndian.Uint32(e.RawPayload[8:12])) / 1e6
			e.GPS.Speed = float64(binary.BigEndian.Uint32(e.RawPayload[12:16])) / 100
			e.GPS.Direction = float64(binary.BigEndian.Uint32(e.RawPayload[16:20])) / 100
			e.GPS.Altitude = int32(binary.BigEndian.Uint32(e.RawPayload[20:24]))

			var err error
			if e.GPS.Time, err = time.Parse("20060102150405", string(e.RawPayload[24:38])); err != nil {
				log.Printf("Error parsing time: %v", err)
			}
		default:
			fmt.Println("N9M parser warning: unknown special payload type", e.SSRC)
		}
	default:
		fmt.Println("N9M parser warning: unknown payload type", e.PayloadType)
	}

	if r.TryError != nil {
		log.Printf("TryError encountered: %v", r.TryError)
		return false
	}

	return true
}

func (e *Package) PackPayload() (err error) {
	e.RawPayload, err = json.Marshal(e.Payload)
	e.payloadLen = uint64(len(e.RawPayload))

	if e.payloadLen != 0 {
		e.RawPayload = append(e.RawPayload, 0)
		e.payloadLen++
	}

	return
}

func (e *Package) PackPackage() []byte {
	var err error

	if err = e.PackPayload(); err != nil {
		log.Printf("Error while packing payload: %v", err)
		return []byte{}
	}

	return e.PackRawPackage()
}

func (e *Package) PackRawPackage() []byte {
	var err error

	b := &bytes.Buffer{}
	w := bitio.NewWriter(b)

	w.TryWriteBits(uint64(e.Version), 2)

	w.TryWriteBool(e.EncryptionFlag)
	w.TryWriteBool(e.CompressFlag)

	w.TryWriteBits(uint64(e.CSRCCount), 4)
	w.TryWriteBits(uint64(e.PayloadType), 8)
	w.TryWriteBits(uint64(e.SSRC), 16)

	w.TryWriteBits(e.payloadLen, 32)

	// WTF: e.CC is useless
	for i := uint64(0); i < 1; i++ {
		w.TryWriteBits(e.CSRC[i], 32)
	}

	if e.payloadLen != 0 {
		w.TryWrite(e.RawPayload)
	}

	if err = w.Close(); err != nil {
		log.Printf("Error while closing writer: %v", err)
		return []byte{}
	}

	return b.Bytes()
}

func (e *Package) GetParametersAs(parameters any) error {
	marshal, err := json.Marshal(e.Payload.Parameter)

	if err != nil {
		return err
	}

	return json.Unmarshal(marshal, parameters)
}

func (e *Package) SetParameters(parameters any) {
	e.Payload.Response = struct{}{}
	e.Payload.Parameter = parameters
}

func (e *Package) GetResponseAs(response any) error {
	marshal, err := json.Marshal(e.Payload.Response)

	if err != nil {
		return err
	}

	return json.Unmarshal(marshal, response)
}

func (e *Package) SetResponse(response any) {
	e.Payload.Parameter = struct{}{}
	e.Payload.Response = response
}