package hw

import (
	"time"

	"github.com/warthog618/go-gpiocdev"
)

const (
	pinDoorOpen    = 17
	pinFanPower    = 22
	pinCoolerPower = 23
	pinHeaterPower = 24
	pinLightsPower = 25

	// GPIO21 is located at pin 13 on RPi 1B rev 1. Rev 2 and never boards
	// has GPIO27 here.
	pinOneWirePower = 21

	off = 0
	on  = 1
)

type HWEvent int

const (
	NoOp HWEvent = iota
	DoorClosed
	DoorOpened
)

type Gpio struct {
	chip         *gpiocdev.Chip
	doorOpen     *gpiocdev.Line
	oneWirePower *gpiocdev.Line
	lightsPower  *gpiocdev.Line
	fanPower     *gpiocdev.Line
	coolerPower  *gpiocdev.Line
	heaterPower  *gpiocdev.Line

	C chan HWEvent
}

func NewGpio() (*Gpio, error) {
	var err error
	p := &Gpio{
		C: make(chan HWEvent, 1),
	}

	p.chip, err = gpiocdev.NewChip("gpiochip0", gpiocdev.WithConsumer("fermentord"))
	if err != nil {
		return nil, err
	}

	p.doorOpen, err = p.chip.RequestLine(
		pinDoorOpen,
		gpiocdev.AsInput,
		gpiocdev.LineBiasPullUp,
		gpiocdev.WithDebounce(200*time.Millisecond),
		gpiocdev.WithBothEdges,
		gpiocdev.WithEventHandler(p.onDoorStateChanged),
	)
	if err != nil {
		return nil, err
	}

	// Add an initial door reading to the channel to ensure correct state when booting
	isDoorOpen, err := p.doorOpen.Value()
	if err != nil {
		return nil, err
	}
	switch isDoorOpen {
	case 0: // Line is pulled low when door is open (circuit is closed)
		p.C <- DoorOpened
	default: // Pull-up resistor sets line high when door is closed (open circuit)
		p.C <- DoorClosed
	}

	p.oneWirePower, err = p.chip.RequestLine(pinOneWirePower, gpiocdev.AsOutput(0))
	if err != nil {
		return nil, err
	}

	p.lightsPower, err = p.chip.RequestLine(pinLightsPower, gpiocdev.AsOutput(0))
	if err != nil {
		return nil, err
	}

	p.fanPower, err = p.chip.RequestLine(pinFanPower, gpiocdev.AsOutput(0))
	if err != nil {
		return nil, err
	}

	p.coolerPower, err = p.chip.RequestLine(pinCoolerPower, gpiocdev.AsOutput(0))
	if err != nil {
		return nil, err
	}

	p.heaterPower, err = p.chip.RequestLine(pinHeaterPower, gpiocdev.AsOutput(0))
	if err != nil {
		return nil, err
	}

	return p, nil
}

func (p *Gpio) Close() {
	p.coolerPower.SetValue(off)
	p.heaterPower.SetValue(off)
	p.fanPower.SetValue(off)
	p.lightsPower.SetValue(off)
	p.oneWirePower.SetValue(off)

	p.oneWirePower.Close()
	p.heaterPower.Close()
	p.coolerPower.Close()
	p.fanPower.Close()
	p.lightsPower.Close()
	p.doorOpen.Close()
	p.chip.Close()

	close(p.C)
}

func (p *Gpio) StartCooler() {
	p.coolerPower.SetValue(on)
}

func (p *Gpio) StopCooler() {
	p.coolerPower.SetValue(off)
}

func (p *Gpio) StartHeater() {
	p.heaterPower.SetValue(on)
}

func (p *Gpio) StopHeater() {
	p.heaterPower.SetValue(off)
}

func (p *Gpio) StartFan() {
	p.fanPower.SetValue(on)
}

func (p *Gpio) StopFan() {
	p.fanPower.SetValue(off)
}

func (p *Gpio) LightsOn() {
	p.lightsPower.SetValue(on)
}

func (p *Gpio) LightsOff() {
	p.lightsPower.SetValue(off)
}

func (p *Gpio) StartOneWirePower() {
	// Inverted logic due to PNP transistor switch
	p.oneWirePower.SetValue(off)
}

func (p *Gpio) StopOneWirePower() {
	// Inverted logic due to PNP transistor switch
	p.oneWirePower.SetValue(on)
}

func (p *Gpio) onDoorStateChanged(le gpiocdev.LineEvent) {
	switch le.Type {
	case gpiocdev.LineEventFallingEdge:
		p.C <- DoorOpened

	case gpiocdev.LineEventRisingEdge:
		p.C <- DoorClosed
	}
}
