package main

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"sync"
	"time"

	//"./utils"
	"os/exec"

	"github.com/boltdb/bolt"
	"gopkg.in/natefinch/lumberjack.v2"

	"github.com/yosssi/gmq/mqtt"
	"github.com/yosssi/gmq/mqtt/client"

	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"
)

const (
	// Time allowed to write the file to the client.
	writeWait = 10 * time.Second

	// Time allowed to read the next pong message from the client.
	pongWait = 60 * time.Second

	// Send pings to client with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10

	beaconPeriod = 2 * time.Second
)

// data structures

type Settings struct {
	Location_confidence  int64 `json:"location_confidence"`
	Last_seen_threshold  int64 `json:"last_seen_threshold"`
	Beacon_metrics_size  int   `json:"beacon_metrics_size"`
	HA_send_interval     int64 `json:"ha_send_interval"`
	HA_send_changes_only bool  `json:"ha_send_changes_only"`
}

type Incoming_json struct {
	Hostname         string `json:"hostname"`
	MAC              string `json:"mac"`
	RSSI             int64  `json:"rssi"`
	Is_scan_response string `json:"is_scan_response"`
	Ttype            string `json:"type"`
	Data             string `json:"data"`
	Beacon_type      string `json:"beacon_type"`
	UUID             string `json:"uuid"`
	Major            string `json:"major"`
	Minor            string `json:"minor"`
	TX_power         string `json:"tx_power"`
	Namespace        string `json:"namespace"`
	Instance_id      string `json:"instance_id"`
	// button stuff
	HB_ButtonCounter      int64  `json:"hb_button_counter"`
	HB_ButtonCounter_Prev int64  `json:"hb_button_counter"`
	HB_Battery            int64  `json:"hb_button_battery"`
	HB_RandomNonce        string `json:"hb_button_random"`
	HB_ButtonMode         string `json:"hb_button_mode"`
}

type Advertisement struct {
	ttype   string
	content string
	seen    int64
}

type beacon_metric struct {
	location  string
	distance  float64
	rssi      int64
	timestamp int64
}

type Location struct {
	name string
	lock sync.RWMutex
}

type Best_location struct {
	distance  float64
	name      string
	last_seen int64
}

type HTTP_location struct {
	Previous_confident_location string  `json:"previous_confident_location"`
	Distance                    float64 `json:"distance"`
	Name                        string  `json:"name"`
	Beacon_name                 string  `json:"beacon_name"`
	Beacon_id                   string  `json:"beacon_id"`
	Beacon_type                 string  `json:"beacon_type"`
	HB_Battery                  int64   `json:"hb_button_battery"`
	HB_ButtonMode               string  `json:"hb_button_mode"`
	HB_ButtonCounter            int64   `json:"hb_button_counter"`
	Location                    string  `json:"location"`
	Last_seen                   int64   `json:"last_seen"`
}

type Location_change struct {
	Beacon_ref        Beacon `json:"beacon_info"`
	Name              string `json:"name"`
	Beacon_name       string `json:"beacon_name"`
	Previous_location string `json:"previous_location"`
	New_location      string `json:"new_location"`
	Timestamp         int64  `json:"timestamp"`
}

type HA_message struct {
	Beacon_id   string  `json:"id"`
	Beacon_name string  `json:"name"`
	Distance    float64 `json:"distance"`
}

type HTTP_locations_list struct {
	Beacons []HTTP_location `json:"beacons"`
	//Buttons []Button        `json:"buttons"`
}

type Beacon struct {
	Name                        string        `json:"name"`
	Beacon_id                   string        `json:"beacon_id"`
	Beacon_type                 string        `json:"beacon_type"`
	Beacon_location             string        `json:"beacon_location"`
	Last_seen                   int64         `json:"last_seen"`
	Incoming_JSON               Incoming_json `json:"incoming_json"`
	Distance                    float64       `json:"distance"`
	Previous_location           string
	Previous_confident_location string
	expired_location            string
	Location_confidence         int64
	Location_history            []string
	beacon_metrics              []beacon_metric

	HB_ButtonCounter      int64  `json:"hb_button_counter"`
	HB_ButtonCounter_Prev int64  `json:"hb_button_counter"`
	HB_Battery            int64  `json:"hb_button_battery"`
	HB_RandomNonce        string `json:"hb_button_random"`
	HB_ButtonMode         string `json:"hb_button_mode"`
}

type Button struct {
	Name            string        `json:"name"`
	Button_id       string        `json:"button_id"`
	Button_type     string        `json:"button_type"`
	Button_location string        `json:"button_location"`
	Incoming_JSON   Incoming_json `json:"incoming_json"`
	Distance        float64       `json:"distance"`
	Last_seen       int64         `json:"last_seen"`

	HB_ButtonCounter int64  `json:"hb_button_counter"`
	HB_Battery       int64  `json:"hb_button_battery"`
	HB_RandomNonce   string `json:"hb_button_random"`
	HB_ButtonMode    string `json:"hb_button_mode"`
}

type Beacons_list struct {
	Beacons map[string]Beacon `json:"beacons"`
	lock    sync.RWMutex
}

type Locations_list struct {
	locations map[string]Location
	lock      sync.RWMutex
}

var clients = make(map[*websocket.Conn]bool) // connected clients
var broadcast = make(chan Message)           // broadcast channel

// Define our message object
type Message struct {
	Email    string `json:"email"`
	Username string `json:"username"`
	Message  string `json:"message"`
}

// Struttura per il parsing JSON multiplo
type RawReading struct {
	Timestamp string `json:"timestamp"`
	Type      string `json:"type"`
	MAC       string `json:"mac"`
	RSSI      int    `json:"rssi"`
	RawData   string `json:"rawData"`
}

// GLOBALS

var BEACONS Beacons_list

var Buttons_list map[string]Button

var cli *client.Client

var http_results HTTP_locations_list
var http_results_lock sync.RWMutex

var Latest_beacons_list map[string]Beacon
var latest_list_lock sync.RWMutex

var db *bolt.DB
var err error

