#include "stdafx.h"

#include "xtr/view/RSModelView.h"

#include "resource.h"

#include "xtr/Exteor.h"
#include "xtr/ExteorOptions.h"
#include "xtr/cclDescriptor.h"
#include "xtr/xtrDesign.h"
#include "xtr/xtrProcedures.h"

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

#include "ccl/semantic/CstFilters.hpp"

namespace xtr::view {

using ccl::semantic::CstType;

using doc::RSModelDoc;
using ui::Keyboard;

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

IMPLEMENT_DYNCREATE(RSModelView, CFormView)

BEGIN_MESSAGE_MAP(RSModelView, CFormView)
  ON_WM_CONTEXTMENU()

  ON_COMMAND(IXTRC_XTR_HELP, &ThisClass::OpenHelp)
  ON_BN_CLICKED(IXTRC_MODEL_SEARCH, &ThisClass::OnSearch)
  ON_BN_CLICKED(IXTRC_COMMENT_B, &ThisClass::OnEditComment)
  ON_BN_CLICKED(IXTRC_TRS_UPDATE_STATUS, &ThisClass::OnUpdateStatus)
  ON_BN_CLICKED(IXTRC_MODEL_CALCULATE, &ThisClass::OnCalculateAll)

  ON_COMMAND_EX(ID_EDIT_COPY, &ThisClass::OnCopy)
  ON_COMMAND_EX(ID_EDIT_PASTE, &ThisClass::OnPaste)
  ON_EN_CHANGE(IXTRC_ALIAS, &ThisClass::OnChange)
  ON_EN_CHANGE(IXTRC_TITLE, &ThisClass::OnChange)

  XTR_COMMAND_UPD(IXTRC_MOVE_UP)
  XTR_COMMAND_UPD(IXTRC_MOVE_DOWN)
  XTR_COMMAND_UPD(IXTRC_SELECT_BASIS)
  XTR_COMMAND_UPD(IXTRC_SELECT_DEPENDENT)
  XTR_COMMAND_UPD(IXTRC_DELETE_CST)
  XTR_COMMAND_UPD(IXTRC_RENAME)
  XTR_COMMAND_UPD(IXTRC_TERM_TO_FUNC)
  XTR_COMMAND_UPD(IXTRC_FUNC_TO_TERM)
  XTR_COMMAND_UPD(IXTRC_STRUCTURE_TO_TERMS)
  XTR_COMMAND_UPD(IXTRC_RESET_NAMES)
  XTR_COMMAND_UPD(IXTRC_CALCULATE)
  XTR_COMMAND_UPD(IXTRC_MODEL_CALCULATE)

  ON_COMMAND_EX(IXTRC_INSERT_BASE_SET, &ThisClass::OnInsertCst)
  ON_COMMAND_EX(IXTRC_INSERT_CONST_SET, &ThisClass::OnInsertCst)
  ON_COMMAND_EX(IXTRC_INSERT_RS, &ThisClass::OnInsertCst)
  ON_COMMAND_EX(IXTRC_INSERT_AXIOM, &ThisClass::OnInsertCst)
  ON_COMMAND_EX(IXTRC_INSERT_TERM, &ThisClass::OnInsertCst)
  ON_COMMAND_EX(IXTRC_INSERT_THEOREMA, &ThisClass::OnInsertCst)
  ON_COMMAND_EX(IXTRC_INSERT_FUNCTION, &ThisClass::OnInsertCst)
  ON_COMMAND_EX(IXTRC_INSERT_PREDICATE, &ThisClass::OnInsertCst)
  ON_COMMAND_EX(IXTRC_INSERT_FREE, &ThisClass::OnInsertCst)

