import time
from machine import Pin, UART
from picographics import PicoGraphics, DISPLAY_ENVIRO_PLUS
from pimoroni import RGBLED, Button
from breakout_bme68x import BreakoutBME68X, STATUS_HEATER_STABLE
from pimoroni_i2c import PimoroniI2C
from breakout_ltr559 import BreakoutLTR559
from adcfft import ADCFFT
# comment out the next line if no particulate sensor
from pms5003 import PMS5003

"""
This example reads from all the sensors on Enviro+.
(plus the optional particulate sensor)
Displays the results on screen.
Press A and B to turn the backlight on and off
Press X and Y to switch between sensor mode and graphic equaliser mode!
"""

# change this to adjust temperature compensation
TEMPERATURE_OFFSET = 3

BRIGHTNESS = 0.8

# change this to adjust pressure based on your altitude
altitude = 0

# light the LED red if the gas reading is less than 50%
GAS_ALERT = 0.5


def graphic_equaliser():
    m_arr = [0 for _ in range(16)]
    i = 0

    adcfft.update()
    m = 0
    for x in range(5, 240):
        v = adcfft.get_scaled(x, 144)
        m = max(m, v)
        v = min(239, v)
        v = 239 - v
        display.line(x - 5, v, x - 5, 239)
    m_arr[i] = min(255, m)
    i += 1
    if i >= len(m_arr):
        i = 0
    ms = int(sum(m_arr) / len(m_arr))
    led.set_rgb(0, ms, 0)


def adjust_to_sea_pressure(pressure_hpa, temperature, altitude):
    """
    Adjust pressure based on your altitude.

    credits to @cubapp https://gist.github.com/cubapp/23dd4e91814a995b8ff06f406679abcf
    """

    # Adjusted-to-the-sea barometric pressure
    adjusted_hpa = pressure_hpa + ((pressure_hpa * 9.80665 * altitude) / (287 * (273 + temperature + (altitude / 400))))
    return adjusted_hpa


def describe_pressure(pressure_hpa):
    """Convert pressure into barometer-type description."""
    if pressure_hpa < 970:
        description = "storm"
    elif 970 <= pressure_hpa < 990:
        description = "rain"
    elif 990 <= pressure_hpa < 1010:
        description = "change"
    elif 1010 <= pressure_hpa < 1030:
        description = "fair"
    elif pressure_hpa >= 1030:
        description = "dry"
    else:
        description = ""
    return description


def describe_humidity(corrected_humidity):
    """Convert relative humidity into good/bad description."""
    if 40 < corrected_humidity < 60:
        description = "good"
    else:
        description = "bad"
    return description


def describe_light(lux):
    """Convert light level in lux to descriptive value."""
    if lux < 50:
        description = "dark"
    elif 50 <= lux < 100:
        description = "dim"
    elif 100 <= lux < 500:
        description = "light"
    elif lux >= 500:
        description = "bright"
    return description


def draw_hist(results_array):
    result_index = 0
    for result in results_array:
        display.set_pen(YELLOW)
        display.rectangle(2 * result_index, 240 - result.pm_ug_per_m3(10), 2, 240)
        display.set_pen(MAGENTA)
        display.rectangle(2 * result_index, 240 - result.pm_ug_per_m3(2.5), 2, 240)
        display.set_pen(BLUE)
        display.rectangle(2 * result_index, 240 - result.pm_ug_per_m3(1.0), 2, 240)
        result_index += 1


# set up the display
display = PicoGraphics(display=DISPLAY_ENVIRO_PLUS)

# set up the LED
led = RGBLED(6, 7, 10, invert=True)

# set up the buttons
button_a = Button(12, invert=True)
button_b = Button(13, invert=True)
button_x = Button(14, invert=True)
button_y = Button(15, invert=True)

# set up the Pico W's I2C
PINS_BREAKOUT_GARDEN = {"sda": 4, "scl": 5}
i2c = PimoroniI2C(**PINS_BREAKOUT_GARDEN)

# set up BME688 and LTR559 sensors
bme = BreakoutBME68X(i2c, address=0x77)
ltr = BreakoutLTR559(i2c)

# set up ADCFFT library to read mic with fast fourier transform
adcfft = ADCFFT()

# configure the PMS5003 for Enviro+
# comment out this section if no particulate sensor
pms5003 = PMS5003(
    uart=UART(1, tx=Pin(8), rx=Pin(9), baudrate=9600),
    pin_enable=Pin(3),
    pin_reset=Pin(2),
    mode="active"
)

# some constants we'll use for drawing
WHITE = display.create_pen(255, 255, 255)
BLACK = display.create_pen(0, 0, 0)
RED = display.create_pen(255, 0, 0)
GREEN = display.create_pen(0, 255, 0)
CYAN = display.create_pen(0, 255, 255)
MAGENTA = display.create_pen(200, 0, 200)
YELLOW = display.create_pen(200, 200, 0)
BLUE = display.create_pen(0, 0, 200)
FFT_COLOUR = display.create_pen(255, 0, 255)
GREY = display.create_pen(75, 75, 75)

WIDTH, HEIGHT = display.get_bounds()
display.set_font("bitmap8")

# some other variables we'll use to keep track of stuff

