package main

/*
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "extensionCallback.h"
*/
import "C" // This is required to import the C code

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"time"
	"unsafe"

	influxdb2 "github.com/influxdata/influxdb-client-go/v2"
)

var EXTENSION_VERSION string = "0.0.1"
var extensionCallbackFnc C.extensionCallback
var influxConnectionSettings influxSettings
var a3Settings arma3Settings

// InfluxDB variables
var DB_CLIENT influxdb2.Client

// file paths
var ADDON_FOLDER string = "./@RangerMetrics"
var LOG_FILE string = ADDON_FOLDER + "/rangermetrics.log"
var SETTINGS_FILE string = ADDON_FOLDER + "/settings.json"

// configure log output
func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	// log to file
	f, err := os.OpenFile(LOG_FILE, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		log.Fatalf("error opening file: %v", err)
	}
	// log to console as well
	log.SetOutput(io.MultiWriter(f, os.Stdout))
}

// func RVExtensionContext(output *C.char, argc *C.int) {

// }

type influxSettings struct {
	Host          string `json:"host"`
	Token         string `json:"token"`
	Org           string `json:"org"`
	DefaultBucket string `json:"defaultBucket"`
}

type arma3Settings struct {
	RefreshRateMs int `json:"refreshRateMs"`
}

type settingsJson struct {
	Influx influxSettings `json:"influxdb"`
	Arma3  arma3Settings  `json:"arma3"`
}

func connectToInflux() string {
	if influxConnectionSettings.Host == "" {
		logLine("connectToInflux", `["influxConnectionSettings.Host is empty", "ERROR"]`)
		return "ERROR"
	}

	DB_CLIENT = influxdb2.NewClientWithOptions(influxConnectionSettings.Host, influxConnectionSettings.Token, influxdb2.DefaultOptions().SetBatchSize(500).SetFlushInterval(2000))

	logLine("connectToInflux", `["DB_CLIENT created", "INFO"]`)
	return "CONNECTED"
}

func deinitialize() {
	logLine("deinitialize", `["deinitialize called", "INFO"]`)
	DB_CLIENT.Close()
}

func getDir() string {
	dir, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	return dir
}

func loadSettings() (dir string, result string, host string) {
	logLine("loadSettings", fmt.Sprintf(`["ADDON_FOLDER: %s", "INFO"]`, ADDON_FOLDER))
	logLine("loadSettings", fmt.Sprintf(`["LOG_FILE: %s", "INFO"]`, LOG_FILE))
	logLine("loadSettings", fmt.Sprintf(`["SETTINGS_FILE: %s", "INFO"]`, SETTINGS_FILE))

	// print the current working directory
	var file *os.File
	var err error
	// read settings from file
	// settings.json should be in the same directory as the .dll
	// see if the file exists
	if _, err = os.Stat(SETTINGS_FILE); os.IsNotExist(err) {
		// file does not exist
		log.Println("settings.json does not exist")
		// create the file
		file, err = os.Create(SETTINGS_FILE)
		if err != nil {
			log.Fatal(err)
		}
		defer file.Close()
		// write the default settings to the file
		ifSet := influxSettings{
			Host:          "http://localhost:8086",
			Token:         "my-token",
			Org:           "my-org",
			DefaultBucket: "my-bucket",
		}
		a3Set := arma3Settings{
			RefreshRateMs: 1000,
		}
		defaultSettings := map[string]interface{}{
			"influxdb": ifSet,
			"arma3":    a3Set,
		}
		encoder := json.NewEncoder(file)
		err = encoder.Encode(defaultSettings)
		if err != nil {
			log.Fatal(err)
		}
		result = `["settings.json created - please modify!", "WARN"]`
		host = ifSet.Host
		return dir, result, host
	} else {
		// file exists
		log.Println("settings.json exists")
		// read the file
		file, err = os.Open(SETTINGS_FILE)
		if err != nil {
			log.Fatal(err)
		}
		defer file.Close()
		decoder := json.NewDecoder(file)
		var settings settingsJson
		err = decoder.Decode(&settings)
		if err != nil {
			log.Fatal(err)
		}
		// set the settings
		influxConnectionSettings = settings.Influx
		a3Settings = settings.Arma3

		// set the result
		result = `["settings.json loaded", "INFO"]`
		host = influxConnectionSettings.Host
	}

	return dir, result, host
}

func runExtensionCallback(name *C.char, function *C.char, data *C.char) C.int {
	return C.runExtensionCallback(extensionCallbackFnc, name, function, data)
}

//export goRVExtensionVersion
func goRVExtensionVersion(output *C.char, outputsize C.size_t) {
	result := C.CString(EXTENSION_VERSION)
	defer C.free(unsafe.Pointer(result))
	var size = C.strlen(result) + 1
	if size > outputsize {
		size = outputsize
	}
	C.memmove(unsafe.Pointer(output), unsafe.Pointer(result), size)
}

//export goRVExtensionArgs
func goRVExtensionArgs(output *C.char, outputsize C.size_t, input *C.char, argv **C.char, argc C.int) {
	var offset = unsafe.Sizeof(uintptr(0))
	var out []string
	for index := C.int(0); index < argc; index++ {
		out = append(out, C.GoString(*argv))
		argv = (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + offset))
	}
	temp := fmt.Sprintf("Function: %s nb params: %d params: %s!", C.GoString(input), argc, out)

	// Return a result to Arma
	result := C.CString(temp)
	defer C.free(unsafe.Pointer(result))
	var size = C.strlen(result) + 1
	if size > outputsize {
		size = outputsize
	}

	if C.GoString(input) == "sendToInflux" {
		// start a goroutine to send the data to influx
		// param string is argv[0] which is the data to send to influx
		// go sendToInflux(out)
		go sendToInflux(&out)
	}

	C.memmove(unsafe.Pointer(output), unsafe.Pointer(result), size)
}

