#include "Display.h"
#include "Config.h"
#include "Font_Cyrillic.h"
#include "Log.h"
#include <Wire.h>
#include <esp_log.h>
#include <string.h>

namespace {
uint32_t nextUtf8Codepoint(const char *&text) {
  const uint8_t first = static_cast<uint8_t>(*text++);
  if (first < 0x80U) return first;
  if ((first & 0xE0U) == 0xC0U) {
    const uint8_t second = static_cast<uint8_t>(*text);
    if ((second & 0xC0U) == 0x80U) {
      ++text;
      return ((first & 0x1FU) << 6) | (second & 0x3FU);
    }
  }
  return '?';
}
}

Display::Display() : oled_(128, 32, &Wire, -1) {}

bool Display::begin() {
  Wire.begin(GPIO_SDA, GPIO_SCL);
  Wire.setClock(400000); // keeps a full 128x32 framebuffer update near 15 ms
  Wire.setTimeOut(30);   // a faulty/stretched I2C bus must not stall button polling for seconds
  // An absent optional OLED produces a large burst of ESP-IDF NACK messages.
  // Probe it once and keep the I2C driver quiet when no display is connected.
  esp_log_level_set("i2c.master", ESP_LOG_NONE);
  Wire.beginTransmission(OLED_ADDRESS);
  if (Wire.endTransmission() != 0) {
    ok_ = false;
    Log::printf("OLED", "not detected at I2C address=0x%02X", OLED_ADDRESS);
    return false;
  }
  ok_ = oled_.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS);
  if (ok_) {
    oled_.setRotation(OLED_ROTATION);
    oled_.setTextColor(SSD1306_WHITE);
    oled_.setTextSize(1);
    powered_ = true;
  }
  Log::printf("OLED", "initialization %s, I2C address=0x%02X", ok_ ? "OK" : "FAILED", OLED_ADDRESS);
  return ok_;
}

void Display::setPower(bool enabled) {
  if (!ok_ || powered_ == enabled) return;
  oled_.ssd1306_command(enabled ? SSD1306_DISPLAYON : SSD1306_DISPLAYOFF);
  powered_ = enabled;
  Log::printf("OLED", "display power %s", enabled ? "ON" : "OFF");
}

void Display::drawTextLine(const char *text, int16_t y, int16_t startX) {
  int16_t x = startX;
  while (text && *text && x + CyrillicFont::WIDTH <= oled_.width()) {
    const uint32_t codepoint = nextUtf8Codepoint(text);
    const uint8_t *glyph = CyrillicFont::glyph(codepoint);
    if (glyph) {
      for (uint8_t row = 0; row < CyrillicFont::HEIGHT; ++row) {
        const uint8_t pixels = pgm_read_byte(glyph + row);
        for (uint8_t column = 0; column < CyrillicFont::WIDTH; ++column)
          if (pixels & (1U << (CyrillicFont::WIDTH - 1U - column)))
            oled_.drawPixel(x + column, y + row, SSD1306_WHITE);
      }
    } else if (codepoint < 0x100U) {
      oled_.drawChar(x, y, static_cast<unsigned char>(codepoint),
        SSD1306_WHITE, SSD1306_BLACK, 1);
    } else {
      oled_.drawChar(x, y, '?', SSD1306_WHITE, SSD1306_BLACK, 1);
    }
    x += CyrillicFont::ADVANCE;
  }
}

void Display::show(const char *a, const char *b, uint32_t progress,
                   uint32_t progressTotal, const char *topRight) {
  char one[64], two[64];
  snprintf(one, sizeof(one), "%s", a ? a : ""); snprintf(two, sizeof(two), "%s", b ? b : "");
  // Serial is the primary UI mirror and remains available when OLED is absent.
  Log::printf("UI", "%s | %s", one, two);
  if (!ok_) return;
  setPower(true);
  oled_.clearDisplay();
  drawTextLine(one, 3);
  drawTextLine(two, 19);
  if (topRight && *topRight) {
    oled_.fillRect(oled_.width() - CyrillicFont::ADVANCE, 0,
      CyrillicFont::ADVANCE, CyrillicFont::HEIGHT + 3, SSD1306_BLACK);
    drawTextLine(topRight, 3, oled_.width() - CyrillicFont::ADVANCE);
  }
  if (progressTotal) {
    if (progress > progressTotal) progress = progressTotal;
    const uint16_t width = static_cast<uint16_t>(
      (static_cast<uint64_t>(progress) * 128U + progressTotal - 1U) / progressTotal);
    if (width) oled_.drawFastHLine(0, 31, width, SSD1306_WHITE);
  }
  oled_.display();
}

void Display::formatFrequency(float hz, char *out, size_t n) {
  float value = hz; const char *suffix = "Hz";
  if (hz >= 999950.0f) { value = hz / 1000000.0f; suffix = "M"; }
  else if (hz >= 999.5f) { value = hz / 1000.0f; suffix = "k"; }
  if (suffix[0] == 'M' && fabsf(value - roundf(value)) < 0.0005f)
    snprintf(out, n, "%.0f%s", value, suffix);
  else if (value >= 100.0f) snprintf(out, n, "%.1f%s", value, suffix);
  else if (value >= 10.0f) snprintf(out, n, "%.2f%s", value, suffix);
  else snprintf(out, n, "%.3f%s", value, suffix);
}

void Display::formatTestFrequency(uint32_t hz, char *out, size_t n) {
  if (hz >= 1000000U && hz % 1000000U == 0)
    snprintf(out, n, "%luM", hz / 1000000U);
  else if (hz >= 1000U && hz % 1000U == 0)
    snprintf(out, n, "%luk", hz / 1000U);
  else if (hz >= 1000U) {
    const float khz = hz / 1000.0f;
    if (khz >= 100.0f) snprintf(out, n, "%.1fk", khz);
    else if (khz >= 10.0f) snprintf(out, n, "%.2fk", khz);
    else snprintf(out, n, "%.3fk", khz);
  } else
    snprintf(out, n, "%lu", hz);
}

void Display::formatPwmFrequency(uint32_t hz, char *out, size_t n) {
  if (hz >= 1000000U && hz % 1000000U == 0U)
    snprintf(out, n, "%luMHz", hz / 1000000U);
  else if (hz >= 1000U && hz % 1000U == 0U)
    snprintf(out, n, "%lukHz", hz / 1000U);
  else if (hz >= 1000U)
    snprintf(out, n, "%.3gkHz", hz / 1000.0f);
  else
    snprintf(out, n, "%luHz", hz);
}

void Display::formatPulse(uint32_t pulseNs, char *out, size_t n, bool measured) {
  if (pulseNs >= 1000U) {
    const float us = pulseNs / 1000.0f;
    if (measured) snprintf(out, n, "%.2fu", us);
    else if (pulseNs % 1000U == 0U) snprintf(out, n, "%luus", pulseNs / 1000U);
    else snprintf(out, n, "%.2fus", us);
  } else if (measured) {
    snprintf(out, n, "%.3fu", pulseNs / 1000.0f);
  } else {
    snprintf(out, n, "%luns", pulseNs);
  }
}

void Display::formatDuration(uint64_t us, char *out, size_t n) {
  const uint64_t totalSeconds = (us + 999999ULL) / 1000000ULL;
  const uint64_t minutes = totalSeconds / 60ULL;
  if (minutes < 60) snprintf(out, n, "%02llu:%02llu", minutes, totalSeconds % 60ULL);
  else snprintf(out, n, "%llu:%02llu", minutes / 60ULL, minutes % 60ULL);
}
