"""Keep track of app state in persistent flash storage."""

import os
import gc
import time
import json
import machine
import badger2040


def get_battery_level():
    # Battery measurement
    vbat_adc = machine.ADC(badger2040.PIN_BATTERY)
    vref_adc = machine.ADC(badger2040.PIN_1V2_REF)
    vref_en = machine.Pin(badger2040.PIN_VREF_POWER)
    vref_en.init(machine.Pin.OUT)
    vref_en.value(0)

    # Enable the onboard voltage reference
    vref_en.value(1)

    # Calculate the logic supply voltage, as will be lower that the usual 3.3V when running off low batteries
    vdd = 1.24 * (65535 / vref_adc.read_u16())
    vbat = (
        (vbat_adc.read_u16() / 65535) * 3 * vdd
    )  # 3 in this is a gain, not rounding of 3.3V

    # Disable the onboard voltage reference
    vref_en.value(0)

    # Convert the voltage to a level to display onscreen
    return vbat


def get_disk_usage():
    # f_bfree and f_bavail should be the same?
    # f_files, f_ffree, f_favail and f_flag are unsupported.
    f_bsize, f_frsize, f_blocks, f_bfree, _, _, _, _, _, f_namemax = os.statvfs("/")

    f_total_size = f_frsize * f_blocks
    f_total_free = f_bsize * f_bfree
    f_total_used = f_total_size - f_total_free

    f_used = 100 / f_total_size * f_total_used
    f_free = 100 / f_total_size * f_total_free

    return f_total_size, f_used, f_free


def state_running():
    state = {"running": "launcher"}
    state_load("launcher", state)
    return state["running"]


def state_clear_running():
    running = state_running()
    state_modify("launcher", {"running": "launcher"})
    return running != "launcher"


def state_set_running(app):
    state_modify("launcher", {"running": app})


def state_launch():
    app = state_running()
    if app is not None and app != "launcher":
        launch("_" + app)


def state_delete(app):
    try:
        os.remove("/state/{}.json".format(app))
    except OSError:
        pass


def state_save(app, data):
    try:
        with open("/state/{}.json".format(app), "w") as f:
            f.write(json.dumps(data))
            f.flush()
    except OSError:
        import os
        try:
            os.stat("/state")
        except OSError:
            os.mkdir("/state")
            state_save(app, data)


def state_modify(app, data):
    state = {}
    state_load(app, state)
    state.update(data)
    state_save(app, state)


def state_load(app, defaults):
    try:
        data = json.loads(open("/state/{}.json".format(app), "r").read())
        if type(data) is dict:
            defaults.update(data)
            return True
    except (OSError, ValueError):
        pass

    state_save(app, defaults)
    return False


def launch(file):
    state_set_running(file[1:])

    gc.collect()

    button_a = machine.Pin(badger2040.BUTTON_A, machine.Pin.IN, machine.Pin.PULL_DOWN)
    button_c = machine.Pin(badger2040.BUTTON_C, machine.Pin.IN, machine.Pin.PULL_DOWN)

    def quit_to_launcher(pin):
        if button_a.value() and button_c.value():
            machine.reset()

    button_a.irq(trigger=machine.Pin.IRQ_RISING, handler=quit_to_launcher)
    button_c.irq(trigger=machine.Pin.IRQ_RISING, handler=quit_to_launcher)

    try:
        try:
            __import__(file[1:])  # Try to import _[file] (drop underscore prefix)
        except ImportError:
            __import__(file)      # Failover to importing [_file]

    except ImportError:
        # If the app doesn't exist, notify the user
        warning(None, "Could not launch: " + file[1:])
        time.sleep(4.0)
    except Exception as e:
        # If the app throws an error, catch it and display!
        print(e)
        warning(None, str(e))
        time.sleep(4.0)

    # If the app exits or errors, do not relaunch!
    state_clear_running()
    machine.reset()  # Exit back to launcher


# Draw an overlay box with a given message within it
def warning(display, message, width=badger2040.WIDTH - 40, height=badger2040.HEIGHT - 40, line_spacing=20, text_size=0.6):
    if display is None:
        display = badger2040.Badger2040()
        display.led(128)

    # Draw a light grey background
    display.pen(12)
    display.rectangle((badger2040.WIDTH - width) // 2, (badger2040.HEIGHT - height) // 2, width, height)

    # Take the provided message and split it up into
    # lines that fit within the specified width
    words = message.split(" ")

    lines = []
    current_line = ""
    for word in words:
        if display.measure_text(current_line + word + " ", text_size) < width:
            current_line += word + " "
        else:
            lines.append(current_line.strip())
            current_line = word + " "
    lines.append(current_line.strip())

    display.pen(0)
    display.thickness(2)

    # Display each line of text from the message, centre-aligned
    num_lines = len(lines)
    for i in range(num_lines):
        length = display.measure_text(lines[i], text_size)
        current_line = (i * line_spacing) - ((num_lines - 1) * line_spacing) // 2
        display.text(lines[i], (badger2040.WIDTH - length) // 2, (badger2040.HEIGHT // 2) + current_line, text_size)

    display.update()