var world = []byte("presence")

var settings = Settings{
	Location_confidence:  4,
	Last_seen_threshold:  15,
	Beacon_metrics_size:  30,
	HA_send_interval:     5,
	HA_send_changes_only: false,
}

// utility function
func parseButtonState(raw string) int64 {
	raw = strings.ToUpper(raw)

	// Minew B7 / C7 / D7 - frame tipo: 0201060303E1FF1216E1FFA103...
	if strings.HasPrefix(raw, "0201060303E1FF12") && len(raw) >= 38 {
		// La posizione 34-38 (indice 26:30) contiene il buttonCounter su 2 byte (hex)
		buttonField := raw[34:38] // NB: offset 34-38  zero-based
		if buttonValue, err := strconv.ParseInt(buttonField, 16, 64); err == nil {
			return buttonValue
		}
	}

	// Ingics (02010612FF590)
	if strings.HasPrefix(raw, "02010612FF590") && len(raw) >= 24 {
		counterField := raw[22:24]
		buttonState, err := strconv.ParseInt(counterField, 16, 64)
		if err == nil {
			return buttonState
		}
	}

	// Aggiungeremo qui facilmente nuovi beacon in futuro

	return 0
}

func twos_comp(inp string) int64 {
	i, _ := strconv.ParseInt("0x"+inp, 0, 64)

	return i - 256
}

func getBeaconID(incoming Incoming_json) string {
	unique_id := fmt.Sprintf("%s", incoming.MAC)
	/*if incoming.Beacon_type == "ibeacon" {
		unique_id = fmt.Sprintf("%s_%s_%s", incoming.UUID, incoming.Major, incoming.Minor)
	} else if incoming.Beacon_type == "eddystone" {
		unique_id = fmt.Sprintf("%s_%s", incoming.Namespace, incoming.Instance_id)
	} else if incoming.Beacon_type == "hb_button" {
		unique_id = fmt.Sprintf("%s_%s", incoming.Namespace, incoming.Instance_id)
	}*/
	return unique_id
}

func incomingBeaconFilter(incoming Incoming_json) Incoming_json {
	out_json := incoming
	if incoming.Beacon_type == "hb_button" {
		//do additional checks here to detect if a Habby Bubbles Button
		// looks like 020104020a0011ff045600012d3859db59e1000b9453

		raw_data := incoming.Data
		//company_id := []byte{0x04, 0x56}
		//product_id := []byte{0x00, 0x01}
		hb_button_prefix_str := fmt.Sprintf("02010612FF5900")
		if strings.HasPrefix(raw_data, hb_button_prefix_str) {
			out_json.Namespace = "ddddeeeeeeffff5544ff"
			//out_json.Instance_id = raw_data[24:36]
			counter_str := fmt.Sprintf("0x%s", raw_data[22:24])
			counter, _ := strconv.ParseInt(counter_str, 0, 64)
			out_json.HB_ButtonCounter = counter

			battery_str := fmt.Sprintf("0x%s%s", raw_data[20:22], raw_data[18:20])
			////fmt.Println("battery  has %s\n", battery_str)

			battery, _ := strconv.ParseInt(battery_str, 0, 64)
			out_json.HB_Battery = battery

			out_json.TX_power = fmt.Sprintf("0x%s", "4")

			out_json.Beacon_type = "hb_button"
			out_json.HB_ButtonMode = "presence_button"

			///fmt.Println("Button adv has %#v\n", out_json)
		}
	}

	return out_json
}

func processButton(bbeacon Beacon, cl *client.Client) {
	btn := Button{Name: bbeacon.Name}
	btn.Button_id = bbeacon.Beacon_id
	btn.Button_type = bbeacon.Beacon_type
	btn.Button_location = bbeacon.Previous_location
	btn.Incoming_JSON = bbeacon.Incoming_JSON
	btn.Distance = bbeacon.Distance
	btn.Last_seen = bbeacon.Last_seen
	btn.HB_ButtonCounter = bbeacon.HB_ButtonCounter
	btn.HB_Battery = bbeacon.HB_Battery
	btn.HB_RandomNonce = bbeacon.HB_RandomNonce
	btn.HB_ButtonMode = bbeacon.HB_ButtonMode

	nonce, ok := Buttons_list[btn.Button_id]
	if !ok || nonce.HB_RandomNonce != btn.HB_RandomNonce {
		// send the button message to MQTT
		sendButtonMessage(btn, cl)
	}
	Buttons_list[btn.Button_id] = btn
}

func getiBeaconDistance(rssi int64, power string) float64 {

	ratio := float64(rssi) * (1.0 / float64(twos_comp(power)))
	//fmt.Printf("beaconpower: rssi %d ratio %e power %e  \n",rssi, ratio, float64(twos_comp(power)))
	distance := 100.0
	if ratio < 1.0 {
		distance = math.Pow(ratio, 10)
	} else {
		distance = (0.89976)*math.Pow(ratio, 7.7095) + 0.111
	}
	return distance
}

func getBeaconDistance(incoming Incoming_json) float64 {
	distance := 1000.0

	distance = getiBeaconDistance(incoming.RSSI, incoming.TX_power)
	//distance = math.Abs(float64(incoming.RSSI))

	return distance
}

func getAverageDistance(beacon_metrics []beacon_metric) float64 {
	total := 0.0

	for _, v := range beacon_metrics {
		total += v.distance
	}
	return (total / float64(len(beacon_metrics)))
}

func sendHARoomMessage(beacon_id string, beacon_name string, distance float64, location string, cl *client.Client) {
	//first make the json
	ha_msg, err := json.Marshal(HA_message{Beacon_id: beacon_id, Beacon_name: beacon_name, Distance: distance})
	if err != nil {
		panic(err)
	}

	//send the message to HA
	err = cl.Publish(&client.PublishOptions{
		QoS:       mqtt.QoS1,
		TopicName: []byte("afa-systems/presence/ha/" + location),
		Message:   ha_msg,
	})
	if err != nil {
		panic(err)
	}
}

