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 (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"reflect"
	"strconv"
	"strings"
	"time"
	"unsafe"

	influxdb2 "github.com/influxdata/influxdb-client-go/v2"
	"github.com/jackc/pgx/v4"
	"github.com/jackc/pgx/v4/pgxpool"
)

// declare list of functions available for call
var AVAILABLE_FUNCTIONS = map[string]interface{}{
	"initExtension":        initExtension,
	"deinitExtension":      deinitExtension,
	"loadSettings":         loadSettings,
	"connectToInflux":      connectToInflux,
	"writeToInflux":        writeToInflux,
	"connectToTimescale":   connectToTimescale,
	"initTimescale":        initTimescale,
	"writeToTimescale":     writeToTimescale,
	"getDir":               getDir,
	"sanitizeLineProtocol": sanitizeLineProtocol,
	"version":              version,
	"getUnixTimeNano":      getUnixTimeNano,
}

var EXTENSION_VERSION string = "0.0.1"
var extensionCallbackFnc C.extensionCallback

type ServerPollSetting struct {
	Name        string `json:"name"`
	Enabled     bool   `json:"enabled"`
	ServerOnly  bool   `json:"serverOnly"`
	IntervalMs  int    `json:"intervalMs"`
	Bucket      string `json:"bucket"`
	Measurement string `json:"measurement"`
	Description string `json:"description"`
}

var ServerPollSettingProperties []string = []string{
	"Name",
	"Enabled",
	"ServerOnly",
	"IntervalMs",
	"Bucket",
	"Measurement",
	"Description",
}

type CBAEventHandler struct {
	Name        string `json:"name"`
	Enabled     bool   `json:"enabled"`
	Description string `json:"description"`
}

var CBAEventHandlerProperties []string = []string{
	"Name",
	"Enabled",
	"Description",
}

type settingsJson struct {
	Influx struct {
		Enabled bool   `json:"enabled"`
		Host    string `json:"host"`
		Token   string `json:"token"`
		Org     string `json:"org"`
	} `json:"influxdb"`
	Timescale struct {
		Enabled       bool   `json:"enabled"`
		ConnectionUrl string `json:"connectionUrl"`
		DatabaseName  string `json:"databaseName"`
	} `json:"timescaledb"`
	Arma3 struct {
		RefreshRateMs int  `json:"refreshRateMs"`
		Debug         bool `json:"debug"`
	} `json:"arma3"`
	RecordingSettings map[string]interface{} `json:"recordingSettings"`
}

var activeSettings settingsJson

// InfluxDB variables
var influxClient influxdb2.Client

// TimescaleDB variables
var timescaleDbPool *pgxpool.Pool

// file paths
var ADDON_FOLDER string = getDir() + "\\@RangerMetrics"
var LOG_FILE string = ADDON_FOLDER + "\\rangermetrics.log"
var SETTINGS_FILE string = ADDON_FOLDER + "\\settings.json"
var SETTINGS_FILE_EXAMPLE string = ADDON_FOLDER + "\\settings.example.json"

// var BACKUP_FILE_PATH string = ADDON_FOLDER + "/local_backup.log.gzip"
// var BACKUP_WRITER *gzip.Writer

// 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))
	// log only to file
	log.SetOutput(f)
}

func initExtension() {
	functionName := "initExtension"
	logLine(functionName, `["Initializing RangerMetrics extension", "INFO"]`, false)
	logLine(functionName, fmt.Sprintf(`["RangerMetrics Extension Version: %v", "INFO"]`, EXTENSION_VERSION), false)

	var err error

	activeSettings, err = loadSettings()
	if err != nil {
		logLine(functionName, fmt.Sprintf(`["Error loading settings: %v", "ERROR"]`, err), false)
		return
	}

	if activeSettings.Influx.Enabled {
		influxClient, err = connectToInflux()
		if err != nil {
			logLine(functionName, fmt.Sprintf(`["Error connecting to InfluxDB: %v", "ERROR"]`, err), false)
			return
		}
	}
	if activeSettings.Timescale.Enabled {
		timescaleDbPool, err = connectToTimescale()
		if err != nil {
			logLine(functionName, fmt.Sprintf(`["Error connecting to TimescaleDB: %v", "ERROR"]`, err), false)
			return
		}

		initTimescale()
	}
	logLine(functionName, `["Extension ready", "INFO"]`, false)
}

