#include "stdafx.h"

#include "xtr/dialog/Term2Function.h"

#include "resource.h"

#include "xtr/ExteorOptions.h"
#include "xtr/cclDescriptor.h"

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

#include "ccl/semantic/CstFilters.hpp"
#include "ccl/rslang/RSGenerator.h"
#include "ccl/rslang/RSExpr.h"

namespace xtr::dialog {

using ccl::semantic::CstType;

struct Term2Function::Header : xtr::ui::Header {
  enum Columns : uint8_t {
    POS_NAME = 0,
    POS_TYPE,
    COLOUMN_COUNT
  };

  static constexpr auto widthName = 60;
  static constexpr auto widthTypification = 250;

  Header() {
    AddColumn({ IXTRS_HEADER_NAME, Alignment::left, widthName });
    AddColumn({ IXTRS_HEADER_TYPE, Alignment::left, widthTypification });
  }
};

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

BEGIN_MESSAGE_MAP(Term2Function, CDialog)
  ON_CBN_SELCHANGE(IXTRC_TERM_CB, &ThisClass::OnSelchangeTermCb)
  ON_BN_CLICKED(IXTRC_UPDATE_EXP, &ThisClass::OnBnClickedUpdateExp)
  ON_BN_CLICKED(IXTRC_RIGHT, &ThisClass::OnBnClickedRight)
  ON_BN_CLICKED(IXTRC_LEFT, &ThisClass::OnBnClickedLeft)
  ON_BN_CLICKED(IXTRC_ADDFUNC, &ThisClass::OnBnClickedApply)

  ON_NOTIFY(NM_DBLCLK, IXTRC_GLOBAL_LIST, &ThisClass::OnDblClickGlobal)
  ON_NOTIFY(NM_DBLCLK, IXTRC_ARG_LIST, &ThisClass::OnDblClickArg)
END_MESSAGE_MAP()

#pragma warning( pop )

Term2Function::Term2Function(doc::RSFacade& editor, const EntityUID startID, CWnd* pParent)
  : CDialog(IXTRD_GENERATE_FUNC, pParent), editor{ editor } {
  if (const auto& cst = editor.GetRS(startID); cst.type == CstType::term) {
    cstName = mfc::ToMFC(cst.alias);
    inputExpr = mfc::ToMFC(cst.definition);
  }
}

BOOL Term2Function::OnInitDialog() {
  CDialog::OnInitDialog();

  InitControls();
  InitHeaders();
  InitCstNameList();

  UpdateData(FALSE);
  return TRUE;
}

void Term2Function::InitControls() {
  inputCtrl.InitRE(ui::Keyboard::Layout::MATH, TRUE, TRUE);
  expressionCtrl.InitRE(ui::Keyboard::Layout::MATH, TRUE, TRUE);
  definitionCtrl.InitRE(ui::Keyboard::Layout::RUS);
  definitionCtrl.SetFont(&XTROptions::App().fontText);
}

void Term2Function::InitCstNameList() {
  const auto selection = cstPicker.GetCurSel();
  cstPicker.ResetContent();
  for (const auto uid : ccl::semantic::FilterCst(editor.Core(), CstType::term)) {
    cstPicker.AddString(mfc::ToMFC(editor.GetRS(uid).alias));
  }
  cstPicker.SetCurSel(selection);
}

void Term2Function::InitHeaders() {
  static const Header listHeader{};
  ui::InitHeaderFor(globalsCtrl, listHeader);
  ui::InitHeaderFor(argsCtrl, listHeader);
}

void Term2Function::DoDataExchange(CDataExchange* pDX) {
  CDialog::DoDataExchange(pDX);

  ::DDX_Control(pDX, IXTRC_TERM_CB, cstPicker);
  ::DDX_Control(pDX, IXTRC_EXPR_INPUT, inputCtrl);
  ::DDX_Control(pDX, IXTRC_EXPR_OUTPUT, expressionCtrl);
  ::DDX_Control(pDX, IXTRC_INTER, definitionCtrl);
  ::DDX_Control(pDX, IXTRC_GLOBAL_LIST, globalsCtrl);
  ::DDX_Control(pDX, IXTRC_ARG_LIST, argsCtrl);

  ::DDX_Text(pDX, IXTRC_EXPR_INPUT, inputExpr);
  ::DDX_Text(pDX, IXTRC_EXPR_OUTPUT, expression);
  ::DDX_Text(pDX, IXTRC_INTER, textRef);
  ::DDX_CBString(pDX, IXTRC_TERM_CB, cstName);

  if (!pDX->m_bSaveAndValidate) {
    UpdateUI();
  }
}

BOOL Term2Function::PreTranslateMessage(MSG* pMsg) {
  if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_F1) {
    ui::HelpHandler::RunHelp(ui::HelpID::generateCst);
    return TRUE;
  }
  return CDialog::PreTranslateMessage(pMsg);
}