func sendButtonMessage(btn Button, cl *client.Client) {
	//first make the json
	btn_msg, err := json.Marshal(btn)
	if err != nil {
		panic(err)
	}

	//send the message to HA
	err = cl.Publish(&client.PublishOptions{
		QoS:       mqtt.QoS1,
		TopicName: []byte("afa-systems/presence/button/" + btn.Button_id),
		Message:   btn_msg,
	})
	if err != nil {
		panic(err)
	}

}

func sendButtonPressed(bcn Beacon, cl *client.Client) {
	//first make the json
	btn_msg, err := json.Marshal(bcn)
	if err != nil {
		panic(err)
	}

	//send the message to HA
	err = cl.Publish(&client.PublishOptions{
		QoS:       mqtt.QoS1,
		TopicName: []byte("afa-systems/presence/button/" + bcn.Beacon_id),
		Message:   btn_msg,
	})
	if err != nil {
		panic(err)
	}
	///utils.Log.Printf("%s pressed ",bcn.Beacon_id)
	s := fmt.Sprintf("/usr/bin/php /usr/local/presence/alarm_handler.php  --idt=%s --idr=%s --st=%d", bcn.Beacon_id, bcn.Incoming_JSON.Hostname, bcn.HB_ButtonCounter)
	///utils.Log.Printf("%s",s)
	err, out, errout := Shellout(s)
	if err != nil {
		log.Printf("error: %v\n", err)
	}
	fmt.Println("--- stdout ---")
	fmt.Println(out)
	fmt.Println("--- stderr ---")
	fmt.Println(errout)

	// create the file if it doesn't exists with O_CREATE, Set the file up for read write, add the append flag and set the permission
	//f, err := os.OpenFile("/data/conf/presence/db.json", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0660)
	//if err != nil {
	//    log.Fatal(err)
	//}
	// write to file, f.Write()
	//f.Write(btn_msg)
}

