#include "message_editor.h"

#include <algorithm>
#include <regex>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>

#include "absl/status/status.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h"
#include "app/core/common.h"
#include "app/core/platform/renderer.h"
#include "app/editor/utils/editor.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/snes_tile.h"
#include "app/gui/canvas.h"
#include "app/gui/icons.h"
#include "app/gui/style.h"
#include "app/rom.h"

namespace yaze {
namespace app {
namespace editor {

using core::Renderer;

using ImGui::Begin;
using ImGui::BeginChild;
using ImGui::BeginTable;
using ImGui::Button;
using ImGui::End;
using ImGui::EndChild;
using ImGui::EndTable;
using ImGui::InputText;
using ImGui::InputTextMultiline;
using ImGui::SameLine;
using ImGui::Separator;
using ImGui::TableHeadersRow;
using ImGui::TableNextColumn;
using ImGui::TableNextRow;
using ImGui::TableSetupColumn;
using ImGui::Text;
using ImGui::TextWrapped;
using ImGui::TreeNode;

absl::Status MessageEditor::Initialize() {
  for (int i = 0; i < 100; i++) {
    width_array[i] = rom()->data()[kCharactersWidth + i];
  }

  BuildDictionaryEntries();
  ReadAllTextData();

  font_preview_colors_.AddColor(0x7FFF);  // White
  font_preview_colors_.AddColor(0x7C00);  // Red
  font_preview_colors_.AddColor(0x03E0);  // Green
  font_preview_colors_.AddColor(0x001F);  // Blue

  std::vector<uint8_t> data(0x4000, 0);
  for (int i = 0; i < 0x4000; i++) {
    data[i] = rom()->data()[kGfxFont + i];
  }
  font_gfx16_data_ = gfx::SnesTo8bppSheet(data, /*bpp=*/2);

  // 4bpp
  RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
      128, 128, 8, font_gfx16_data_, font_gfx_bitmap_, font_preview_colors_))

  current_font_gfx16_data_.reserve(172 * 4096);
  for (int i = 0; i < 172 * 4096; i++) {
    current_font_gfx16_data_.push_back(0);
  }

  // 8bpp
  RETURN_IF_ERROR(Renderer::GetInstance().CreateAndRenderBitmap(
      172, 4096, 64, current_font_gfx16_data_, current_font_gfx16_bitmap_,
      font_preview_colors_))

  gfx::SnesPalette color_palette = font_gfx_bitmap_.palette();
  for (int i = 0; i < font_preview_colors_.size(); i++) {
    *color_palette.mutable_color(i) = font_preview_colors_[i];
  }

  *font_gfx_bitmap_.mutable_palette() = color_palette;

  for (const auto& message : list_of_texts_) {
    displayed_messages_.push_back(message);
  }

  for (const auto& each_message : list_of_texts_) {
    // Each string has a [:XX] char encoded
    // The corresponding character is found in CharEncoder unordered_map
    std::string parsed_message = "";
    for (const auto& byte : each_message.Data) {
      // Find the char byte in the CharEncoder map
      if (CharEncoder.contains(byte)) {
        parsed_message.push_back(CharEncoder.at(byte));
      } else {
        // If the byte is not found in the CharEncoder map, it is a command
        // or a dictionary entry
        if (byte >= DICTOFF && byte < (DICTOFF + 97)) {
          // Dictionary entry
          auto dictionaryEntry = GetDictionaryFromID(byte - DICTOFF);
          parsed_message.append(dictionaryEntry.Contents);
        } else {
          // Command
          TextElement textElement = FindMatchingCommand(byte);
          if (!textElement.Empty()) {
            // If the element is line 2, 3 or V we add a newline
            if (textElement.ID == kScrollVertical || textElement.ID == kLine2 ||
                textElement.ID == kLine3)
              parsed_message.append("\n");

            parsed_message.append(textElement.GenericToken);
          }
        }
      }
    }
    parsed_messages_.push_back(parsed_message);
  }

  DrawMessagePreview();