void Term2Function::OnSelchangeTermCb() {
  UpdateData(TRUE);
  const auto uid = editor.Core().FindAlias(mfc::ToSTL(cstName)).value();
  inputExpr = mfc::ToMFC(editor.GetRS(uid).definition);
  UpdateData(FALSE);
}

void Term2Function::OnBnClickedUpdateExp() {
  using ccl::rslang::Generator;

  UpdateData(TRUE);
  if (argsCtrl.GetItemCount() == 0) {
    ::AfxMessageBox(IXTRE_NO_ARGS, MB_ICONEXCLAMATION);
    return;
  }
  std::vector<std::string> args{};
  args.reserve(argsCtrl.GetItemCount());
  for (auto i = 0; i < argsCtrl.GetItemCount(); ++i) {
    args.emplace_back(mfc::ToSTL(argsCtrl.GetItemText(i, Header::POS_NAME)));
  }
  expression = mfc::ToMFC(Generator(editor.Core().RSLang()).FunctionFromExpr(args, mfc::ToSTL(inputExpr)));
  expressionCtrl.SetWindowTextW(expression);
}

void Term2Function::OnOK() {
  if (AddFunction().has_value()) {
    CDialog::OnOK();
  }
}

void Term2Function::OnBnClickedApply() {
  if (const auto uid = AddFunction(); uid.has_value()) {
    ::AfxMessageBox(mfc::FormatSID(IXTRS_TERMFUNC_SUCCESS, editor.GetRS(uid.value()).alias.c_str()), MB_OK | MB_ICONINFORMATION);
  }
}

void Term2Function::OnBnClickedRight() {
  globalsCtrl.MoveSelectedItemsTo(argsCtrl, Header::COLOUMN_COUNT);
}

void Term2Function::OnBnClickedLeft() {
  argsCtrl.MoveSelectedItemsTo(globalsCtrl, Header::COLOUMN_COUNT);
}

void Term2Function::OnDblClickGlobal(NMHDR* /*pNMHDR*/, LRESULT* pResult) {
  OnBnClickedRight();
  *pResult = TRUE;
}

void Term2Function::OnDblClickArg(NMHDR* /*pNMHDR*/, LRESULT* pResult) {
  OnBnClickedLeft();
  *pResult = TRUE;
}

std::optional<EntityUID> Term2Function::AddFunction() {
  UpdateData(TRUE);
  if (expression.IsEmpty())  {
    ::AfxMessageBox(IXTRE_FUNC2TERM, MB_OK | MB_ICONEXCLAMATION);
    expressionCtrl.SetActiveWindow();
    return std::nullopt;
  }

  const auto convention = mfc::ToSTL(mfc::FormatSID(IXTRS_TERMFUNC_FROM_TERM, cstName.GetString()));
  const auto uid = editor.Emplace(CstType::function, mfc::ToSTL(expressionCtrl.Text()));
  editor.SetDefinitionFor(uid, mfc::ToSTL(definitionCtrl.Text()));
  editor.SetConventionFor(uid, convention);
  addedCst.emplace_back(uid);

  textRef.Empty();
  expression.Empty();
  argsCtrl.DeleteAllItems();

  UpdateData(FALSE);
  return uid;
}

void Term2Function::UpdateUI() {
  InputGlobals();
  UpdateArgs();
  inputCtrl.UpdateFormat();
}

void Term2Function::InputGlobals() {
  globalsCtrl.DeleteAllItems();
  const auto cstFromExpr = ccl::rslang::ExtractUGlobals(mfc::ToSTL(inputExpr));
  SetOfEntities entities{};
  entities.reserve(size(cstFromExpr));
  for (const auto& name : cstFromExpr) {
    if (auto uid = editor.Core().FindAlias(name); uid.has_value()) {
      entities.emplace(uid.value());
    }
  }
  const auto sortedCst = editor.List().SortSubset(entities);
  auto line = 0;
  for (const auto uid : sortedCst) {
    const auto& cst = editor.GetRS(uid);
    if (const auto type = cst.type; type == CstType::structured || type == CstType::term) {
      auto typification = mfc::ToSTL(info::Label(editor.GetParse(uid).TypePtr()));
      globalsCtrl.InsertItem(line, mfc::ToMFC(cst.alias));
      globalsCtrl.SetItemText(line, Header::POS_TYPE, mfc::ToMFC(typification));
      ++line;
    }
  }
}

void Term2Function::UpdateArgs() {
  LVFINDINFOW info{};
  info.flags = LVFI_STRING;
  for (auto i = 0; i < argsCtrl.GetItemCount(); ++i) {
    auto name = argsCtrl.GetItemText(i, 0);
    info.psz = name;

    if (const auto findItem = globalsCtrl.FindItem(&info); findItem == mfc::noItems) {
      argsCtrl.DeleteItem(i);
      i = i == 0 ? i : i - 1;
    } else {
      globalsCtrl.DeleteItem(findItem);
    }
  }
}

} // namespace xtr::dialog