func getLikelyLocations(settings Settings, locations_list Locations_list, cl *client.Client) {
	// create the http results structure
	http_results_lock.Lock()
	http_results = HTTP_locations_list{}
	http_results.Beacons = make([]HTTP_location, 0)
	///http_results.Buttons = make([]Button, 0)
	http_results_lock.Unlock()

	should_persist := false

	// iterate through the beacons we want to search for
	for _, beacon := range BEACONS.Beacons {

		if len(beacon.beacon_metrics) == 0 {
			////fmt.Printf("beacon_metrics = 0:\n")
			continue
		}

		if (int64(time.Now().Unix()) - (beacon.beacon_metrics[len(beacon.beacon_metrics)-1].timestamp)) > settings.Last_seen_threshold {
			////fmt.Printf("beacon_metrics  timestamp = %s  %s \n",beacon.Name, beacon.beacon_metrics[len(beacon.beacon_metrics)-1].timestamp )
			if beacon.expired_location == "expired" {
				//beacon.Location_confidence =  - 1
				continue
			} else {
				beacon.expired_location = "expired"
				msg := Message{
					Email:    beacon.Previous_confident_location,
					Username: beacon.Name,
					Message:  beacon.expired_location}
				res1B, _ := json.Marshal(msg)
				fmt.Println(string(res1B))

				if err != nil {
					log.Printf("error: %v", err)

				}
				// Send the newly received message to the broadcast channel
				broadcast <- msg
			}
		} else {
			beacon.expired_location = ""

		}

		best_location := Best_location{}

		// go through its beacon metrics and pick out the location that appears most often
		loc_list := make(map[string]float64)
		seen_weight := 1.5
		rssi_weight := 0.75
		for _, metric := range beacon.beacon_metrics {
			loc, ok := loc_list[metric.location]
			if !ok {
				loc = seen_weight + (rssi_weight * (1.0 - (float64(metric.rssi) / -100.0)))
			} else {
				loc = loc + seen_weight + (rssi_weight * (1.0 - (float64(metric.rssi) / -100.0)))
			}
			loc_list[metric.location] = loc
		}
		//fmt.Printf("beacon: %s list: %#v\n", beacon.Name, loc_list)
		// now go through the list and find the largest, that's the location
		best_name := ""
		ts := 0.0
		for name, times_seen := range loc_list {
			if times_seen > ts {
				best_name = name
				ts = times_seen
			}
		}
		/////fmt.Printf("BEST LOCATION FOR %s IS: %s with score: %f\n", beacon.Name, best_name, ts)
		best_location = Best_location{name: best_name, distance: beacon.beacon_metrics[len(beacon.beacon_metrics)-1].distance, last_seen: beacon.beacon_metrics[len(beacon.beacon_metrics)-1].timestamp}

		//		//filter, only let this location become best if it was X times in a row
		//		if best_location.name == beacon.Previous_location {
		//			beacon.Location_confidence = beacon.Location_confidence + 1
		//		} else {
		//			beacon.Location_confidence = 0
		//			/////fmt.Printf("beacon.Location_confidence  %f\n", beacon.Location_confidence)
		//		}

		// Aggiungiamo il nuovo best_location allo storico
		beacon.Location_history = append(beacon.Location_history, best_location.name)
		if len(beacon.Location_history) > 10 {
			beacon.Location_history = beacon.Location_history[1:] // manteniamo solo gli ultimi 10
		}

		// Calcoliamo la location più votata nello storico
		location_counts := make(map[string]int)
		for _, loc := range beacon.Location_history {
			location_counts[loc]++
		}

		max_count := 0
		most_common_location := ""
		for loc, count := range location_counts {
			if count > max_count {
				max_count = count
				most_common_location = loc
			}
		}

		// Applichiamo un filtro: consideriamo il cambio solo se almeno 7 su 10 votano per una location
		if max_count >= 7 {
			beacon.Previous_location = most_common_location
			if most_common_location == beacon.Previous_confident_location {
				beacon.Location_confidence++
			} else {
				beacon.Location_confidence = 1
				beacon.Previous_confident_location = most_common_location
			}
		}

		//create an http result from this
		r := HTTP_location{}
		r.Distance = best_location.distance
		r.Name = beacon.Name
		r.Beacon_name = beacon.Name
		r.Beacon_id = beacon.Beacon_id
		r.Beacon_type = beacon.Beacon_type
		r.HB_Battery = beacon.HB_Battery
		r.HB_ButtonMode = beacon.HB_ButtonMode
		r.HB_ButtonCounter = beacon.HB_ButtonCounter
		r.Location = best_location.name
		r.Last_seen = best_location.last_seen

		////fmt.Printf("beacon.Location_confidence %s, settings.Location_confidence %s, beacon.Previous_confident_location %s:  best_location.name %s\n",beacon.Location_confidence, settings.Location_confidence, beacon.Previous_confident_location, best_location.name)

		if (beacon.Location_confidence == settings.Location_confidence && beacon.Previous_confident_location != best_location.name) || beacon.expired_location == "expired" {
			// location has changed, send an mqtt message

			should_persist = true
			fmt.Printf("detected a change!!! %#v\n\n", beacon)
			if beacon.Previous_confident_location == "expired" && beacon.expired_location == "" {
				msg := Message{
					Email:    beacon.Previous_confident_location,
					Username: beacon.Name,
					Message:  "OK"}
				res1B, _ := json.Marshal(msg)
				fmt.Println(string(res1B))

				if err != nil {
					log.Printf("error: %v", err)

				}
				// Send the newly received message to the broadcast channel
				broadcast <- msg
			}
			beacon.Location_confidence = 0
			location := ""
			if beacon.expired_location == "expired" {

				location = "expired"
			} else {
				location = best_location.name
			}
			//first make the json
			js, err := json.Marshal(Location_change{Beacon_ref: beacon, Name: beacon.Name, Beacon_name: beacon.Name, Previous_location: beacon.Previous_confident_location, New_location: location, Timestamp: time.Now().Unix()})
			if err != nil {
				continue
			}

			//send the message
			err = cl.Publish(&client.PublishOptions{
				QoS:       mqtt.QoS1,
				TopicName: []byte("afa-systems/presence/changes"),
				Message:   js,
			})
			if err != nil {
				panic(err)
			}
			s := fmt.Sprintf("/usr/bin/php /usr/local/presence/alarm_handler.php  --idt=%s --idr=%s --loct=%s", beacon.Beacon_id, beacon.Incoming_JSON.Hostname, location)
			///utils.Log.Printf("%s",s)
			err, out, errout := Shellout(s)
			if err != nil {
				log.Printf("error: %v\n", err)
			}
			fmt.Println("--- stdout ---")
			fmt.Println(out)
			fmt.Println("--- stderr ---")
			fmt.Println(errout)
			//////beacon.logger.Printf("Log content: user id %v \n", best_location.name)
			if settings.HA_send_changes_only {
				sendHARoomMessage(beacon.Beacon_id, beacon.Name, best_location.distance, best_location.name, cl)
			}

			if beacon.expired_location == "expired" {

				beacon.Previous_confident_location = "expired"
				r.Location = "expired"
			} else {
				beacon.Previous_confident_location = best_location.name
			}
			///beacon.Previous_confident_location = best_location.name

		}

		beacon.Previous_location = best_location.name
		r.Previous_confident_location = beacon.expired_location
		BEACONS.Beacons[beacon.Beacon_id] = beacon

		http_results_lock.Lock()
		http_results.Beacons = append(http_results.Beacons, r)
		http_results_lock.Unlock()

		if best_location.name != "" {
			if !settings.HA_send_changes_only {
				secs := int64(time.Now().Unix())
				if secs%settings.HA_send_interval == 0 {
					sendHARoomMessage(beacon.Beacon_id, beacon.Name, best_location.distance, best_location.name, cl)
				}
			}
		}

		/////fmt.Printf("\n\n%s is most likely in %s with average distance %f \n\n", beacon.Name, best_location.name, best_location.distance)
		////beacon.logger.Printf("Log content: user id %v \n", beacon.Name)
		// publish this to a topic
		// Publish a message.
		err := cl.Publish(&client.PublishOptions{
			QoS:       mqtt.QoS0,
			TopicName: []byte("afa-systems/presence"),
			Message:   []byte(fmt.Sprintf("%s is most likely in %s with average distance %f", beacon.Name, best_location.name, best_location.distance)),
		})
		if err != nil {
			panic(err)
		}

	}

	/*for _, button := range Buttons_list {
		http_results.Buttons = append(http_results.Buttons, button)
	}*/

	if should_persist {
		persistBeacons()
	}
}

/*func doSomething(bcon Beacon, testo string ) {
    bcon.logger.Printf("Log content: user id %v \n", beacon.Name)
}*/

