package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"regexp"
	"strings"
	"time"
)

var CommandNumber int = 1

type FlexSpot struct {
	ID              int
	CommandNumber   int
	FlexSpotNumber  int
	DX              string
	FrequencyMhz    string
	FrequencyHz     string
	Band            string
	Mode            string
	FlexMode        string
	Source          string
	SpotterCallsign string
	TimeStamp       int64
	UTCTime         string
	LifeTime        string
	Priority        string
	Comment         string
	Color           string
	BackgroundColor string
	NewDXCC         bool
	NewBand         bool
	NewMode         bool
	Worked          bool
}

type Discovery struct {
	IP       string
	NickName string
	Model    string
	Serial   string
	Version  string
}

type FlexClient struct {
	Address        string
	Port           string
	Timeout        time.Duration
	LogWriter      *bufio.Writer
	Reader         *bufio.Reader
	Writer         *bufio.Writer
	Conn           *net.TCPConn
	SpotChanToFlex chan TelnetSpot
	MsgChan        chan string
	Repo           FlexDXClusterRepository
	TCPServer      *TCPServer
	IsConnected    bool
	Application    *Application
}

func NewFlexClient(repo FlexDXClusterRepository, TCPServer *TCPServer, SpotChanToFlex chan TelnetSpot, Application *Application) *FlexClient {
	return &FlexClient{
		Port:           "4992",
		SpotChanToFlex: SpotChanToFlex,
		MsgChan:        TCPServer.MsgChan,
		Repo:           repo,
		TCPServer:      TCPServer,
		Application:    Application,
		IsConnected:    false,
	}
}

func (fc *FlexClient) StartFlexClient() {

	if Cfg.Flex.IP == "" && !Cfg.Flex.Discover {
		Log.Errorln("You must either turn FlexRadio Discovery on or provide an IP address for the Flex")

	} else if Cfg.Flex.Discover {
		ok, d := DiscoverFlexRadio()
		if ok {
			fc.Address = d.IP
			Log.Infof("Found: %s with Nick: %s, Version: %s - using IP: %s", d.Model, d.NickName, d.Version, d.IP)
		} else {
			Log.Errorln("Could not discover any FlexRadio on the network, please provide an IP address in the config file.")
			fc.StartFlexClient()
		}
	} else if Cfg.Flex.IP != "" {
		fc.Address = Cfg.Flex.IP
	}

	if fc.Address != "" {
		addr, err := net.ResolveTCPAddr("tcp", fc.Address+":"+fc.Port)
		if err != nil {
			Log.Error("Cannot resolve Telnet Client address")
		}

		fc.LogWriter = bufio.NewWriter(os.Stdout)

		fc.Timeout = 600 * time.Second

		Log.Debugf("Trying to connect to FlexRadio at %s:%s", fc.Address, fc.Port)

		fc.Conn, err = net.DialTCP("tcp", nil, addr)
		if err != nil {
			Log.Errorf("Could not connect to FlexRadio on %s", Cfg.Flex.IP)
			Log.Error("Retrying to connect to FlexRadio in 5 seconds")
			time.Sleep(time.Second * 5)
			fc.IsConnected = false
			fc.Application.StatusFlexChan <- fc.IsConnected
			fc.StartFlexClient()
		}
		Log.Infof("Connected to FlexRadio at %s:%s", fc.Address, fc.Port)
		fc.IsConnected = true
		fc.Application.StatusFlexChan <- fc.IsConnected

		go func() {
			for message := range fc.SpotChanToFlex {
				fc.SendSpottoFlex(message)
			}
		}()

		fc.Reader = bufio.NewReader(fc.Conn)
		fc.Writer = bufio.NewWriter(fc.Conn)

		err = fc.Conn.SetKeepAlive(true)
		if err != nil {
			Log.Error("error while setting keep alive")
		}

		go fc.ReadLine()

		subSpotAllCmd := fmt.Sprintf("C%v|sub spot all", CommandNumber)
		fc.Write(subSpotAllCmd)
		CommandNumber++

		clrSpotAllCmd := fmt.Sprintf("C%v|spot clear", CommandNumber)
		fc.Write(clrSpotAllCmd)
		CommandNumber++

		Log.Debug("Subscribed to spot on FlexRadio and Deleted all spots from panadapter")
	} else {
		Log.Errorln("You must either turn FlexRadio Discovery on or provide an IP address for the Flex")
	}
}

