#include "font.h"

#include "esphome/core/color.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"

namespace esphome::font {
static const char *const TAG = "font";

#ifdef USE_LVGL_FONT
static const uint8_t OPA4_TABLE[16] = {0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255};

static const uint8_t OPA2_TABLE[4] = {0, 85, 170, 255};

const void *Font::get_glyph_bitmap(lv_font_glyph_dsc_t *dsc, lv_draw_buf_t *draw_buf) {
  const auto *font = dsc->resolved_font;
  auto *const fe = (Font *) font->dsc;

  const auto *gd = fe->get_glyph_data_(dsc->gid.index);
  if (gd == nullptr) {
    return nullptr;
  }

  const uint8_t *bitmap_in = gd->data;
  uint8_t *bitmap_out_tmp = draw_buf->data;
  int32_t i = 0;
  int32_t x, y;
  uint32_t stride = lv_draw_buf_width_to_stride(gd->width, LV_COLOR_FORMAT_A8);

  switch (fe->get_bpp()) {
    case 1: {
      uint8_t mask = 0;
      uint8_t byte = 0;
      for (y = 0; y != gd->height; y++) {
        for (x = 0; x != gd->width; x++) {
          if (mask == 0) {
            mask = 0x80;
            byte = *bitmap_in++;
          }
          bitmap_out_tmp[x] = byte & mask ? 255 : 0;
          mask >>= 1;
        }
        bitmap_out_tmp += stride;
      }
    } break;

    case 2:
      for (y = 0; y != gd->height; y++) {
        for (x = 0; x != gd->width; x++, i++) {
          switch (i & 0x3) {
            default:
              bitmap_out_tmp[x] = OPA2_TABLE[(*bitmap_in) >> 6];
              break;
            case 1:
              bitmap_out_tmp[x] = OPA2_TABLE[((*bitmap_in) >> 4) & 0x3];
              break;
            case 2:
              bitmap_out_tmp[x] = OPA2_TABLE[((*bitmap_in) >> 2) & 0x3];
              break;
            case 3:
              bitmap_out_tmp[x] = OPA2_TABLE[((*bitmap_in) >> 0) & 0x3];
              bitmap_in++;
          }
        }
        bitmap_out_tmp += stride;
      }
      break;

    case 4:
      for (y = 0; y != gd->height; y++) {
        for (x = 0; x != gd->width; x++, i++) {
          i = i & 0x1;
          if (i == 0) {
            bitmap_out_tmp[x] = OPA4_TABLE[(*bitmap_in) >> 4];
          } else if (i == 1) {
            bitmap_out_tmp[x] = OPA4_TABLE[(*bitmap_in) & 0xF];
            bitmap_in++;
          }
        }
        bitmap_out_tmp += stride;
      }
      break;

    case 8:
      memcpy(bitmap_out_tmp, bitmap_in, gd->width * gd->height);
      break;
    default:
      ESP_LOGD(TAG, "Unknown bpp: %d", fe->get_bpp());
      break;
  }
  return draw_buf;
}

bool Font::get_glyph_dsc_cb(const lv_font_t *font, lv_font_glyph_dsc_t *dsc, uint32_t unicode_letter, uint32_t next) {
  auto *fe = (Font *) font->dsc;
  const auto *gd = fe->get_glyph_data_(unicode_letter);
  if (gd == nullptr) {
    return false;
  }
  dsc->adv_w = gd->advance;
  dsc->ofs_x = gd->offset_x;
  dsc->ofs_y = fe->height_ - gd->height - gd->offset_y - fe->lv_font_.base_line;
  dsc->box_w = gd->width;
  dsc->box_h = gd->height;
  dsc->is_placeholder = 0;
  dsc->format = (lv_font_glyph_format_t) fe->get_bpp();
  dsc->gid.index = unicode_letter;
  return true;
}

const Glyph *Font::get_glyph_data_(uint32_t unicode_letter) {
  if (unicode_letter == this->last_letter_ && this->last_letter_ != 0)
    return this->last_data_;
  auto *glyph = this->find_glyph(unicode_letter);
  if (glyph == nullptr) {
    return nullptr;
  }
  this->last_data_ = glyph;
  this->last_letter_ = unicode_letter;
  return glyph;
}
#endif

/**
 *  Attempt to extract a 32 bit Unicode codepoint from a UTF-8 string.
 *  If successful, return the codepoint and set the length to the number of bytes read.
 *  If the end of the string has been reached and a valid codepoint has not been found, return 0 and set the length to
 * 0.
 *
 * @param utf8_str The input string
 * @param length Pointer to length storage
 * @return The extracted code point
 */
static uint32_t extract_unicode_codepoint(const char *utf8_str, size_t *length) {
  // Safely cast to uint8_t* for correct bitwise operations on bytes
  const uint8_t *current = reinterpret_cast<const uint8_t *>(utf8_str);
  uint32_t code_point = 0;
  uint8_t c1 = *current++;

  // check for end of string
  if (c1 == 0) {
    *length = 0;
    return 0;
  }

  // --- 1-Byte Sequence: 0xxxxxxx (ASCII) ---
  if (c1 < 0x80) {
    // Valid ASCII byte.
    code_point = c1;
    // Optimization: No need to check for continuation bytes.
  }
  // --- 2-Byte Sequence: 110xxxxx 10xxxxxx ---
  else if ((c1 & 0xE0) == 0xC0) {
    uint8_t c2 = *current++;

    // Error Check 1: Check if c2 is a valid continuation byte (10xxxxxx)
    if ((c2 & 0xC0) != 0x80) {
      *length = 0;
      return 0;
    }

    code_point = (c1 & 0x1F) << 6;
    code_point |= (c2 & 0x3F);

    // Error Check 2: Overlong check (2-byte must be > 0x7F)
    if (code_point <= 0x7F) {
      *length = 0;
      return 0;
    }
  }
  // --- 3-Byte Sequence: 1110xxxx 10xxxxxx 10xxxxxx ---
  else if ((c1 & 0xF0) == 0xE0) {
    uint8_t c2 = *current++;
    uint8_t c3 = *current++;

    // Error Check 1: Check continuation bytes
    if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
      *length = 0;
      return 0;
    }

    code_point = (c1 & 0x0F) << 12;
    code_point |= (c2 & 0x3F) << 6;
    code_point |= (c3 & 0x3F);

    // Error Check 2: Overlong check (3-byte must be > 0x7FF)
    // Also check for surrogates (0xD800-0xDFFF)
    if (code_point <= 0x7FF || (code_point >= 0xD800 && code_point <= 0xDFFF)) {
      *length = 0;
      return 0;
    }
  }
  // --- 4-Byte Sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx ---
  else if ((c1 & 0xF8) == 0xF0) {
    uint8_t c2 = *current++;
    uint8_t c3 = *current++;
    uint8_t c4 = *current++;

    // Error Check 1: Check continuation bytes
    if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80) || ((c4 & 0xC0) != 0x80)) {
      *length = 0;
      return 0;
    }

    code_point = (c1 & 0x07) << 18;
    code_point |= (c2 & 0x3F) << 12;
    code_point |= (c3 & 0x3F) << 6;
    code_point |= (c4 & 0x3F);

    // Error Check 2: Overlong check (4-byte must be > 0xFFFF)
    // Also check for valid Unicode range (must be <= 0x10FFFF)
    if (code_point <= 0xFFFF || code_point > 0x10FFFF) {
      *length = 0;
      return 0;
    }
  }
  // --- Invalid leading byte (e.g., 10xxxxxx or 11111xxx) ---
  else {
    *length = 0;
    return 0;
  }
  *length = current - reinterpret_cast<const uint8_t *>(utf8_str);
  return code_point;
}