func IncomingMQTTProcessor(updateInterval time.Duration, cl *client.Client, db *bolt.DB, logger []*user) chan<- Incoming_json {

	incoming_msgs_chan := make(chan Incoming_json, 2000)

	// load initial BEACONS
	BEACONS.Beacons = make(map[string]Beacon)
	// retrieve the data

	// create bucket if not exist
	err = db.Update(func(tx *bolt.Tx) error {
		_, err := tx.CreateBucketIfNotExists(world)
		if err != nil {
			return err
		}
		return nil
	})

	err = db.View(func(tx *bolt.Tx) error {
		bucket := tx.Bucket(world)
		if bucket == nil {
			return err
		}

		key := []byte("beacons_list")
		val := bucket.Get(key)
		if val != nil {
			buf := bytes.NewBuffer(val)
			dec := gob.NewDecoder(buf)
			err = dec.Decode(&BEACONS)
			if err != nil {
				log.Fatal("decode error:", err)
			}
		}

		key = []byte("buttons_list")
		val = bucket.Get(key)
		if val != nil {
			buf := bytes.NewBuffer(val)
			dec := gob.NewDecoder(buf)
			err = dec.Decode(&Buttons_list)
			if err != nil {
				log.Fatal("decode error:", err)
			}
		}

		key = []byte("settings")
		val = bucket.Get(key)
		if val != nil {
			buf := bytes.NewBuffer(val)
			dec := gob.NewDecoder(buf)
			err = dec.Decode(&settings)
			if err != nil {
				log.Fatal("decode error:", err)
			}
		}

		return nil
	})

	if err != nil {
		log.Fatal(err)
	}

	//debug list them out

	/*fmt.Println("Database beacons:")
			for _, beacon := range BEACONS.Beacons {
				fmt.Println("Database has known beacon: " + beacon.Beacon_id + " " + beacon.Name)
				dog := new(user)
				//createUser( beacon.Name, true)

				//user1 := createUser( beacon.Name, true)
				//doSomething(beacon, "hello")
				//

				userFIle := &lumberjack.Logger{
	            Filename:   "/data/presence/presence/beacon_log_" + beacon.Name + ".log",
	            MaxSize:    250, // mb
	            MaxBackups: 5,
	            MaxAge:     10, // in days
				}
				dog.id =  beacon.Name
				dog.logger = log.New(userFIle, "User: ", log.Ldate|log.Ltime|log.Lshortfile)
				dog.logger.Printf("Log content: user id %v \n", beacon.Name)
				logger=append(logger,dog)
			}
			fmt.Println("leng has %d\n",len(logger))
			fmt.Printf("%v", logger)
			fmt.Println("Settings has %#v\n", settings)*/
	/**/
	Latest_beacons_list = make(map[string]Beacon)

	Buttons_list = make(map[string]Button)

	//create a map of locations, looked up by hostnames
	locations_list := Locations_list{}
	ls := make(map[string]Location)
	locations_list.locations = ls

	ticker := time.NewTicker(updateInterval)

	go func() {
		for {
			select {

			case <-ticker.C:
				getLikelyLocations(settings, locations_list, cl)
			case incoming := <-incoming_msgs_chan:
				func() {
					defer func() {
						if err := recover(); err != nil {
							log.Println("work failed:", err)
						}
					}()

					incoming = incomingBeaconFilter(incoming)
					this_beacon_id := getBeaconID(incoming)

					now := time.Now().Unix()

					///fmt.Println("sawbeacon  " + this_beacon_id + " at " + incoming.Hostname)
					//logger["FCB8351F5A21"].logger.Printf("Log content: user id  \n")
					//if this beacon isn't in our search list, add it to the latest_beacons pile.
					beacon, ok := BEACONS.Beacons[this_beacon_id]
					if !ok {
						//should be unique
						//if it's already in list, forget it.
						latest_list_lock.Lock()
						x, ok := Latest_beacons_list[this_beacon_id]
						if ok {
							//update its timestamp
							x.Last_seen = now
							x.Incoming_JSON = incoming
							x.Distance = getBeaconDistance(incoming)

							Latest_beacons_list[this_beacon_id] = x
						} else {
							Latest_beacons_list[this_beacon_id] = Beacon{Beacon_id: this_beacon_id, Beacon_type: incoming.Beacon_type, Last_seen: now, Incoming_JSON: incoming, Beacon_location: incoming.Hostname, Distance: getBeaconDistance(incoming)}
						}
						for k, v := range Latest_beacons_list {
							if (now - v.Last_seen) > 10 { // 10 seconds
								delete(Latest_beacons_list, k)
							}
						}
						latest_list_lock.Unlock()
						//continue
						return
					}

					beacon.Incoming_JSON = incoming
					beacon.Last_seen = now
					beacon.Beacon_type = incoming.Beacon_type
					beacon.HB_ButtonCounter = incoming.HB_ButtonCounter
					beacon.HB_Battery = incoming.HB_Battery
					beacon.HB_RandomNonce = incoming.HB_RandomNonce
					beacon.HB_ButtonMode = incoming.HB_ButtonMode
					////fmt.Println("button pressed  " + this_beacon_id + " at " +  strconv.Itoa(int(incoming.HB_ButtonCounter)) )

					if beacon.beacon_metrics == nil {
						beacon.beacon_metrics = make([]beacon_metric, settings.Beacon_metrics_size)
					}
					//create metric for this beacon
					this_metric := beacon_metric{}
					this_metric.distance = getBeaconDistance(incoming)
					this_metric.timestamp = now
					this_metric.rssi = int64(incoming.RSSI)
					this_metric.location = incoming.Hostname
					beacon.beacon_metrics = append(beacon.beacon_metrics, this_metric)
					///fmt.Printf("APPENDING a metric from %s len %d\n", beacon.Name, len(beacon.beacon_metrics))
					if len(beacon.beacon_metrics) > settings.Beacon_metrics_size {
						//fmt.Printf("deleting a metric from %s len %d\n", beacon.Name, len(beacon.beacon_metrics))
						beacon.beacon_metrics = append(beacon.beacon_metrics[:0], beacon.beacon_metrics[0+1:]...)
					}
					//fmt.Printf("%#v\n", beacon.beacon_metrics)
					if beacon.HB_ButtonCounter_Prev != beacon.HB_ButtonCounter {
						beacon.HB_ButtonCounter_Prev = incoming.HB_ButtonCounter
						// send the button message to MQTT
						sendButtonPressed(beacon, cl)
					}
					BEACONS.Beacons[beacon.Beacon_id] = beacon

					/*if beacon.Beacon_type == "hb_button" {
						processButton(beacon, cl)
					}*/

					//lookup location by hostname in locations
					location, ok := locations_list.locations[incoming.Hostname]
					if !ok {
						//create the location
						locations_list.locations[incoming.Hostname] = Location{}
						location, ok = locations_list.locations[incoming.Hostname]
						location.name = incoming.Hostname
					}
					locations_list.locations[incoming.Hostname] = location
				}()
			}
		}
	}()

	return incoming_msgs_chan
}
func ParseTimeStamp(utime string) (string, error) {
	i, err := strconv.ParseInt(utime, 10, 64)
	if err != nil {
		return "", err
	}
	t := time.Unix(i, 0)
	return t.Format(time.UnixDate), nil
}

var http_host_path_ptr *string

