package core

import (
	"log"
	"time"

	"github.com/brutella/hc/accessory"
	"github.com/bwdezend/astoria-hc/internal/telemetry"
	"github.com/vemo-france/max31865"
)

var p = 3.0

// GetCurrentTemp read from the MAX31865 to read
// the current state of the steam side of the boiler temperature
func GetCurrentTemp(acc accessory.Thermostat) {
	if err := max31865.Init(); err != nil {
		log.Fatalf("initialization failed : %s", err)
	}
	sensor := max31865.Create("8", "9", "10", "11")
	var boilerTemperature float64
	for {
		boilerTemperature = float64(sensor.ReadTemperature(100, 430))
		if boilerTemperature < 1.0 {
			telemetry.SensorFaultCount.Inc()
			log.Println("Sensor fault")
		} else {
			acc.Thermostat.CurrentTemperature.SetValue(boilerTemperature)
			telemetry.CurrentTemperature.Set(boilerTemperature)
		}
		time.Sleep(500 * time.Millisecond)
	}
}

// SetTargetTemp adjusts the setpoint for the PID loop and updates the homekit interfaces
func SetTargetTemp(acc accessory.Thermostat, setTemp float64) {
	if setTemp > 124.0 {
		setTemp = 124.0
	}

	log.Printf("setting setpoint to %.2f", setTemp)
	PersistTemp(setTemp)

	acc.Thermostat.TargetTemperature.SetValue(setTemp)
	telemetry.SetpointTemperature.Set(setTemp)
}

// HeaterWindow doc Take two inputs - the duration of the cycle and the proportion of the cycle
// and turn the heating element on for that percentage of the cycle. If the
// windowSize is 15.0 and the enabledTime is 0.7, this turns the heating element
// on for 10.5 seconds and off for 4.5 seconds before returning
func HeaterWindow(windowSize float64, enabledTime float64, gpioEnabled bool) {
	var disabledTime float64 = 0
	enabledTime = windowSize * enabledTime * 1000
	disabledTime = windowSize*1000 - enabledTime

	if enabledTime > 1000.0 {
		enabledTime = 1000.0
	}
	if enabledTime > 0 {
		if gpioEnabled {
			HeaterControl(true)
		}
		time.Sleep(time.Duration(enabledTime) * time.Millisecond)
		telemetry.SecondsActive.Add(enabledTime / 1000)
	}
	if gpioEnabled {
		HeaterControl(false)
	}
	time.Sleep(time.Duration(disabledTime) * time.Millisecond)
}

// TemperatureProportional is a dead simple proportional control loop. Take the difference in setpoint
// and current temp, multiply by the gain, and use the result to control
// the duty cycle on the boiler, represented as a float between 0.0 and 1.0
func TemperatureProportional(acc accessory.Thermostat, gpioEnabled bool) {

	for {
		current := 0.0
		if gpioEnabled {
			current = acc.Thermostat.CurrentTemperature.GetValue()
		}
		setpoint := acc.Thermostat.TargetTemperature.GetValue()
		error := (setpoint - current) * p * 0.1

		if error > 1.0 {
			error = 1.0
		}

		if error < 0.01 {
			error = 0.0
		}

		// log.Printf("Duty Cycle: %.2f, Current: %.2f, Setpoint: %.2f\n", error, current, setpoint)

		HeaterWindow(1.0, error, gpioEnabled)

	}
}

// TemperatureErrorDetection is a error handling function to turn the SSR off if the boiler doesn't
// appear to be warming as a result of input. The most common reason for this
// is that the power switch on the machine is turned off, but the timer still
// fired. Less common reasons would be a heater element malfunction, a boiler
// rupture, or a failure of the temperature sensor itself.
func TemperatureErrorDetection(acc accessory.Thermostat) {

	lastTemp := 0.0
	countdown := 2
	for {
		setpoint := acc.Thermostat.TargetTemperature.GetValue()
		time.Sleep(10 * time.Second)

		current := acc.Thermostat.CurrentTemperature.GetValue()
		error := (setpoint - current)

		if error > 5.0 {
			if lastTemp >= current {
				if countdown > 0 {
					log.Printf("Warning: boiler does not appear to be heating (%.2f gt %.2f). Countdown: %d", lastTemp, current, countdown)
					countdown--
				} else if countdown == 0 {
					log.Printf("Error: boiler does not appear to be heating (%.2f gt %.2f). Changing setpoint to 0.0", lastTemp, current)
					SetTargetTemp(acc, 0.0)
					countdown = 2
				}
			}
		} else if countdown < 2 {
			log.Printf("Warning: boiler does not appear to be heating (%.2f gt %.2f). Countdown: %d", lastTemp, current, countdown)
			countdown = 2
		}
		lastTemp = current
	}
}

// Re-evaluate if we want to be able to set the temp from a REST endpoint
// type updateVars struct {
// 	Temperature float64 `json:"temperature,omitempty"`
// 	Proportion  float64 `json:"proportion,omitempty"`
// }
//
// // UpdateSettings doc
// func UpdateSettings(w http.ResponseWriter, r *http.Request) {
// 	var update updateVars
// 	if r.Method == "POST" {
// 		body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
// 		if err != nil {
// 			http.Error(w, "Error reading request body",
// 				http.StatusInternalServerError)
// 		}
// 		if err := r.Body.Close(); err != nil {
// 			panic(err)
// 		}
//
// 		if err := json.Unmarshal(body, &update); err != nil {
// 			w.Header().Set("Content-Type", "application/json; charset=UTF-8")
// 			w.WriteHeader(422) // unprocessable entity
// 			if err := json.NewEncoder(w).Encode(err); err != nil {
// 				panic(err)
// 			}
// 		}
// 		if update.Temperature != 0 {
// 			SetTargetTemp(update.Temperature)
// 		}
// 		if update.Proportion != 0 {
// 			log.Printf("updating p constant to %.2f\n", update.Proportion)
// 			p = update.Proportion
// 		}
//
// 	} else {
// 		http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
// 	}
// }
//
// // SetpointHandler handles
// func SetpointHandler() {
// 	router := mux.NewRouter().StrictSlash(true)
// 	router.HandleFunc("/update", UpdateSettings)
// 	log.Fatal(http.ListenAndServe(":2113", router))
// }
