#ifndef MENU_H
#define MENU_H

#include "display.h"
#include "flash.h"

#include <cmath>
#include <new>
#include <pico/stdlib.h>

class Menu
{
public:
  static constexpr int HOLD_MS_THRESHOLD = 250;

  explicit Menu(lib::Display& display, lib::Flash& flash);
  virtual ~Menu() = default;

  virtual void onTick(float dt) = 0;
  virtual void onResume() = 0;

  virtual void onLeftPressed() = 0;
  virtual void onLeftHeld() = 0;

  virtual void onMenuPressed() = 0;
  virtual void onMenuHeld() = 0;

  virtual void onRightPressed() = 0;
  virtual void onRightHeld() = 0;

protected:
  lib::Display* const display;
  lib::Flash* const flash;
};

namespace ease
{
// adapted from easings.net
inline float inOutElastic(float x)
{
  constexpr float c5 = (2 * 3.1415926f) / 4.5f;

  if (x == 0)
  {
    return 0;
  }
  if (x == 1)
  {
    return 1;
  }
  if (x < 0.5)
  {
    return -powf(2, 20 * x - 10) * sinf((20 * x - 11.125f) * c5) / 2.0f;
  }
  else
  {
    return powf(2, -20 * x + 10) * sinf((20 * x - 11.25f) * c5) / 2.0f + 1;
  }
}

// adapted from easings.net
inline float outElastic(float x)
{
  constexpr float c4 = (2 * 3.1415926f) / 3;

  if (x == 0)
  {
    return 0;
  }
  if (x == 1)
  {
    return 1;
  }
  return powf(2, -10 * x) * sinf((x * 10 - 0.75f) * c4) + 1;
}

// adapted from easings.net
inline float inElastic(float x)
{
  constexpr float c4 = (2 * 3.1415926f) / 3;

  if (x == 0)
  {
    return 0;
  }
  if (x == 1)
  {
    return 1;
  }

  return -powf(2, 10 * x - 10) * sinf((x * 10 - 10.75f) * c4);
}

}

namespace menus
{

class ManaMenu : public Menu
{
public:
  explicit ManaMenu(lib::Display& display, lib::Flash& flash);
  ~ManaMenu() override = default;

  void onTick(float dt) override;
  void onResume() override;

  void onLeftPressed() override;
  void onLeftHeld() override;

  void onMenuPressed() override;
  void onMenuHeld() override;

  void onRightPressed() override;
  void onRightHeld() override;

private:
  uint8_t current;
  uint8_t previous;
  bool going_right;
  float progress;
  float old_progress;
};

class CMCMenu : public Menu
{
public:
  explicit CMCMenu(lib::Display& display, lib::Flash& flash, uint8_t color);
  ~CMCMenu() override = default;

  void onTick(float dt) override;
  void onResume() override;

  void onLeftPressed() override;
  void onLeftHeld() override;

  void onMenuPressed() override;
  void onMenuHeld() override;

  void onRightPressed() override;
  void onRightHeld() override;

private:
  uint8_t color;
  uint8_t current;
  uint8_t previous;
  float progress;
  float old_progress;
};

class SelectMenu : public Menu
{
public:
  explicit SelectMenu(lib::Display& display, lib::Flash& flash, uint8_t color,
                      uint8_t cmc);
  ~SelectMenu() override = default;

  void onTick(float dt) override;
  void onResume() override;

  void onLeftPressed() override;
  void onLeftHeld() override;

  void onMenuPressed() override;
  void onMenuHeld() override;

  void onRightPressed() override;
  void onRightHeld() override;

private:
  uint8_t color;
  uint8_t cmc;
  uint8_t current;
  uint16_t found;
  uint16_t filtered[12];

  void draw();
  void draw_letter(uint8_t x, uint8_t y, uint8_t letter);
};

class CardMenu : public Menu
{
public:
  explicit CardMenu(lib::Display& display, lib::Flash& flash, uint16_t card);
  ~CardMenu() override = default;

  void onTick(float dt) override;
  void onResume() override;

  void onLeftPressed() override;
  void onLeftHeld() override;

  void onMenuPressed() override;
  void onMenuHeld() override;

  void onRightPressed() override;
  void onRightHeld() override;

private:
  uint8_t card;

  void draw();
};

namespace detail
{

union Menus
{
  ManaMenu mana;
  CMCMenu cmc;
  SelectMenu select;
  CardMenu card;
};
}

constexpr size_t MAX_MENU_COUNT = 5;

extern uint8_t MENU_MEMORY[sizeof(detail::Menus) * MAX_MENU_COUNT];
extern int8_t current_menu;

inline Menu* get_current_menu()
{
  return reinterpret_cast<Menu*>(MENU_MEMORY
                                 + current_menu * sizeof(detail::Menus));
}

template <typename T, typename... Args> inline void push_menu(Args&&... args)
{
  current_menu += 1;
  new (get_current_menu()) T(args...);
}
inline void pop_menu(uint8_t count = 1)
{
  for (int i = 0; i < count; i++)
  {
    get_current_menu()->~Menu();
    current_menu -= 1;
  }
  get_current_menu()->onResume();
}

}

#endif // MENU_H
