#include "display.h"

#include <Arduino.h>
#include <Wire.h>

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1327.h>

namespace {

static constexpr uint8_t kDisplayWidth = 128;
static constexpr uint8_t kDisplayHeight = 128;
static constexpr uint8_t kDefaultI2cAddr = 0x3D;

static constexpr uint8_t kGrayDim = 0x04;
static constexpr uint8_t kGrayBar = 0x08;
static constexpr uint8_t kGrayText = 0x0F;

Adafruit_SSD1327 g_display(kDisplayWidth, kDisplayHeight, &Wire, -1, 400000, 100000);

bool g_displayPresent = false;
bool g_dirty = true;
display_view_t g_view = DISPLAY_VIEW_VOLUME;
uint8_t g_volume = 50;
bool g_signerMode = false;
char g_status[24] = "BOOT";

uint8_t g_menuSelected = 0;
uint8_t g_mnemonicPage = 0;
char g_mnemonic[256] = "";
char g_msgTitle[18] = "";
char g_msgLine1[22] = "";
char g_msgLine2[22] = "";
char g_enterSlots[12][16] = {{0}};
uint8_t g_enterActiveSlot = 0;
uint8_t g_enterStage = 1;
char g_enterBuilding[16] = "";
uint8_t g_enterCursorPos = 0;

enum signer_screen_t {
  SIGNER_SCREEN_MENU = 0,
  SIGNER_SCREEN_ENTER_GRID = 1,
  SIGNER_SCREEN_MNEMONIC_PAGE = 2,
  SIGNER_SCREEN_MESSAGE = 3,
  SIGNER_SCREEN_READY = 4,
};

signer_screen_t g_signerScreen = SIGNER_SCREEN_MENU;

uint8_t clampVolume(uint8_t v) {
  return (v > 100) ? 100 : v;
}

void drawStatusLine() {
  g_display.setTextSize(1);
  g_display.setTextColor(kGrayText);
  g_display.setCursor(2, 118);
  g_display.print(g_status);
}

void drawButtonLegend(const char *left, const char *center, const char *right) {
  g_display.setTextSize(1);
  g_display.setTextColor(kGrayText);
  g_display.setTextWrap(false);

  if (left && left[0] != '\0') {
    g_display.setCursor(2, 118);
    g_display.print(left);
  }

  int16_t x1 = 0;
  int16_t y1 = 0;
  uint16_t w = 0;
  uint16_t h = 0;

  if (center && center[0] != '\0') {
    g_display.getTextBounds(center, 0, 0, &x1, &y1, &w, &h);
    const int16_t x = static_cast<int16_t>((kDisplayWidth - w) / 2);
    g_display.setCursor(x, 118);
    g_display.print(center);
  }

  if (right && right[0] != '\0') {
    g_display.getTextBounds(right, 0, 0, &x1, &y1, &w, &h);
    const int16_t x = static_cast<int16_t>(126 - w);
    g_display.setCursor(x, 118);
    g_display.print(right);
  }
}

void drawVolumeView() {
  g_display.setTextWrap(false);

  g_display.setTextColor(kGrayDim);
  g_display.setTextSize(2);
  g_display.setCursor(40, 18);
  g_display.print("VOL");

  char volBuf[4];
  snprintf(volBuf, sizeof(volBuf), "%u", g_volume);

  int16_t x1 = 0;
  int16_t y1 = 0;
  uint16_t w = 0;
  uint16_t h = 0;
  g_display.setTextSize(6);
  g_display.getTextBounds(volBuf, 0, 0, &x1, &y1, &w, &h);

  const int16_t x = static_cast<int16_t>((kDisplayWidth - w) / 2);
  const int16_t y = 42;
  g_display.setTextColor(kGrayText);
  g_display.setCursor(x, y);
  g_display.print(volBuf);

  const int16_t barX = 8;
  const int16_t barY = 102;
  const int16_t barW = kDisplayWidth - 16;
  const int16_t barH = 8;
  g_display.drawRect(barX, barY, barW, barH, kGrayDim);

  const int16_t fillW = static_cast<int16_t>((static_cast<uint32_t>(barW - 2) * g_volume) / 100U);
  if (fillW > 0) {
    g_display.fillRect(barX + 1, barY + 1, fillW, barH - 2, kGrayBar);
  }

  drawButtonLegend("prev", "play", "next");
}

void drawSignerMenu() {
  g_display.setTextWrap(false);
  g_display.setTextSize(1);
  g_display.setTextColor(kGrayDim);
  g_display.setCursor(2, 2);
  g_display.print("SIGNER MENU");

  const char *items[2] = {"Generate new", "Enter mnemonic"};

  g_display.setTextColor(kGrayText);
  g_display.setTextSize(2);
  for (uint8_t i = 0; i < 2; ++i) {
    g_display.setCursor(6, 28 + i * 24);
    g_display.print((i == g_menuSelected) ? ">" : " ");
    g_display.setCursor(20, 28 + i * 24);
    g_display.print(items[i]);
  }

  drawButtonLegend("", "select", "");
}

void drawEnterMnemonic() {
  g_display.setTextWrap(false);
  g_display.setTextSize(1);
  g_display.setTextColor(kGrayDim);
  g_display.setCursor(2, 2);
  g_display.print("ENTER MNEMONIC");

  g_display.setTextColor(kGrayText);
  for (uint8_t i = 0; i < 12; ++i) {
    const uint8_t row = (i < 6) ? i : (i - 6);
    const int16_t x = (i < 6) ? 0 : 64;
    const int16_t y = static_cast<int16_t>(18 + row * 9);

    const bool isActive = (i == g_enterActiveSlot);
    const char cursor = isActive ? '>' : ' ';

    const char *slotWord = g_enterSlots[i];
    if (!slotWord) {
      slotWord = "";
    }

    char activeBuf[16];
    const char *shownWord = slotWord;
    if (isActive) {
      const char *src = g_enterBuilding;
      if (!src) {
        src = "";
      }

      if (g_enterStage < 3) {
        size_t srcLen = strlen(src);
        if (srcLen > 14) {
          srcLen = 14;
        }
        size_t pos = g_enterCursorPos;
        if (pos > srcLen) {
          pos = srcLen;
        }

        size_t out = 0;
        for (size_t j = 0; j < pos && out < sizeof(activeBuf) - 1; ++j) {
          activeBuf[out++] = src[j];
        }
        if (out < sizeof(activeBuf) - 1) {
          activeBuf[out++] = '_';
        }
        for (size_t j = pos; j < srcLen && out < sizeof(activeBuf) - 1; ++j) {
          activeBuf[out++] = src[j];
        }
        activeBuf[out] = '\0';
        shownWord = activeBuf;
      } else if (src[0] != '\0') {
        shownWord = src;
      }
    }

    char lineBuf[22];
    snprintf(lineBuf, sizeof(lineBuf), "%c%2u %s", cursor, (unsigned)(i + 1), shownWord);
    g_display.setCursor(x, y);
    g_display.print(lineBuf);
  }

  if (g_enterStage == 3) {
    drawButtonLegend("", "select", "back");
  } else {
    drawButtonLegend("", "letter", "back");
  }
}

void drawMnemonicPage() {
  g_display.setTextWrap(false);
  g_display.setTextSize(1);
  g_display.setTextColor(kGrayDim);
  g_display.setCursor(2, 2);
  g_display.print("GENERATED PHRASE");

  char copy[256];
  strncpy(copy, g_mnemonic, sizeof(copy) - 1);
  copy[sizeof(copy) - 1] = '\0';

  char *words[12] = {nullptr};
  uint8_t count = 0;
  char *saveptr = nullptr;
  char *tok = strtok_r(copy, " ", &saveptr);
  while (tok && count < 12) {
    words[count++] = tok;
    tok = strtok_r(nullptr, " ", &saveptr);
  }

  g_display.setTextColor(kGrayText);
  for (uint8_t i = 0; i < count; ++i) {
    const uint8_t row = (i < 6) ? i : (i - 6);
    const int16_t x = (i < 6) ? 0 : 64;
    const int16_t y = static_cast<int16_t>(18 + row * 9);

    char lineBuf[20];
    snprintf(lineBuf, sizeof(lineBuf), "%2u %s", (unsigned)(i + 1), words[i]);
    g_display.setCursor(x, y);
    g_display.print(lineBuf);
  }

  drawButtonLegend("cancel", "confirm", "new");
}

void drawMessage() {
  g_display.setTextWrap(false);
  g_display.setTextSize(1);
  g_display.setTextColor(kGrayDim);
  g_display.setCursor(2, 2);
  g_display.print(g_msgTitle);

  g_display.setTextSize(2);
  g_display.setTextColor(kGrayText);
  g_display.setCursor(6, 34);
  g_display.print(g_msgLine1);
  g_display.setCursor(6, 62);
  g_display.print(g_msgLine2);

  const char *left = "";
  const char *center = "";
  const char *right = "";

  if (strcmp(g_msgTitle, "APPROVAL") == 0) {
    left = "deny";
    center = "allow";
  } else if (strcmp(g_msgTitle, "INVALID") == 0) {
    center = "select";
    right = "back";
  } else if (strcmp(g_msgTitle, "ERROR") == 0) {
    center = "select";
  }

  drawButtonLegend(left, center, right);
}

void drawSignerReady() {
  g_display.setTextWrap(false);
  g_display.setTextSize(1);
  g_display.setTextColor(kGrayDim);
  g_display.setCursor(2, 2);
  g_display.print("SIGNER READY");

  g_display.drawRect(4, 22, 120, 62, kGrayDim);

  g_display.setTextSize(2);
  g_display.setTextColor(kGrayText);
  g_display.setCursor(16, 36);
  g_display.print("Awaiting");
  g_display.setCursor(30, 58);
  g_display.print("host");

  drawButtonLegend("", "", "menu");
}

void renderFrame() {
  if (!g_displayPresent) {
    return;
  }

  g_display.clearDisplay();

  if (g_view == DISPLAY_VIEW_VOLUME) {
    drawVolumeView();
  } else {
    switch (g_signerScreen) {
      case SIGNER_SCREEN_MENU:
        drawSignerMenu();
        break;
      case SIGNER_SCREEN_ENTER_GRID:
        drawEnterMnemonic();
        break;
      case SIGNER_SCREEN_MNEMONIC_PAGE:
        drawMnemonicPage();
        break;
      case SIGNER_SCREEN_MESSAGE:
        drawMessage();
        break;
      case SIGNER_SCREEN_READY:
        drawSignerReady();
        break;
      default:
        drawMessage();
        break;
    }
  }

  g_display.display();
}

void copy_text(char *dst, size_t dst_sz, const char *src) {
  if (!dst || dst_sz == 0) {
    return;
  }
  if (!src) {
    dst[0] = '\0';
    return;
  }
  strncpy(dst, src, dst_sz - 1);
  dst[dst_sz - 1] = '\0';
}

}  // namespace