func deinitExtension() {
	functionName := "deinitExtension"
	logLine(functionName, `["Deinitializing RangerMetrics extension", "INFO"]`, false)

	if timescaleDbPool != nil {
		logLine(functionName, `["Closing TimescaleDB connection", "INFO"]`, false)
		timescaleDbPool.Close()
	} else {
		logLine(functionName, `["TimescaleDB connection not open", "INFO"]`, false)
	}

	logLine(functionName, `[true]`, false)
}

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

// }

func version() {
	functionName := "version"
	logLine(functionName, fmt.Sprintf(`["RangerMetrics Extension Version:%s", "INFO"]`, EXTENSION_VERSION), false)
}

// return db client and error
func connectToInflux() (influxdb2.Client, error) {
	if activeSettings.Influx.Host == "" ||
		activeSettings.Influx.Host == "http://host:8086" {

		return nil, errors.New("influxConnectionSettings.Host is empty")
		// logLine("connectToInflux", `["Creating backup file", "INFO"]`)
		// file, err := os.Open(BACKUP_FILE_PATH)
		// if err != nil {
		// 	log.Fatal(err)
		// 	logLine("connectToInflux", `["Error opening backup file", "ERROR"]`)
		// }
		// BACKUP_WRITER = gzip.NewWriter(file)
		// if err != nil {
		// 	log.Fatal(err)
		// 	logLine("connectToInflux", `["Error creating gzip writer", "ERROR"]`)
		// }
		// return "Error connecting to Influx. Using local backup"
	}

	influxClient := influxdb2.NewClientWithOptions(activeSettings.Influx.Host, activeSettings.Influx.Token, influxdb2.DefaultOptions().SetBatchSize(1000).SetFlushInterval(1000))

	return influxClient, nil
}

//////////////////////////////////
// TIMESCALE
//////////////////////////////////

func connectToTimescale() (*pgxpool.Pool, error) {
	functionName := "connectToTimescale"
	var err error

	// urlExample := "postgres://username:password@localhost:5432/database_name"
	// logLine("connectToTimescale", fmt.Sprintf(`["timescaleConnectionSettings.ConnectionUrl: %s", "INFO"]`, timescaleConnectionSettings.ConnectionUrl))
	conn, err := pgx.Connect(context.Background(), activeSettings.Timescale.ConnectionUrl+"/postgres")

	if err != nil {
		logLine(
			functionName,
			fmt.Sprintf(`["Error connecting to Timescale DB: %v", "ERROR"]`, err.Error()),
			false,
		)
		return nil, err
	}

	// ensure database exists
	logLine(
		functionName,
		fmt.Sprintf(`["TimescaleDB: 'CREATE DATABASE %s'", "INFO"]`, activeSettings.Timescale.DatabaseName),
		false,
	)
	conn.Query(context.Background(), fmt.Sprintf(`CREATE DATABASE %s`, activeSettings.Timescale.DatabaseName))

	// close connection
	conn.Close(context.Background())

	// create connection pool
	var dbPool *pgxpool.Pool
	dbPool, err = pgxpool.Connect(
		context.Background(),
		fmt.Sprintf(`%s/%s`, activeSettings.Timescale.ConnectionUrl, activeSettings.Timescale.DatabaseName),
	)
	if err != nil {
		logLine(
			functionName,
			fmt.Sprintf(`["Error connecting to Timescale DB: %v", "ERROR"]`, err.Error()),
			false,
		)
		return nil, err
	}

	logLine("connectToTimescale", `["Connected to Timescale successfully", "INFO"]`, false)
	return dbPool, nil
}