mode = "sensors"  # start off in sensor mode

# these values will get updated later on
min_temperature = 100.0
max_temperature = 0.0
min_gas = 100000.0
max_gas = 0.0

# array for storing particulate readings
results = []

# setup
led.set_rgb(255, 0, 0)
display.set_backlight(BRIGHTNESS)
display.set_pen(RED)
display.text("waiting for sensors", 0, 0, WIDTH, scale=3)
display.update()

# the gas sensor gives a few weird readings to start, lets discard them
temperature, pressure, humidity, gas, status, _, _ = bme.read()
time.sleep(0.5)
temperature, pressure, humidity, gas, status, _, _ = bme.read()
time.sleep(0.5)

while True:
    # turn off the backlight with A and turn it back on with B
    # switch between sensor and equaliser mode with X and Y
    if button_a.is_pressed:
        display.set_backlight(BRIGHTNESS)
        time.sleep(0.2)
    elif button_b.is_pressed:
        display.set_backlight(0)
        time.sleep(0.2)
    elif button_x.is_pressed:
        mode = "sensors"
        display.set_backlight(BRIGHTNESS)
        time.sleep(0.2)
    elif button_y.is_pressed:
        mode = "equaliser"
        display.set_backlight(BRIGHTNESS)
        time.sleep(0.2)

    if mode == "sensors":
        # read BME688
        temperature, pressure, humidity, gas, status, _, _ = bme.read()
        heater = "Stable" if status & STATUS_HEATER_STABLE else "Unstable"

        # correct temperature and humidity using an offset
        corrected_temperature = temperature - TEMPERATURE_OFFSET
        dewpoint = temperature - ((100 - humidity) / 5)
        corrected_humidity = 100 - (5 * (corrected_temperature - dewpoint))

        # record min and max temperatures
        if corrected_temperature >= max_temperature:
            max_temperature = corrected_temperature
        if corrected_temperature <= min_temperature:
            min_temperature = corrected_temperature

        # record min and max gas readings
        if gas > max_gas:
            max_gas = gas
        if gas < min_gas:
            min_gas = gas

        # convert pressure into hpa
        pressure_hpa = pressure / 100

        # correct pressure
        pressure_hpa = adjust_to_sea_pressure(pressure_hpa, corrected_temperature, altitude)

        # read LTR559
        ltr_reading = ltr.get_reading()
        lux = ltr_reading[BreakoutLTR559.LUX]
        prox = ltr_reading[BreakoutLTR559.PROXIMITY]

        # read particulate sensor and put the results into the array
        # comment out if no PM sensor
        data = pms5003.read()
        results.append(data)
        if (len(results) > 120):  # Scroll the result list by removing the first value
            results.pop(0)

        if heater == "Stable" and ltr_reading is not None:
            led.set_rgb(0, 0, 0)

            # draw some stuff on the screen
            display.set_pen(BLACK)
            display.clear()

            # draw particulate graph on screen, comment out if no PM sensor
            draw_hist(results)

            # draw the top box
            display.set_pen(GREY)
            display.rectangle(0, 0, WIDTH, 60)

            # pick a pen colour based on the temperature
            display.set_pen(GREEN)
            if corrected_temperature > 30:
                display.set_pen(RED)
            if corrected_temperature < 10:
                display.set_pen(CYAN)
            display.text(f"{corrected_temperature:.1f}°c", 5, 15, WIDTH, scale=4)

            # draw temp max and min
            display.set_pen(CYAN)
            display.text(f"min {min_temperature:.1f}", 125, 5, WIDTH, scale=3)
            display.set_pen(RED)
            display.text(f"max {max_temperature:.1f}", 125, 30, WIDTH, scale=3)

            # draw the first column of text
            display.set_pen(WHITE)
            display.text(f"rh {corrected_humidity:.0f}%", 0, 75, WIDTH, scale=3)
            display.text(f"{pressure_hpa:.0f}hPa", 0, 125, WIDTH, scale=3)
            display.text(f"{lux} lux", 0, 175, WIDTH, scale=3)

            # draw the second column of text
            display.text(f"{describe_humidity(corrected_humidity)}", 125, 75, WIDTH, scale=3)
            display.text(f"{describe_pressure(pressure_hpa)}", 125, 125, WIDTH, scale=3)
            display.text(f"{describe_light(lux)}", 125, 175, WIDTH, scale=3)

            # draw bar for gas
            if min_gas != max_gas:
                # light the LED and set pen to red if the gas / air quality reading is less than 50%
                if (gas - min_gas) / (max_gas - min_gas) < GAS_ALERT:
                    led.set_rgb(255, 0, 0)
                    display.set_pen(RED)
                else:
                    display.set_pen(GREEN)

                display.rectangle(236, HEIGHT - round((gas - min_gas) / (max_gas - min_gas) * HEIGHT), 4, round((gas - min_gas) / (max_gas - min_gas) * HEIGHT))
                display.text("gas", 185, 210, WIDTH, scale=3)

            display.update()
            time.sleep(0.5)

    elif mode == "equaliser":
        display.set_pen(BLACK)
        display.clear()
        display.set_pen(FFT_COLOUR)
        display.text("mic", 0, 0, WIDTH, scale=3)
        graphic_equaliser()
        display.update()