  ON_COMMAND_EX(IXTRC_CONVERT_TO_RSFORM, &ThisClass::OnOperation)
  ON_COMMAND_EX(IXTRC_EXPORT_SCHEMA, &ThisClass::OnOperation)
  ON_COMMAND_EX(IXTRC_EXPORT_GRAPH, &ThisClass::OnOperation)
  ON_COMMAND_EX(IXTRC_EXPORT_MODEL, &ThisClass::OnOperation)
END_MESSAGE_MAP()

#pragma warning( pop )

RSModelView::RSModelView()
  : CFormView(IXTRD_VIEW_RSMODEL) {}

void RSModelView::OnInitialUpdate() {
  editor = doc::RSFacade{ dynamic_cast<doc::RSModelDoc&>(*m_pDocument) };
  model = &editor.EditModel();

  CFormView::OnInitialUpdate();

  InitControls();
  InitTooltips();
}

void RSModelView::InitControls() {
  titleCtrl.InitRE(Keyboard::Layout::RUS);
  aliasCtrl.InitRE(Keyboard::Layout::RUS);
  searchCtrl.InitRE(Keyboard::Layout::RUS);

  titleCtrl.SetFont(&XTROptions::App().fontMath);
  aliasCtrl.SetFont(&XTROptions::App().fontMath);
  cstTable.SetFont(&XTROptions::App().fontMath);
  searchCtrl.SetFont(&XTROptions::App().fontMath);
  cstTable.Init(editor);

  rsformStatusCtrl.SetFont(&XTROptions::App().fontMath);
  rsModelStatusCtrl.SetFont(&XTROptions::App().fontMath);
}

void RSModelView::InitTooltips() {
  if (!tooltips.Create(this)) {
    TRACE(L"Unable to create the ToolTip!");
  } else {
    tooltips.SetMaxTipWidth(ui::s_TooltipWidth);
    tooltips.SetFont(&XTROptions::App().fontTooltip);
    tooltips.Activate(TRUE);

    tooltips.AddTool(&rsformStatusCtrl, CString{});
    tooltips.AddTool(&rsModelStatusCtrl, CString{});
  }
}

void RSModelView::DoDataExchange(CDataExchange* pDX) {
  CFormView::DoDataExchange(pDX);

  ::DDX_Control(pDX, IXTRC_TITLE, titleCtrl);
  ::DDX_Control(pDX, IXTRC_ALIAS, aliasCtrl);
  ::DDX_Control(pDX, IXTRC_RSFORM_LIST, cstTable);
  ::DDX_Control(pDX, IXTRC_RSFORM_STATUS, rsformStatusCtrl);
  ::DDX_Control(pDX, IXTRC_MODEL_STATUS, rsModelStatusCtrl);
  ::DDX_Control(pDX, IXTRC_RSMODEL_SEARCH_TEXT, searchCtrl);

  if (!pDX->m_bSaveAndValidate) {
    title = mfc::ToMFC(model->title);
    alias = mfc::ToMFC(model->alias);
    if (cstTable.IsInit()) {
      cstTable.Update(TRUE);
    }
  }

  ::DDX_Text(pDX, IXTRC_ALIAS, alias);
  ::DDX_Text(pDX, IXTRC_TITLE, title);

  if (pDX->m_bSaveAndValidate) {
    model->title = mfc::ToSTL(title);
    model->alias = mfc::ToSTL(alias);
    editor.Document().SetSelection(cstTable.Selection());
    editor.Document().UpdateAllViews(this);
  }
  UpdateStatusBar();
}

BOOL RSModelView::PreTranslateMessage(MSG* pMsg) {
  if (tooltips.m_hWnd != nullptr) {
    tooltips.RelayEvent(pMsg);
  }
  if (pMsg->message == WM_KEYDOWN) {
    if (pMsg->hwnd == cstTable.GetSafeHwnd() && pMsg->wParam == VK_DELETE) {
      OnCommandRun(IXTRC_DELETE_CST);
      return TRUE;
    }
    if (pMsg->hwnd == searchCtrl.GetSafeHwnd() && pMsg->wParam == VK_RETURN) {
      OnSearch();
      return TRUE;
    }
  }
  return CFormView::PreTranslateMessage(pMsg);
}

void RSModelView::OnSearch() {
  if (const auto searchStr = mfc::ToSTL(searchCtrl.Text()); !std::empty(searchStr)) {
    if (const auto foundItems = ccl::semantic::FilterCst(model->Core(), searchStr); !std::empty(foundItems)) {
      cstTable.SelectMultiple({ std::begin(foundItems), std::end(foundItems) });
      cstTable.SetFocus();
    } else {
      ::AfxMessageBox(IXTRS_SEARCH_NO_MATCH, MB_ICONINFORMATION);
    }
  }
}

void RSModelView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) {
  CView::OnUpdate(pSender, lHint, pHint);
  if (pSender != this) {
    UpdateData(FALSE);
  }
}

BOOL RSModelView::OnCmdMsg(uint32_t nID, int32_t nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) {
  if (GetFocus() == &cstTable &&
      cstTable.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) {
    return TRUE;
  } else {
    return CFormView::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
  }
}