void display_init() {
  Wire.setClock(400000);
  Wire.begin();

  Wire.beginTransmission(kDefaultI2cAddr);
  const uint8_t probeErr = Wire.endTransmission();
  if (probeErr != 0) {
    g_displayPresent = false;
    return;
  }

  g_displayPresent = g_display.begin(kDefaultI2cAddr, true);
  if (!g_displayPresent) {
    return;
  }

  g_dirty = true;
  renderFrame();
}

bool display_is_present() {
  return g_displayPresent;
}

void display_set_view(display_view_t view) {
  if (g_view == view) {
    return;
  }
  g_view = view;
  g_dirty = true;
}

void display_set_mode(bool signer_mode) {
  if (g_signerMode == signer_mode) {
    return;
  }
  g_signerMode = signer_mode;
  g_dirty = true;
}

void display_set_volume(uint8_t volume) {
  const uint8_t v = clampVolume(volume);
  if (v == g_volume) {
    return;
  }

  g_volume = v;
  g_dirty = true;
}

void display_show_signer_menu(uint8_t selected_index) {
  g_signerScreen = SIGNER_SCREEN_MENU;
  g_menuSelected = selected_index > 1 ? 1 : selected_index;
  g_dirty = true;
}

void display_show_enter_mnemonic(const char *const slots[12], uint8_t active_slot, uint8_t stage,
                                 const char *building_text, uint8_t cursor_pos) {
  g_signerScreen = SIGNER_SCREEN_ENTER_GRID;
  g_enterActiveSlot = (active_slot > 11) ? 11 : active_slot;
  g_enterStage = stage;
  g_enterCursorPos = cursor_pos;
  copy_text(g_enterBuilding, sizeof(g_enterBuilding), building_text);

  for (uint8_t i = 0; i < 12; ++i) {
    const char *src = "";
    if (slots && slots[i]) {
      src = slots[i];
    }
    copy_text(g_enterSlots[i], sizeof(g_enterSlots[i]), src);
  }

  g_dirty = true;
}

