// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "DolphinQt/Debugger/GekkoSyntaxHighlight.h"

#include "Common/Assembler/GekkoParser.h"

#include <QLabel>
#include <QPalette>

namespace
{
using namespace Common::GekkoAssembler;
using namespace Common::GekkoAssembler::detail;

class HighlightParsePlugin : public ParsePlugin
{
public:
  virtual ~HighlightParsePlugin() = default;

  std::vector<std::pair<int, int>>&& MoveParens() { return std::move(m_matched_parens); }
  std::vector<std::tuple<int, int, HighlightFormat>>&& MoveFormatting()
  {
    return std::move(m_formatting);
  }

  void OnDirectivePre(GekkoDirective) override { HighlightCurToken(HighlightFormat::Directive); }

  void OnInstructionPre(const ParseInfo&, bool) override
  {
    HighlightCurToken(HighlightFormat::Mnemonic);
  }

  void OnTerminal(Terminal type, const AssemblerToken& val) override
  {
    switch (type)
    {
    case Terminal::Id:
      HighlightCurToken(HighlightFormat::Symbol);
      break;

    case Terminal::Hex:
    case Terminal::Dec:
    case Terminal::Oct:
    case Terminal::Bin:
    case Terminal::Flt:
      HighlightCurToken(HighlightFormat::Immediate);
      break;

    case Terminal::GPR:
      HighlightCurToken(HighlightFormat::GPR);
      break;

    case Terminal::FPR:
      HighlightCurToken(HighlightFormat::FPR);
      break;

    case Terminal::SPR:
      HighlightCurToken(HighlightFormat::SPR);
      break;

    case Terminal::CRField:
      HighlightCurToken(HighlightFormat::CRField);
      break;

    case Terminal::Lt:
    case Terminal::Gt:
    case Terminal::Eq:
    case Terminal::So:
      HighlightCurToken(HighlightFormat::CRFlag);
      break;

    case Terminal::Str:
      HighlightCurToken(HighlightFormat::Str);
      break;

    default:
      break;
    }
  }

  void OnHiaddr(std::string_view) override
  {
    HighlightCurToken(HighlightFormat::Symbol);
    auto&& [ha_pos, ha_tok] = m_owner->lexer.LookaheadTagRef(2);
    m_formatting.emplace_back(static_cast<int>(ha_pos.col),
                              static_cast<int>(ha_tok.token_val.length()), HighlightFormat::HaLa);
  }

  void OnLoaddr(std::string_view id) override { OnHiaddr(id); }

  void OnOpenParen(ParenType type) override
  {
    m_paren_stack.push_back(static_cast<int>(m_owner->lexer.ColNumber()));
  }

  void OnCloseParen(ParenType type) override
  {
    if (m_paren_stack.empty())
    {
      return;
    }

    m_matched_parens.emplace_back(m_paren_stack.back(),
                                  static_cast<int>(m_owner->lexer.ColNumber()));
    m_paren_stack.pop_back();
  }

  void OnError() override
  {
    m_formatting.emplace_back(static_cast<int>(m_owner->error->col),
                              static_cast<int>(m_owner->error->len), HighlightFormat::Error);
  }

  void OnLabelDecl(std::string_view name) override
  {
    const int len = static_cast<int>(m_owner->lexer.LookaheadRef().token_val.length());
    const int off = static_cast<int>(m_owner->lexer.ColNumber());
    m_formatting.emplace_back(len, off, HighlightFormat::Symbol);
  }

  void OnVarDecl(std::string_view name) override { OnLabelDecl(name); }

private:
  std::vector<int> m_paren_stack;
  std::vector<std::pair<int, int>> m_matched_parens;
  std::vector<std::tuple<int, int, HighlightFormat>> m_formatting;

  void HighlightCurToken(HighlightFormat format)
  {
    const int len = static_cast<int>(m_owner->lexer.LookaheadRef().token_val.length());
    const int off = static_cast<int>(m_owner->lexer.ColNumber());
    m_formatting.emplace_back(off, len, format);
  }
};
}  // namespace