// var https_host_path_ptr *string
var httpws_host_path_ptr *string

//var httpwss_host_path_ptr *string

type Todo struct {
	Id    string `json:"id"`
	Value string `json:"value" binding:"required"`
}

type Job interface {
	ExitChan() chan error
	Run(todos map[string]Todo) (map[string]Todo, error)
}

func ProcessJobs(jobs chan Job, db string) {
	for {
		j := <-jobs

		todos := make(map[string]Todo, 0)
		content, err := ioutil.ReadFile(db)
		if err == nil {
			if err = json.Unmarshal(content, &todos); err == nil {
				todosMod, err := j.Run(todos)

				if err == nil && todosMod != nil {
					b, err := json.Marshal(todosMod)
					if err == nil {
						err = ioutil.WriteFile(db, b, 0644)
					}
				}
			}
		}

		j.ExitChan() <- err
	}
}

type user struct {
	id     string
	logger *log.Logger
}

const ShellToUse = "bash"

func Shellout(command string) (error, string, string) {
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	///utils.Log.Printf("command: %s",command)
	cmd := exec.Command(ShellToUse, "-c", command)
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()
	return err, stdout.String(), stderr.String()
}

func createUser(id string, logWanted bool) user {
	var l *log.Logger

	if logWanted {
		// Here the log content will be added in the user log file
		userFIle := &lumberjack.Logger{
			Filename:   "/data/var/log/presence/presence/log_" + id + ".log",
			MaxSize:    250, // mb
			MaxBackups: 5,
			MaxAge:     10, // in days
		}
		l = log.New(userFIle, "User: ", log.Ldate|log.Ltime|log.Lshortfile)
	} else {
		// Here the log content will go nowhere
		l = log.New(ioutil.Discard, "User: ", log.Ldate|log.Ltime|log.Lshortfile)
	}
	return user{id, l}
}

func main() {

	loggers := []*user{}

	http_host_path_ptr = flag.String("http_host_path", "0.0.0.0:8080", "The host:port that the HTTP server should listen on")
	//https_host_path_ptr = flag.String("https_host_path", "0.0.0.0:5443", "The host:port that the HTTP server should listen on")
	httpws_host_path_ptr = flag.String("httpws_host_path", "0.0.0.0:8088", "The host:port websocket listen")
	//httpwss_host_path_ptr = flag.String("httpwss_host_path", "0.0.0.0:8443", "The host:port secure websocket listen")

	mqtt_host_ptr := flag.String("mqtt_host", "localhost:1883", "The host:port of the MQTT server to listen for beacons on")
	mqtt_username_ptr := flag.String("mqtt_username", "none", "The username needed to connect to the MQTT server, 'none' if it doesn't need one")
	mqtt_password_ptr := flag.String("mqtt_password", "none", "The password needed to connect to the MQTT server, 'none' if it doesn't need one")
	mqtt_client_id_ptr := flag.String("mqtt_client_id", "presence-detector", "The client ID for the MQTT server")

	flag.Parse()

	///utils.NewLog(*logpath)
	///utils.Log.Println("hello")
	// Set up channel on which to send signal notifications.
	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, os.Interrupt, os.Kill)

	// Create an MQTT Client.
	cli := client.New(&client.Options{
		// Define the processing of the error handler.
		ErrorHandler: func(err error) {
			fmt.Println(err)
		},
	})
	// Terminate the Client.
	defer cli.Terminate()

	//open the database
	db, err = bolt.Open("/data/conf/presence/presence.db", 0644, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Connect to the MQTT Server.
	err = cli.Connect(&client.ConnectOptions{
		Network:  "tcp",
		Address:  *mqtt_host_ptr,
		ClientID: []byte(*mqtt_client_id_ptr),
		UserName: []byte(*mqtt_username_ptr),
		Password: []byte(*mqtt_password_ptr),
	})
	if err != nil {
		panic(err)
	}

	incoming_updates_chan := IncomingMQTTProcessor(1*time.Second, cli, db, loggers)

	// Subscribe to topics.
	err = cli.Subscribe(&client.SubscribeOptions{
		SubReqs: []*client.SubReq{
			&client.SubReq{
				TopicFilter: []byte("publish_out/#"),
				QoS:         mqtt.QoS0,
				Handler: func(topicName, message []byte) {
					msgStr := string(message)
					t := strings.Split(string(topicName), "/")
					hostname := t[1]

					//Formato JSON multiplo
					//publish_out/170361001234 [{"timestamp":"2025-06-11T11:27:28.492Z","type":"Gateway","mac":"E4B3230DB5CC","nums":10},{"timestamp":"2025-06-11T11:27:28.483Z","mac":"36CE2D7CA4E5","rssi":-27,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"},{"timestamp":"2025-06-11T11:27:28.586Z","mac":"36CE2D7CA4E5","rssi":-30,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"},{"timestamp":"2025-06-11T11:27:28.612Z","mac":"406260A302FC","rssi":-35,"rawData":"02011A020A0B0BFF4C001006371AAE2F6F5B"},{"timestamp":"2025-06-11T11:27:28.798Z","mac":"36CE2D7CA4E5","rssi":-28,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"},{"timestamp":"2025-06-11T11:27:28.905Z","mac":"36CE2D7CA4E5","rssi":-30,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"},{"timestamp":"2025-06-11T11:27:28.945Z","mac":"C300003947DF","rssi":-32,"rawData":"0201061AFF4C000215FDA50693A4E24FB1AFCFC6EB0764782500000000C5"},{"timestamp":"2025-06-11T11:27:29.013Z","mac":"36CE2D7CA4E5","rssi":-29,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"},{"timestamp":"2025-06-11T11:27:29.120Z","mac":"36CE2D7CA4E5","rssi":-27,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"},{"timestamp":"2025-06-11T11:27:29.166Z","mac":"406260A302FC","rssi":-34,"rawData":"02011A020A0B0BFF4C001006371AAE2F6F5B"},{"timestamp":"2025-06-11T11:27:29.337Z","mac":"36CE2D7CA4E5","rssi":-26,"rawData":"1EFF0600010F20226F50BB5F834F6C9CE3D876B0C3F665882955B368D3B96C"}]
					if strings.HasPrefix(msgStr, "[") {
						var readings []RawReading
						err := json.Unmarshal(message, &readings)
						if err != nil {
							log.Printf("Errore parsing JSON: %v", err)
							return
						}

						for _, reading := range readings {
							if reading.Type == "Gateway" {
								continue
							}
							incoming := Incoming_json{
								Hostname:         hostname,
								MAC:              reading.MAC,
								RSSI:             int64(reading.RSSI),
								Data:             reading.RawData,
								HB_ButtonCounter: parseButtonState(reading.RawData),
							}
							incoming_updates_chan <- incoming
						}
					} else {
						//Formato CSV
						//ingics solo annuncio
						//publish_out/171061001180 $GPRP,C83F8F17DB35,F5B0B0419FEF,-44,02010612FF590080BC280102FFFFFFFF000000000000,1749648798
						//ingics tasto premuto
						//publish_out/171061001180 $GPRP,C83F8F17DB35,F5B0B0419FEF,-44,02010612FF590080BC280103FFFFFFFF000000000000,1749648798
						s := strings.Split(string(message), ",")
						if len(s) < 6 {
							log.Printf("Messaggio CSV non valido: %s", msgStr)
							return
						}

						rawdata := s[4]
						buttonCounter := parseButtonState(rawdata)
						if buttonCounter > 0 {
							incoming := Incoming_json{}
							i, _ := strconv.ParseInt(s[3], 10, 64)
							incoming.Hostname = hostname
							incoming.Beacon_type = "hb_button"
							incoming.MAC = s[1]
							incoming.RSSI = i
							incoming.Data = rawdata
							incoming.HB_ButtonCounter = buttonCounter

							read_line := strings.TrimRight(string(s[5]), "\r\n")
							it, err33 := strconv.Atoi(read_line)
							if err33 != nil {
								fmt.Println(it)
								fmt.Println(err33)
								os.Exit(2)
							}
							incoming_updates_chan <- incoming
						}
					}
				},
			},
		},
	})
	if err != nil {
		panic(err)
	}

	fmt.Println("CONNECTED TO MQTT")
	fmt.Println("\n ")
	fmt.Println("Visit http://" + *http_host_path_ptr + " on your browser to see the web interface")
	fmt.Println("\n ")

	go startServer()

	// Wait for receiving a signal.
	<-sigc

	// Disconnect the Network Connection.
	if err := cli.Disconnect(); err != nil {
		panic(err)
	}
}

