from time import ticks_ms, sleep
from math import log, ceil, floor
from bme680.bme680 import BME680_I2C
from mch22 import exit_python
from machine import SoftI2C, Pin
import display
import buttons

bme = BME680_I2C(SoftI2C(scl=Pin(21), sda=Pin(22)))

BACKGROUND = 0x491D88
PALETTE_0 = 0xFEC859
PALETTE_1 = 0xFA448C
PALETTE_2 = 0x331A38
PALETTE_3 = 0x43B5A0

IIR_FILTER_TIME_S = 5


def reboot(pressed):
    if pressed:
        exit_python()


buttons.attach(buttons.BTN_HOME, reboot)

# define functions to round, floor and ceil with float resolutions
def round_res(num, res):
    return round(num / res) * res


def floor_res(num, res):
    return floor(num / res) * res


def ceil_res(num, res):
    return ceil(num / res) * res


def map_vals_fn(in_min, in_max, out_min, out_max):
    scaleFactor = float(out_max - out_min) / float(in_max - in_min)

    def map_vals(value):
        return out_min + (value - in_min) * scaleFactor

    return map_vals


# generic function to draw samples in a given area on the display
def draw_samples(
    samples,
    x_min=0,
    x_max=display.width(),
    y_min=0,
    y_max=display.height(),
    padding=0.05,
    grid=True,
    n_grid=5,
):
    keys, values = list(zip(*samples))

    min_key = min(keys)
    max_key = max(keys)
    min_value = min(values)
    max_value = max(values)

    width = x_max - x_min
    height = y_max - y_min

    x_map_fn = map_vals_fn(
        min_key, max_key, x_min + padding * width, x_min + (1 - padding) * width
    )
    y_map_fn = map_vals_fn(
        max_value,
        min_value,
        x_min + padding * height,
        x_min + (1 - padding) * height,
    )

    xs = map(int, map(x_map_fn, keys))
    ys = map(int, map(y_map_fn, values))

    nodes = list(zip(xs, ys))

    if grid:
        key_res = 10 ** round(log(max_key - min_key, 10)) / n_grid
        value_res = 10 ** round(log(max_value - min_value, 10)) / n_grid

        min_key_grid = round_res(min_key, key_res)
        min_value_grid = round_res(min_value, value_res)

        n_key_grids = ceil((max_key - min_key_grid) / key_res + 0.5)
        n_value_grids = ceil((max_value - min_value_grid) / value_res + 0.5)

        for x_grid_idx in range(n_key_grids):
            x = x_map_fn(min_key_grid + x_grid_idx * key_res)
            display.drawLine(x, y_min, x, y_max, PALETTE_2)

        for y_grid_idx in range(n_value_grids):
            y = y_map_fn(min_value_grid + y_grid_idx * value_res)
            display.drawLine(x_min, y, x_max, y, PALETTE_2)

    for idx in range(len(nodes) - 1):
        display.drawLine(
            nodes[idx][0],
            nodes[idx][1],
            nodes[idx + 1][0],
            nodes[idx + 1][1],
            PALETTE_3,
        )


samples = [(ticks_ms(), bme.pressure)]

while True:
    try:
        display.drawFill(BACKGROUND)

        current_ticks_ms = ticks_ms()
        current_pressure = bme.pressure
        diff_ticks_ms = current_ticks_ms - samples[-1][0]

        # apply single pole IIR filter
        # use 1/5 of IIR_FILTER_TIME_S, as 97% of signal level will then be achieve within IIR_FILTER_TIME_S
        alpha = (diff_ticks_ms) / ((diff_ticks_ms) + IIR_FILTER_TIME_S * 10 ** 3 / 5)

        fitered_presure = samples[-1][1] + alpha * (current_pressure - samples[-1][1])

        samples.append((current_ticks_ms, fitered_presure))
        draw_samples(samples, y_max=display.height() * 4 // 5)

        display.drawText(
            22-display.height() // 20,
            display.height() * 19 // 20 - 22,
            "{} mbar".format(current_pressure),
            PALETTE_3,
            "roboto_regular22",
        )

        display.drawText(
            display.width() * 2 // 3,
            display.height() * 19 // 20 - 24,
            "   press any\r\nbutton to exit",
            PALETTE_1,
            "roboto_regular12",
        )

        display.flush()

        # if screen is full, remove oldest sample
        if len(samples) > (display.width() / 3):
            samples = samples[1:]

        sleep(0)
    except:
        exit_python()
