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

#include "Common/Assembler/GekkoParser.h"

#include <algorithm>
#include <array>
#include <functional>

#include <fmt/format.h>

#include "Common/Assembler/AssemblerShared.h"
#include "Common/Assembler/AssemblerTables.h"
#include "Common/Assembler/GekkoLexer.h"
#include "Common/Assert.h"

namespace Common::GekkoAssembler::detail
{
namespace
{
bool MatchOperandFirst(const AssemblerToken& tok)
{
  switch (tok.token_type)
  {
  case TokenType::Minus:
  case TokenType::Tilde:
  case TokenType::Lparen:
  case TokenType::Grave:
  case TokenType::Identifier:
  case TokenType::DecimalLit:
  case TokenType::OctalLit:
  case TokenType::HexadecimalLit:
  case TokenType::BinaryLit:
  case TokenType::Dot:
    return true;
  default:
    return false;
  }
}

void ParseImm(ParseState* state)
{
  AssemblerToken tok = state->lexer.Lookahead();
  switch (tok.token_type)
  {
  case TokenType::HexadecimalLit:
    state->plugin.OnTerminal(Terminal::Hex, tok);
    break;
  case TokenType::DecimalLit:
    state->plugin.OnTerminal(Terminal::Dec, tok);
    break;
  case TokenType::OctalLit:
    state->plugin.OnTerminal(Terminal::Oct, tok);
    break;
  case TokenType::BinaryLit:
    state->plugin.OnTerminal(Terminal::Bin, tok);
    break;
  default:
    state->EmitErrorHere(fmt::format("Invalid {} with value '{}'", tok.TypeStr(), tok.ValStr()));
    return;
  }
  if (state->error)
  {
    return;
  }
  state->lexer.Eat();
}

void ParseId(ParseState* state)
{
  AssemblerToken tok = state->lexer.Lookahead();
  if (tok.token_type == TokenType::Identifier)
  {
    state->plugin.OnTerminal(Terminal::Id, tok);
    if (state->error)
    {
      return;
    }
    state->lexer.Eat();
  }
  else
  {
    state->EmitErrorHere(fmt::format("Expected an identifier, but found '{}'", tok.ValStr()));
  }
}

void ParseNumLocation(ParseState* state)
{
  AssemblerToken tok = state->lexer.Lookahead();
  switch (tok.token_type)
  {
  case TokenType::NumLabFwd:
    state->plugin.OnTerminal(Terminal::NumLabFwd, tok);
    break;

  case TokenType::NumLabBwd:
    state->plugin.OnTerminal(Terminal::NumLabBwd, tok);
    break;

  default:
    state->EmitErrorHere(fmt::format("Invalid {} with value '{}'", tok.TypeStr(), tok.ValStr()));
    return;
  }
  state->lexer.Eat();
}

void ParseIdLocation(ParseState* state)
{
  std::array<AssemblerToken, 3> toks;
  state->lexer.LookaheadN(&toks);

  if (toks[1].token_type == TokenType::At)
  {
    if (toks[2].token_val == "ha")
    {
      state->plugin.OnHiaddr(toks[0].token_val);
      if (state->error)
      {
        return;
      }
      state->lexer.EatN<3>();
      return;
    }
    else if (toks[2].token_val == "l")
    {
      state->plugin.OnLoaddr(toks[0].token_val);
      if (state->error)
      {
        return;
      }
      state->lexer.EatN<3>();
      return;
    }
  }

  ParseId(state);
}

void ParsePpcBuiltin(ParseState* state)
{
  AssemblerToken tok = state->lexer.Lookahead();
  switch (tok.token_type)
  {
  case TokenType::GPR:
    state->plugin.OnTerminal(Terminal::GPR, tok);
    break;
  case TokenType::FPR:
    state->plugin.OnTerminal(Terminal::FPR, tok);
    break;
  case TokenType::SPR:
    state->plugin.OnTerminal(Terminal::SPR, tok);
    break;
  case TokenType::CRField:
    state->plugin.OnTerminal(Terminal::CRField, tok);
    break;
  case TokenType::Lt:
    state->plugin.OnTerminal(Terminal::Lt, tok);
    break;
  case TokenType::Gt:
    state->plugin.OnTerminal(Terminal::Gt, tok);
    break;
  case TokenType::Eq:
    state->plugin.OnTerminal(Terminal::Eq, tok);
    break;
  case TokenType::So:
    state->plugin.OnTerminal(Terminal::So, tok);
    break;
  default:
    state->EmitErrorHere(
        fmt::format("Unexpected token '{}' in ppc builtin", state->lexer.LookaheadRef().ValStr()));
    break;
  }
  if (state->error)
  {
    return;
  }
  state->lexer.Eat();
}

void ParseBaseexpr(ParseState* state)
{
  TokenType tok = state->lexer.LookaheadType();
  switch (tok)
  {
  case TokenType::HexadecimalLit:
  case TokenType::DecimalLit:
  case TokenType::OctalLit:
  case TokenType::BinaryLit:
    ParseImm(state);
    break;

  case TokenType::Identifier:
    ParseIdLocation(state);
    break;

  case TokenType::GPR:
  case TokenType::FPR:
  case TokenType::SPR:
  case TokenType::CRField:
  case TokenType::Lt:
  case TokenType::Gt:
  case TokenType::Eq:
  case TokenType::So:
    ParsePpcBuiltin(state);
    break;

  case TokenType::NumLabFwd:
  case TokenType::NumLabBwd:
    ParseNumLocation(state);
    break;

  case TokenType::Dot:
    state->plugin.OnTerminal(Terminal::Dot, state->lexer.Lookahead());
    if (state->error)
    {
      return;
    }
    state->lexer.Eat();
    break;

  default:
    state->EmitErrorHere(
        fmt::format("Unexpected token '{}' in expression", state->lexer.LookaheadRef().ValStr()));
    break;
  }
}

void ParseBitor(ParseState* state);
void ParseParen(ParseState* state)
{
  if (state->HasToken(TokenType::Lparen))
  {
    state->plugin.OnOpenParen(ParenType::Normal);
    if (state->error)
    {
      return;
    }

    state->lexer.Eat();
    ParseBitor(state);
    if (state->error)
    {
      return;
    }

    if (state->HasToken(TokenType::Rparen))
    {
      state->plugin.OnCloseParen(ParenType::Normal);
    }
    state->ParseToken(TokenType::Rparen);
  }
  else if (state->HasToken(TokenType::Grave))
  {
    state->plugin.OnOpenParen(ParenType::RelConv);

    state->lexer.Eat();
    ParseBitor(state);
    if (state->error)
    {
      return;
    }

    if (state->HasToken(TokenType::Grave))
    {
      state->plugin.OnCloseParen(ParenType::RelConv);
    }
    state->ParseToken(TokenType::Grave);
  }
  else
  {
    ParseBaseexpr(state);
  }
}

void ParseUnary(ParseState* state)
{
  TokenType tok = state->lexer.LookaheadType();
  if (tok == TokenType::Minus || tok == TokenType::Tilde)
  {
    state->lexer.Eat();
    ParseUnary(state);
    if (state->error)
    {
      return;
    }

    if (tok == TokenType::Minus)
    {
      state->plugin.OnOperator(AsmOp::Neg);
    }
    else
    {
      state->plugin.OnOperator(AsmOp::Not);
    }
  }
  else
  {
    ParseParen(state);
  }
}

void ParseMultiplication(ParseState* state)
{
  ParseUnary(state);
  if (state->error)
  {
    return;
  }

  TokenType tok = state->lexer.LookaheadType();
  while (tok == TokenType::Star || tok == TokenType::Slash)
  {
    state->lexer.Eat();
    ParseUnary(state);
    if (state->error)
    {
      return;
    }

    if (tok == TokenType::Star)
    {
      state->plugin.OnOperator(AsmOp::Mul);
    }
    else
    {
      state->plugin.OnOperator(AsmOp::Div);
    }
    tok = state->lexer.LookaheadType();
  }
}

void ParseAddition(ParseState* state)
{
  ParseMultiplication(state);
  if (state->error)
  {
    return;
  }

  TokenType tok = state->lexer.LookaheadType();
  while (tok == TokenType::Plus || tok == TokenType::Minus)
  {
    state->lexer.Eat();
    ParseMultiplication(state);
    if (state->error)
    {
      return;
    }

    if (tok == TokenType::Plus)
    {
      state->plugin.OnOperator(AsmOp::Add);
    }
    else
    {
      state->plugin.OnOperator(AsmOp::Sub);
    }
    tok = state->lexer.LookaheadType();
  }
}

void ParseShift(ParseState* state)
{
  ParseAddition(state);
  if (state->error)
  {
    return;
  }

  TokenType tok = state->lexer.LookaheadType();
  while (tok == TokenType::Lsh || tok == TokenType::Rsh)
  {
    state->lexer.Eat();
    ParseAddition(state);
    if (state->error)
    {
      return;
    }

    if (tok == TokenType::Lsh)
    {
      state->plugin.OnOperator(AsmOp::Lsh);
    }
    else
    {
      state->plugin.OnOperator(AsmOp::Rsh);
    }
    tok = state->lexer.LookaheadType();
  }
}

void ParseBitand(ParseState* state)
{
  ParseShift(state);
  if (state->error)
  {
    return;
  }

  while (state->HasToken(TokenType::Ampersand))
  {
    state->lexer.Eat();
    ParseShift(state);
    if (state->error)
    {
      return;
    }

    state->plugin.OnOperator(AsmOp::And);
  }
}

void ParseBitxor(ParseState* state)
{
  ParseBitand(state);
  if (state->error)
  {
    return;
  }

  while (state->HasToken(TokenType::Caret))
  {
    state->lexer.Eat();
    ParseBitand(state);
    if (state->error)
    {
      return;
    }

    state->plugin.OnOperator(AsmOp::Xor);
  }
}

void ParseBitor(ParseState* state)
{
  ParseBitxor(state);
  if (state->error)
  {
    return;
  }

  while (state->HasToken(TokenType::Pipe))
  {
    state->lexer.Eat();
    ParseBitxor(state);
    if (state->error)
    {
      return;
    }

    state->plugin.OnOperator(AsmOp::Or);
  }
}

void ParseOperand(ParseState* state)
{
  state->plugin.OnOperandPre();
  ParseBitor(state);
  if (state->error)
  {
    return;
  }
  state->plugin.OnOperandPost();
}

void ParseOperandList(ParseState* state, ParseAlg alg)
{
  if (alg == ParseAlg::None)
  {
    return;
  }
  if (alg == ParseAlg::NoneOrOp1)
  {
    if (MatchOperandFirst(state->lexer.Lookahead()))
    {
      ParseOperand(state);
    }
    return;
  }

  enum ParseStep
  {
    _Operand,
    _Comma,
    _Lparen,
    _Rparen,
    _OptComma
  };
  std::vector<ParseStep> steps;

  switch (alg)
  {
  case ParseAlg::Op1:
    steps = {_Operand};
    break;
  case ParseAlg::Op1Or2:
    steps = {_Operand, _OptComma, _Operand};
    break;
  case ParseAlg::Op2Or3:
    steps = {_Operand, _Comma, _Operand, _OptComma, _Operand};
    break;
  case ParseAlg::Op1Off1:
    steps = {_Operand, _Comma, _Operand, _Lparen, _Operand, _Rparen};
    break;
  case ParseAlg::Op2:
    steps = {_Operand, _Comma, _Operand};
    break;
  case ParseAlg::Op3:
    steps = {_Operand, _Comma, _Operand, _Comma, _Operand};
    break;
  case ParseAlg::Op4:
    steps = {_Operand, _Comma, _Operand, _Comma, _Operand, _Comma, _Operand};
    break;
  case ParseAlg::Op5:
    steps = {_Operand, _Comma, _Operand, _Comma, _Operand, _Comma, _Operand, _Comma, _Operand};
    break;
  case ParseAlg::Op1Off1Op2:
    steps = {_Operand, _Comma, _Operand, _Lparen, _Operand,
             _Rparen,  _Comma, _Operand, _Comma,  _Operand};
    break;
  default:
    ASSERT(false);
    return;
  }

  for (ParseStep step : steps)
  {
    bool stop_parse = false;
    switch (step)
    {
    case _Operand:
      ParseOperand(state);
      break;
    case _Comma:
      state->ParseToken(TokenType::Comma);
      break;
    case _Lparen:
      state->ParseToken(TokenType::Lparen);
      break;
    case _Rparen:
      state->ParseToken(TokenType::Rparen);
      break;
    case _OptComma:
      if (state->HasToken(TokenType::Comma))
      {
        state->ParseToken(TokenType::Comma);
      }
      else
      {
        stop_parse = true;
      }
      break;
    }
    if (state->error)
    {
      return;
    }
    if (stop_parse)
    {
      break;
    }
  }
}

void ParseInstruction(ParseState* state)
{
  state->lexer.SetIdentifierMatchRule(Lexer::IdentifierMatchRule::Mnemonic);

  AssemblerToken mnemonic_token = state->lexer.Lookahead();
  if (mnemonic_token.token_type != TokenType::Identifier)
  {
    state->lexer.SetIdentifierMatchRule(Lexer::IdentifierMatchRule::Typical);
    return;
  }

  ParseInfo const* parse_info = mnemonic_tokens.Find(mnemonic_token.token_val);
  bool is_extended = false;
  if (parse_info == nullptr)
  {
    parse_info = extended_mnemonic_tokens.Find(mnemonic_token.token_val);
    if (parse_info == nullptr)
    {
      state->EmitErrorHere(
          fmt::format("Unknown or unsupported mnemonic '{}'", mnemonic_token.ValStr()));
      return;
    }
    is_extended = true;
  }

  state->plugin.OnInstructionPre(*parse_info, is_extended);

  state->lexer.EatAndReset();

  ParseOperandList(state, parse_info->parse_algorithm);
  if (state->error)
  {
    return;
  }

  state->plugin.OnInstructionPost(*parse_info, is_extended);
}

void ParseLabel(ParseState* state)
{
  std::array<AssemblerToken, 2> tokens;
  state->lexer.LookaheadN(&tokens);

  if (tokens[0].token_type == TokenType::Identifier && tokens[1].token_type == TokenType::Colon)
  {
    state->plugin.OnLabelDecl(tokens[0].token_val);
    if (state->error)
    {
      return;
    }
    state->lexer.EatN<2>();
  }

  if (tokens[0].token_type == TokenType::DecimalLit && tokens[1].token_type == TokenType::Colon)
  {
    std::optional<u32> labnum = tokens[0].EvalToken<u32>();
    if (!labnum)
    {
      return;
    }
    state->plugin.OnNumericLabelDecl(tokens[0].token_val, *labnum);
    if (state->error)
    {
      return;
    }
    state->lexer.EatN<2>();
  }
}

void ParseResolvedExpr(ParseState* state)
{
  state->plugin.OnResolvedExprPre();
  ParseBitor(state);
  if (state->error)
  {
    return;
  }
  state->plugin.OnResolvedExprPost();
}

void ParseExpressionList(ParseState* state)
{
  ParseResolvedExpr(state);
  if (state->error)
  {
    return;
  }

  while (state->HasToken(TokenType::Comma))
  {
    state->lexer.Eat();
    ParseResolvedExpr(state);
    if (state->error)
    {
      return;
    }
  }
}

void ParseFloat(ParseState* state)
{
  AssemblerToken flt_token = state->lexer.LookaheadFloat();
  if (flt_token.token_type != TokenType::FloatLit)
  {
    state->EmitErrorHere("Invalid floating point literal");
    return;
  }
  state->plugin.OnTerminal(Terminal::Flt, flt_token);
  state->lexer.Eat();
}

void ParseFloatList(ParseState* state)
{
  ParseFloat(state);
  if (state->error)
  {
    return;
  }

  while (state->HasToken(TokenType::Comma))
  {
    state->lexer.Eat();
    ParseFloat(state);
    if (state->error)
    {
      return;
    }
  }
}

void ParseDefvar(ParseState* state)
{
  AssemblerToken tok = state->lexer.Lookahead();
  if (tok.token_type == TokenType::Identifier)
  {
    state->plugin.OnVarDecl(tok.token_val);
    if (state->error)
    {
      return;
    }
    state->lexer.Eat();

    state->ParseToken(TokenType::Comma);
    if (state->error)
    {
      return;
    }

    ParseResolvedExpr(state);
  }
  else
  {
    state->EmitErrorHere(fmt::format("Expected an identifier, but found '{}'", tok.ValStr()));
  }
}

void ParseString(ParseState* state)
{
  AssemblerToken tok = state->lexer.Lookahead();
  if (tok.token_type == TokenType::StringLit)
  {
    state->plugin.OnTerminal(Terminal::Str, tok);
    state->lexer.Eat();
  }
  else
  {
    state->EmitErrorHere(fmt::format("Expected a string literal, but found '{}'", tok.ValStr()));
  }
}

void ParseDirective(ParseState* state)
{
  // TODO: test directives
  state->lexer.SetIdentifierMatchRule(Lexer::IdentifierMatchRule::Directive);
  AssemblerToken tok = state->lexer.Lookahead();
  if (tok.token_type != TokenType::Identifier)
  {
    state->EmitErrorHere(fmt::format("Unexpected token '{}' in directive type", tok.ValStr()));
    return;
  }

  GekkoDirective const* directive_enum = directives_map.Find(tok.token_val);
  if (directive_enum == nullptr)
  {
    state->EmitErrorHere(fmt::format("Unknown assembler directive '{}'", tok.ValStr()));
    return;
  }

  state->plugin.OnDirectivePre(*directive_enum);

  state->lexer.EatAndReset();
  switch (*directive_enum)
  {
  case GekkoDirective::Byte:
  case GekkoDirective::_2byte:
  case GekkoDirective::_4byte:
  case GekkoDirective::_8byte:
    ParseExpressionList(state);
    break;

  case GekkoDirective::Float:
  case GekkoDirective::Double:
    ParseFloatList(state);
    break;

  case GekkoDirective::Locate:
  case GekkoDirective::Zeros:
  case GekkoDirective::Skip:
    ParseResolvedExpr(state);
    break;

  case GekkoDirective::PadAlign:
  case GekkoDirective::Align:
    ParseImm(state);
    break;

  case GekkoDirective::DefVar:
    ParseDefvar(state);
    break;

  case GekkoDirective::Ascii:
  case GekkoDirective::Asciz:
    ParseString(state);
    break;
  }

  if (state->error)
  {
    return;
  }

  state->plugin.OnDirectivePost(*directive_enum);
}

void ParseLine(ParseState* state)
{
  if (state->HasToken(TokenType::Dot))
  {
    state->ParseToken(TokenType::Dot);
    ParseDirective(state);
  }
  else
  {
    ParseInstruction(state);
  }
}

void ParseProgram(ParseState* state)
{
  AssemblerToken tok = state->lexer.Lookahead();
  if (tok.token_type == TokenType::Eof)
  {
    state->eof = true;
    return;
  }
  ParseLabel(state);
  if (state->error)
  {
    return;
  }
  ParseLine(state);
  if (state->error)
  {
    return;
  }

  while (!state->eof && !state->error)
  {
    tok = state->lexer.Lookahead();
    if (tok.token_type == TokenType::Eof)
    {
      state->eof = true;
    }
    else if (tok.token_type == TokenType::Eol)
    {
      state->lexer.Eat();
      ParseLabel(state);
      if (state->error)
      {
        return;
      }
      ParseLine(state);
    }
    else
    {
      state->EmitErrorHere(
          fmt::format("Unexpected token '{}' where line should have ended", tok.ValStr()));
    }
  }
}
}  // namespace

ParseState::ParseState(std::string_view input_str, ParsePlugin& p)
    : lexer(input_str), plugin(p), eof(false)
{
}

bool ParseState::HasToken(TokenType tp) const
{
  return lexer.LookaheadType() == tp;
}

void ParseState::ParseToken(TokenType tp)
{
  AssemblerToken tok = lexer.LookaheadRef();
  if (tok.token_type == tp)
  {
    lexer.Eat();
  }
  else
  {
    EmitErrorHere(fmt::format("Expected '{}' but found '{}'", TokenTypeToStr(tp), tok.ValStr()));
  }
}

void ParseState::EmitErrorHere(std::string&& message)
{
  AssemblerToken cur_token = lexer.Lookahead();
  if (cur_token.token_type == TokenType::Invalid)
  {
    error = AssemblerError{
        std::string(cur_token.invalid_reason),
        lexer.CurrentLine(),
        lexer.LineNumber(),
        lexer.ColNumber() + cur_token.invalid_region.begin,
        cur_token.invalid_region.len,
    };
  }
  else
  {
    error = AssemblerError{
        std::move(message), lexer.CurrentLine(),        lexer.LineNumber(),
        lexer.ColNumber(),  cur_token.token_val.size(),
    };
  }
}

void ParseWithPlugin(ParsePlugin* plugin, std::string_view input)
{
  ParseState parse_state = ParseState(input, *plugin);
  plugin->SetOwner(&parse_state);
  ParseProgram(&parse_state);

  if (parse_state.error)
  {
    plugin->OnError();
    plugin->ForwardError(std::move(*parse_state.error));
  }
  else
  {
    plugin->PostParseAction();
    if (parse_state.error)
    {
      plugin->OnError();
      plugin->ForwardError(std::move(*parse_state.error));
    }
  }

  plugin->SetOwner(nullptr);
}
}  // namespace Common::GekkoAssembler::detail
