package main

// CO2 sensor example using SCD4x (I2C).
// Connect the sensor to I2C1: SDA=P0_17, SCL=P0_20 (3.3V power).
//
// The display background changes color based on CO2 level:
//   green  → < 800 ppm  (good air quality)
//   yellow → < 1500 ppm (ventilate soon)
//   red    → ≥ 1500 ppm (ventilate now!)
//
// The WS2812 LEDs mirror the same color, and the buzzer sounds a
// warning tone when CO2 exceeds the danger threshold.

import (
	"image/color"
	"machine"
	"strconv"
	"time"

	"tinygo.org/x/drivers/scd4x"
	"tinygo.org/x/drivers/st7789"
	"tinygo.org/x/drivers/ws2812"
	"tinygo.org/x/tinyfont"
	"tinygo.org/x/tinyfont/freesans"
)

var (
	display st7789.Device
	leds    ws2812.Device
	bzrPin  machine.Pin
	co2dev  *scd4x.Device
)

func main() {
	time.Sleep(3 * time.Second)

	machine.SPI0.Configure(machine.SPIConfig{
		SCK:       machine.P1_01,
		SDO:       machine.P1_02,
		Frequency: 8000000,
		Mode:      0,
	})

	machine.I2C1.Configure(machine.I2CConfig{
		SDA:       machine.P0_17,
		SCL:       machine.P0_20,
		Frequency: 400000,
	})

	display = st7789.New(machine.SPI0,
		machine.P1_15, // TFT_RESET
		machine.P1_13, // TFT_DC
		machine.P0_10, // TFT_CS
		machine.P0_09) // TFT_LITE

	display.Configure(st7789.Config{
		Rotation:     st7789.ROTATION_90,
		Width:        135,
		Height:       240,
		RowOffset:    40,
		ColumnOffset: 53,
	})

	neo := machine.P1_11
	neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
	leds = ws2812.New(neo)

	bzrPin = machine.P0_31
	bzrPin.Configure(machine.PinConfig{Mode: machine.PinOutput})

	co2dev = scd4x.New(machine.I2C1)
	co2dev.Configure()

	display.FillScreen(color.RGBA{0, 0, 0, 255})

	if err := co2dev.StartPeriodicMeasurement(); err != nil {
		println(err)
	}

	var (
		co2  int32
		temp int32
		hum  int32
		err  error
		bg   color.RGBA
		oldbg = color.RGBA{R: 0xff, G: 0xff, B: 0xff}
		black = color.RGBA{0, 0, 0, 255}
	)

	for {
		// SCD4x needs up to 5s between measurements; retry a few times
		for i := 0; i < 5; i++ {
			co2, err = co2dev.ReadCO2()
			temp, _ = co2dev.ReadTemperature()
			hum, _ = co2dev.ReadHumidity()
			if err != nil {
				println(err)
			}
			if co2 != 0 {
				break
			}
			time.Sleep(200 * time.Millisecond)
		}

		switch {
		case co2 < 800:
			bg = color.RGBA{0, 220, 0, 255}
		case co2 < 1500:
			bg = color.RGBA{220, 220, 0, 255}
		default:
			bg = color.RGBA{220, 0, 0, 255}
		}

		if bg != oldbg {
			display.FillScreen(bg)
			oldbg = bg
		}

		// mirror CO2 status on the LEDs
		leds.WriteColors([]color.RGBA{bg, bg})

		// buzzer warning when CO2 is dangerously high
		if co2 >= 1500 {
			warn()
		}

		// display 240x135, text layout:
		//   y=40  CO2 level (most prominent)
		//   y=75  Temperature
		//   y=110 Humidity
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 40, "CO2: "+strconv.Itoa(int(co2))+" ppm", black)
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 75, "Temp: "+formatMilliC(temp), black)
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 110, "Hum:  "+formatMilliPct(hum), black)

		time.Sleep(time.Second)

		// erase text by redrawing in background color (avoids full FillScreen flicker)
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 40, "CO2: "+strconv.Itoa(int(co2))+" ppm", bg)
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 75, "Temp: "+formatMilliC(temp), bg)
		tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 110, "Hum:  "+formatMilliPct(hum), bg)
	}
}

// formatMilliC formats a millidegree Celsius value as "XX.X C".
func formatMilliC(mc int32) string {
	if mc < 0 {
		mc = -mc
		return "-" + strconv.Itoa(int(mc/1000)) + "." + strconv.Itoa(int((mc%1000)/100)) + " C"
	}
	return strconv.Itoa(int(mc/1000)) + "." + strconv.Itoa(int((mc%1000)/100)) + " C"
}

// formatMilliPct formats a millipercent value as "XX.X %".
func formatMilliPct(mp int32) string {
	return strconv.Itoa(int(mp/1000)) + "." + strconv.Itoa(int((mp%1000)/100)) + " %"
}

// warn sounds two short beeps on the buzzer.
func warn() {
	for beep := 0; beep < 2; beep++ {
		for i := 0; i < 100; i++ {
			bzrPin.High()
			time.Sleep(500 * time.Microsecond)
			bzrPin.Low()
			time.Sleep(500 * time.Microsecond)
		}
		time.Sleep(200 * time.Millisecond)
	}
}
