package radar_lib

import (
	"errors"
	"log"
	"sync"

	"go.bug.st/serial"
)

type configResponse struct {
	CommandCode int
	Success     bool
}

type Direction int
type OutputLogic int
type Port int
type Baud int
type OutputType int
type TriggerMethod int
type OperatingMode int

const (
	ControlPinRelay = iota
	ControlPinCtrl1
	ControlPinCtrl2
)
const (
	DirectionBidirectional Direction = iota
	DirectionIncoming
	DirectionOutgoing
)
const (
	TargetStateInvalid = iota
	TargetStateMovingInside
	TargetStateExit
	TargetStateWithin
)
const (
	OutputLogicLargestSpeed = iota
	OutputLogicStrongestEngergy
	OutputLogicMostPlausible
)
const (
	PortTTL Port = iota
	PortRS485
)
const (
	Baud9600 Baud = iota
	Baud19200
	Baud57600
	Baud115200
)
const (
	OutputTypeNoOutput OutputType = iota
	OutputTypePeriodicOutput
	OutputTypeValidOutput
)
const (
	TriggerMethodHigh TriggerMethod = iota
	TriggerMethodLow
	TriggerMethodPositivePulse
	TriggerMethodNegativePulse
)
const (
	OperatingModeNoOutput OperatingMode = iota
	OperatingModeFirstProtocol
	OperatingModeSecondProtocol
)

var (
	startSequence = []byte{0x43, 0x46}
	endSequence   = []byte{0x0D, 0x0A}
)

func New(port string, baudrate int) *Radar {
	s, err := serial.Open(port, &serial.Mode{
		BaudRate: baudrate,
		DataBits: 8,
		Parity:   serial.NoParity,
	})
	if err != nil {
		log.Fatal(err)
	}
	r := &Radar{
		port:                  s,
		baudrate:              baudrate,
		configResponseChannel: make(chan configResponse),
	}
	go r.listenSerial()
	return r
}

type Radar struct {
	port                  serial.Port
	lock                  sync.RWMutex
	configLock            sync.RWMutex
	configResponseChannel chan (configResponse)
	baudrate              int
	handler               func(int)
	speedHandler          func(int)
}

func (r *Radar) write(data []byte) error {
	r.lock.Lock()
	defer r.lock.Unlock()
	_, err := r.port.Write(data)
	if err != nil {
		return err
	}
	return r.port.Drain()
}

// SetPinTrigger configures a pin to trigger on the radar when an object is detected in a specific range.
// controlPin defines the Pin to trigger, distance is the target distance in 0.5 meters, outputLevel is the voltage in 0.1V steps, direction defines weather to trigger on incoming / outgoing or both
func (r *Radar) SetPinTrigger(controlPin int, distance int, outputLevel int, direction Direction) error {
	data := startSequence
	data = append(data, 0x00, byte(controlPin), byte(distance), byte(outputLevel), byte(direction))
	data = append(data, endSequence...)
	return r.write(data)
}

// SetBaseConfig configures the base configuration. height defines the mounted height in 1cm steps, angle defines the vertical angle in degrees, waveformConfig is used to differentiate 2 different radars
func (r *Radar) SetBaseConfig(height int, angle int, waveformConfig int) error {
	data := startSequence
	data = append(data, 0x01, byte(height), byte(angle), byte(waveformConfig), 0x00)
	data = append(data, endSequence...)
	return r.write(data)
}

// SetEventConfig can configure up to 8 events that will trigger a response from the radar. distances are in 0.5m, speeds are in km/h.
func (r *Radar) SetEventConfig(eventNumber, minDistance, maxDistance, minSpeed, maxSpeed, direction, state int) error {
	if eventNumber < 1 || eventNumber > 1 {
		return errors.New("eventNumber needs to be between 1 and 8")
	}
	data := startSequence
	data = append(data, 0x02, byte(eventNumber), byte(minDistance), byte(maxDistance), byte(minSpeed), byte(maxSpeed), byte(direction), byte(state))
	data = append(data, endSequence...)
	return r.write(data)
}

func (r *Radar) SetTargetSpeedConfig(direction Direction, minDistance, maxDistance, minSpeed, maxSpeed, speeding int, outputLogic OutputLogic) error {
	data := startSequence
	data = append(data, 0x03, byte(direction), byte(minDistance), byte(maxDistance), byte(minSpeed), byte(maxSpeed), byte(speeding), byte(outputLogic))
	data = append(data, endSequence...)
	return r.write(data)
}

func (r *Radar) SetCommunicationConfig(port Port, baud Baud, speedOutput, targetOutput, triggerOutput OutputType, communicationPeriod int) error {
	data := startSequence
	outputProtocol := 0
	switch speedOutput {
	case OutputTypePeriodicOutput:
		outputProtocol |= (1 << 4)
	case OutputTypeValidOutput:
		outputProtocol |= (1 << 5)
	}

	switch targetOutput {
	case OutputTypePeriodicOutput:
		outputProtocol |= (1 << 2)
	case OutputTypeValidOutput:
		outputProtocol |= (1 << 3)
	}

	switch triggerOutput {
	case OutputTypePeriodicOutput:
		outputProtocol |= (1 << 0)
	case OutputTypeValidOutput:
		outputProtocol |= (1 << 1)
	}

	data = append(data, 0x03, byte(port), byte(baud), byte(outputProtocol), byte(communicationPeriod))
	data = append(data, endSequence...)
	return r.write(data)
}

func (r *Radar) SetControlPinConfig(controlPin int, triggerMethod TriggerMethod, outputLevel int, triggerEvent int, span int) error {
	data := startSequence
	data = append(data, 0x05, byte(controlPin), byte(triggerMethod), byte(outputLevel), byte(triggerEvent), byte(span))
	data = append(data, endSequence...)
	return r.write(data)
}

func (r *Radar) SetLampBoardOutput(operatingMode OperatingMode, luminance int, span int) error {
	data := startSequence
	data = append(data, 0x06, byte(operatingMode), byte(luminance), byte(span))
	data = append(data, endSequence...)
	return r.write(data)
}

func (r *Radar) SetEventHandler(handler func(int)) {
	r.handler = handler
}

func (r *Radar) SetSpeedHandler(handler func(int)) {
	r.speedHandler = handler
}