func initTimescale() {

	functionName := "initTimescale"
	var err error

	// schema init sql
	var tableCreationSql string = `
CREATE TABLE "Missions" (
	"id" serial NOT NULL UNIQUE,
	"world_name" VARCHAR(255) NOT NULL,
	"briefing_name" VARCHAR(255) NOT NULL,
	"mission_name" VARCHAR(255) NOT NULL,
	"mission_author" VARCHAR(255) NOT NULL,
	"server_name" VARCHAR(255) NOT NULL,
	"server_mods" TEXT,
	"ace_medical" BOOLEAN NOT NULL,
	"radio_tfar" BOOLEAN NOT NULL,
	"radio_acre" BOOLEAN NOT NULL,
	"start_game" TIMESTAMP NOT NULL,
	"start_utc" TIMESTAMP NOT NULL,
	"frame_count" FLOAT NOT NULL,
	"capture_delay_s" FLOAT NOT NULL,
	"addon_ver_major" integer NOT NULL,
	"addon_ver_minor" integer NOT NULL,
	"addon_ver_patch" integer NOT NULL,
	"extension_ver_major" integer NOT NULL,
	"extension_ver_minor" integer NOT NULL,
	"extension_ver_patch" integer NOT NULL,
	"tags" VARCHAR(255) NOT NULL,
	CONSTRAINT "Missions_pk" PRIMARY KEY ("id")
) WITH (
  OIDS=FALSE
);



CREATE TABLE "Worlds" (
	"world_name" VARCHAR(255) NOT NULL,
	"display_name" VARCHAR(255) NOT NULL,
	"world_size_m" integer NOT NULL,
	CONSTRAINT "Worlds_pk" PRIMARY KEY ("world_name")
) WITH (
  OIDS=FALSE
);



CREATE TABLE "Units" (
	"mission_id" integer NOT NULL,
	"unit_id" integer NOT NULL,
	"frame" integer NOT NULL,
	"steamid64" varchar(100),
	"steam_name" VARCHAR(255) NOT NULL,
	"a3_profile_name" VARCHAR(255) NOT NULL,
	"is_human" BOOLEAN NOT NULL,
	"is_afk" BOOLEAN NOT NULL,
	"is_alive" BOOLEAN NOT NULL,
	"unit_type" VARCHAR(255) NOT NULL,
	"role_description" VARCHAR(255),
	"side" integer NOT NULL,
	"group_id" varchar(100) NOT NULL,
	"name" VARCHAR(255),
	"position" VARCHAR(255) NOT NULL,
	"direction" FLOAT NOT NULL,
	"anim_state" varchar(100),
	"stance" VARCHAR(255),
	"traits" VARCHAR(255),
	"damage" FLOAT NOT NULL,
	"is_speaking" integer NOT NULL,
	CONSTRAINT "Units_pk" PRIMARY KEY ("mission_id","unit_id","frame")
) WITH (
  OIDS=FALSE
);



CREATE TABLE "Vehicles" (
	"mission_id" integer NOT NULL,
	"frame" integer NOT NULL,
	"vehicle_id" integer NOT NULL,
	"object_type" VARCHAR(255),
	"weapons" varchar(3000),
	"customization" varchar(1000),
	"position" VARCHAR(255) NOT NULL,
	"direction" FLOAT NOT NULL,
	"vector_dir" varchar(70) NOT NULL,
	"vector_up" varchar(70) NOT NULL,
	"is_alive" BOOLEAN NOT NULL,
	"damage" FLOAT NOT NULL,
	CONSTRAINT "Vehicles_pk" PRIMARY KEY ("mission_id","frame","vehicle_id")
) WITH (
  OIDS=FALSE
);



CREATE TABLE "Markers" (
	"mission_id" integer NOT NULL,
	"frame" integer NOT NULL,
	"marker_name" VARCHAR(255) NOT NULL,
	"data" VARCHAR(255) NOT NULL,
	CONSTRAINT "Markers_pk" PRIMARY KEY ("mission_id","frame","marker_name")
) WITH (
  OIDS=FALSE
);



CREATE TABLE "Chat" (
	"id" serial NOT NULL,
	"mission_id" integer NOT NULL,
	"sender_id" integer NOT NULL,
	"frame" integer NOT NULL,
	"timestamp_utc" TIMESTAMP NOT NULL,
	"content" VARCHAR(255) NOT NULL,
	"channel" integer NOT NULL,
	CONSTRAINT "Chat_pk" PRIMARY KEY ("id")
) WITH (
  OIDS=FALSE
);



CREATE TABLE "UniqueUsers" (
	"steamid64" varchar(100) NOT NULL,
	CONSTRAINT "UniqueUsers_pk" PRIMARY KEY ("steamid64")
) WITH (
  OIDS=FALSE
);



CREATE TABLE "Environment" (
	"mission_id" integer NOT NULL,
	"frame" integer NOT NULL,
	"date_game" TIMESTAMP NOT NULL,
	"date_utc" TIMESTAMP NOT NULL,
	"overcast" FLOAT NOT NULL,
	"rain" FLOAT NOT NULL,
	"humidity" FLOAT NOT NULL,
	"fog_value" FLOAT NOT NULL,
	"fog_decay" FLOAT NOT NULL,
	"fog_base" FLOAT NOT NULL,
	"wind_vector" VARCHAR(255) NOT NULL,
	"gusts" FLOAT NOT NULL,
	"waves" FLOAT NOT NULL,
	CONSTRAINT "Environment_pk" PRIMARY KEY ("mission_id","frame")
) WITH (
  OIDS=FALSE
);



CREATE TABLE "StaticObjects" (
	"mission_id" integer NOT NULL,
	"frame" integer NOT NULL,
	"building_id" integer NOT NULL,
	"position" integer NOT NULL,
	"direction" FLOAT NOT NULL,
	"vector_dir" varchar(70) NOT NULL,
	"vector_up" varchar(70) NOT NULL,
	"object_type" VARCHAR(255) NOT NULL,
	"classname" VARCHAR(255) NOT NULL,
	"simple_object_data" varchar(1500) NOT NULL,
	CONSTRAINT "StaticObjects_pk" PRIMARY KEY ("mission_id","frame","building_id")
) WITH (
  OIDS=FALSE
);



CREATE TABLE "Campaigns" (
	"id" serial NOT NULL,
	"name" VARCHAR(255) NOT NULL,
	"description" TEXT NOT NULL,
	"image" bytea NOT NULL,
	CONSTRAINT "Campaigns_pk" PRIMARY KEY ("id")
) WITH (
  OIDS=FALSE
);



CREATE TABLE "Missions_In_Campaigns" (
	"mission" integer NOT NULL,
	"campaign" integer NOT NULL,
	CONSTRAINT "Missions_In_Campaigns_pk" PRIMARY KEY ("mission","campaign")
) WITH (
  OIDS=FALSE
);
`

	relationCreationSql := []string{
		`ALTER TABLE "Missions" ADD CONSTRAINT "Missions_fk0" FOREIGN KEY ("world_name") REFERENCES "Worlds"("world_name");`,

		`ALTER TABLE "Units" ADD CONSTRAINT "Units_fk0" FOREIGN KEY ("mission_id") REFERENCES "Missions"("id");`,
		`ALTER TABLE "Units" ADD CONSTRAINT "Units_fk1" FOREIGN KEY ("steamid64") REFERENCES "UniqueUsers"("steamid64");`,

		`ALTER TABLE "Vehicles" ADD CONSTRAINT "Vehicles_fk0" FOREIGN KEY ("mission_id") REFERENCES "Missions"("id");`,

		`ALTER TABLE "Markers" ADD CONSTRAINT "Markers_fk0" FOREIGN KEY ("mission_id") REFERENCES "Missions"("id");`,

		`ALTER TABLE "Chat" ADD CONSTRAINT "Chat_fk0" FOREIGN KEY ("mission_id") REFERENCES "Missions"("id");`,
		`ALTER TABLE "Chat" ADD CONSTRAINT "Chat_fk1" FOREIGN KEY ("sender_id") REFERENCES "Units"("unit_id");`,

		`ALTER TABLE "Environment" ADD CONSTRAINT "Environment_fk0" FOREIGN KEY ("mission_id") REFERENCES "Missions"("id");`,

		`ALTER TABLE "StaticObjects" ADD CONSTRAINT "StaticObjects_fk0" FOREIGN KEY ("mission_id") REFERENCES "Missions"("id");`,

		`ALTER TABLE "Missions_In_Campaigns" ADD CONSTRAINT "Missions_In_Campaigns_fk0" FOREIGN KEY ("mission") REFERENCES "Missions"("id");`,
		`ALTER TABLE "Missions_In_Campaigns" ADD CONSTRAINT "Missions_In_Campaigns_fk1" FOREIGN KEY ("campaign") REFERENCES "Campaigns"("id");`,
	}

	var tx pgx.Tx
	tx, err = timescaleDbPool.Begin(context.Background())
	if err != nil {
		logLine(functionName, fmt.Sprintf(`["Error creating transaction: %v", "ERROR"]`, err.Error()), false)
		return
	}

	_, err = tx.Exec(context.Background(), tableCreationSql)
	if err != nil {
		logLine(functionName, fmt.Sprintf(`["Error creating tables: %v", "ERROR"]`, err.Error()), false)
		tx.Rollback(context.Background())
		tx.Commit(context.Background())
		return
	}
	err = tx.Commit(context.Background())
	if err != nil {
		logLine(functionName, fmt.Sprintf(`["Error committing table creation transaction: %v", "ERROR"]`, err.Error()), false)
		return
	}

	// run each relation creation sql statement
	for _, sql := range relationCreationSql {
		_, err = tx.Exec(context.Background(), sql)

		if err != nil {
			logLine(functionName, fmt.Sprintf(`["Error creating relation: %v", "ERROR"]`, err.Error()), false)
			tx.Rollback(context.Background())
			tx.Commit(context.Background())
			return
		}
	}

	err = tx.Commit(context.Background())
	if err != nil {
		logLine(functionName, fmt.Sprintf(`["Error committing relation creation transaction: %v", "ERROR"]`, err.Error()), false)
		return
	}

	logLine(functionName, `["Timescale schema initialized", "INFO"]`, false)

}