Font::Font(const Glyph *data, int data_nr, int baseline, int height, int descender, int xheight, int capheight,
           uint8_t bpp)
    : glyphs_(ConstVector(data, data_nr)),
      baseline_(baseline),
      height_(height),
      descender_(descender),
      linegap_(height - baseline - descender),
      xheight_(xheight),
      capheight_(capheight),
      bpp_(bpp) {
#ifdef USE_LVGL_FONT
  this->lv_font_.dsc = this;
  this->lv_font_.line_height = this->get_height();
  this->lv_font_.base_line = this->lv_font_.line_height - this->get_baseline();
  this->lv_font_.get_glyph_dsc = get_glyph_dsc_cb;
  this->lv_font_.get_glyph_bitmap = get_glyph_bitmap;
  this->lv_font_.subpx = LV_FONT_SUBPX_NONE;
  this->lv_font_.underline_position = -1;
  this->lv_font_.underline_thickness = 1;
#endif
}

const Glyph *Font::find_glyph(uint32_t codepoint) const {
  int lo = 0;
  int hi = this->glyphs_.size() - 1;
  while (lo != hi) {
    int mid = (lo + hi + 1) / 2;
    if (this->glyphs_[mid].is_less_or_equal(codepoint)) {
      lo = mid;
    } else {
      hi = mid - 1;
    }
  }
  auto *result = &this->glyphs_[lo];
  if (result->code_point == codepoint)
    return result;
  return nullptr;
}

