package main

import (
	"fmt"
	"github.com/tarm/serial"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// read serial buffer
func readUntilEmpty(p *serial.Port) {
	p.Flush()
	time.Sleep(100 * time.Millisecond)
	ignorebuf := make([]byte, 1024)
	p.Read(ignorebuf)
}

// returns last line of output
func sendCommand(p *serial.Port, command string) string {
	fmt.Printf("%s\n", command)
	var first_line string = ""
	p.Flush()
	time.Sleep(500 * time.Millisecond)
	readUntilEmpty(p)
	_, err := p.Write([]byte(command + "\n"))
	if err != nil {
		log.Fatal(err)
	}
	buf := make([]byte, 1024)

	time.Sleep(500 * time.Millisecond)
	n, _ := p.Read(buf)
	if n > 0 {
		lines := strings.Split(string(buf[:n]), "\n")
		//fmt.Printf("%+q\n", lines)
		first_line = lines[0]

	}
	return first_line
}

func getDevEui(p *serial.Port) string {
	var reply = sendCommand(p, "AT+DADDR=?")
	//fmt.Printf("XXX%sYYY\n",reply)
	if len(reply) >= 10 {
		return "a8 40 41 00 " + reply[0:11]
	} else {
		return ""
	}
}

func getFWVersion(p *serial.Port) string {
	var reply = sendCommand(p, "AT+VER=?")
	//fmt.Printf("XXX%sYYY\n",reply)
	if len(reply) >= 10 {
		return reply
	} else {
		return ""
	}
}

func getAppKey(deveui string) string {
	if len(deveui) == 23 {
		mdeveui := strings.ReplaceAll(deveui, " ", "")
		appkey := fmt.Sprintf("%c%c %c%c %c%c %c%c %c%c %c%c %c%c %c%c %c%c %c%c %c%c %c%c %c%c %c%c %c%c %c%c",
			mdeveui[10],
			mdeveui[3],
			mdeveui[4],
			mdeveui[7],
			mdeveui[15],
			mdeveui[9],
			mdeveui[11],
			mdeveui[2],
			mdeveui[0],
			mdeveui[8],
			mdeveui[1],
			mdeveui[6],
			mdeveui[5],
			mdeveui[12],
			mdeveui[14],
			mdeveui[13],
			mdeveui[3],
			mdeveui[6],
			mdeveui[12],
			mdeveui[7],
			mdeveui[15],
			mdeveui[1],
			mdeveui[9],
			mdeveui[11],
			mdeveui[2],
			mdeveui[10],
			mdeveui[0],
			mdeveui[8],
			mdeveui[5],
			mdeveui[14],
			mdeveui[4],
			mdeveui[13])

		fmt.Printf("calculated AppKey: %s\n", appkey)
		return appkey

	} else {
		fmt.Printf("devEui has wrong length: %s\n", deveui)
		return ""
	}
}

func readSerial(p *serial.Port) {
	p.Flush()
	buf := make([]byte, 32)

	time.Sleep(200 * time.Millisecond)
	n, _ := p.Read(buf)
	for n > 0 {
		// ignoring error as EOF raises error on Linux
		reply := string(buf[:n])
		fmt.Printf("%s", reply)
		n, _ = p.Read(buf)
	}
}

func isValidTXP(txp string) bool {
	switch txp {
	case
		"0", "1", "2", "3", "4", "5", "-1":
		return true
	}
	return false
}

func main() {
	if len(os.Args) != 3 {
		fmt.Println("usage: " + filepath.Base(os.Args[0]) + " <SerialDevice> <txp: 0-5, -1=adr>")
		os.Exit(1)
	}

	serialdev := os.Args[1]
	txp := os.Args[2]
	if !(isValidTXP(txp)) {
		fmt.Println("invalid TXP, valid values are 0-5 and -1 (ADR)")
		os.Exit(2)
	}

	appEui := "a8 40 41 35 10 35 10 35"

	c := &serial.Config{Name: serialdev, Baud: 9600, ReadTimeout: time.Second * 1}
	s, err := serial.OpenPort(c)
	if err != nil {
		log.Fatal("Problem mit Serial Interface")
	}

	location, err := time.LoadLocation("Europe/Zurich")

	f, err := os.OpenFile("setup-node.log",
		os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Println(err)
	}
	defer f.Close()

	sendCommand(s, "AT")
	sendCommand(s, "ATNJM=0")
	devEui := getDevEui(s)
	FWVersion := getFWVersion(s)
	if devEui != "" {
		fmt.Printf("devEui: %s\n", devEui)
		sendCommand(s, "AT+APPEUI="+appEui)
		appkey := getAppKey(devEui)
		sendCommand(s, "AT+APPKEY="+appkey)
		sendCommand(s, "AT+FDR")
		sendCommand(s, "AT+FDR")
		sendCommand(s, "AT+LON=0")
		sendCommand(s, "AT+NMEA353=4")
		if txp != "-1" {
			// we use no ADR
			sendCommand(s, "AT+ADR=0")
			sendCommand(s, "AT+TXP="+txp)
		}
		sendCommand(s, "ATNJM=1")
		sendCommand(s, "ATZ")
		logline := strings.ToUpper(fmt.Sprintf("%s: %s %s %s %s\n", time.Now().In(location).Format("02.01.2006 15:04"), strings.ReplaceAll(devEui, " ", ""), strings.ReplaceAll(appEui, " ", ""), strings.ReplaceAll(appkey, " ", ""), FWVersion))
		if _, err := f.WriteString(logline); err != nil {
			log.Println(err)
		}
		readSerial(s)
	} else {
		log.Fatal("Could not get devEui")
	}
}
