#include "imgui.hpp"

#include <algorithm>
#include <cfloat>
#include <string>

namespace game::util::imgui
{
  constexpr auto DRAW_LIST_TEXT_WRAP_WIDTH_NONE = 0.0f;
  constexpr auto DRAW_LIST_TEXT_CENTER_RATIO = 0.5f;
  constexpr auto DRAW_LIST_TEXT_DIGIT_SLOT_PADDING = 1.0f;

#define DRAW_LIST_TEXT_DIGITS(X)                                                                                      \
  X('0')                                                                                                              \
  X('1')                                                                                                              \
  X('2')                                                                                                              \
  X('3')                                                                                                              \
  X('4')                                                                                                              \
  X('5')                                                                                                              \
  X('6')                                                                                                              \
  X('7')                                                                                                              \
  X('8')                                                                                                              \
  X('9')

  void triangle_draw(ImDrawList& drawList, ImVec2 pos, ImVec2 size, TriangleDirection direction, ImU32 color)
  {
    static constexpr auto HALF_MULTIPLIER = 0.5f;
    static constexpr auto TRIANGLE_HALF_MULTIPLIER = 0.22f;

    auto center = ImVec2(pos.x + (size.x * HALF_MULTIPLIER), pos.y + (size.y * HALF_MULTIPLIER));
    auto half = size.x * TRIANGLE_HALF_MULTIPLIER;
    ImVec2 tip;
    ImVec2 baseA;
    ImVec2 baseB;
    if (direction == TriangleDirection::LEFT)
    {
      tip = ImVec2(center.x - half, center.y);
      baseA = ImVec2(center.x + half, center.y - half);
      baseB = ImVec2(center.x + half, center.y + half);
    }
    else
    {
      tip = ImVec2(center.x + half, center.y);
      baseA = ImVec2(center.x - half, center.y - half);
      baseB = ImVec2(center.x - half, center.y + half);
    }

    drawList.AddTriangleFilled(tip, baseA, baseB, color);
  }

  ImVec2 draw_list_text_size_get(const char* text, float fontSize)
  {
    auto* font = ImGui::GetFont();
    if (!font || !text) return ImVec2();
    return font->CalcTextSizeA(fontSize, FLT_MAX, DRAW_LIST_TEXT_WRAP_WIDTH_NONE, text);
  }

  ImVec2 draw_list_text_size_get(const std::string& text, float fontSize)
  {
    return draw_list_text_size_get(text.c_str(), fontSize);
  }

  void draw_list_text_draw(ImDrawList& drawList, ImVec2 position, const std::string& text, float fontSize, ImU32 color)
  {
    auto* font = ImGui::GetFont();
    if (!font) return;
    drawList.AddText(font, fontSize, position, color, text.c_str());
  }

  ImVec2 draw_list_text_center_position_get(ImVec2 canvasMin, ImVec2 canvasSize, const std::string& text, float fontSize,
                                            float xRatio, float yRatio)
  {
    auto textSize = draw_list_text_size_get(text, fontSize);
    return ImVec2(canvasMin.x + (canvasSize.x * xRatio) - (textSize.x * DRAW_LIST_TEXT_CENTER_RATIO),
                  canvasMin.y + (canvasSize.y * yRatio) - (textSize.y * DRAW_LIST_TEXT_CENTER_RATIO));
  }

  void draw_list_text_center_draw(ImDrawList& drawList, ImVec2 canvasMin, ImVec2 canvasSize, const std::string& text,
                                  float fontSize, float xRatio, float yRatio, ImU32 color)
  {
    draw_list_text_draw(drawList, draw_list_text_center_position_get(canvasMin, canvasSize, text, fontSize, xRatio, yRatio),
                        text, fontSize, color);
  }

  bool draw_list_text_digit_get(char character)
  {
    switch (character)
    {
#define DRAW_LIST_TEXT_DIGIT_CASE(characterValue)                                                                      \
  case characterValue:                                                                                                \
    return true;
      DRAW_LIST_TEXT_DIGITS(DRAW_LIST_TEXT_DIGIT_CASE)
#undef DRAW_LIST_TEXT_DIGIT_CASE
      default:
        return false;
    }
  }

  float draw_list_text_digit_width_get(float fontSize)
  {
    auto width = DRAW_LIST_TEXT_WRAP_WIDTH_NONE;
#define DRAW_LIST_TEXT_DIGIT_WIDTH(characterValue)                                                                     \
  width = std::max(width, draw_list_text_size_get(std::string(1, characterValue), fontSize).x);
    DRAW_LIST_TEXT_DIGITS(DRAW_LIST_TEXT_DIGIT_WIDTH)
#undef DRAW_LIST_TEXT_DIGIT_WIDTH
    return width + DRAW_LIST_TEXT_DIGIT_SLOT_PADDING;
  }

