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 (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"strconv"
	"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

// configure log output
func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
	// log to file
	f, err := os.OpenFile("rangermetrics.log", 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"`
	Bucket string `json:"bucket"`
}

func loadSettings() (dir string, result string, host string) {
	// print the current working directory

	var file *os.File
	var err error
	dir, err = os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	log.Println("getSettings", dir)
	// 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.json"); os.IsNotExist(err) {
		// file does not exist
		log.Println("settings.json does not exist")
		// create the file
		file, err = os.Create("settings.json")
		if err != nil {
			log.Fatal(err)
		}
		defer file.Close()
		// write the default settings to the file
		defaultSettings := influxSettings{
			Host:   "http://localhost:8086",
			Token:  "my-token",
			Org:    "my-org",
			Bucket: "my-bucket",
		}
		encoder := json.NewEncoder(file)
		err = encoder.Encode(defaultSettings)
		if err != nil {
			log.Fatal(err)
		}
		result = "settings.json created - please modify!"
		host = defaultSettings.Host
		return dir, result, host
	} else {
		// file exists
		log.Println("settings.json exists")
		// read the file
		file, err = os.Open("settings.json")
		if err != nil {
			log.Fatal(err)
		}
		defer file.Close()
		decoder := json.NewDecoder(file)
		err = decoder.Decode(&influxConnectionSettings)
		if err != nil {
			log.Fatal(err)
		}
		result = "settings.json read"
		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)
}

//aexport goRVExtensionVersion
// func goRVExtensionVersion(output *C.char, outputsize C.size_t) {
// 	return
// 	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) {
// 	return
// 	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)
// 	}

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

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 sanitize(s string) string {
	t := strings.ReplaceAll(s, "\"", "")
	return t
}

func sendToInflux(a3Data []string) {
	EXPECTED_PARAMS := 8

	var logData string
	functionName := "sendToInflux"

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

	// sanitize all elements
	for i, v := range a3Data {
		a3Data[i] = sanitize(v)
	}

	host := influxConnectionSettings.Host
	token := influxConnectionSettings.Token
	org := influxConnectionSettings.Org
	bucket := influxConnectionSettings.Bucket
	profile, locality := a3Data[0], a3Data[1]
	missionName, worldName, serverName := a3Data[2], a3Data[3], a3Data[4]
	metric := a3Data[5]
	valueType := a3Data[6]

	tags := map[string]string{
		"profile":    profile,
		"locality":   locality,
		"worldName":  worldName,
		"serverName": serverName,
	}
	fields := map[string]interface{}{
		"missionName": missionName,
		// "count": value,
	}

	// parse the value
	var err error
	// allow for float or int values, but remove any auto backslashes
	// check if includes certain strings
	if strings.Contains(valueType, "float") {
		fields["count"], err = strconv.ParseFloat(sanitize(a3Data[7]), 64)
	} else if strings.Contains(valueType, "int") {
		fields["count"], err = strconv.Atoi(sanitize(a3Data[7]))
	} else {
		logData = fmt.Sprintf("valueType must be 'float' or 'int': %s, %s, %s", metric, valueType, a3Data[7])
		logLine(functionName, logData, "ERROR")
		return
	}

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

	// influxDB init
	client := influxdb2.NewClientWithOptions(host, token, influxdb2.DefaultOptions().SetBatchSize(120))
	writeAPI := client.WriteAPIBlocking(org, bucket)

	p := influxdb2.NewPoint(metric, tags, fields, time.Now())

	// write synchronously
	err = writeAPI.WritePoint(context.Background(), p)

	if err != nil {
		logData = fmt.Sprintf("Error writing to InfluxDB: %s", err.Error())
		logLine(functionName, logData, "ERROR")
	}

	defer client.Close()

	logData = fmt.Sprintf("Sent to Influx: %s, %s", metric, a3Data[7])
	logLine(functionName, logData, "INFO")
}

func logLine(functionName string, data string, resultType string) {
	statusName := C.CString("RangerMetrics")
	defer C.free(unsafe.Pointer(statusName))
	statusFunction := C.CString(functionName)
	defer C.free(unsafe.Pointer(statusFunction))
	statusParam := C.CString(fmt.Sprintf(`["%s", "%s"]`, data, resultType))
	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) {
	temp := fmt.Sprintf("Hello %s!", C.GoString(input))
	// 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
	}
	C.memmove(unsafe.Pointer(output), unsafe.Pointer(result), size)

	// var temp string

	// logLine("goRVExtension", fmt.Sprintf("Input: %s", C.GoString(input)), "INFO")

	// switch C.GoString(input) {
	// case "version":
	// 	temp = fmt.Sprintf("%s", EXTENSION_VERSION)
	// case "loadSettings":
	// 	cwd, result, influxHost := loadSettings()
	// 	log.Println("CWD:", cwd)
	// 	log.Println("RESULT:", result)
	// 	log.Println("INFLUX HOST:", influxHost)
	// 	if result != "" {
	// 		temp = fmt.Sprintf(`["CWD: %s", "RESULT: %s", "%s"]`, cwd, result, influxHost)
	// 	}
	// default:
	// 	temp = fmt.Sprintf(`["ERR", "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
}

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

func main() {}