void display_show_mnemonic_page(const char *mnemonic, uint8_t page_index) {
  g_signerScreen = SIGNER_SCREEN_MNEMONIC_PAGE;
  g_mnemonicPage = page_index;
  copy_text(g_mnemonic, sizeof(g_mnemonic), mnemonic);
  g_dirty = true;
}

void display_show_message(const char *title, const char *line1, const char *line2) {
  g_signerScreen = SIGNER_SCREEN_MESSAGE;
  copy_text(g_msgTitle, sizeof(g_msgTitle), title);
  copy_text(g_msgLine1, sizeof(g_msgLine1), line1);
  copy_text(g_msgLine2, sizeof(g_msgLine2), line2);
  g_dirty = true;
}

void display_show_signer_ready() {
  g_signerScreen = SIGNER_SCREEN_READY;
  g_dirty = true;
}

// Note: status-line updates are currently disabled at call sites in
// kb2040_hidden_signer.ino by commenting out display_set_status(...)
// invocations to keep the bottom status line from being overwritten.
void display_set_status(const char *status) {
  if (!status) {
    return;
  }

  copy_text(g_status, sizeof(g_status), status);
  g_dirty = true;
}

void display_tick() {
  if (!g_displayPresent || !g_dirty) {
    return;
  }

  renderFrame();
  g_dirty = false;
}
