package main

import (
	"context"
	"log"
	"sync"
	"time"

	"git.joco.dk/snr/fermentord/internal/configuration"
	"git.joco.dk/snr/fermentord/internal/controllers"
	"git.joco.dk/snr/fermentord/internal/dwingest"
	"git.joco.dk/snr/fermentord/internal/hw"
	"git.joco.dk/snr/fermentord/internal/lcd"
	"git.joco.dk/snr/fermentord/pkg/temperature"
	"git.joco.dk/snr/fermentord/pkg/tilt"
	"github.com/getsentry/sentry-go"
	"github.com/nats-io/nats.go"
	"github.com/spf13/viper"
)

func mainLoop(ctx context.Context, wg *sync.WaitGroup, js nats.JetStream) {
	defer wg.Done()

	hub := sentry.CurrentHub().Clone()
	defer hub.Flush(10 * time.Second)

	config := configuration.Global()

	// Display
	var hasDisplay bool
	display, err := lcd.NewLCD(config.I2C.Bus)
	if err != nil {
		hub.CaptureException(err)
		log.Printf("Error initializing LCD: %v", err)
	} else {
		hasDisplay = true
		defer display.Close()
	}

	// Controller
	ctrl := controllers.NewChamberController(config)

	chConfigChange := make(chan configuration.Configuration, 1)
	chn := configuration.NewChangeNotifier()
	defer chn.Close()

	// Configuration updates
	chn.Subscribe(chConfigChange)
	chn.Subscribe(temperature.ConfigUpdate)
	chn.Subscribe(ctrl.ConfigUpdates)
	//chn.Subscribe(display.ConfigUpdate)
	chn.Notify(configuration.Global())
	viper.OnConfigChange(chn.OnConfigChange)
	viper.WatchConfig()

	// NATS
	ingest := dwingest.NewDWIngest(js, config)

	gpio, err := hw.NewGpio()
	if err != nil {
		hub.CaptureException(err)
		log.Fatal(err)
	}
	defer gpio.Close()

	if hasDisplay {
		wg.Add(1)
		go display.Run(ctx, wg)
	}

	wg.Add(4)
	go ctrl.Run(ctx, wg)
	go ingest.Run(ctx, wg)
	go temperature.PollSensors(ctx, wg, 1*time.Second, config.Sensors.Weight)
	go tilt.PollSensors(ctx, wg, 1*time.Minute, 20*time.Second)

	oneWirePowerUpChannel := make(chan bool)
	oneWirePowerUpTimer := time.AfterFunc(1*time.Millisecond, func() {
		oneWirePowerUpChannel <- true
	})

	for {
		select {
		case reading := <-temperature.Reading:
			ctrl.SetTemperature(reading)
			ingest.AddReading(reading)
			if hasDisplay {
				display.SetTemperature(reading)
			}

		case <-temperature.RequestReset:
			log.Print("Powering down one wire bus for 20 seconds")
			gpio.StopOneWirePower()
			oneWirePowerUpTimer.Reset(20 * time.Second)
			ingest.AddEvent("ONEWIRE_RESET")

		case <-oneWirePowerUpChannel:
			log.Print("Powering up OneWire bus")
			gpio.StartOneWirePower()

		case ev := <-gpio.C:
			switch ev {
			case hw.DoorClosed:
				log.Print("Door closed")
				gpio.LightsOff()
				if config.CoolerEnabled || config.HeaterEnabled {
					gpio.StartFan()
				}
				ctrl.Resume()
				ingest.AddEvent("DOOR_CLOSED")

			case hw.DoorOpened:
				log.Print("Door opened")
				ctrl.Pause()
				gpio.LightsOn()
				gpio.StopFan()
				ingest.AddEvent("DOOR_OPENED")
			}

		case state := <-ctrl.C:
			gpioSetState(state, gpio, config)
			ingest.AddState(state)
			if hasDisplay {
				display.SetState(state)
			}

		case c := <-chConfigChange:
			config = c

		case <-ctx.Done():
			return
		}
	}
}
