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 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
}

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

func (fc *FlexClient) StartFlexClient() {
	var err error

	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.Infof("Trying to connect to flex radio at %s:%s", fc.Address, fc.Port)

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

	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")
}

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:           "#eaeaea",
		BackgroundColor: "#000000",
		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 = "#3bf908"
		flexSpot.Priority = "1"
		flexSpot.BackgroundColor = "#000000"
		flexSpot.Comment = flexSpot.Comment + " [New DXCC]"
	} else if spot.DX == Cfg.SQLite.Callsign {
		flexSpot.Color = "#ff0000"
		flexSpot.Priority = "1"
		flexSpot.BackgroundColor = "#000000"
	} else if spot.CallsignWorked {
		flexSpot.Color = "#000000"
		flexSpot.BackgroundColor = "#00c0c0"
		flexSpot.Priority = "5"
		flexSpot.Comment = flexSpot.Comment + " [Worked]"
	} else if spot.NewMode && spot.NewBand {
		flexSpot.Color = "#c603fc"
		flexSpot.Priority = "1"
		flexSpot.BackgroundColor = "#000000"
		flexSpot.Comment = flexSpot.Comment + " [New Band & Mode]"
	} else if spot.NewMode && !spot.NewBand {
		flexSpot.Color = "#f9a908"
		flexSpot.Priority = "2"
		flexSpot.BackgroundColor = "#000000"
		flexSpot.Comment = flexSpot.Comment + " [New Mode]"
	} else if spot.NewBand && !spot.NewMode {
		flexSpot.Color = "#f9f508"
		flexSpot.Priority = "3"
		flexSpot.BackgroundColor = "#000000"
		flexSpot.Comment = flexSpot.Comment + " [New Band]"
	} else if !spot.NewBand && !spot.NewMode && !spot.NewDXCC && !spot.CallsignWorked {
		flexSpot.Color = "#eaeaea"
		flexSpot.Priority = "5"
		flexSpot.BackgroundColor = "#000000"
	}

	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])
			Log.Debugf("Spot: DX: %s - Spotter: %s - Freq: %s - Band: %s - FlexID: %v deleted from database", spot.DX, spot.SpotterCallsign, spot.FrequencyHz, spot.Band, 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
}