void RSModelView::OnChange() {
  UpdateData(TRUE);
}

BOOL RSModelView::OnCopy(uint32_t /*msgID*/) { // NOLINT(readability-convert-member-functions-to-static)
  if (auto* active = dynamic_cast<ui::RSEdit*>(GetFocus()); active != nullptr) {
    active->Copy();
  }
  return TRUE;
}

BOOL RSModelView::OnPaste(uint32_t /*msgID*/) { // NOLINT(readability-convert-member-functions-to-static)
  if (auto* active = dynamic_cast<ui::RSEdit*>(GetFocus()); active != nullptr) {
    active->Paste();
  }
  return TRUE;
}

void RSModelView::OpenHelp() { // NOLINT(readability-convert-member-functions-to-static)
  ui::HelpHandler::RunHelp(ui::HelpID::viewRSModel);
}

void RSModelView::OnUpdateCommandUI(CCmdUI *pCmdUI) {
  pCmdUI->Enable(IsCommandActive(pCmdUI->m_nID));
}

BOOL RSModelView::IsCommandActive(uint32_t command) const noexcept {
  const auto selSize = ssize(Selection());
  switch (command) {
  case IXTRC_MODEL_CALCULATE:
  case IXTRC_RESET_NAMES:
  default: return TRUE;

  case IXTRC_MOVE_UP:
  case IXTRC_MOVE_DOWN:
  case IXTRC_SELECT_BASIS:
  case IXTRC_SELECT_DEPENDENT: return cstTable.IsCommandActive(command);

  case IXTRC_DELETE_CST:
  case IXTRC_CALCULATE: return selSize != 0;

  case IXTRC_TERM_TO_FUNC:
  case IXTRC_FUNC_TO_TERM:
  case IXTRC_STRUCTURE_TO_TERMS:
  case IXTRC_RENAME: return selSize == 1;
  }
}

BOOL RSModelView::OnCommandRun(uint32_t command) {
  if (!IsCommandActive(command)) {
    return TRUE;
  } 

  UpdateData();
  switch (command) {
  case IXTRC_MOVE_UP:
  case IXTRC_MOVE_DOWN:
  case IXTRC_SELECT_BASIS:
  case IXTRC_SELECT_DEPENDENT:
    return cstTable.RunCommand(command);

  case IXTRC_RESET_NAMES: proc::ResetNames(editor); break;
  case IXTRC_CALCULATE: proc::CalculateSelected(dynamic_cast<doc::RSModelDoc&>(*m_pDocument)); break;
  case IXTRC_MODEL_CALCULATE: proc::CalculateAll(dynamic_cast<doc::RSModelDoc&>(*m_pDocument)); break;

  case IXTRC_RENAME: proc::RenameCst(editor); break;
  case IXTRC_DELETE_CST: proc::EraseSelected(dynamic_cast<doc::RSModelDoc&>(*m_pDocument)); break;

  case IXTRC_TERM_TO_FUNC: proc::Term2Function(editor); break;
  case IXTRC_FUNC_TO_TERM: proc::Function2Term(editor); break;
  case IXTRC_STRUCTURE_TO_TERMS: proc::Structure2Terms(editor); break;
  }
  return TRUE;
}

BOOL RSModelView::OnInsertCst(uint32_t command) {
  const auto nWhere = cstTable.GetNextItem(mfc::noItems, LVNI_FOCUSED);
  switch(command) {
  default: return TRUE;
  case IXTRC_INSERT_BASE_SET: InsertNew(CstType::base); break;
  case IXTRC_INSERT_CONST_SET: InsertNew(CstType::constant); break;
  case IXTRC_INSERT_RS: InsertNew(CstType::structured, nWhere);  break;
  case IXTRC_INSERT_AXIOM: InsertNew(CstType::axiom, nWhere); break;
  case IXTRC_INSERT_TERM: InsertNew(CstType::term, nWhere);  break;
  case IXTRC_INSERT_THEOREMA: InsertNew(CstType::theorem, nWhere); break;
  case IXTRC_INSERT_FUNCTION: InsertNew(CstType::function, nWhere); break;
  case IXTRC_INSERT_PREDICATE: InsertNew(CstType::predicate, nWhere); break;
  case IXTRC_INSERT_FREE: InsertFree();  break;
  }
  return TRUE;
}