func writeToTimescale(table string, line string) {
	// logLine("writeToTimescale", fmt.Sprintf(`["line: %s", "INFO"]`, line))
	functionName := "writeToTimescale"
	_, err := timescaleDbPool.Exec(context.Background(), "INSERT INTO %1 (time, line) VALUES (NOW(), $2);", table, line)
	if err != nil {
		logLine(functionName, fmt.Sprintf(`["Error writing to timescale: %v", "ERROR"]`, err.Error()), false)
	}
}

func writeToInflux(a3DataRaw *[]string) string {

	// convert to string array
	a3Data := *a3DataRaw

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

	MIN_PARAMS_COUNT := 1

	var logData string
	functionName := "writeToInflux"

	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, false)
		return logData
	}

	// use custom bucket or default
	var bucket string = fixEscapeQuotes(trimQuotes(string(a3Data[0])))

	// Get non-blocking write client
	WRITE_API := influxClient.WriteAPI(activeSettings.Influx.Org, bucket)

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

	// Get errors channel
	errorsCh := WRITE_API.Errors()
	go func() {
		for writeErr := range errorsCh {
			logData = fmt.Sprintf(`["Error parsing line protocol: %s", "ERROR"]`, strings.Replace(writeErr.Error(), `"`, `'`, -1))
			logLine(functionName, logData, false)
		}
	}()

	// 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 the line to influx
		WRITE_API.WriteRecord(p)

		// TODO: Add backup writer
		// // append backup line to file if BACKUP_WRITER is set
		// //
		// if BACKUP_WRITER != nil {
		// 	_, err = BACKUP_WRITER.Write([]byte(p + "\n"))
		// }

	}

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

	return "Success"
}

