package main

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

	log "github.com/sirupsen/logrus"
)

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
	SpotChan     chan TelnetSpot
	MsgChan      chan string
	FlexSpotChan chan FlexSpot
	Repo         FlexDXClusterRepository
	Log          *log.Logger
	TCPServer    *TCPServer
}

func NewFlexClient(repo FlexDXClusterRepository, TCPServer *TCPServer, log *log.Logger) *FlexClient {
	return &FlexClient{
		Address:      Cfg.Flex.IP,
		Port:         "4992",
		SpotChan:     make(chan TelnetSpot, 100),
		FlexSpotChan: make(chan FlexSpot, 100),
		MsgChan:      TCPServer.MsgChan,
		Repo:         repo,
		TCPServer:    TCPServer,
		Log:          log,
	}
}

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

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

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

	fc.Timeout = 600 * time.Second

	fc.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 {
		fc.Log.Errorf("Could not connect to flex radio on %s, exiting...", Cfg.Flex.IP)
		os.Exit(1)
	}
	fc.Log.Infof("Connected to flex radio at %s:%s", fc.Address, fc.Port)

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

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

	err = fc.Conn.SetKeepAlive(true)
	if err != nil {
		fc.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++

	fc.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,
	}

	// If new DXCC
	if spot.NewDXCC {
		flexSpot.Color = "#3bf908"
		flexSpot.Priority = "1"
		flexSpot.BackgroundColor = "#000000"
	} 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"
	} else if spot.NewMode {
		flexSpot.Color = "#f9a908"
		flexSpot.Priority = "2"
		flexSpot.BackgroundColor = "#000000"
	} else if spot.NewBand {
		flexSpot.Color = "#f9f508"
		flexSpot.Priority = "3"
		flexSpot.BackgroundColor = "#000000"
	} else if !spot.NewBand && !spot.NewMode && !spot.NewDXCC && !spot.CallsignWorked {
		flexSpot.Color = "#eaeaea"
		flexSpot.Priority = "5"
		flexSpot.BackgroundColor = "#000000"
	}

	flexSpot.Comment = flexSpot.Comment + " [" + flexSpot.Mode + "] [" + flexSpot.SpotterCallsign + "]"
	flexSpot.Comment = strings.ReplaceAll(flexSpot.Comment, " ", "\u00A0")

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

	// send FlexSpot to HTTP Server
	if Cfg.General.HTTPServer {
		fc.FlexSpotChan <- flexSpot
	}

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

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

func (fc *FlexClient) ReadLine() {

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

		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 {
				fc.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 {
				fc.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
				fc.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])
			fc.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
}

func (fc *FlexClient) parseMessage(message string) {
	msgType := string(message[0])

	switch msgType {
	case "R":
		// reply
	case "S":
		// status
	case "V":
		// Version
	case "M":
		// Message
	}
}