#include "stdafx.h"

#include "xtr/ui/RSEdit.h"

#include "resource.h"

#include "xtr/core/Encoder.h"
#include "xtr/ExteorOptions.h"
#include "xtr/xtrDesign.h"
#include "xtr/cclDescriptor.h"

#include "xtr/ui/KeyboardHandler.h"
#include "xtr/ui/UIHelper.h"

#include "xtr/dialog/InsertCstDlg.h"

#include "ccl/rslang/RSGenerator.h"

namespace xtr::ui {

using ccl::rslang::TokenID;
using ccl::rslang::Token;
using ccl::rslang::Generator;
using ccl::semantic::RSCore;
using ccl::semantic::CstType;
using index::Digits;

namespace {
[[nodiscard]] CString DefaultInputFor(const TokenID token) {
  auto baseStr = mfc::ToMFC(Token::Str(token));
  switch (token) {
  default: {
    return baseStr;
  }
  case TokenID::DECLARATIVE: {
    return baseStr + '{' + mfc::ToMFC(Token::Str(TokenID::IN)) + LR"( | })";
  }
  case TokenID::IMPERATIVE: {
    return baseStr + LR"({ | )" + mfc::ToMFC(Token::Str(TokenID::ITERATE)) + LR"(; := })";
  }
  case TokenID::RECURSIVE: {
    return baseStr + LR"({ := |  | })";
  }
  case TokenID::BOOLEAN:
  case TokenID::CARD:
  case TokenID::BOOL:
  case TokenID::REDUCE:
  case TokenID::DEBOOL: {
    return baseStr + L"()";
  }
  case TokenID::BIGPR:
  case TokenID::SMALLPR: {
    return baseStr + L"1()";
  }

  case TokenID::FILTER: {
    return baseStr + LR"(1[]())";
  }
  }
}
} // anonymous namespace

#pragma warning( push )
#pragma warning( disable : 26440 26436 26454 )

BEGIN_MESSAGE_MAP(RSEdit, BuffRichEdit)
  ON_WM_CHAR()
  ON_WM_KEYDOWN()
  ON_WM_SYSCHAR()
END_MESSAGE_MAP()

#pragma warning( pop )

RSEdit::RSEdit() {
  defaultToolTipText = mfc::LoadSID(IXTRS_EXP_TOOLTIP_DEFAULT);
}

void RSEdit::InputBrackets() {
  if ((GetStyle() & ES_READONLY) == 0 && IsWindowEnabled()) {
    const auto sel = GetSelectionRange();
    InsertRSText(LR"([])", sel);
    RedrawWindow();
    SetFocus();
  }
}

void RSEdit::InputParenthesis() {
  if ((GetStyle() & ES_READONLY) == 0 && IsWindowEnabled()) {
    const auto sel = GetSelectionRange();
    InsertRSText(L"()", sel);
    RedrawWindow();
    SetFocus();
  }
}

void RSEdit::InputToken(const TokenID token) {
  if ((GetStyle() & ES_READONLY) != 0 || !IsWindowEnabled()) {
    return;
  }
  const auto sel = GetSelectionRange();
  if (token == TokenID::BOOLEAN) {
    auto selTxt = GetSelText();
    auto pos = 0;
    while (selTxt.GetLength() > pos + 1
           && selTxt.Mid(pos, 1) == mfc::ToMFC(Token::Str(TokenID::BOOLEAN))) {
      ++pos;
    }
    if (pos != 0 && selTxt[pos] == '('
        && mfc::FindClosingParanthesis(selTxt, pos + 1).value_or(0) == selTxt.GetLength() - 1) {
      SetSel(sel.start, sel.start);
      InsertString(mfc::ToMFC(Token::Str(TokenID::BOOLEAN)));
      SetSel(sel.start, sel.finish + 1);
      return;
    }
  }

  InsertRSText(DefaultInputFor(token), sel);
  RedrawWindow();
  SetFocus();
}

void RSEdit::InsertRSText(const CString text, const StrRange sel) {
  if (text.Right(2) != L"()" && text.Right(2) != L"[]") {
    InsertString(text);
  } else if (const auto strLen = text.GetLength(); sel.start == sel.finish) {
    InsertString(text);
    SetSel(sel.finish + strLen - 1, sel.finish + strLen - 1);
  } else {
    SetSel(sel.start, sel.start);
    InsertString(text.Left(strLen - 1));

    const auto newPos = sel.finish + strLen - 1;
    SetSel(newPos, newPos);
    InsertString(text.Right(1));
    SetSel(newPos, newPos);
  }
}

void RSEdit::Init(const RSCore& cntxt, const EntityUID active) {
  context = &cntxt;
  activeUID = active;
  astText.Empty();
  prefixLen = static_cast<StrPos>(cntxt.GetRS(activeUID).alias.length() + 3);
  navigatorEnabled = FALSE;
  activeNode = std::nullopt;
  checker = cntxt.RSLang().MakeAuditor();
}

BOOL RSEdit::PreTranslateMessage(MSG* pMsg) {
  if (pMsg->message == WM_KEYDOWN) {
    if (Keyboard::IsPressed(VK_CONTROL) &&
        pMsg->wParam == VK_SPACE &&
        context != nullptr &&
        (GetStyle() & ES_READONLY) == 0) {
      AddReference();
      return TRUE;
    } else if (Keyboard::IsPressed(VK_CONTROL) &&
               Keyboard::IsPressed(VK_SHIFT)) {
      auto updateNeeded = FALSE;
      switch (pMsg->wParam) {
      default: return BuffRichEdit::PreTranslateMessage(pMsg);
      case 'W': NavigatorOn(); updateNeeded = MoveUp(); break;
      case 'S': NavigatorOn(); updateNeeded = MoveDown(); break;
      case 'D': NavigatorOn(); updateNeeded = MoveRight(); break;
      case 'A': NavigatorOn(); updateNeeded = MoveLeft(); break;
      case 'Q': {
        updateNeeded = TRUE;
        navigatorEnabled = FALSE;
        activeNode = std::nullopt;
        break;
      }
      }
      if (updateNeeded) {
        UpdateFormat(TRUE);
      }
      return TRUE;
    }
  }
  return BuffRichEdit::PreTranslateMessage(pMsg);
}

void RSEdit::PreSubclassWindow() {
  BuffRichEdit::PreSubclassWindow();
  SetFont(&XTROptions::App().fontMath);
}

void RSEdit::OnChar(uint32_t nChar, uint32_t nRepCnt, uint32_t nFlags) {
  if (Keyboard::IsPressed(VK_CONTROL) ||
      Keyboard::Instance().GetLayout() != Keyboard::Layout::MATH) {
    BuffRichEdit::OnChar(nChar, nRepCnt, nFlags);
    return;
  }
  switch (nChar) {
  default: BuffRichEdit::OnChar(nChar, nRepCnt, nFlags); return;
  case 'a': InsertChar(encode::US_A);  return;
  case 'b': InsertChar(encode::US_B);  return;
  case 'g': InsertChar(encode::US_G);  return;
  case 'd': InsertChar(encode::US_D);  return;
  case 'e': InsertChar(encode::US_E);  return;
  case 'z': InsertChar(encode::US_Z);  return;
  case 'n': InsertChar(encode::US_H);  return;
  case 'v': InsertChar(encode::US_V);  return;
  case 'h': InsertChar(encode::US_L);  return;
  case 'm': InsertChar(encode::US_M);  return;
  case 'x': InsertChar(encode::US_X);  return;
  case 'y': InsertChar(encode::US_P);  return;
  case 'r': InsertChar(encode::US_R);  return;
  case 's': InsertChar(encode::US_S);  return;
  case 't': InsertChar(encode::US_T);  return;
  case 'f': InsertChar(encode::US_F);  return;
  case 'c': InsertChar(encode::US_J);  return;
  case 'w': InsertChar(encode::US_W);  return;

  case '~': InsertChar(encode::US_LOGIC_EXISTS); return;
  case '`': InsertChar(encode::US_LOGIC_FORALL); return;

  case 'B': InsertChar(encode::US_BOOL); return;
  case '*': InsertChar(encode::US_CARTH); return;
  }
}

void RSEdit::OnKeyDown(const uint32_t nChar, const uint32_t nRepCnt, const uint32_t nFlags) {
  BuffRichEdit::OnKeyDown(nChar, nRepCnt, nFlags);
  if (Keyboard::IsPressed(VK_CONTROL) && nChar == VK_F3) {
    SelectPairedBracket(GetSelectionRange().start);
  }
}

void RSEdit::SelectPairedBracket(const StrPos caretPos) {
  if (const auto bracketPair = GetBracketPair(caretPos, '(', ')'); bracketPair.has_value()) {
    if (bracketPair->left.value() == caretPos - 1) {
      MoveCaretTo(bracketPair->right.value());
    } else {
      MoveCaretTo(bracketPair->left.value() + 1);
    }
    if (Keyboard::IsPressed(VK_SHIFT)) {
      SetSel(bracketPair->left.value() + 1, bracketPair->right.value());
    }
  }
}

std::optional<RSEdit::BracketPair> RSEdit::GetBracketPair(const StrPos pos,
                                                          const TCHAR leftBracket,
                                                          const TCHAR rightBracket) const {
  const auto wholeText = Text();
  const auto left = mfc::FindBracketBlockStart(wholeText, { leftBracket, rightBracket, pos });
  const auto right = mfc::FindBracketBlockEnd(wholeText, { leftBracket, rightBracket, pos });
  if (!left.has_value() && !right.has_value()) {
    return std::nullopt;
  } else {
    return BracketPair{ left, right };
  }
}

void RSEdit::OnSysChar(const uint32_t nChar, const uint32_t nRepCnt, const uint32_t nFlags) {
  switch(nChar) {
  default: BuffRichEdit::OnSysChar(nChar, nRepCnt, nFlags); return;
  case 'e': InputToken(TokenID::BOOLEAN); return;
  case 'E': InputToken(TokenID::DECART); return;
  case 'c': InputToken(TokenID::CARD); return;
  case 'b': InputToken(TokenID::BOOL); return;
  case 'v': InputToken(TokenID::DEBOOL); return;
  case 'r': InputToken(TokenID::REDUCE); return;
  case 'q': InputToken(TokenID::BIGPR); return;
  case 'w': InputToken(TokenID::SMALLPR); return;
  case 'f': InputToken(TokenID::FILTER); return;
  case 'a': InsertChar(encode::US_SET_INTERSECTION); return;
  case 's': InsertChar(encode::US_SET_UNION); return;
  case 'x': InsertChar(encode::US_EMPTYSET); return;
  case 'z': InsertChar('Z'); return;

  case 't': InputToken(TokenID::NT_RECURSIVE_FULL); return;
  case 'd': InputToken(TokenID::NT_DECLARATIVE_EXPR); return;
  case 'g': InputToken(TokenID::NT_IMPERATIVE_EXPR); return;

  case '`': InsertChar(encode::US_LOGIC_NOT); return;
  case '~': InsertChar(encode::US_NOTEQUAL); return;

  case '1': InsertChar(encode::US_ELEMENT); return;
  case '!': InsertChar(encode::US_NOTELEMENT); return;
  case '2': InsertChar(encode::US_SUBSET_OR_EQ); return;
  case '@': InsertChar(encode::US_NOTSUBSET); return;
  case '3': InsertChar('&'); return;
  case '#': InsertChar(encode::US_LOGIC_OR); return;
  case '4': InsertChar(encode::US_IMPLICATION); return;
  case '$': InsertChar(encode::US_LOGIC_EQUAL); return;
  case '5': InsertChar('\\'); return;
  case '%': InsertChar(encode::US_SET_SYMMINUS); return;
  case '6': InputToken(TokenID::ITERATE); return;
  case '^': InputToken(TokenID::ASSIGN); return;
  case '7': InsertChar(encode::US_SUBSET); return;
  case '8': InsertChar('*'); return;  
  }
}

void RSEdit::UpdateFormatItems() {
  formatItems.clear();

  const auto text = Text();
  const auto rsText = mfc::ToSTL(text);
  std::string str{};
  auto parlevel = 0;
  auto tokenStream = parser.Lex(rsText, ccl::rslang::Syntax::MATH);
  for (auto token = tokenStream(); token.id != TokenID::END; token = tokenStream()) {
    switch (token.id) {
    default: break;
    case TokenID::PUNC_PL: {
      ++parlevel;
      if (XTROptions::ParseRSL().colorBrackets) {
        FormatBracket(parlevel, token.pos);
      }
      break;
    }
    case TokenID::PUNC_PR: {
      if (XTROptions::ParseRSL().colorBrackets) {
        FormatBracket(parlevel, token.pos);
      }
      --parlevel;
      break;
    }
    case TokenID::BOOL:
    case TokenID::REDUCE:
    case TokenID::DEBOOL:
    case TokenID::CARD: {
      FormatIdentifier(token.id, token.pos);
      break;
    }

    case TokenID::ID_GLOBAL:
    case TokenID::ID_RADICAL:
    case TokenID::ID_FUNCTION:
    case TokenID::ID_PREDICATE: {
      const auto pos = token.pos;
      assert(pos.finish <= text.GetLength());
      auto prefixEnd = pos.finish - 1;
      for (prefixEnd; std::isdigit(text.GetAt(prefixEnd)); --prefixEnd) {
        ;
      }
      FormatIndex(token.id, pos, prefixEnd + 1 - pos.start, 
                  XTROptions::ParseRSL().indexing.global == Digits::subText);
      break;
    }
    case TokenID::ID_LOCAL: {
      const auto pos = token.pos;
      assert(pos.finish <= text.GetLength());
      auto prefixEnd = pos.finish - 1;
      for (prefixEnd; std::isdigit(text.GetAt(prefixEnd)); --prefixEnd) {
        ;
      }
      FormatIndex(token.id, pos, prefixEnd + 1 - pos.start, 
                  XTROptions::ParseRSL().indexing.local == Digits::subText);
      break;
    }
    case TokenID::BIGPR:
    case TokenID::FILTER:
    case TokenID::SMALLPR: {
      assert(token.pos.finish <= text.GetLength());
      FormatIndex(token.id, token.pos, 2,
                  XTROptions::ParseRSL().indexing.operators == Digits::subText);
      break;
    }
    }
  }
}

void RSEdit::FormatIndex(const TokenID tid, const StrRange pos, const StrPos prefix, const BOOL subscript) {
  auto format = design::CharFormatRE();
  format.dwMask = CFM_COLOR;
  format.crTextColor = design::Color(tid);
  formatItems.emplace_back(FormatItem{ StrRange::FromLength(pos.start, prefix), format });

  if (subscript) {
    format.dwMask |= CFM_SUBSCRIPT;
    format.dwEffects |= CFE_SUBSCRIPT;
  }
  formatItems.emplace_back(FormatItem{ StrRange{ pos.start + prefix, pos.finish }, format });
}

void RSEdit::AdditionalFormat() {
  const auto selection = GetSelectionRange();
  FormatBracketsAround(selection.start);
  FormatSelectedBlock(selection);
}

void RSEdit::FormatBracketsAround(const StrPos caretPos) {
  std::vector<BracketPair> brackets{};

  if (const auto parens = GetBracketPair(caretPos, '(', ')'); parens.has_value()) {
    brackets.emplace_back(parens.value());
  }
  if (const auto curly = GetBracketPair(caretPos, '{', '}'); curly.has_value()) {
    brackets.emplace_back(curly.value());
  }
  if (const auto square = GetBracketPair(caretPos, '[', ']'); square.has_value()) {
    brackets.emplace_back(square.value());
  }
  auto format = design::CharFormatRE();
  format.dwMask = CFM_BOLD;
  format.dwEffects = CFE_BOLD;
  format.crTextColor = design::BracketsColor(-1);
  for (const auto& bracketPair : brackets) {
    if (bracketPair.IsCorrect()) {
      SelectChar(bracketPair.left.value());
      SetSelectionCharFormat(format);

      SelectChar(bracketPair.right.value());
      SetSelectionCharFormat(format);
    } else if (!bracketPair.IsNull()) {
      format.dwMask |= CFM_COLOR;
      if (bracketPair.left.has_value()) {
        SelectChar(bracketPair.left.value());
      } else {
        SelectChar(bracketPair.right.value());
      }
      SetSelectionCharFormat(format);
      format.dwMask &= ~CFM_COLOR;
    }
  }
}

BOOL RSEdit::UpdateAST() {
  if (const auto text = Text(); text == astText) {
    return TRUE;
  } else if (!context->Contains(activeUID) || checker == nullptr) {
    return FALSE;
  } else {
    const auto& cst = context->GetRS(activeUID);
    const auto expression = Generator::GlobalDefinition(cst.alias, mfc::ToSTL(text), cst.type == CstType::structured);
    checker->CheckType(expression, ccl::rslang::Syntax::MATH);
    astText = text;
    navigatorEnabled = FALSE;
    activeNode = std::nullopt;
    return TRUE;
  }
}

void RSEdit::FormatSelectedBlock(const StrRange selection) {
  static constexpr COLORREF clrBg{ RGB(235, 235, 235) };
  static constexpr COLORREF clrReadOnlyBg{ RGB(255, 255, 211) };

  if (XTROptions::ParseRSL().enableNavigator &&
      GetFocus()->GetSafeHwnd() == m_hWnd &&
      context != nullptr && UpdateAST() && checker->isParsed) {
    auto selectedNode = activeNode;
    if (!navigatorEnabled) {
      const auto clientRange = StrRange{ selection.start + prefixLen, selection.finish + prefixLen };
      selectedNode = ccl::rslang::FindMinimalNode(checker->parser.AST().Root(), clientRange);
    }
    if (selectedNode.has_value()) {
      const auto start = std::max(selectedNode.value()->pos.start - prefixLen, 0);
      const auto end = std::min(selectedNode.value()->pos.finish - prefixLen, astText.GetLength());

      auto format = design::CharFormatRE();
      format.dwMask = CFM_BACKCOLOR;
      format.crBackColor = (GetStyle() & ES_READONLY) == 0 ? clrBg : clrReadOnlyBg;
      SetSel(start, end);
      SetSelectionCharFormat(format);
    }
  }
}

void RSEdit::FormatIdentifier(const ccl::rslang::TokenID tid, const StrRange pos) {
  auto format = design::CharFormatRE();
  format.dwMask = CFM_COLOR;
  format.crTextColor = design::Color(tid);
  formatItems.emplace_back(FormatItem{ pos, format });
}

void RSEdit::FormatBracket(const int32_t level, const StrRange pos) {
  auto format = design::CharFormatRE();
  format.dwMask = CFM_COLOR;
  format.crTextColor = design::BracketsColor(level);
  formatItems.emplace_back(FormatItem{ pos, format });
}

CString RSEdit::GenerateToolTipText() {
  if (context == nullptr) {
    return defaultToolTipText;
  } 
  
  const auto uid = context->FindAlias(mfc::ToSTL(GetSelText()));
  if (!uid.has_value()) {
    return defaultToolTipText;
  }
  
  const auto& cst = context->GetText(uid.value());
  auto result = info::Label(context->GetParse(uid.value()).TypePtr());
  if (!std::empty(cst.term.Nominal())) {
    result += '\n';
  }
  result += mfc::ToMFC(cst.term.Nominal());

  if (!std::empty(cst.definition)) {
    result += '\n';
  }
  result += mfc::ToMFC(cst.definition.Str());
  return result;
}

void RSEdit::AddReference() {
  const auto pos = GetSubscriptDialogPosition();
  dialog::InsertCstDlg dialog(*context, pos, GetSelText());
  if (dialog.DoModal() == IDOK && !dialog.cstName.IsEmpty()) {
    InsertString(dialog.cstName);
  }
}

BOOL RSEdit::NavigatorOn() {
  using ccl::rslang::FindMinimalNode;

  if (navigatorEnabled) {
    return TRUE;
  } else if (!checker->isParsed) {
    activeNode = std::nullopt;
    MessageBeep(MB_ICONERROR);
    return FALSE;
  } else {
    navigatorEnabled = TRUE;
    const auto sel = GetSelectionRange();
    activeNode = FindMinimalNode(checker->parser.AST().Root(),
                                 StrRange{ sel.start + prefixLen, sel.finish + prefixLen });
    return TRUE;
  }
}

BOOL RSEdit::MoveUp() noexcept {
  return activeNode.has_value() && activeNode->MoveToParent();
}

BOOL RSEdit::MoveDown() {
  if (!activeNode.has_value() || activeNode->ChildrenCount() == 0) {
    return false;
  } else  if (activeNode->IsRoot()) {
    activeNode->MoveToChild(1); // Note: Для корневого узла перывым потомком является сама конституента
    return true;
  } else {
    activeNode->MoveToChild(0);
    return true;
  }
}

BOOL RSEdit::MoveRight() {
  return activeNode.has_value() && activeNode->MoveToNextSibling();
}

BOOL RSEdit::MoveLeft() {
  return activeNode.has_value() && activeNode->MoveToPrevSibling();
}

} // namespace xtr::ui