// sanitize line protocol for influx
func sanitizeLineProtocol(line string) string {
	// replace all spaces with underscores
	// line = strings.ReplaceAll(line, ` `, `\ `)
	// replace all commas with underscores
	// line = strings.ReplaceAll(line, `,`, `\,`)
	// replace all equals with underscores
	// line = strings.ReplaceAll(line, "=", "_")
	// replace all quotes with underscores
	// line = strings.ReplaceAll(line, "\"", "_")

	return line
}

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

// return true if the program should continue
func loadSettings() (settingsJson, error) {
	functionName := "loadSettings"
	logLine(functionName, fmt.Sprintf(`["ADDON_FOLDER: %s", "INFO"]`, ADDON_FOLDER), false)
	logLine(functionName, fmt.Sprintf(`["LOG_FILE: %s", "INFO"]`, LOG_FILE), false)
	logLine(functionName, fmt.Sprintf(`["SETTINGS_FILE: %s", "INFO"]`, SETTINGS_FILE), false)

	settings := settingsJson{}

	// 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
		logLine(
			functionName,
			fmt.Sprintf(`["%s does not exist", "ERROR"]`, SETTINGS_FILE),
			false,
		)
		// copy settings.json.example to settings.json
		// load contents
		fileContents, err := ioutil.ReadFile(SETTINGS_FILE_EXAMPLE)
		if err != nil {
			return settings, err
		}
		// write contents to settings.json
		err = ioutil.WriteFile(SETTINGS_FILE, fileContents, 0644)
		if err != nil {
			return settings, err
		}

		// Exit false to discontinue initialization since settings are defaults
		logLine(functionName, `["CREATED SETTINGS"]`, false)
		// return a new error
		return settings, errors.New("settings.json does not exist")
	} else {
		// file exists
		logLine(functionName, `["settings.json found", "DEBUG"]`, true)
		// read the file
		file, err = os.Open(SETTINGS_FILE)
		if err != nil {
			return settings, err
		}
		defer file.Close()
		decoder := json.NewDecoder(file)
		err = decoder.Decode(&settings)
		if err != nil {
			return settings, err
		}

		// send contents of settings file
		// get the file contents
		fileContents, err := ioutil.ReadFile(SETTINGS_FILE)
		if err != nil {
			return settings, err
		}

		// compact the json
		var jsonStr bytes.Buffer
		err = json.Compact(&jsonStr, fileContents)
		if err != nil {
			return settings, err
		}

		logLine(
			"loadSettingsJSON",
			jsonStr.String(),
			false,
		)

	}

	return settings, nil
}

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

	var temp string
	temp = fmt.Sprintf("Function: %s nb params: %d params: %s!", C.GoString(input), argc, out)

	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 writeToInflux(&out)
		temp = fmt.Sprintf("Function: %s nb params: %d", C.GoString(input), argc)
	}

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

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 logLine(functionName string, data string, isDebug bool) {
	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)

	if activeSettings.Arma3.Debug && isDebug {
		log.Println(data)
	} else if !isDebug {
		log.Println(data)
	}
}

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

	var temp string

	// logLine("goRVExtension", fmt.Sprintf(`["Input: %s",  "DEBUG"]`, C.GoString(input)), true)

	switch C.GoString(input) {
	case "version":
		temp = EXTENSION_VERSION
	case "getDir":
		temp = getDir()
	case "getUnixTimeNano":
		time := getUnixTimeNano()
		temp = fmt.Sprintf(`["%s"]`, strconv.FormatInt(time, 10))

	default:
		// check if input is in AVAILABLE_FUNCTIONS
		// if not, return error
		// if yes, continue
		if _, ok := AVAILABLE_FUNCTIONS[C.GoString(input)]; !ok {
			temp = fmt.Sprintf(`["Function: %s not found!", "ERROR"]`, C.GoString(input))
		} else {
			// call the function by name
			go reflect.ValueOf(AVAILABLE_FUNCTIONS[C.GoString(input)]).Call([]reflect.Value{})
			temp = fmt.Sprintf(`["Function: %s called successfully", "DEBUG"]`, C.GoString(input))
		}
	}

	// 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, timescaleUrl := loadSettings()
	// 	log.Println("CWD:", cwd)
	// 	log.Println("RESULT:", result)
	// 	log.Println("INFLUX HOST:", influxHost)
	// 	log.Println("TIMESCALE URL:", timescaleUrl)
	// 	if result != "" {
	// 		logLine("goRVExtension", result)
	// 		temp = fmt.Sprintf(
	// 			`["%s", "%s", "%s", "%d"]`,
	// 			EXTENSION_VERSION,
	// 			influxConnectionSettings.Host,
	// 			influxConnectionSettings.Org,
	// 			a3Settings.RefreshRateMs,
	// 		)
	// 	}
	// case "connectToInflux":
	// 	// logLine("goRVExtension", fmt.Sprintf(`["Input: %s",  "INFO"]`, C.GoString(input)))
	// 	go connectToInflux()
	// 	temp = `["Connecting to InfluxDB", "INFO"]`
	// case "connectToTimescale":
	// 	// logLine("goRVExtension", fmt.Sprintf(`["Input: %s",  "INFO"]`, C.GoString(input)))
	// 	go connectToTimescale()
	// 	temp = `["Connecting to TimescaleDB", "INFO"]`
	// 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", "ERROR"]`, 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() {}