  return absl::OkStatus();
}

absl::Status MessageEditor::Update() {
  if (rom()->is_loaded() && !data_loaded_) {
    RETURN_IF_ERROR(Initialize());
    current_message_ = list_of_texts_[1];
    data_loaded_ = true;
  }

  if (BeginTable("##MessageEditor", 3,
                 ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable)) {
    TableSetupColumn("List");
    TableSetupColumn("Contents");
    TableSetupColumn("Commands");

    TableHeadersRow();

    TableNextColumn();
    DrawMessageList();

    TableNextColumn();
    DrawCurrentMessage();

    TableNextColumn();
    DrawTextCommands();

    EndTable();
  }

  return absl::OkStatus();
}

void MessageEditor::DrawMessageList() {
  if (InputText("Search", &search_text_)) {
    displayed_messages_.clear();
    for (const auto& message : list_of_texts_) {
      if (absl::StrContains(message.ContentsParsed, search_text_)) {
        displayed_messages_.push_back(message);
      }
    }
  }

  if (BeginChild("##MessagesList", ImVec2(0, 0), true,
                 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
    if (BeginTable("##MessagesTable", 3,
                   ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders |
                       ImGuiTableFlags_Resizable)) {
      TableSetupColumn("ID");
      TableSetupColumn("Contents");
      TableSetupColumn("Data");

      TableHeadersRow();

      for (const auto& message : list_of_texts_) {
        TableNextColumn();
        if (Button(core::UppercaseHexWord(message.ID).c_str())) {
          current_message_ = message;
          DrawMessagePreview();
        }
        TableNextColumn();
        TextWrapped("%s", parsed_messages_[message.ID].c_str());
        TableNextColumn();
        TextWrapped(
            "%s",
            core::UppercaseHexLong(list_of_texts_[message.ID].Address).c_str());
      }

      EndTable();
    }
    EndChild();
  }
}

void MessageEditor::DrawCurrentMessage() {
  Button(absl::StrCat("Message ", current_message_.ID).c_str());
  if (InputTextMultiline("##MessageEditor",
                         &parsed_messages_[current_message_.ID],
                         ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
    current_message_.Data = ParseMessageToData(message_text_box_.text);
    DrawMessagePreview();
  }
  Separator();

  Text("Font Graphics");
  gui::BeginPadding(1);
  BeginChild("MessageEditorCanvas", ImVec2(0, 130));
  font_gfx_canvas_.DrawBackground();
  font_gfx_canvas_.DrawContextMenu();
  font_gfx_canvas_.DrawBitmap(font_gfx_bitmap_, 0, 0);
  font_gfx_canvas_.DrawGrid();
  font_gfx_canvas_.DrawOverlay();
  EndChild();
  gui::EndPadding();
  Separator();

  Text("Message Preview");
  if (Button("Refresh Bitmap")) {
    Renderer::GetInstance().UpdateBitmap(&current_font_gfx16_bitmap_);
  }
  gui::BeginPadding(1);
  BeginChild("CurrentGfxFont", ImVec2(0, 0), true,
             ImGuiWindowFlags_AlwaysVerticalScrollbar);
  current_font_gfx16_canvas_.DrawBackground();
  gui::EndPadding();
  current_font_gfx16_canvas_.DrawContextMenu();
  current_font_gfx16_canvas_.DrawBitmap(current_font_gfx16_bitmap_, 0, 0);
  current_font_gfx16_canvas_.DrawGrid();
  current_font_gfx16_canvas_.DrawOverlay();
  EndChild();
}

void MessageEditor::DrawTextCommands() {
  if (BeginChild("##TextCommands", ImVec2(0, 0), true,
                 ImGuiWindowFlags_AlwaysVerticalScrollbar)) {
    for (const auto& text_element : TextCommands) {
      if (Button(text_element.GenericToken.c_str())) {
      }
      SameLine();
      TextWrapped("%s", text_element.Description.c_str());
      Separator();
    }
    EndChild();
  }
}

void MessageEditor::BuildDictionaryEntries() {
  for (int i = 0; i < kNumDictionaryEntries; i++) {
    std::vector<uint8_t> bytes;
    std::stringstream stringBuilder;

    int address = core::SnesToPc(
        kTextData + (rom()->data()[kPointersDictionaries + (i * 2) + 1] << 8) +
        rom()->data()[kPointersDictionaries + (i * 2)]);

    int temppush_backress = core::SnesToPc(
        kTextData +
        (rom()->data()[kPointersDictionaries + ((i + 1) * 2) + 1] << 8) +
        rom()->data()[kPointersDictionaries + ((i + 1) * 2)]);

    while (address < temppush_backress) {
      uint8_t uint8_tDictionary = rom()->data()[address++];
      bytes.push_back(uint8_tDictionary);
      stringBuilder << ParseTextDataByte(uint8_tDictionary);
    }

    AllDictionaries.push_back(DictionaryEntry{(uint8_t)i, stringBuilder.str()});
  }

  // AllDictionaries.OrderByDescending(dictionary = > dictionary.Length);
  AllDictionaries[0].Length = 0;
}

void MessageEditor::ReadAllTextData() {
  int pos = kTextData;
  int message_id = 0;
  uint8_t current_byte;
  std::vector<uint8_t> temp_bytes_raw;
  std::vector<uint8_t> temp_bytes_parsed;

  std::string current_message_raw;
  std::string current_message_parsed;
  TextElement text_element;

  while (true) {
    current_byte = rom()->data()[pos++];

    if (current_byte == MESSAGETERMINATOR) {
      auto message =
          MessageData(message_id++, pos, current_message_raw, temp_bytes_raw,
                      current_message_parsed, temp_bytes_parsed);

      list_of_texts_.push_back(message);

      temp_bytes_raw.clear();
      temp_bytes_parsed.clear();
      current_message_raw.clear();
      current_message_parsed.clear();

      continue;
    }

    if (current_byte == 0xFF) {
      break;
    }

    temp_bytes_raw.push_back(current_byte);

    // Check for command.
    text_element = FindMatchingCommand(current_byte);

    if (!text_element.Empty()) {
      temp_bytes_parsed.push_back(current_byte);
      if (text_element.HasArgument) {
        current_byte = rom()->data()[pos++];
        temp_bytes_raw.push_back(current_byte);
        temp_bytes_parsed.push_back(current_byte);
      }

      current_message_raw.append(
          text_element.GetParameterizedToken(current_byte));
      current_message_parsed.append(
          text_element.GetParameterizedToken(current_byte));

      if (text_element.Token == BANKToken) {
        pos = kTextData2;
      }

      continue;
    }

    // Check for special characters.
    text_element = FindMatchingSpecial(current_byte);
    if (!text_element.Empty()) {
      current_message_raw.append(text_element.GetParameterizedToken());
      current_message_parsed.append(text_element.GetParameterizedToken());
      temp_bytes_parsed.push_back(current_byte);
      continue;
    }

    // Check for dictionary.
    int dictionary = FindDictionaryEntry(current_byte);

    if (dictionary >= 0) {
      current_message_raw.append("[");
      current_message_raw.append(DICTIONARYTOKEN);
      current_message_raw.append(":");
      current_message_raw.append(core::UppercaseHexWord(dictionary));
      current_message_raw.append("]");

      uint32_t address = core::Get24LocalFromPC(
          rom()->data(), kPointersDictionaries + (dictionary * 2));
      uint32_t address_end = core::Get24LocalFromPC(
          rom()->data(), kPointersDictionaries + ((dictionary + 1) * 2));

      for (uint32_t i = address; i < address_end; i++) {
        temp_bytes_parsed.push_back(rom()->data()[i]);
        current_message_parsed.append(ParseTextDataByte(rom()->data()[i]));
      }

      continue;
    }

    // Everything else.
    if (CharEncoder.contains(current_byte)) {
      std::string str = "";
      str.push_back(CharEncoder.at(current_byte));
      current_message_raw.append(str);
      current_message_parsed.append(str);
      temp_bytes_parsed.push_back(current_byte);
    }
  }
}

TextElement MessageEditor::FindMatchingCommand(uint8_t b) {
  TextElement empty_element;
  for (const auto text_element : TextCommands) {
    if (text_element.ID == b) {
      return text_element;
    }
  }
  return empty_element;
}

TextElement MessageEditor::FindMatchingSpecial(uint8_t value) {
  auto it = std::find_if(SpecialChars.begin(), SpecialChars.end(),
                         [value](const TextElement& text_element) {
                           return text_element.ID == value;
                         });
  if (it != SpecialChars.end()) {
    return *it;
  }

  return TextElement();
}

ParsedElement MessageEditor::FindMatchingElement(std::string str) {
  std::smatch match;
  for (auto& textElement : TextCommands) {
    match = textElement.MatchMe(str);
    if (match.size() > 0) {
      if (textElement.HasArgument) {
        return ParsedElement(textElement,
                             std::stoi(match[1].str(), nullptr, 16));
      } else {
        return ParsedElement(textElement, 0);
      }
    }
  }

  const auto dictionary_element =
      TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");

  match = dictionary_element.MatchMe(str);
  if (match.size() > 0) {
    return ParsedElement(dictionary_element,
                         DICTOFF + std::stoi(match[1].str(), nullptr, 16));
  }
  return ParsedElement();
}

std::string ReplaceAllDictionaryWords(std::string str) {
  std::string temp = str;
  for (const auto& entry : AllDictionaries) {
    if (absl::StrContains(temp, entry.Contents)) {
      temp = absl::StrReplaceAll(temp, {{entry.Contents, entry.Contents}});
    }
  }

  return temp;
}

std::vector<uint8_t> MessageEditor::ParseMessageToData(std::string str) {
  std::vector<uint8_t> bytes;
  std::string temp_string = str;
  int pos = 0;

  while (pos < temp_string.size()) {
    // Get next text fragment.
    if (temp_string[pos] == '[') {
      int next = temp_string.find(']', pos);
      if (next == -1) {
        break;
      }

      ParsedElement parsedElement =
          FindMatchingElement(temp_string.substr(pos, next - pos + 1));

      const auto dictionary_element =
          TextElement(0x80, DICTIONARYTOKEN, true, "Dictionary");

      if (!parsedElement.Active) {
        core::logf("Error parsing message: %s", temp_string);
        break;
      } else if (parsedElement.Parent == dictionary_element) {
        bytes.push_back(parsedElement.Value);
      } else {
        bytes.push_back(parsedElement.Parent.ID);

        if (parsedElement.Parent.HasArgument) {
          bytes.push_back(parsedElement.Value);
        }
      }

      pos = next + 1;
      continue;
    } else {
      uint8_t bb = FindMatchingCharacter(temp_string[pos++]);

      if (bb != 0xFF) {
        core::logf("Error parsing message: %s", temp_string);
        bytes.push_back(bb);
      }
    }
  }

  return bytes;
}

MessageEditor::DictionaryEntry MessageEditor::GetDictionaryFromID(
    uint8_t value) {
  if (value < 0 || value >= AllDictionaries.size()) {
    return DictionaryEntry();
  }
  return AllDictionaries[value];
}

uint8_t MessageEditor::FindDictionaryEntry(uint8_t value) {
  if (value < DICTOFF || value == 0xFF) {
    return -1;
  }

  return value - DICTOFF;
}

uint8_t MessageEditor::FindMatchingCharacter(char value) {
  for (const auto [key, char_value] : CharEncoder) {
    if (value == char_value) {
      return key;
    }
  }
  return 0xFF;
}

std::string MessageEditor::ParseTextDataByte(uint8_t value) {
  if (CharEncoder.contains(value)) {
    char c = CharEncoder.at(value);
    std::string str = "";
    str.push_back(c);
    return str;
  }

  // Check for command.
  TextElement textElement = FindMatchingCommand(value);
  if (!textElement.Empty()) {
    return textElement.GenericToken;
  }

  // Check for special characters.
  textElement = FindMatchingSpecial(value);
  if (!textElement.Empty()) {
    return textElement.GenericToken;
  }

  // Check for dictionary.
  int dictionary = FindDictionaryEntry(value);
  if (dictionary >= 0) {
    return absl::StrFormat("[%s:%X]", DICTIONARYTOKEN, dictionary);
  }

  return "";
}

void MessageEditor::DrawTileToPreview(int x, int y, int srcx, int srcy, int pal,
                                      int sizex, int sizey) {
  int drawid = srcx + (srcy * 32);
  for (int yl = 0; yl < sizey * 8; yl++) {
    for (int xl = 0; xl < 4; xl++) {
      int mx = xl;
      int my = yl;

      // Formula information to get tile index position in the array.
      // ((ID / nbrofXtiles) * (imgwidth/2) + (ID - ((ID/16)*16) ))
      int tx = ((drawid / 16) * 512) + ((drawid - ((drawid / 16) * 16)) * 4);
      uint8_t pixel = font_gfx16_data_[tx + (yl * 64) + xl];

      // nx,ny = object position, xx,yy = tile position, xl,yl = pixel
      // position
      int index = x + (y * 172) + (mx * 2) + (my * 172);
      if ((pixel & 0x0F) != 0) {
        current_font_gfx16_data_[index + 1] =
            (uint8_t)((pixel & 0x0F) + (0 * 4));
      }

      if (((pixel >> 4) & 0x0F) != 0) {
        current_font_gfx16_data_[index + 0] =
            (uint8_t)(((pixel >> 4) & 0x0F) + (0 * 4));
      }
    }
  }
}

void MessageEditor::DrawStringToPreview(std::string str) {
  for (const auto c : str) {
    DrawCharacterToPreview(c);
  }
}

void MessageEditor::DrawCharacterToPreview(char c) {
  DrawCharacterToPreview(FindMatchingCharacter(c));
}

void MessageEditor::DrawCharacterToPreview(const std::vector<uint8_t>& text) {
  for (const uint8_t& value : text) {
    if (skip_next) {
      skip_next = false;
      continue;
    }

    if (value < 100) {
      int srcy = value / 16;
      int srcx = value - (value & (~0xF));

      if (text_position_ >= 170) {
        text_position_ = 0;
        text_line_++;
      }

      DrawTileToPreview(text_position_, text_line_ * 16, srcx, srcy, 0, 1, 2);
      text_position_ += width_array[value];
    } else if (value == kLine1) {
      text_position_ = 0;
      text_line_ = 0;
    } else if (value == kScrollVertical) {
      text_position_ = 0;
      text_line_ += 1;
    } else if (value == kLine2) {
      text_position_ = 0;
      text_line_ = 1;
    } else if (value == kLine3) {
      text_position_ = 0;
      text_line_ = 2;
    } else if (value == 0x6B || value == 0x6D || value == 0x6E ||
               value == 0x77 || value == 0x78 || value == 0x79 ||
               value == 0x7A) {
      skip_next = true;

      continue;
    } else if (value == 0x6C)  // BCD numbers.
    {
      DrawCharacterToPreview('0');
      skip_next = true;

      continue;
    } else if (value == 0x6A) {
      // Includes parentheses to be longer, since player names can be up to 6
      // characters.
      DrawStringToPreview("(NAME)");
    } else if (value >= DICTOFF && value < (DICTOFF + 97)) {
      auto dictionaryEntry = GetDictionaryFromID(value - DICTOFF);
      DrawCharacterToPreview(dictionaryEntry.Data);
    }
  }
}

void MessageEditor::DrawMessagePreview() {
  // From Parsing.
  text_line_ = 0;
  for (int i = 0; i < (172 * 4096); i++) {
    current_font_gfx16_data_[i] = 0;
  }
  text_position_ = 0;
  DrawCharacterToPreview(current_message_.Data);
  shown_lines_ = 0;
}

absl::Status MessageEditor::Cut() {
  // Ensure that text is currently selected in the text box.
  if (!message_text_box_.text.empty()) {
    // Cut the selected text in the control and paste it into the Clipboard.
    message_text_box_.Cut();
  }
  return absl::OkStatus();
}

absl::Status MessageEditor::Paste() {
  // Determine if there is any text in the Clipboard to paste into the
  if (ImGui::GetClipboardText() != nullptr) {
    // Paste the text from the Clipboard into the text box.
    message_text_box_.Paste();
  }
  return absl::OkStatus();
}

absl::Status MessageEditor::Copy() {
  // Ensure that text is selected in the text box.
  if (message_text_box_.selection_length > 0) {
    // Copy the selected text to the Clipboard.
    message_text_box_.Copy();
  }
  return absl::OkStatus();
}

absl::Status MessageEditor::Undo() {
  // Determine if last operation can be undone in text box.
  if (message_text_box_.can_undo) {
    // Undo the last operation.
    message_text_box_.Undo();

    // clear the undo buffer to prevent last action from being redone.
    message_text_box_.clearUndo();
  }
  return absl::OkStatus();
}

absl::Status MessageEditor::Save() {
  std::vector<uint8_t> backup = rom()->vector();

  for (int i = 0; i < 100; i++) {
    RETURN_IF_ERROR(rom()->Write(kCharactersWidth + i, width_array[i]));
  }

  int pos = kTextData;
  bool in_second_bank = false;

  for (const auto& message : list_of_texts_) {
    for (const auto value : message.Data) {
      RETURN_IF_ERROR(rom()->Write(pos, value));

      if (value == kBlockTerminator) {
        // Make sure we didn't go over the space available in the first block.
        // 0x7FFF available.
        if ((!in_second_bank & pos) > kTextDataEnd) {
          return absl::InternalError(DisplayTextOverflowError(pos, true));
        }

        // Switch to the second block.
        pos = kTextData2 - 1;
        in_second_bank = true;
      }

      pos++;
    }

    RETURN_IF_ERROR(
        rom()->Write(pos++, MESSAGETERMINATOR));  // , true, "Terminator text"
  }

  // Verify that we didn't go over the space available for the second block.
  // 0x14BF available.
  if ((in_second_bank & pos) > kTextData2End) {
    // rom()->data() = backup;
    return absl::InternalError(DisplayTextOverflowError(pos, false));
  }

  RETURN_IF_ERROR(rom()->Write(pos, 0xFF));  // , true, "End of text"

  return absl::OkStatus();
}

std::string MessageEditor::DisplayTextOverflowError(int pos, bool bank) {
  int space = bank ? kTextDataEnd - kTextData : kTextData2End - kTextData2;
  std::string bankSTR = bank ? "1st" : "2nd";
  std::string posSTR =
      bank ? absl::StrFormat("%X4", pos & 0xFFFF)
           : absl::StrFormat("%X4", (pos - kTextData2) & 0xFFFF);
  std::string message = absl::StrFormat(
      "There is too much text data in the %s block to save.\n"
      "Available: %X4 | Used: %s",
      bankSTR, space, posSTR);
  return message;
}

// push_backs a command to the text field when the push_back command button is
// pressed or the command is double clicked in the list.
void MessageEditor::InsertCommandButton_Click_1() {
  // InsertSelectedText(
  //     TextCommands[TextCommandList.SelectedIndex].GetParameterizedToken(
  //         (uint8_t)ParamsBox.HexValue));
}

// push_backs a special character to the text field when the push_back command
// button is pressed or the character is double clicked in the list.
void MessageEditor::InsertSpecialButton_Click() {
  // InsertSelectedText(
  //     SpecialChars[SpecialsList.SelectedIndex].GetParameterizedToken());
}

void MessageEditor::InsertSelectedText(std::string str) {
  int textboxPos = message_text_box_.selection_start;
  from_form = true;
  // message_text_box_.Text = message_text_box_.Text.Insert(textboxPos, str);
  from_form = false;
  message_text_box_.selection_start = textboxPos + str.size();
  message_text_box_.Focus();
}

void MessageEditor::Delete() {
  // Determine if any text is selected in the TextBox control.
  if (message_text_box_.selection_length == 0) {
    // clear all of the text in the textbox.
    message_text_box_.clear();
  }
}

void MessageEditor::SelectAll() {
  // Determine if any text is selected in the TextBox control.
  if (message_text_box_.selection_length == 0) {
    // Select all text in the text box.
    message_text_box_.SelectAll();

    // Move the cursor to the text box.
    message_text_box_.Focus();
  }
}

}  // namespace editor
}  // namespace app
}  // namespace yaze