func (fc *FlexClient) SendSpottoFlex(spot TelnetSpot) {

	freq := FreqMhztoHz(spot.Frequency)

	flexSpot := FlexSpot{
		CommandNumber:   CommandNumber,
		DX:              spot.DX,
		FrequencyMhz:    freq,
		FrequencyHz:     spot.Frequency,
		Band:            spot.Band,
		Mode:            spot.Mode,
		Source:          "FlexDXCluster",
		SpotterCallsign: spot.Spotter,
		TimeStamp:       time.Now().Unix(),
		UTCTime:         spot.Time,
		LifeTime:        Cfg.Flex.SpotLife,
		Comment:         spot.Comment,
		Color:           "#ffeaeaea",
		BackgroundColor: "#ff000000",
		Priority:        "5",
		NewDXCC:         spot.NewDXCC,
		NewBand:         spot.NewBand,
		NewMode:         spot.NewMode,
		Worked:          spot.CallsignWorked,
	}

	flexSpot.Comment = flexSpot.Comment + " [" + flexSpot.Mode + "] [" + flexSpot.SpotterCallsign + "]"

	// If new DXCC
	if spot.NewDXCC {
		flexSpot.Color = "#ff3bf908"
		flexSpot.Priority = "1"
		flexSpot.BackgroundColor = "#ff000000"
		flexSpot.Comment = flexSpot.Comment + " [New DXCC]"
	} else if spot.DX == Cfg.SQLite.Callsign {
		flexSpot.Color = "#ffff0000"
		flexSpot.Priority = "1"
		flexSpot.BackgroundColor = "#ff000000"
	} else if spot.CallsignWorked {
		flexSpot.Color = "#ff000000"
		flexSpot.BackgroundColor = "#ff00c0c0"
		flexSpot.Priority = "5"
		flexSpot.Comment = flexSpot.Comment + " [Worked]"
	} else if spot.NewMode && spot.NewBand {
		flexSpot.Color = "#ffc603fc"
		flexSpot.Priority = "1"
		flexSpot.BackgroundColor = "#ff000000"
		flexSpot.Comment = flexSpot.Comment + " [New Band & Mode]"
	} else if spot.NewMode && !spot.NewBand {
		flexSpot.Color = "#fff9a908"
		flexSpot.Priority = "2"
		flexSpot.BackgroundColor = "#ff000000"
		flexSpot.Comment = flexSpot.Comment + " [New Mode]"
	} else if spot.NewBand && !spot.NewMode {
		flexSpot.Color = "#fff9f508"
		flexSpot.Priority = "3"
		flexSpot.BackgroundColor = "#ff000000"
		flexSpot.Comment = flexSpot.Comment + " [New Band]"
	} else if !spot.NewBand && !spot.NewMode && !spot.NewDXCC && !spot.CallsignWorked {
		flexSpot.Color = "#ffeaeaea"
		flexSpot.Priority = "5"
		flexSpot.BackgroundColor = "#ff000000"
	}

	flexSpot.Comment = strings.ReplaceAll(flexSpot.Comment, " ", "\u00A0")

	srcFlexSpot, err := fc.Repo.FindDXSameBand(flexSpot)
	if err != nil {
		Log.Debugf("Could not find the DX in the database: ", err)
	}

	var stringSpot string
	if srcFlexSpot.DX == "" {
		fc.Repo.CreateSpot(flexSpot)
		stringSpot = fmt.Sprintf("C%v|spot add rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", flexSpot.CommandNumber, flexSpot.FrequencyMhz,
			flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
		CommandNumber++

	} else if srcFlexSpot.DX != "" && srcFlexSpot.Band == flexSpot.Band {
		fc.Repo.UpdateSpotSameBand(flexSpot)
		stringSpot = fmt.Sprintf("C%v|spot set %v rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", flexSpot.CommandNumber, srcFlexSpot.FlexSpotNumber, flexSpot.FrequencyMhz,
			flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
		CommandNumber++

	} else if srcFlexSpot.DX != "" && srcFlexSpot.Band != flexSpot.Band {
		fc.Repo.CreateSpot(flexSpot)
		stringSpot = fmt.Sprintf("C%v|spot add rx_freq=%v callsign=%s mode=%s source=%s spotter_callsign=%s timestamp=%v lifetime_seconds=%s comment=%s color=%s background_color=%s priority=%s", flexSpot.CommandNumber, flexSpot.FrequencyMhz,
			flexSpot.DX, flexSpot.Mode, flexSpot.Source, flexSpot.SpotterCallsign, flexSpot.TimeStamp, flexSpot.LifeTime, flexSpot.Comment, flexSpot.Color, flexSpot.BackgroundColor, flexSpot.Priority)
		CommandNumber++
	}

	fc.SendSpot(stringSpot)
	// Log.Debugf("Sending spot to FlexRadio: %s", stringSpot)
}

func (fc *FlexClient) SendSpot(stringSpot string) {
	fc.Write(stringSpot)
}

func (fc *FlexClient) ReadLine() {

	for {
		message, err := fc.Reader.ReadString(byte('\n'))
		if err != nil {
			Log.Errorf("Error reading message from FlexRadio, closing program: %s", err)
			os.Exit(1)
		}

		// Log.Debugf("Received message from FlexRadio: %s", strings.Trim(message, "\n"))

		regRespSpot := *regexp.MustCompile(`R(\d+)\|0\|(\d+)\n`)
		respSpot := regRespSpot.FindStringSubmatch(message)

		if len(respSpot) > 0 {
			spot, _ := fc.Repo.FindSpotByCommandNumber(respSpot[1])
			_, err := fc.Repo.UpdateFlexSpotNumberByID(respSpot[2], *spot)
			if err != nil {
				Log.Errorf("Could not update Flex spot number in database: %s", err)
			}
		}

		// Response when a spot is clicked
		regTriggerSpot := *regexp.MustCompile(`.*spot (\d+) triggered.*\n`)
		respTrigger := regTriggerSpot.FindStringSubmatch(message)

		if len(respTrigger) > 0 {
			spot, err := fc.Repo.FindSpotByFlexSpotNumber(respTrigger[1])
			if err != nil {
				Log.Errorf("could not find spot by flex spot number in database: %s", err)
			}

			msg := fmt.Sprintf(`To ALL de %s <%s> : Clicked on "%s" at %s`, Cfg.SQLite.Callsign, spot.UTCTime, spot.DX, spot.FrequencyHz)
			if len(fc.TCPServer.Clients) > 0 {
				fc.MsgChan <- msg
				Log.Infof("%s clicked on spot \"%s\" at %s", Cfg.SQLite.Callsign, spot.DX, spot.FrequencyMhz)
			}
		}

		// Status when a spot is deleted
		regSpotDeleted := *regexp.MustCompile(`S\d+\|spot (\d+) removed`)
		respDelete := regSpotDeleted.FindStringSubmatch(message)

		if len(respDelete) > 0 {
			// spot, _ := fc.Repo.FindSpotByFlexSpotNumber(respDelete[1])
			fc.Repo.DeleteSpotByFlexSpotNumber(respDelete[1])
		}
	}
}

// Write sends raw data to remove telnet server
func (fc *FlexClient) Write(data string) (n int, err error) {
	n, err = fc.Writer.Write([]byte(data + "\n"))
	if err == nil {
		err = fc.Writer.Flush()
	}
	return
}

func DiscoverFlexRadio() (bool, *Discovery) {
	if Cfg.Flex.Discover {
		Log.Infoln("FlexRadio Discovery is turned on...searching for radio on the network")

		pc, err := net.ListenPacket("udp", ":4992")
		if err != nil {
			Log.Errorf("Could not receive UDP packets to discover FlexRadio: ", err)
		}

		buf := make([]byte, 1024)

		for {
			n, _, err := pc.ReadFrom(buf)
			if err != nil {
				Log.Errorln("Could not read data on UDP port 4992")
			}

			discoverRe := regexp.MustCompile(`discovery_protocol_version=.*\smodel=(.*)\sserial=(.*)\sversion=(.*)\snickname=(.*)\scallsign=.*\sip=(.*)\sport=.*`)
			match := discoverRe.FindStringSubmatch(string(buf[:n]))

			if len(match) > 0 {
				d := &Discovery{
					NickName: match[4],
					Model:    match[1],
					Serial:   match[2],
					Version:  match[3],
					IP:       match[5],
				}
				return true, d
			}
		}
	} else {
		Log.Infoln("FlexRadio Discovery is turned off...using IP provided in the config file")
	}

	return false, nil
}