package main

import (
	"bytes"
	"crypto/rand"
	"encoding/binary"
	"errors"
	"time"
)

// This is sent every 100ms by the server, but as this seems to be only a ping-line packet,
// it is also fine to send it in a longer interval.
const pkt7SendInterval = 3 * time.Second
const pkt7TimeoutDuration = 3 * time.Second

type pkt7Type struct {
	sendSeq      uint16
	innerSendSeq uint16
	// lastConfirmedSeq uint16

	sendTicker   *time.Ticker
	timeoutTimer *time.Timer
	latency      time.Duration
	lastSendAt   time.Time

	periodicStopNeededChan   chan bool
	periodicStopFinishedChan chan bool
}

var controlStreamLatency time.Duration

func (p *pkt7Type) isPkt7(r []byte) bool {
	return len(r) == 21 && bytes.Equal(r[1:6], []byte{0x00, 0x00, 0x00, 0x07, 0x00}) // Note that the first byte can be 0x15 or 0x00, so we ignore that.
}

func (p *pkt7Type) handle(s *streamCommon, r []byte) error {
	gotSeq := binary.LittleEndian.Uint16(r[6:8])
	if r[16] == 0x00 { // This is a pkt7 request from the radio.
		// Replying to the radio.
		// Example request from radio: 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x1c, 0x0e, 0xe4, 0x35, 0xdd, 0x72, 0xbe, 0xd9, 0xf2, 0x63, 0x00, 0x57, 0x2b, 0x12, 0x00
		// Example answer from PC:     0x15, 0x00, 0x00, 0x00, 0x07, 0x00, 0x1c, 0x0e, 0xbe, 0xd9, 0xf2, 0x63, 0xe4, 0x35, 0xdd, 0x72, 0x01, 0x57, 0x2b, 0x12, 0x00
		if p.sendTicker != nil { // Only replying if the auth is already done.
			if err := p.sendReply(s, r[17:21], gotSeq); err != nil {
				return err
			}
		}
	} else { // This is a pkt7 reply to our request.
		if p.sendTicker != nil { // Auth is already done?
			if p.timeoutTimer != nil {
				p.timeoutTimer.Stop()
				p.timeoutTimer.Reset(pkt7TimeoutDuration)
			}

			if s.name == "control" { // Only measure latency on the control stream.
				// Only measure latency after the timeout has been initialized, so the auth is already done.
				p.latency += time.Since(p.lastSendAt)
				p.latency /= 2
				statusLog.reportRTTLatency(p.latency)

				controlStreamLatency = p.latency
			}
		}

		// expectedSeq := p.lastConfirmedSeq + 1
		// if expectedSeq != gotSeq && gotSeq != p.lastConfirmedSeq {
		// 	var missingPkts int
		// 	if gotSeq > expectedSeq {
		// 		missingPkts = int(gotSeq) - int(expectedSeq)
		// 	} else {
		// 		missingPkts = int(gotSeq) + 65536 - int(expectedSeq)
		// 	}
		// 	log.Error(s.name+"/lost ", missingPkts, " packets")
		// }
		// p.lastConfirmedSeq = gotSeq
	}
	return nil
}

func (p *pkt7Type) sendDo(s *streamCommon, replyID []byte, seq uint16) error {
	// Example request from PC:  0x15, 0x00, 0x00, 0x00, 0x07, 0x00, 0x09, 0x00, 0xbe, 0xd9, 0xf2, 0x63, 0xe4, 0x35, 0xdd, 0x72, 0x00, 0x78, 0x40, 0xf6, 0x02
	// Example reply from radio: 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x09, 0x00, 0xe4, 0x35, 0xdd, 0x72, 0xbe, 0xd9, 0xf2, 0x63, 0x01, 0x78, 0x40, 0xf6, 0x02
	var replyFlag byte
	if replyID == nil {
		replyID = make([]byte, 4)
		var randID [1]byte
		if _, err := rand.Read(randID[:]); err != nil {
			return err
		}
		replyID[0] = randID[0]
		replyID[1] = byte(p.innerSendSeq)
		replyID[2] = byte(p.innerSendSeq >> 8)
		replyID[3] = 0x06
		p.innerSendSeq++
	} else {
		replyFlag = 0x01
	}

	d := []byte{0x15, 0x00, 0x00, 0x00, 0x07, 0x00, byte(seq), byte(seq >> 8),
		byte(s.localSID >> 24), byte(s.localSID >> 16), byte(s.localSID >> 8), byte(s.localSID),
		byte(s.remoteSID >> 24), byte(s.remoteSID >> 16), byte(s.remoteSID >> 8), byte(s.remoteSID),
		replyFlag, replyID[0], replyID[1], replyID[2], replyID[3]}
	if err := s.send(d); err != nil {
		return err
	}
	return nil
}

func (p *pkt7Type) send(s *streamCommon) error {
	if err := p.sendDo(s, nil, p.sendSeq); err != nil {
		return err
	}
	p.lastSendAt = time.Now()
	p.sendSeq++
	return nil
}

func (p *pkt7Type) sendReply(s *streamCommon, replyID []byte, seq uint16) error {
	return p.sendDo(s, replyID, seq)
}

func (p *pkt7Type) loop(s *streamCommon) {
	for {
		if p.timeoutTimer != nil {
			select {
			case <-p.timeoutTimer.C:
				reportError(errors.New(s.name + "/ping timeout"))

			case <-p.sendTicker.C:
				if err := p.send(s); err != nil {
					reportError(err)
				}
			case <-p.periodicStopNeededChan:
				p.periodicStopFinishedChan <- true
				return
			}
		} else {
			select {
			case <-p.sendTicker.C:
				if err := p.send(s); err != nil {
					reportError(err)
				}
			case <-p.periodicStopNeededChan:
				p.periodicStopFinishedChan <- true
				return
			}
		}
	}
}

func (p *pkt7Type) startPeriodicSend(s *streamCommon, firstSeqNo uint16, checkPingTimeout bool) {
	p.sendSeq = firstSeqNo
	p.innerSendSeq = 0x8304
	// p.lastConfirmedSeq = p.sendSeq - 1

	p.sendTicker = time.NewTicker(pkt7SendInterval)
	if checkPingTimeout {
		p.timeoutTimer = time.NewTimer(pkt7TimeoutDuration)
	}

	p.periodicStopNeededChan = make(chan bool)
	p.periodicStopFinishedChan = make(chan bool)
	go p.loop(s)
}

func (p *pkt7Type) stopPeriodicSend() {
	if p.sendTicker == nil { // Periodic send has not started?
		return
	}

	p.periodicStopNeededChan <- true
	<-p.periodicStopFinishedChan

	if p.timeoutTimer != nil {
		p.timeoutTimer.Stop()
	}
	p.sendTicker.Stop()
}
