#include "stdafx.h"

#include "xtr/dialog/Function2Term.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/rslang/RSGenerator.h"
#include "ccl/semantic/CstFilters.hpp"

namespace xtr::dialog {

using ui::Keyboard;
using ccl::semantic::CstType;
using ccl::rslang::Generator;

struct Func2Term::ArgsHeader : ui::Header {
  enum Columns : uint8_t {
    POS_NAME = 0,
    POS_VAL,
    POS_TYPE,
    count
  };

  static constexpr auto widthName = 50;
  static constexpr auto widthTypification = 190;

  ArgsHeader() {
    AddColumn({ IXTRS_HEADER_ARGUMENT, Alignment::left, widthName });
    AddColumn({ IXTRS_HEADER_VALUE, Alignment::left, widthName });
    AddColumn({ IXTRS_HEADER_TYPE, Alignment::left, widthTypification });
  }
};

struct Func2Term::ValuesHeader : ui::Header {
  enum Columns : uint8_t {
    POS_VAL = 0,
    POS_TERM,
    count
  };

  static constexpr auto widthName = 50;
  static constexpr auto widthTerm = 290;

  ValuesHeader() {
    AddColumn({ IXTRS_HEADER_VALUE, Alignment::left, widthName });
    AddColumn({ IXTRS_HEADER_TERM, Alignment::left, widthTerm });
  }
};

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

BEGIN_MESSAGE_MAP(Func2Term, CDialog)
  ON_CBN_SELCHANGE(IXTRC_FUNC_CB, &ThisClass::OnChangeFunction)
  ON_BN_CLICKED(IXTRC_UPDATE_EXP, &ThisClass::OnBnClickedUpdateExp)
  ON_BN_CLICKED(IXTRC_LEFT, &ThisClass::OnBnClickedLeft)
  ON_BN_CLICKED(IXTRC_APPLY, &ThisClass::OnBnClickedApply)

  ON_NOTIFY(NM_DBLCLK, IXTRC_ARG_LIST, &ThisClass::OnDblClickArgument)
  ON_NOTIFY(LVN_ITEMCHANGED, IXTRC_GLOBAL_LIST, &ThisClass::OnSelectArg)
END_MESSAGE_MAP()

#pragma warning( pop )

Func2Term::Func2Term(doc::RSFacade& editor, const EntityUID startID, CWnd* pParent)
  : CDialog(IXTRD_GENERATE_TERM, pParent), editor{ editor }, activeUID{ startID } {
  doInline = XTROptions::RS().inlineFunctionOnGenerate;
  if (const auto& cst = editor.GetRS(activeUID); ccl::semantic::IsCallable(cst.type)) {
    activeName = mfc::ToMFC(cst.alias);
    inputExpr = mfc::ToMFC(cst.definition);
  }
}

Func2Term::~Func2Term() {
  XTROptions::RS().inlineFunctionOnGenerate = doInline;
}

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

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

  UpdateData(FALSE);
  return TRUE;
}

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

void Func2Term::InitCstNameList() {
  const auto selection = namesCB.GetCurSel();
  namesCB.ResetContent();
  const auto callables = ccl::semantic::FilterCst(editor.Core(),
    [&editor = this->editor](const auto uid) { return ccl::semantic::IsCallable(editor.GetRS(uid).type); });
  for (const auto uid : callables) {
    namesCB.AddString(mfc::ToMFC(editor.GetRS(uid).alias));
  }
  namesCB.SetCurSel(selection);
}

void Func2Term::InitHeaders() {
  static const ArgsHeader globalsHead{};
  static const ValuesHeader argsHead{};

  ui::InitHeaderFor(argsCtrl, globalsHead);
  ui::InitHeaderFor(valuesCtrl, argsHead);
}

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

  ::DDX_Control(pDX, IXTRC_FUNC_CB, namesCB);
  ::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, argsCtrl);
  ::DDX_Control(pDX, IXTRC_ARG_LIST, valuesCtrl);

  ::DDX_Text(pDX, IXTRC_EXPR_INPUT, inputExpr);
  ::DDX_Text(pDX, IXTRC_EXPR_OUTPUT, expression);
  ::DDX_Text(pDX, IXTRC_INTER, textRef);
  ::DDX_CBString(pDX, IXTRC_FUNC_CB, activeName);
  ::DDX_Check(pDX, IXTRC_FULL_EXPR, doInline);

  if (pDX->m_bSaveAndValidate) {
    activeUID = editor.Core().FindAlias(mfc::ToSTL(activeName)).value();
  } else {
    UpdateUI();
  }
}

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

void Func2Term::OnChangeFunction() {
  UpdateData(TRUE);
  inputExpr = mfc::ToMFC(editor.GetRS(activeUID).definition);
  UpdateData(FALSE);
}

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

void Func2Term::OnSelectArg(NMHDR* pNMHDR, LRESULT* pResult) {
  const auto* pNMListView = reinterpret_cast<const NM_LISTVIEW*>(pNMHDR);
  if ((pNMListView->uChanged & LVIF_STATE) && (pNMListView->uNewState & LVNI_SELECTED)) {
    UpdateValueList();
  }
  *pResult = TRUE;
}