void GekkoSyntaxHighlight::highlightBlock(const QString& text)
{
  BlockInfo* info = static_cast<BlockInfo*>(currentBlockUserData());
  if (info == nullptr)
  {
    info = new BlockInfo;
    setCurrentBlockUserData(info);
  }

  qsizetype comment_idx = text.indexOf(QLatin1Char('#'));
  if (comment_idx != -1)
  {
    HighlightSubstr(comment_idx, text.length() - comment_idx, HighlightFormat::Comment);
  }

  if (m_mode == 0)
  {
    HighlightParsePlugin plugin;
    ParseWithPlugin(&plugin, text.toStdString());

    info->block_format = plugin.MoveFormatting();
    info->parens = plugin.MoveParens();
    info->error = std::move(plugin.Error());
    info->error_at_eol = info->error && info->error->len == 0;
  }
  else if (m_mode == 1)
  {
    auto paren_it = std::find_if(info->parens.begin(), info->parens.end(),
                                 [this](const std::pair<int, int>& p) {
                                   return p.first == m_cursor_loc || p.second == m_cursor_loc;
                                 });
    if (paren_it != info->parens.end())
    {
      HighlightSubstr(paren_it->first, 1, HighlightFormat::Paren);
      HighlightSubstr(paren_it->second, 1, HighlightFormat::Paren);
    }
  }

  for (auto&& [off, len, format] : info->block_format)
  {
    HighlightSubstr(off, len, format);
  }
}

GekkoSyntaxHighlight::GekkoSyntaxHighlight(QTextDocument* document, QTextCharFormat base_format,
                                           bool dark_scheme)
    : QSyntaxHighlighter(document), m_base_format(base_format)
{
  QPalette base_scheme;
  m_theme_idx = dark_scheme ? 1 : 0;
}

void GekkoSyntaxHighlight::HighlightSubstr(int start, int len, HighlightFormat format)
{
  QTextCharFormat hl_format = m_base_format;
  const QColor DIRECTIVE_COLOR[2] = {QColor(0x9d, 0x00, 0x06),
                                     QColor(0xfb, 0x49, 0x34)};  // Gruvbox darkred
  const QColor MNEMONIC_COLOR[2] = {QColor(0x79, 0x74, 0x0e),
                                    QColor(0xb8, 0xbb, 0x26)};  // Gruvbox darkgreen
  const QColor IMM_COLOR[2] = {QColor(0xb5, 0x76, 0x14),
                               QColor(0xfa, 0xbd, 0x2f)};  // Gruvbox darkyellow
  const QColor BUILTIN_COLOR[2] = {QColor(0x07, 0x66, 0x78),
                                   QColor(0x83, 0xa5, 0x98)};  // Gruvbox darkblue
  const QColor HA_LA_COLOR[2] = {QColor(0xaf, 0x3a, 0x03),
                                 QColor(0xfe, 0x80, 0x19)};  // Gruvbox darkorange
  const QColor HOVER_BG_COLOR[2] = {QColor(0xd5, 0xc4, 0xa1),
                                    QColor(0x50, 0x49, 0x45)};  // Gruvbox bg2
  const QColor STRING_COLOR[2] = {QColor(0x98, 0x97, 0x1a),
                                  QColor(0x98, 0x97, 0x1a)};  // Gruvbox green
  const QColor COMMENT_COLOR[2] = {QColor(0x68, 0x9d, 0x6a),
                                   QColor(0x68, 0x9d, 0x6a)};  // Gruvbox aqua

  switch (format)
  {
  case HighlightFormat::Directive:
    hl_format.setForeground(DIRECTIVE_COLOR[m_theme_idx]);
    break;
  case HighlightFormat::Mnemonic:
    hl_format.setForeground(MNEMONIC_COLOR[m_theme_idx]);
    break;
  case HighlightFormat::Symbol:
    break;
  case HighlightFormat::Immediate:
    hl_format.setForeground(IMM_COLOR[m_theme_idx]);
    break;
  case HighlightFormat::GPR:
  case HighlightFormat::FPR:
  case HighlightFormat::SPR:
  case HighlightFormat::CRField:
  case HighlightFormat::CRFlag:
    hl_format.setForeground(BUILTIN_COLOR[m_theme_idx]);
    break;
  case HighlightFormat::Str:
    hl_format.setForeground(STRING_COLOR[m_theme_idx]);
    break;
  case HighlightFormat::HaLa:
    hl_format.setForeground(HA_LA_COLOR[m_theme_idx]);
    break;
  case HighlightFormat::Paren:
    hl_format.setBackground(HOVER_BG_COLOR[m_theme_idx]);
    break;
  case HighlightFormat::Default:
    hl_format.clearForeground();
    hl_format.clearBackground();
    break;
  case HighlightFormat::Comment:
    hl_format.setForeground(COMMENT_COLOR[m_theme_idx]);
    break;
  case HighlightFormat::Error:
    hl_format.setUnderlineColor(Qt::red);
    hl_format.setUnderlineStyle(QTextCharFormat::WaveUnderline);
    break;
  }

  setFormat(start, len, hl_format);
}