void RSModelView::InsertNew(const CstType cstType, int32_t nWhere) {
  UpdateData();
  const auto idWhere = cstTable.UIDForIndex(nWhere);
  const auto newUID = model->Emplace(cstType);
  if (idWhere.has_value()) {
    model->MoveBefore(newUID, std::next(model->List().Find(idWhere.value())));
  }
  editor.Document().ResetSelection();
  editor.Document().Select(newUID);
  editor.Document().UpdateAllViews(nullptr);
}

void RSModelView::InsertFree() {
  if (std::empty(model->Core())) {
    InsertNew(CstType::base);
  } else if (const auto nItem = cstTable.GetNextItem(mfc::noItems, LVNI_FOCUSED); 
             nItem != mfc::noItems) {
    const auto& cst = model->GetRS(cstTable.UIDForIndex(nItem).value());
    InsertNew(cst.type, nItem);
  }
}

void RSModelView::OnUpdateStatus() {
  BeginWaitCursor();

  model->UpdateState();
  UpdateData(FALSE);
  const auto message = info::DescriptionOf(model->Core());

  EndWaitCursor();

  RedrawWindow();
  ::AfxMessageBox(message, MB_ICONINFORMATION);
}

void RSModelView::OnCalculateAll() {
  OnCommandRun(IXTRC_MODEL_CALCULATE);
}

void RSModelView::OnEditComment() {
  proc::EditComment(editor);
}

void RSModelView::OnContextMenu(CWnd* pWnd, const CPoint point) {
  static constexpr auto separator = 0;
  if (pWnd == &cstTable) {
    CMenu popup{};
    popup.LoadMenuW(IXTRAD_RSVIEW_POPUP);

    auto* item0 = popup.GetSubMenu(0);
    for (auto i = 0; i < item0->GetMenuItemCount(); ++i) {
      if (const auto command = item0->GetMenuItemID(i); command != separator) {
        if (!IsContextActive(command)) {
          item0->EnableMenuItem(command, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
        } else {
          item0->EnableMenuItem(command, MF_BYCOMMAND | MF_ENABLED);
        }
      }
    }
    item0->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
  }
}

BOOL RSModelView::OnOperation(uint32_t command) {
  switch(command) {
    case IXTRC_EXPORT_SCHEMA: proc::ExportModelSchema(editor.Model(), m_pDocument->GetPathName()); break;
    case IXTRC_EXPORT_GRAPH: proc::ExportCstGraph(editor.Core(), m_pDocument->GetPathName()); break;
    case IXTRC_EXPORT_MODEL: proc::ExportModel(dynamic_cast<const doc::RSModelDoc&>(*m_pDocument)); break;
    case IXTRC_CONVERT_TO_RSFORM:  proc::SpawnSchemaDocument(*model); break;
  }
  return TRUE;
}

const SetOfEntities& RSModelView::Selection() const noexcept {
  return cstTable.Selection();
}

BOOL RSModelView::IsContextActive(uint32_t command) const {
  const auto& selected = Selection();
  if (ssize(selected) != 1 &&
    (command == IXTRC_TERM_TO_FUNC 
     || command == IXTRC_FUNC_TO_TERM 
     || command == IXTRC_STRUCTURE_TO_TERMS)) {
    return FALSE;
  }
  switch (command) {
  default: return IsCommandActive(command);
  case ID_EDIT_PASTE: return !std::empty(g_Exteor.cstClipboard);
  case IXTRC_TERM_TO_FUNC: {
    const auto selType = model->GetRS(*std::begin(selected)).type;
    return selType == CstType::term;
  }
  case IXTRC_FUNC_TO_TERM: {
    const auto selType = model->GetRS(*std::begin(selected)).type;
    return ccl::semantic::IsCallable(selType);
  }
  case IXTRC_STRUCTURE_TO_TERMS: {
    const auto selType = model->GetRS(*std::begin(selected)).type;
    return ccl::semantic::IsRSObject(selType) && !ccl::semantic::IsBaseSet(selType);
  }
  }
}

void RSModelView::UpdateStatusBar() {
  rsformStatusCtrl.SetBackgroundColor(FALSE, design::Color(info::StatusOf(model->Core())));
  rsformStatusCtrl.SetWindowTextW(info::IndicatorFor(model->Core()));

  rsModelStatusCtrl.SetBackgroundColor(FALSE, design::Color(info::StatusOf(*model)));
  rsModelStatusCtrl.SetWindowTextW(info::IndicatorFor(*model));
}

} // namespace xtr::view