void Func2Term::OnBnClickedUpdateExp() {
  UpdateData(TRUE);

  std::vector<std::string> args{};
  for (auto i = 0; i < argsCtrl.GetItemCount(); ++i) {
    if (const auto name = argsCtrl.GetItemText(i, ArgsHeader::POS_VAL); !name.IsEmpty()) {
      args.emplace_back(mfc::ToSTL(name));
    } else {
      ::AfxMessageBox(IXTRE_NO_VAL, MB_ICONEXCLAMATION);
      return;
    }
  }
  
  if (doInline) {
    const Generator generator{ editor.Core().RSLang() };
    expression = mfc::ToMFC(generator.TermFromFunction(mfc::ToSTL(activeName),
                                                        editor.GetRS(activeUID).definition,
                                                        args));
  } else {
    expression = mfc::ToMFC(Generator::CreateCall(mfc::ToSTL(activeName), args));
  }
  expressionCtrl.SetWindowTextW(expression);
}

void Func2Term::OnOK() {
  if (AddTerm().has_value()) {
    CDialog::OnOK();
  }
}

void Func2Term::OnBnClickedApply() {
  if (const auto uid = AddTerm(); uid.has_value()) {
    ::AfxMessageBox(mfc::FormatSID(IXTRS_TERM_CREATE_SUCCESS, 
                                   mfc::ToMFC(editor.GetRS(uid.value()).alias).GetString()),  MB_OK | MB_ICONINFORMATION);
  }
}

void Func2Term::OnBnClickedLeft() {
  const auto destLine = argsCtrl.GetSelectedIndex();
  const auto sourceLine = valuesCtrl.GetSelectedIndex();
  if (destLine != mfc::noItems && sourceLine != mfc::noItems) {
    auto name = valuesCtrl.GetItemText(sourceLine, ValuesHeader::POS_VAL);
    argsCtrl.SetItemText(destLine, ArgsHeader::POS_VAL, name);
  }
}

void Func2Term::UpdateValueList() {
  valuesCtrl.DeleteAllItems();
  const auto selected = argsCtrl.GetSelectedIndex();
  if (selected == mfc::noItems) {
    return;
  }
  const auto curArg = argsCtrl.GetItemText(selected, ArgsHeader::POS_NAME);
  const auto& args = editor.GetParse(activeUID).arguments;
  if (args.has_value()) {
    const auto argName = mfc::ToSTL(curArg);
    for (const auto& typeID : args.value()) {
      if (typeID.name == argName) {
        const auto cstList = ccl::semantic::FilterCst(editor.Core(), ccl::rslang::ExpressionType{ typeID.type });
        if (std::empty(cstList)) {
          return;
        }
        auto line = 0;
        for (const auto uid : cstList) {
          if (const auto cst = editor.GetRS(uid); !ccl::semantic::IsCallable(cst.type)) {
            valuesCtrl.InsertItem(line, mfc::ToMFC(cst.alias));
            valuesCtrl.SetItemText(line, 1, mfc::ToMFC(editor.GetText(uid).term.Nominal()));
            ++line;
          }
        }
        break;
      }
    }
  }
  const auto curVal = argsCtrl.GetItemText(selected, ArgsHeader::POS_VAL);
  if (!curVal.IsEmpty()) {
    LVFINDINFO info{};
    info.flags = LVFI_STRING;
    info.psz = curVal;

    if (const auto findItem = valuesCtrl.FindItem(&info); findItem != mfc::noItems) {
      valuesCtrl.SetItemState(mfc::noItems, 0, LVIS_SELECTED | LVIS_FOCUSED);
      valuesCtrl.SetItemState(findItem, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
      valuesCtrl.EnsureVisible(findItem, FALSE);
    }
  }
}

std::optional<EntityUID> Func2Term::AddTerm() {
  UpdateData(TRUE);

  if (expression.IsEmpty()) {
    ::AfxMessageBox(IXTRE_FUNC2TERM, MB_OK | MB_ICONEXCLAMATION);
    expressionCtrl.SetActiveWindow();
    return std::nullopt;
  } else {
    const auto convention = mfc::ToSTL(mfc::FormatSID(IXTRS_TERM_FROM_TERMFUNC, activeName.GetString()));
    const auto uid = editor.Emplace(CstType::term, mfc::ToSTL(expressionCtrl.Text()));
    editor.SetDefinitionFor(uid, mfc::ToSTL(definitionCtrl.Text()));
    editor.SetConventionFor(uid, convention);
    addedCst.emplace_back(uid);

    expression.Empty();
    textRef.Empty();
    UpdateData(FALSE);

    return uid;
  }
}

void Func2Term::UpdateUI() {
  inputCtrl.Init(editor.Core(), activeUID);
  Clear();
  InputArgs();
  inputCtrl.UpdateFormat();
}

void Func2Term::Clear() {
  argsCtrl.DeleteAllItems();
  valuesCtrl.DeleteAllItems();
}

void Func2Term::InputArgs() {
  const auto* args = editor.Core().RSLang().FunctionArgsFor(mfc::ToSTL(activeName));
  if (args == nullptr) {
    return;
  }
  for (auto i = 0; i < ssize(*args); ++i) {
    const auto name = mfc::ToMFC(args->at(i).name);
    const auto type = mfc::ToMFC(args->at(i).type.ToString());
    argsCtrl.InsertItem(i, name);
    argsCtrl.SetItemText(i, ArgsHeader::POS_TYPE, type);
  }
}

} // namespace xtr::dialog