  float draw_list_digit_fixed_width_get(const std::string& text, float fontSize)
  {
    auto width = DRAW_LIST_TEXT_WRAP_WIDTH_NONE;
    auto digitWidth = draw_list_text_digit_width_get(fontSize);
    for (auto character : text)
    {
      auto glyph = std::string(1, character);
      width += draw_list_text_digit_get(character) ? digitWidth : draw_list_text_size_get(glyph, fontSize).x;
    }
    return width;
  }

  void draw_list_digit_fixed_width_draw(ImDrawList& drawList, ImVec2 position, const std::string& text, float fontSize,
                                        ImU32 color)
  {
    auto x = position.x;
    auto digitWidth = draw_list_text_digit_width_get(fontSize);
    for (auto character : text)
    {
      auto glyph = std::string(1, character);
      auto glyphSize = draw_list_text_size_get(glyph, fontSize);
      auto slotWidth = draw_list_text_digit_get(character) ? digitWidth : glyphSize.x;
      auto glyphPosition = ImVec2(x + ((slotWidth - glyphSize.x) * DRAW_LIST_TEXT_CENTER_RATIO), position.y);
      draw_list_text_draw(drawList, glyphPosition, glyph, fontSize, color);
      x += slotWidth;
    }
  }

  float row_widget_width_get(int count, float width)
  {
    return (width - (ImGui::GetStyle().ItemSpacing.x * (float)(count - 1))) / (float)count;
  }

  ImVec2 row_widget_size_get(int count, float width) { return ImVec2(row_widget_width_get(count, width), 0); }

  float footer_height_get(int itemCount)
  {
    return ImGui::GetTextLineHeightWithSpacing() * itemCount + ImGui::GetStyle().WindowPadding.y +
           ImGui::GetStyle().ItemSpacing.y * (itemCount);
  }

  ImVec2 footer_size_get(int itemCount)
  {
    return ImVec2(ImGui::GetContentRegionAvail().x, footer_height_get(itemCount));
  }

  ImVec2 size_without_footer_get(int rowCount)
  {
    return ImVec2(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y - footer_height_get(rowCount));
  }

  float side_bar_width_get()
  {
    auto& style = ImGui::GetStyle();
    return std::max(ImGui::GetTextLineHeightWithSpacing(), style.WindowMinSize.x);
  }

  float text_font_size_fit_get(const char* text, ImVec2 bounds, float fontSizeMax, float fontSizeMin)
  {
    static constexpr auto ZERO_FLOAT = 0.0f;
    static constexpr auto ONE_FLOAT = 1.0f;

    auto* font = ImGui::GetFont();
    if (!font || !text || fontSizeMax <= ZERO_FLOAT) return fontSizeMax;

    auto textSize = font->CalcTextSizeA(fontSizeMax, FLT_MAX, ZERO_FLOAT, text);
    if (textSize.x <= ZERO_FLOAT || textSize.y <= ZERO_FLOAT) return fontSizeMax;

    auto widthScale = bounds.x > ZERO_FLOAT ? bounds.x / textSize.x : ONE_FLOAT;
    auto heightScale = bounds.y > ZERO_FLOAT ? bounds.y / textSize.y : ONE_FLOAT;
    auto scale = std::min({ONE_FLOAT, widthScale, heightScale});
    return std::clamp(fontSizeMax * scale, fontSizeMin, fontSizeMax);
  }

  float text_unformatted_fit_draw(const char* text, ImVec2 bounds, float fontSizeMax, float fontSizeMin)
  {
    static constexpr auto HALF_MULTIPLIER = 0.5f;
    static constexpr auto ZERO_FLOAT = 0.0f;

    auto* font = ImGui::GetFont();
    auto fontSize = text_font_size_fit_get(text, bounds, fontSizeMax, fontSizeMin);
    auto pos = ImGui::GetCursorScreenPos();
    ImGui::Dummy(bounds);
    if (font && text)
    {
      auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, ZERO_FLOAT, text);
      auto textPos = ImVec2(pos.x, pos.y + std::max(ZERO_FLOAT, (bounds.y - textSize.y) * HALF_MULTIPLIER));
      auto clipRect = ImVec4(pos.x, pos.y, pos.x + bounds.x, pos.y + bounds.y);
      ImGui::GetWindowDrawList()->AddText(font, fontSize, textPos, ImGui::GetColorU32(ImGuiCol_Text), text, nullptr,
                                          ZERO_FLOAT, &clipRect);
    }
    return fontSize;
  }
#undef DRAW_LIST_TEXT_DIGITS
}