#ifdef USE_DISPLAY
void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) {
  *baseline = this->baseline_;
  *height = this->height_;
  int min_x = 0;
  bool has_char = false;
  int x = 0;
  for (;;) {
    size_t length;
    auto code_point = extract_unicode_codepoint(str, &length);
    if (length == 0)
      break;
    str += length;
    auto *glyph = this->find_glyph(code_point);
    if (glyph == nullptr) {
      // Unknown char, skip
      if (!this->glyphs_.empty())
        x += this->glyphs_[0].advance;
      continue;
    }

    if (!has_char) {
      min_x = glyph->offset_x;
    } else {
      min_x = std::min(min_x, x + glyph->offset_x);
    }
    x += glyph->advance;

    has_char = true;
  }
  *x_offset = min_x;
  *width = x - min_x;
}

void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text, Color background) {
  int x_at = x_start;
  for (;;) {
    size_t length;
    auto code_point = extract_unicode_codepoint(text, &length);
    if (length == 0)
      break;
    text += length;
    auto *glyph = this->find_glyph(code_point);
    if (glyph == nullptr) {
      // Unknown char, skip
      ESP_LOGW(TAG, "Codepoint 0x%08" PRIx32 " not found in font", code_point);
      if (!this->glyphs_.empty()) {
        uint8_t glyph_width = this->glyphs_[0].advance;
        display->rectangle(x_at, y_start, glyph_width, this->height_, color);
        x_at += glyph_width;
      }
      continue;
    }

    const uint8_t *data = glyph->data;
    const int max_x = x_at + glyph->offset_x + glyph->width;
    const int max_y = y_start + glyph->offset_y + glyph->height;

    uint8_t bitmask = 0;
    uint8_t pixel_data = 0;
    uint8_t bpp_max = (1 << this->bpp_) - 1;
    auto diff_r = (float) color.r - (float) background.r;
    auto diff_g = (float) color.g - (float) background.g;
    auto diff_b = (float) color.b - (float) background.b;
    auto diff_w = (float) color.w - (float) background.w;
    auto b_r = (float) background.r;
    auto b_g = (float) background.g;
    auto b_b = (float) background.b;
    auto b_w = (float) background.w;
    for (int glyph_y = y_start + glyph->offset_y; glyph_y != max_y; glyph_y++) {
      for (int glyph_x = x_at + glyph->offset_x; glyph_x != max_x; glyph_x++) {
        uint8_t pixel = 0;
        for (uint8_t bit_num = 0; bit_num != this->bpp_; bit_num++) {
          if (bitmask == 0) {
            pixel_data = progmem_read_byte(data++);
            bitmask = 0x80;
          }
          pixel <<= 1;
          if ((pixel_data & bitmask) != 0)
            pixel |= 1;
          bitmask >>= 1;
        }
        if (pixel == bpp_max) {
          display->draw_pixel_at(glyph_x, glyph_y, color);
        } else if (pixel != 0) {
          auto on = (float) pixel / (float) bpp_max;
          auto blended = Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g),
                               (uint8_t) (diff_b * on + b_b), (uint8_t) (diff_w * on + b_w));
          display->draw_pixel_at(glyph_x, glyph_y, blended);
        }
      }
    }
    x_at += glyph->advance;
  }
}
#endif
}  // namespace esphome::font