func startServer() {

	headersOk := handlers.AllowedHeaders([]string{"X-Requested-With"})
	originsOk := handlers.AllowedOrigins([]string{"*"})
	methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"})

	// Set up HTTP server
	r := mux.NewRouter()
	r.HandleFunc("/api/results", resultsHandler)

	r.HandleFunc("/api/beacons/{beacon_id}", beaconsDeleteHandler).Methods("DELETE")
	r.HandleFunc("/api/beacons", beaconsListHandler).Methods("GET")
	r.HandleFunc("/api/beacons", beaconsAddHandler).Methods("POST") //since beacons are hashmap, just have put and post be same thing. it'll either add or modify that entry
	r.HandleFunc("/api/beacons", beaconsAddHandler).Methods("PUT")

	r.HandleFunc("/api/latest-beacons", latestBeaconsListHandler).Methods("GET")

	r.HandleFunc("/api/settings", settingsListHandler).Methods("GET")
	r.HandleFunc("/api/settings", settingsEditHandler).Methods("POST")

	r.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(http.Dir("static_html/js/"))))
	r.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(http.Dir("static_html/css/"))))
	r.PathPrefix("/img/").Handler(http.StripPrefix("/img/", http.FileServer(http.Dir("static_html/img/"))))
	r.PathPrefix("/").Handler(http.FileServer(http.Dir("static_html/")))

	http.Handle("/", r)

	mxWS := mux.NewRouter()
	mxWS.HandleFunc("/ws/api/beacons", serveWs)
	mxWS.HandleFunc("/ws/api/beacons/latest", serveLatestBeaconsWs)
	mxWS.HandleFunc("/ws/broadcast", handleConnections)
	http.Handle("/ws/", mxWS)

	go func() {
		log.Fatal(http.ListenAndServe(*httpws_host_path_ptr, nil))
	}()

	// Start listening for incoming chat messages
	go handleMessages()

	///"/conf/etc/cert/certs/services/htdocs/majornet.crt", "/conf/etc/cert/private/services/htdocs/majornet.key"
	http.ListenAndServe(*http_host_path_ptr, handlers.CORS(originsOk, headersOk, methodsOk)(r))

}

func handleConnections(w http.ResponseWriter, r *http.Request) {
	// Upgrade initial GET request to a websocket
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Fatal(err)
	}
	// Make sure we close the connection when the function returns
	defer ws.Close()

	// Register our new client
	clients[ws] = true

	for {
		var msg Message
		// Read in a new message as JSON and map it to a Message object
		err := ws.ReadJSON(&msg)
		if err != nil {
			log.Printf("error: %v", err)
			delete(clients, ws)
			break
		}
		// Send the newly received message to the broadcast channel
		broadcast <- msg
	}
}

func handleMessages() {
	for {
		// Grab the next message from the broadcast channel
		msg := <-broadcast
		// Send it out to every client that is currently connected
		for client := range clients {
			err := client.WriteJSON(msg)
			if err != nil {
				log.Printf("error: %v", err)
				client.Close()
				delete(clients, client)
			}
		}
	}
}

func resultsHandler(w http.ResponseWriter, r *http.Request) {
	http_results_lock.RLock()
	js, err := json.Marshal(http_results)
	http_results_lock.RUnlock()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Write(js)
}

func beaconsListHandler(w http.ResponseWriter, r *http.Request) {
	latest_list_lock.RLock()
	js, err := json.Marshal(BEACONS)
	latest_list_lock.RUnlock()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Write(js)
}

