package lcd

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

	"git.joco.dk/snr/fermentord/internal/controllers"
	"git.joco.dk/snr/fermentord/pkg/temperature"
	device "github.com/d2r2/go-hd44780"
	"github.com/d2r2/go-i2c"
	"github.com/getsentry/sentry-go"
)

type LCD struct {
	ambient, chamber, setpoint, wort float64
	state                            string

	bus        *i2c.I2C
	lcd        *device.Lcd
	l1, l2     string
	chTemp     chan temperature.TemperatureReading
	chState    chan controllers.ChamberState
	chSetpoint chan float64
	lastUpdate time.Time
	hub        *sentry.Hub
}

func NewLCD(bus int) (*LCD, error) {
	var err error

	p := &LCD{
		chTemp:     make(chan temperature.TemperatureReading, 10),
		chState:    make(chan controllers.ChamberState, 10),
		chSetpoint: make(chan float64, 10),
		lastUpdate: time.Now(),
		hub:        sentry.CurrentHub().Clone(),
	}

	p.bus, err = i2c.NewI2C(0x27, bus)
	if err != nil {
		return nil, err
	}

	p.lcd, err = device.NewLcd(p.bus, device.LCD_16x2)
	if err != nil {
		p.bus.Close()
		return nil, err
	}

	err = p.lcd.BacklightOn()
	if err != nil {
		p.bus.Close()
		return nil, err
	}

	err = p.lcd.ShowMessage("Fermentor", device.SHOW_LINE_1)
	if err != nil {
		p.bus.Close()
		return nil, err
	}

	err = p.lcd.ShowMessage("Initializing", device.SHOW_LINE_2|device.SHOW_BLANK_PADDING)
	if err != nil {
		p.bus.Close()
		return nil, err
	}

	return p, nil

}

func (p *LCD) Close() error {
	if err := p.lcd.BacklightOff(); err != nil {
		p.hub.CaptureException(err)
		log.Print(err)
	}

	p.hub.Flush(10 * time.Second)

	return p.bus.Close()
}

func (p *LCD) SetTemperature(t temperature.TemperatureReading) {
	select {
	case p.chTemp <- t:
		break

	default:
		err := fmt.Errorf("channel overflow on display temperature channel")
		p.hub.CaptureException(err)
		log.Print(err)
	}
}

func (p *LCD) SetSetpointTemp(t float64) {
	select {
	case p.chSetpoint <- t:
		break

	default:
		err := fmt.Errorf("channel overflow on display setpoint temperature channel")
		p.hub.CaptureException(err)
		log.Print(err)
	}
}

func (p *LCD) SetState(state controllers.ChamberState) {
	select {
	case p.chState <- state:
		break

	default:
		err := fmt.Errorf("channel overflow on display state channel")
		p.hub.CaptureException(err)
		log.Print(err)
	}
}

func (p *LCD) Run(ctx context.Context, wg *sync.WaitGroup) {
	defer wg.Done()

	for {
		select {
		case t := <-p.chTemp:
			p.ambient = t.Ambient
			p.chamber = t.Chamber
			p.wort = t.Wort

			if err := p.update(); err != nil {
				p.hub.CaptureException(err)
				log.Print(err)
			}

		case state := <-p.chState:
			switch state {
			case controllers.ChamberStateIdle:
				p.state = "Id"

			case controllers.ChamberStateHeating:
				p.state = "He"

			case controllers.ChamberStateCooling:
				p.state = "Co"
			}

			if err := p.update(); err != nil {
				p.hub.CaptureException(err)
				log.Print(err)
			}

		case t := <-p.chSetpoint:
			p.setpoint = t

			if err := p.update(); err != nil {
				p.hub.CaptureException(err)
				log.Print(err)
			}

		case <-ctx.Done():
			if err := p.lcd.ShowMessage("Fermentor", device.SHOW_LINE_1|device.SHOW_BLANK_PADDING); err != nil {
				p.hub.CaptureException(err)
				log.Print(err)
			}
			if err := p.lcd.ShowMessage("Shutting down", device.SHOW_LINE_2|device.SHOW_BLANK_PADDING); err != nil {
				p.hub.CaptureException(err)
				log.Print(err)
			}

			return
		}
	}
}

func (p *LCD) update() error {
	if time.Since(p.lastUpdate) < 3*time.Second {
		return nil
	}

	l1 := fmt.Sprintf("W:%4.1f C:%4.1f %s", p.wort, p.chamber, p.state)
	l2 := fmt.Sprintf("S:%4.1f A:%4.1f", p.ambient, p.setpoint)

	if l1 != p.l1 {
		if err := p.lcd.ShowMessage(l1, device.SHOW_LINE_1|device.SHOW_BLANK_PADDING); err != nil {
			return err
		}

		p.l1 = l1
	}

	if l2 != p.l2 {
		if err := p.lcd.ShowMessage(l2, device.SHOW_LINE_2|device.SHOW_BLANK_PADDING); err != nil {
			return err
		}

		p.l2 = l2
	}

	p.lastUpdate = time.Now()

	return nil
}
