#include "cardslot.h"
#include "devicelib.h"
#include "menu.h"

#include <hardware/clocks.h>
#include <pico/binary_info.h>
#include <pico/bootrom.h>
#include <pico/stdlib.h>
#include <tusb.h>

using namespace lib;

namespace
{

Display display(DISPLAY_SCK, DISPLAY_TX, DISPLAY_CS, DISPLAY_DC,
                100 * 1000 * 1000);
Flash flash(FLASH_SCK, FLASH_TX, FLASH_RX, FLASH_CS, 50 * 1000 * 1000);

enum class RX
{
  WAITING,
  GETTING_CMC,
  GETTING_COLORS,
  GETTING_NAME_LENGTH,
  GETTING_NAME,
  GETTING_IMAGE
};

struct
{
  struct
  {
    uint8_t buffer[32];
    uint8_t size;
    uint8_t written;
  } name;

  uint8_t cmc;
  uint8_t colors;

  struct
  {
    size_t written = 0;
    uint16_t page_index = 0;
  } image;

  RX state = RX::WAITING;

  int32_t card_index = 0;
} rx;

volatile uint8_t debug_requested = false;

constexpr int MS_BUTTON_TIMEOUT = 500;

constexpr int FREQ = 90 * 1000 * 1000;

}

int main()
{
  gpio_set_function(FLASH_IO_2, GPIO_FUNC_SIO);
  gpio_set_function(FLASH_IO_3, GPIO_FUNC_SIO);

  gpio_set_dir(FLASH_IO_2, GPIO_OUT);
  gpio_set_dir(FLASH_IO_3, GPIO_OUT);

  gpio_put(FLASH_IO_2, 1);
  gpio_put(FLASH_IO_3, 1);

  gpio_set_function(BUTTON_LEFT, GPIO_FUNC_SIO);
  gpio_set_function(BUTTON_MIDDLE, GPIO_FUNC_SIO);
  gpio_set_function(BUTTON_RIGHT, GPIO_FUNC_SIO);

  gpio_set_dir(BUTTON_LEFT, GPIO_IN);
  gpio_set_dir(BUTTON_MIDDLE, GPIO_IN);
  gpio_set_dir(BUTTON_RIGHT, GPIO_IN);

  gpio_pull_up(BUTTON_LEFT);
  gpio_pull_up(BUTTON_MIDDLE);
  gpio_pull_up(BUTTON_RIGHT);

  tusb_rhport_init_t dev_init
    = { .role = TUSB_ROLE_DEVICE, .speed = TUSB_SPEED_AUTO };
  tud_init(0);

  uint16_t card_index = 0;
  absolute_time_t last_press = get_absolute_time();
  absolute_time_t previous_time = last_press;

  float dt_accum = 0;

  menus::push_menu<menus::ManaMenu>(display, flash);

  while (true)
  {
    absolute_time_t current = get_absolute_time();
    float dt = absolute_time_diff_us(previous_time, current) / 1000000.0f;
    dt_accum += dt;

    if (!gpio_get(BUTTON_LEFT)
        && absolute_time_diff_us(last_press, current)
             > 1000 * MS_BUTTON_TIMEOUT)
    {
      menus::get_current_menu()->onLeftPressed();

      last_press = current;
    }

    if (!gpio_get(BUTTON_MIDDLE)
        && absolute_time_diff_us(last_press, current)
             > 1000 * MS_BUTTON_TIMEOUT)
    {
      menus::get_current_menu()->onMenuPressed();

      last_press = current;
    }

    if (!gpio_get(BUTTON_RIGHT)
        && absolute_time_diff_us(last_press, current)
             > 1000 * MS_BUTTON_TIMEOUT)
    {
      menus::get_current_menu()->onRightPressed();

      last_press = current;
    }

    tud_task();
    if (dt_accum > 1.0f / 60.0f)
    {
      menus::get_current_menu()->onTick(dt_accum);
      dt_accum = 0;
    }

    previous_time = current;

    if (debug_requested > 0 && tud_vendor_write_available() > 0)
    {
      flash.read_page((debug_requested - 1) * CardSlot::TOTAL_PAGE_COUNT);
      tud_vendor_write(flash.page(), Flash::Page::SIZE);
      tud_vendor_write_flush();
      sleep_ms(500);
      debug_requested -= 1;
    }
  }
}

void tud_vendor_rx_cb(uint8_t itf, const uint8_t* buffer, uint16_t bufsize)
{
  for (size_t read = 0; read < bufsize;)
  {
    if (rx.state == RX::WAITING)
    {
      // special command to mark all as unused
      if (buffer[0] == 0x42)
      {
        for (int i = 0; i < CardSlot::MAX_CARDS; i++)
        {
          CardSlot slot(flash, i);
          slot.mark_unused(flash);
        }

        return;
      }

      // special command to debug flash
      if (buffer[0] == 0x69)
      {
        debug_requested = CardSlot::MAX_CARDS;

        return;
      }

      // special command to reset to bootsel
      if (buffer[0] == 0x99)
      {
        rom_reset_usb_boot(0, 0);
      }

      rx.state = RX::GETTING_CMC;

      rx.card_index = CardSlot::get_unused(flash);
    }
    else if (rx.state == RX::GETTING_CMC)
    {
      rx.state = RX::GETTING_COLORS;

      rx.cmc = buffer[read] > 20 ? 20 : buffer[read];

      read += 1;
    }
    else if (rx.state == RX::GETTING_COLORS)
    {
      rx.state = RX::GETTING_NAME_LENGTH;

      rx.colors = buffer[read] & 0b11111;

      read += 1;
    }
    else if (rx.state == RX::GETTING_NAME_LENGTH)
    {
      rx.state = RX::GETTING_NAME;

      rx.name.size = buffer[read] > 32 ? 32 : buffer[read];
      rx.name.written = 0;

      read += 1;
    }
    else if (rx.state == RX::GETTING_NAME)
    {
      size_t copy_size = bufsize - read < rx.name.size - rx.name.written
                           ? bufsize - read
                           : rx.name.size - rx.name.written;

      memcpy(rx.name.buffer + rx.name.written, buffer + read, copy_size);
      read += copy_size;
      rx.name.written += copy_size;

      if (rx.name.written == rx.name.size)
      {
        rx.state = RX::GETTING_IMAGE;

        rx.image.written = 0;
        rx.image.page_index
          = CardSlot(flash, rx.card_index).get_image_start_page_index();

        if (rx.card_index >= 0)
        {
          CardSlot slot(flash, rx.card_index);
          slot.erase(flash);
          slot.set_name(rx.name.buffer, rx.name.size);
          slot.set_cmc(rx.cmc);
          slot.set_colors(rx.colors);
          slot.save_data(flash);
        }
      }
    }
    else if (rx.state == RX::GETTING_IMAGE)
    {
      size_t copy_size = bufsize - read < Flash::Page::SIZE - rx.image.written
                           ? bufsize - read
                           : Flash::Page::SIZE - rx.image.written;

      memcpy(flash.page() + rx.image.written, buffer + read, copy_size);
      read += copy_size;

      rx.image.written += copy_size;

      if (rx.image.written == 256)
      {
        if (rx.card_index >= 0)
        {
          flash.write_page(rx.image.page_index);
        }
        rx.image.page_index += 1;
        rx.image.written = 0;

        if (rx.image.page_index
              - CardSlot(flash, rx.card_index).get_image_start_page_index()
            == CardSlot::IMAGE_PAGE_COUNT)
        {
          rx.state = RX::WAITING;
        }
      }
    }
  }
}