func persistBeacons() error {
	// gob it first
	buf := &bytes.Buffer{}
	enc := gob.NewEncoder(buf)
	if err := enc.Encode(BEACONS); err != nil {
		return err
	}

	key := []byte("beacons_list")
	// store some data
	err = db.Update(func(tx *bolt.Tx) error {
		bucket, err := tx.CreateBucketIfNotExists(world)
		if err != nil {
			return err
		}

		err = bucket.Put(key, []byte(buf.String()))
		if err != nil {
			return err
		}
		return nil
	})
	return nil
}

func persistSettings() error {
	// gob it first
	buf := &bytes.Buffer{}
	enc := gob.NewEncoder(buf)
	if err := enc.Encode(settings); err != nil {
		return err
	}

	key := []byte("settings")
	// store some data
	err = db.Update(func(tx *bolt.Tx) error {
		bucket, err := tx.CreateBucketIfNotExists(world)
		if err != nil {
			return err
		}

		err = bucket.Put(key, []byte(buf.String()))
		if err != nil {
			return err
		}
		return nil
	})
	return nil
}

func beaconsAddHandler(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	var in_beacon Beacon
	err = decoder.Decode(&in_beacon)
	if err != nil {
		http.Error(w, err.Error(), 400)
		return
	}

	//make sure name and beacon_id are present
	if (len(strings.TrimSpace(in_beacon.Name)) == 0) || (len(strings.TrimSpace(in_beacon.Beacon_id)) == 0) {
		http.Error(w, "name and beacon_id cannot be blank", 400)
		return
	}

	BEACONS.Beacons[in_beacon.Beacon_id] = in_beacon

	err := persistBeacons()
	if err != nil {
		http.Error(w, "trouble persisting beacons list, create bucket", 500)
		return
	}

	w.Write([]byte("ok"))
}

func beaconsDeleteHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	beacon_id := vars["beacon_id"]
	delete(BEACONS.Beacons, beacon_id)

	_, ok := Buttons_list[beacon_id]
	if ok {
		delete(Buttons_list, beacon_id)
	}

	err := persistBeacons()
	if err != nil {
		http.Error(w, "trouble persisting beacons list, create bucket", 500)
		return
	}

	w.Write([]byte("ok"))
}

func latestBeaconsListHandler(w http.ResponseWriter, r *http.Request) {
	latest_list_lock.RLock()
	var la = make([]Beacon, 0)
	for _, b := range Latest_beacons_list {
		la = append(la, b)
	}
	latest_list_lock.RUnlock()
	js, err := json.Marshal(la)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Write(js)
}

func settingsListHandler(w http.ResponseWriter, r *http.Request) {
	js, err := json.Marshal(settings)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Write(js)
}

func settingsEditHandler(w http.ResponseWriter, r *http.Request) {
	decoder := json.NewDecoder(r.Body)
	var in_settings Settings
	err = decoder.Decode(&in_settings)
	if err != nil {
		http.Error(w, err.Error(), 400)
		return
	}

	//make sure values are > 0
	if (in_settings.Location_confidence <= 0) ||
		(in_settings.Last_seen_threshold <= 0) ||
		(in_settings.HA_send_interval <= 0) {
		http.Error(w, "values must be greater than 0", 400)
		return
	}

	settings = in_settings

	err := persistSettings()
	if err != nil {
		http.Error(w, "trouble persisting settings, create bucket", 500)
		return
	}

	w.Write([]byte("ok"))
}

func reader(ws *websocket.Conn) {
	defer ws.Close()
	ws.SetReadLimit(512)
	ws.SetReadDeadline(time.Now().Add(pongWait))
	ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
	for {
		_, _, err := ws.ReadMessage()
		if err != nil {
			break
		}
	}
}

func writer(ws *websocket.Conn) {
	pingTicker := time.NewTicker(pingPeriod)
	beaconTicker := time.NewTicker(beaconPeriod)
	defer func() {
		pingTicker.Stop()
		beaconTicker.Stop()
		ws.Close()
	}()
	for {
		select {
		case <-beaconTicker.C:

			http_results_lock.RLock()
			js, err := json.Marshal(http_results)
			http_results_lock.RUnlock()

			if err != nil {
				js = []byte("error")
			}

			ws.SetWriteDeadline(time.Now().Add(writeWait))
			if err := ws.WriteMessage(websocket.TextMessage, js); err != nil {
				return
			}
		case <-pingTicker.C:
			ws.SetWriteDeadline(time.Now().Add(writeWait))
			if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
				return
			}
		}
	}
}

func serveWs(w http.ResponseWriter, r *http.Request) {
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		if _, ok := err.(websocket.HandshakeError); !ok {
			log.Println(err)
		}
		return
	}

	go writer(ws)
	reader(ws)
}

func latestBeaconWriter(ws *websocket.Conn) {
	pingTicker := time.NewTicker(pingPeriod)
	beaconTicker := time.NewTicker(beaconPeriod)
	defer func() {
		pingTicker.Stop()
		beaconTicker.Stop()
		ws.Close()
	}()
	for {
		select {
		case <-beaconTicker.C:

			latest_list_lock.RLock()
			var la = make([]Beacon, 0)
			for _, b := range Latest_beacons_list {
				la = append(la, b)
			}
			latest_list_lock.RUnlock()
			js, err := json.Marshal(la)

			if err != nil {
				js = []byte("error")
			}

			ws.SetWriteDeadline(time.Now().Add(writeWait))
			if err := ws.WriteMessage(websocket.TextMessage, js); err != nil {
				return
			}
		case <-pingTicker.C:
			ws.SetWriteDeadline(time.Now().Add(writeWait))
			if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
				return
			}
		}
	}
}

func serveLatestBeaconsWs(w http.ResponseWriter, r *http.Request) {
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		if _, ok := err.(websocket.HandshakeError); !ok {
			log.Println(err)
		}
		return
	}

	go latestBeaconWriter(ws)
	reader(ws)
}