func callBackExample() {
	name := C.CString("arma")
	defer C.free(unsafe.Pointer(name))
	function := C.CString("funcToExecute")
	defer C.free(unsafe.Pointer(function))
	// Make a callback to Arma
	for i := 0; i < 3; i++ {
		time.Sleep(2 * time.Second)
		param := C.CString(fmt.Sprintf("Loop: %d", i))
		defer C.free(unsafe.Pointer(param))
		runExtensionCallback(name, function, param)
	}
}

func getUnixTimeNano() int64 {
	// get the current unix timestamp in nanoseconds
	return time.Now().UnixNano()
}

func trimQuotes(s string) string {
	// trim the start and end quotes from a string
	return strings.Trim(s, `"`)
}

func fixEscapeQuotes(s string) string {
	// fix the escape quotes in a string
	return strings.Replace(s, `""`, `"`, -1)
}

func sendToInflux(a3DataRaw *[]string) string {

	// convert to string array
	a3Data := *a3DataRaw

	var err error

	logLine("sendToInflux", fmt.Sprintf(`["Received %d params", "DEBUG"]`, len(a3Data)))

	MIN_PARAMS_COUNT := 1

	var logData string
	functionName := "sendToInflux"

	if len(a3Data) < MIN_PARAMS_COUNT {
		logData = fmt.Sprintf(`["Not all parameters present (got %d, expected at least %d)", "ERROR"]`, len(a3Data), MIN_PARAMS_COUNT)
		logLine(functionName, logData)
		return logData
	}

	// use custom bucket or default
	var customBucket string = trimQuotes(string(a3Data[0]))
	bucket := influxConnectionSettings.DefaultBucket
	if customBucket != "" {
		bucket = customBucket
	}

	WRITE_API := DB_CLIENT.WriteAPI(influxConnectionSettings.Org, bucket)

	if WRITE_API == nil {
		logData = `["Error creating write API", "ERROR"]`
		logLine(functionName, logData)
		return logData
	}

	// now we have our write client, we'll go through the rest of the receive array items in line protocol format and write them to influx
	for i := 1; i < len(a3Data); i++ {
		var p string = fixEscapeQuotes(trimQuotes(string(a3Data[i])))

		WRITE_API.WriteRecord(p)

		if err != nil {
			logData = fmt.Sprintf(`["Error parsing line protocol: %s", "ERROR"]`, err.Error())
			logLine(functionName, logData)
			return logData
		}
	}

	// schedule cleanup
	WRITE_API.Flush()
	logData = fmt.Sprintf(`["Wrote %d lines to influx", "INFO"]`, len(a3Data)-1)
	logLine(functionName, logData)

	return "Success"
}

func logLine(functionName string, data string) {
	statusName := C.CString("RangerMetrics")
	defer C.free(unsafe.Pointer(statusName))
	statusFunction := C.CString(functionName)
	defer C.free(unsafe.Pointer(statusFunction))
	statusParam := C.CString(data)
	defer C.free(unsafe.Pointer(statusParam))
	runExtensionCallback(statusName, statusFunction, statusParam)

	log.Println(data)
}

//export goRVExtension
func goRVExtension(output *C.char, outputsize C.size_t, input *C.char) {

	var temp string

	switch C.GoString(input) {
	case "version":
		logLine("goRVExtension", fmt.Sprintf(`["Input: %s",  "INFO"]`, C.GoString(input)))
		temp = EXTENSION_VERSION
	case "getDir":
		logLine("goRVExtension", fmt.Sprintf(`["Input: %s",  "INFO"]`, C.GoString(input)))
		temp = getDir()
	case "loadSettings":
		logLine("goRVExtension", fmt.Sprintf(`["Input: %s",  "INFO"]`, C.GoString(input)))
		cwd, result, influxHost := loadSettings()
		log.Println("CWD:", cwd)
		log.Println("RESULT:", result)
		log.Println("INFLUX HOST:", influxHost)
		if result != "" {
			logLine("goRVExtension", result)
			temp = fmt.Sprintf(
				`["%s", "%s", "%s", "%s", "%d"]`,
				EXTENSION_VERSION,
				influxConnectionSettings.Host,
				influxConnectionSettings.Org,
				influxConnectionSettings.DefaultBucket,
				a3Settings.RefreshRateMs,
			)
		}
	case "connectToInflux":
		logLine("goRVExtension", fmt.Sprintf(`["Input: %s",  "INFO"]`, C.GoString(input)))
		result := connectToInflux()
		temp = fmt.Sprintf(`["%s", "INFO"]`, result)
	case "getUnixTimeNano":
		temp = fmt.Sprintf(`["%d", "INFO"]`, getUnixTimeNano())
	case "deinitialize":
		logLine("goRVExtension", fmt.Sprintf(`["Input: %s",  "INFO"]`, C.GoString(input)))
		deinitialize()
		temp = `["Deinitializing", "INFO"]`
	default:
		temp = fmt.Sprintf(`["Unknown command: %s", "ERR"]`, C.GoString(input))
	}

	result := C.CString(temp)
	defer C.free(unsafe.Pointer(result))
	var size = C.strlen(result) + 1
	if size > outputsize {
		size = outputsize
	}

	C.memmove(unsafe.Pointer(output), unsafe.Pointer(result), size)
	// return
}

//export goRVExtensionRegisterCallback
func goRVExtensionRegisterCallback(fnc C.extensionCallback) {
	extensionCallbackFnc = fnc
}

func main() {}
