#include "stdafx.h"

#include "xtr/dialog/OpSyntGUI.h"

#include "resource.h"

#include "xtr/Exteor.h"
#include "xtr/cclDescriptor.h"
#include "xtr/doc/RSFormDoc.h"

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

#include "xtr/dialog/BasicDlg.h"

namespace xtr::dialog {

using ccl::ops::Equation;
using ccl::semantic::CstType;

SynthesUI::SynthesUI(const ccl::ops::Type type, const BOOL fromOSS, CWnd* pWndParent)
  : CPropertySheet(IXTRS_PROPSHT_CAPTION, pWndParent), ossModeOn{ fromOSS }, opType{ type } {
  operandsPage = std::make_unique<OperandsPage>(*this);
  optionsPage = std::make_unique<SynthesUI::OptionsPage>(*this);
  confirmationPage = std::make_unique<SynthesUI::ConfirmPage>(*this);

  if (!fromOSS) {
    AddPage(operandsPage.get());
  }
  if (opType == ccl::ops::Type::rsSynt) {
    AddPage(optionsPage.get());
  }
  AddPage(confirmationPage.get());
  SetWizardMode();
}

BOOL SynthesUI::IsCorrect() const {
  if (operand1 == nullptr || operand2 == nullptr) {
    return FALSE;
  } else {
    return ccl::ops::BinarySynthes(*operand1, *operand2, equations).IsCorrectlyDefined();
  }
}

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

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

BEGIN_MESSAGE_MAP(SynthesUI::OperandsPage, CPropertyPage)
  ON_NOTIFY(LVN_ITEMCHANGING, IXTRC_SYNT_OP1, &ThisClass::OnChangeOperands)
  ON_NOTIFY(LVN_ITEMCHANGING, IXTRC_SYNT_OP2, &ThisClass::OnChangeOperands)
END_MESSAGE_MAP()

#pragma warning( pop )

struct SynthesUI::OperandsPage::OperandsHeader
  : ui::Header {
  enum Columns : uint8_t {
    POS_FULLNAME = 0,
    POS_SHORTNAME,
    POS_TITLE,
    POS_PATH,
    count
  };

  static constexpr auto widthFull = 290;
  static constexpr auto widthShort = 80;
  static constexpr auto widthFile = 100;
  static constexpr auto widthPath = 300;

  OperandsHeader() {
    AddColumn({ IXTRS_HEADER_RS_FULL, Alignment::left, widthFull });
    AddColumn({ IXTRS_HEADER_SHORTNAME, Alignment::left, widthShort });
    AddColumn({ IXTRS_HEADER_FILENAME, Alignment::left, widthFile });
    AddColumn({ IXTRS_HEADER_PATH, Alignment::left, widthPath });
  }
};

SynthesUI::OperandsPage::OperandsPage(SynthesUI& parent)
  : CPropertyPage(IXTRD_SYNT_OPERANDS), parentSht{ parent } {}

void SynthesUI::OperandsPage::DoDataExchange(CDataExchange* pDX) {
  CPropertyPage::DoDataExchange(pDX);

  ::DDX_Control(pDX, IXTRC_SYNT_OP1, firstOperandList);
  ::DDX_Control(pDX, IXTRC_SYNT_OP2, secondOperandList);

  int32_t operation{};
  if (!pDX->m_bSaveAndValidate) {
    operation = TypeToRadio(parentSht.opType);
  }
  const auto nItem1 = firstOperandList.GetSelectedIndex();
  const auto nItem2 = secondOperandList.GetSelectedIndex();
  if (nItem1 != mfc::noItems && nItem2 != mfc::noItems)  {
    firstOperand = firstOperandList.GetItemText(nItem1, OperandsHeader::Columns::POS_SHORTNAME);
    secondOperand = secondOperandList.GetItemText(nItem2, OperandsHeader::Columns::POS_SHORTNAME);

    DDVNextBtn(nItem1, nItem2);
  }

  ::DDX_Radio(pDX, IXTRC_SYNT_MERGE, operation);
  ::DDX_Text(pDX, IXTRC_SHEMA1_NAME, firstOperand);
  ::DDX_Text(pDX, IXTRC_SHEMA2_NAME, secondOperand);

  if (pDX->m_bSaveAndValidate) {
    parentSht.opType = RadioToType(operation);
  }
}

BOOL SynthesUI::OperandsPage::OnInitDialog() {
  CPropertyPage::OnInitDialog();

  InitControls();
  InitHeaders();
  InputData();

  UpdateData(FALSE);
  return TRUE;
}

void SynthesUI::OperandsPage::InputData() {
  if (!parentSht.ossModeOn) {
    UpdateDocList(firstOperandList);
    UpdateDocList(secondOperandList);
    secondOperandList.SetSelection(1);
  }
}

void SynthesUI::OperandsPage::InitControls() {
  if (parentSht.ossModeOn) {
    GetDlgItem(IXTRC_SYNT_MERGE)->EnableWindow(FALSE);
    GetDlgItem(IXTRC_SYNT_ABSTRACT)->EnableWindow(FALSE);
  }
}

void SynthesUI::OperandsPage::InitHeaders() {
  static const OperandsHeader header{};
  ui::InitHeaderFor(firstOperandList, header);
  ui::InitHeaderFor(secondOperandList, header);
}

BOOL SynthesUI::OperandsPage::OnSetActive() {
  parentSht.SetWizardButtons(PSWIZB_NEXT);
  UpdateData(FALSE);
  return CPropertyPage::OnSetActive();
}

void SynthesUI::OperandsPage::OnChangeOperands(NMHDR* pNMHDR, LRESULT* pResult) {
  if (firstOperandList.GetItemCount() != 0) {
    const auto* pNMListView = reinterpret_cast<NM_LISTVIEW*>(pNMHDR);
    int32_t nItem1{};
    int32_t nItem2{};
    if (pNMHDR->hwndFrom == firstOperandList.GetSafeHwnd()) {
      nItem1 = pNMListView->iItem;
      nItem2 = secondOperandList.GetFocusIndex();
    } else {
      nItem1 = firstOperandList.GetFocusIndex();
      nItem2 = pNMListView->iItem;
    }
    DDVNextBtn(nItem1, nItem2);
    *pResult = 0;
  }
}

LRESULT SynthesUI::OperandsPage::OnWizardNext() {
  UpdateData(TRUE);
  if (parentSht.ossModeOn) {
    return CPropertyPage::OnWizardNext();
  }
  parentSht.operand1 = &GetTRSDocFromCList(firstOperandList)->Schema();
  parentSht.operand2 = &GetTRSDocFromCList(secondOperandList)->Schema();
  return CPropertyPage::OnWizardNext();
}

void SynthesUI::OperandsPage::DDVNextBtn(int32_t nItem1, int32_t nItem2) {
  firstOperand = firstOperandList.GetItemText(nItem1, 1);
  secondOperand = secondOperandList.GetItemText(nItem2, 1);

  if (nItem1 != nItem2) {
    parentSht.SetWizardButtons(PSWIZB_NEXT);
  } else {
    parentSht.SetWizardButtons(0);
  }
}

doc::RSFormDoc* SynthesUI::OperandsPage::GetTRSDocFromCList(const ui::RSListCtrl& lc) {
  if (const auto nItem = lc.GetFocusIndex(); nItem == mfc::noItems) {
    return nullptr;
  } else {
    const auto path = lc.GetItemText(nItem, OperandsHeader::Columns::POS_PATH);
    return ExteorApplication::Docs().FindRSDocByPath(path);
  }
}

void SynthesUI::OperandsPage::UpdateDocList(ui::RSListCtrl& lc, const doc::RSFormDoc* docExclude) {
  if (!lc.DeleteAllItems()) {
    ::AfxMessageBox(IXTRE_LIST_CLEAR, MB_ICONERROR);
  }
  auto nItem = 0;
  for (const auto& rsDoc : ExteorApplication::Docs().RSFormDocList()) {
    if (rsDoc != docExclude) {
      if (!rsDoc->GetTitle().IsEmpty()) {
#pragma warning( push )
#pragma warning( disable : 6387 )
        lc.InsertItem(LVIF_TEXT | LVIF_STATE, nItem, {}, 0, 0, 0, 0);
#pragma warning( pop )
        lc.SetItemText(nItem, OperandsHeader::Columns::POS_FULLNAME, mfc::ToMFC(rsDoc->Schema().title));
        lc.SetItemText(nItem, OperandsHeader::Columns::POS_SHORTNAME, mfc::ToMFC(rsDoc->Schema().alias));
        lc.SetItemText(nItem, OperandsHeader::Columns::POS_TITLE, rsDoc->GetTitle());
        lc.SetItemText(nItem, OperandsHeader::Columns::POS_PATH, rsDoc->GetPathName());
        ++nItem;
      }
    }
  }
  lc.SetSelection(0);
}

int32_t SynthesUI::OperandsPage::TypeToRadio(ccl::ops::Type _type) noexcept {
  switch(_type)  {
  default: return 0;
  case ccl::ops::Type::rsMerge: return 0;
  case ccl::ops::Type::rsSynt: return 1;
  }
}

ccl::ops::Type SynthesUI::OperandsPage::RadioToType(int32_t radio) noexcept {
  switch(radio)  {
  case 0: return ccl::ops::Type::rsMerge;
  case 1: return ccl::ops::Type::rsSynt;
  }
  return ccl::ops::Type::tba;
}

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

BEGIN_MESSAGE_MAP(SynthesUI::OptionsPage, CPropertyPage)
  ON_BN_CLICKED(IXTRC_IN1, &ThisClass::OnIn1)
  ON_BN_CLICKED(IXTRC_IN2, &ThisClass::OnIn2)
  ON_BN_CLICKED(IXTRC_IN3, &ThisClass::OnIn3)
  ON_NOTIFY(NM_DBLCLK, IXTRC_SYNT_FLAG_CTABLE, &ThisClass::OnDblClickList)

  ON_BN_CLICKED(IXTRC_FLAG_BM, &ThisClass::OnChange)
  ON_BN_CLICKED(IXTRC_FLAG_RS, &ThisClass::OnChange)
  ON_BN_CLICKED(IXTRC_FLAG_TERM, &ThisClass::OnChange)
  ON_BN_CLICKED(IXTRC_FLAG_BM2, &ThisClass::OnChange)
  ON_BN_CLICKED(IXTRC_FLAG_RS2, &ThisClass::OnChange)
  ON_BN_CLICKED(IXTRC_FLAG_TERM2, &ThisClass::OnChange)
END_MESSAGE_MAP()

#pragma warning( pop )

struct SynthesUI::OptionsPage::ArgHeader : ui::Header {
  enum Columns : uint8_t {
    POS_INTERP = 0,
    POS_NAME,
    count
  };

  static constexpr auto widthName = 50;

  explicit ArgHeader(int32_t controlWidth) {
    const auto textWidth = controlWidth - widthName - ui::s_ScrollWidth;
    assert(textWidth > 0);
    AddColumn({ IXTRS_HEADER_NAME, Alignment::left, widthName });
    AddColumn({ IXTRS_HEADER_TERM, Alignment::left, textWidth });
  }
};

SynthesUI::OptionsPage::OptionsPage(SynthesUI& parent)
  : CPropertyPage(IXTRD_SYNT_MAPTABLE), parentSht(parent) {}

BOOL SynthesUI::OptionsPage::OnInitDialog() {
  CPropertyPage::OnInitDialog();

  InitHeaders();
  UpdateData(FALSE);
  return TRUE;
}

void SynthesUI::OptionsPage::InitHeaders() {
  const ArgHeader header1{ ui::WindowRect(operand1List).Width() };
  const ArgHeader header2{ ui::WindowRect(operand2List).Width() };

  ui::InitReversedHeaderFor(operand1List, header1);
  ui::InitHeaderFor(operand2List, header2);

  equationsCtrl.InitHeader();
}

void SynthesUI::OptionsPage::DoDataExchange(CDataExchange* pDX) {
  CPropertyPage::DoDataExchange(pDX);

  ::DDX_Control(pDX, IXTRC_IN1, operand1Btn);
  ::DDX_Control(pDX, IXTRC_IN2, operand2Btn);
  ::DDX_Control(pDX, IXTRC_IN3, equalsBtn);
  ::DDX_Control(pDX, IXTRC_SYNT_FLAG_CTABLE, equationsCtrl.control);
  ::DDX_Control(pDX, IXTRC_BM_OP2, operand2List);
  ::DDX_Control(pDX, IXTRC_BM_OP1, operand1List);

  ::DDX_Check(pDX, IXTRC_FLAG_BM2, basic2Flag);
  ::DDX_Check(pDX, IXTRC_FLAG_BM, basic1Flag);
  ::DDX_Check(pDX, IXTRC_FLAG_RS, structure1Flag);
  ::DDX_Check(pDX, IXTRC_FLAG_TERM, term1Flag);
  ::DDX_Check(pDX, IXTRC_FLAG_RS2, structure2Flag);
  ::DDX_Check(pDX, IXTRC_FLAG_TERM2, term2Flag);

  if (pDX->m_bSaveAndValidate) {
    UpdateCstLists();
  }
  DDV_Buttons();
}

void SynthesUI::OptionsPage::DDV_Buttons() {
  const auto inReady = operand1List.GetItemCount() != 0 && operand2List.GetItemCount() != 0;
  operand1Btn.EnableWindow(inReady);
  operand2Btn.EnableWindow(inReady);
  equalsBtn.EnableWindow(inReady);

  DWORD backBtn = PSWIZB_BACK;
  if (parentSht.ossModeOn  && parentSht.opType != ccl::ops::Type::rsSynt) {
    backBtn = 0;
  }
  const auto next = parentSht.opType == ccl::ops::Type::rsMerge || equationsCtrl.control.GetItemCount() != 0;
  if (next) {
    parentSht.SetWizardButtons(PSWIZB_NEXT | backBtn);
  } else {
    parentSht.SetWizardButtons(backBtn);
  }
}

void SynthesUI::OptionsPage::OnDblClickList(NMHDR* /*pNMHDR*/, LRESULT* pResult) {
  RemovePair();
  *pResult = 1;
}

void SynthesUI::OptionsPage::OnIn1() {
  AddPair(Equation::Mode::keepDel);
}

void SynthesUI::OptionsPage::OnIn2() {
  AddPair(Equation::Mode::keepHier);
}

void SynthesUI::OptionsPage::OnIn3() {
  AddPair(Equation::Mode::createNew);
}

void SynthesUI::OptionsPage::OnChange() {
  UpdateData();
}

void SynthesUI::OptionsPage::AddPair(const Equation::Mode type) {
  UpdateData(TRUE);

  const auto nSelOp1 = operand1List.GetFocusIndex();
  const auto nSelOp2 = operand2List.GetFocusIndex();
  assert(nSelOp1 != mfc::noItems && nSelOp2 != mfc::noItems);

  const auto name1 = operand1List.GetItemText(nSelOp1, ArgHeader::Columns::POS_NAME);
  auto termin1 = operand1List.GetItemText(nSelOp1, ArgHeader::Columns::POS_INTERP);
  const auto name2 = operand2List.GetItemText(nSelOp2, ArgHeader::Columns::POS_INTERP);
  auto termin2 = operand2List.GetItemText(nSelOp2, ArgHeader::Columns::POS_NAME);

  if (type == Equation::Mode::createNew) {
    termin2 = AskForTerm(termin1 + LR"( ~ )" + termin2);
    termin1 = mfc::LoadSID(IXTRS_OP_NEW_TERM);
  }

  const auto nInsertPos = equationsCtrl.InsertItem({ name1, name2, termin1, termin2, type });

  operand1List.DeleteItem(nSelOp1);
  operand2List.DeleteItem(nSelOp2);
  equationsCtrl.control.SetSelection(nInsertPos);
  operand1List.SetSelection(0);
  operand2List.SetSelection(0);

  DDV_Buttons();
}

BOOL SynthesUI::OptionsPage::OnSetActive() {
  if (!parentSht.ossModeOn || parentSht.opType == ccl::ops::Type::rsSynt) {
    parentSht.SetWizardButtons(PSWIZB_NEXT | PSWIZB_BACK);
  } else {
    parentSht.SetWizardButtons(PSWIZB_NEXT);
  }
  if (equationsCtrl.control.m_hWnd == nullptr) {
    return CPropertyPage::OnSetActive();
  } else {
    equationsCtrl.control.DeleteAllItems();
    equationsCtrl.LoadTableSynth(parentSht.equations, *parentSht.operand1, *parentSht.operand2);

    UpdateCstLists();
    UpdateData(FALSE);
    return CPropertyPage::OnSetActive();
  }
}

LRESULT SynthesUI::OptionsPage::OnWizardNext() {
  static constexpr auto dontChangePage = -1;
  if (auto newEqs = equationsCtrl.CreateEquations(*parentSht.operand1, *parentSht.operand2);
      newEqs.has_value()) {
    if (parentSht.equations = newEqs.value(); parentSht.IsCorrect()) {
      return CPropertyPage::OnWizardNext();
    }
  }
  ::AfxMessageBox(IXTRE_SYNT_INCORRECT, MB_ICONERROR);
  return dontChangePage;
}

void SynthesUI::OptionsPage::RemovePair() {
  if (equationsCtrl.control.GetItemCount() != 0) {
    equationsCtrl.control.DeleteItem(equationsCtrl.control.GetFocusIndex());
    equationsCtrl.control.SetSelection(0);

    UpdateCstLists();
    DDV_Buttons();
  }
}

CString SynthesUI::OptionsPage::AskForTerm(const CString initial) {
  EditString dlg(initial, mfc::LoadSID(IXTRS_CAPTION_TERM));
  if (dlg.DoModal() == IDOK) {
    return dlg.resultStr;
  } else {
    return{};
  }
}

static constexpr auto OPERAND_1 = 1;
static constexpr auto OPERAND_2 = 2;

void SynthesUI::OptionsPage::UpdateCstLists() {
  const auto nSelOp1 = operand1List.GetFocusIndex();
  const auto nSelOp2 = operand2List.GetFocusIndex();

  operand1List.DeleteAllItems();
  operand2List.DeleteAllItems();

  if (parentSht.opType != ccl::ops::Type::rsMerge) {
    auto list1 = equationsCtrl.Inputs(*parentSht.operand1);
    auto list2 = equationsCtrl.Outputs(*parentSht.operand2);

    if (basic2Flag) {
      FillCstOfType(OPERAND_2, CstType::base, list2);
    }
    if (structure2Flag) {
      FillCstOfType(OPERAND_2, CstType::structured, list2);
    }
    if (term2Flag) {
      FillCstOfType(OPERAND_2, CstType::term, list2);
    }
    if (basic1Flag) {
      FillCstOfType(OPERAND_1, CstType::base, list1);
    }
    if (structure1Flag) {
      FillCstOfType(OPERAND_1, CstType::structured, list1);
    }
    if (term1Flag) {
      FillCstOfType(OPERAND_1, CstType::term, list1);
    }
    operand1List.SetSelection(nSelOp1 >= operand1List.GetItemCount() ? 0 : nSelOp1);
    operand2List.SetSelection(nSelOp2 >= operand2List.GetItemCount() ? 0 : nSelOp2);
  }
}

void SynthesUI::OptionsPage::FillCstInCL(ui::RSListCtrl& lc, const ccl::semantic::RSForm& schema,
                                         int32_t nameCol, int32_t termCol, 
                                         const CstType type, const SetOfEntities& ignoreList) {
  for (const auto uid : schema.List()) {
    const auto& rsCst = schema.GetRS(uid);
    if (rsCst.type == type && !ignoreList.contains(uid)) {
#pragma warning( push )
#pragma warning( disable : 6387 )
      const auto nItem = lc.InsertItem(LVIF_TEXT | LVIF_STATE, lc.GetItemCount(), {}, 0, 0, 0, 0);
#pragma warning( pop )
      lc.SetItemText(nItem, nameCol, mfc::ToMFC(rsCst.alias));
      lc.SetItemText(nItem, termCol, mfc::ToMFC(schema.GetText(uid).term.Nominal()));
    }
  }
  lc.SetSelection(0);
}

void SynthesUI::OptionsPage::FillCstOfType(int32_t list1Or2, const CstType type, const SetOfEntities& ignoreList) {
  if (list1Or2 == OPERAND_1) {
    FillCstInCL(operand1List, *parentSht.operand1, ArgHeader::Columns::POS_NAME, ArgHeader::Columns::POS_INTERP, type, ignoreList);
  } else {
    FillCstInCL(operand2List, *parentSht.operand2, ArgHeader::Columns::POS_INTERP, ArgHeader::Columns::POS_NAME, type, ignoreList);
  }
}

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

BEGIN_MESSAGE_MAP(SynthesUI::ConfirmPage, CPropertyPage)
  ON_CBN_SELENDOK(IXTRC_RESULT_TITLE, &ThisClass::OnComboChanged)
  ON_CBN_SELENDOK(IXTRC_RESULT_ALIAS, &ThisClass::OnComboChanged)
  ON_CBN_EDITCHANGE(IXTRC_RESULT_TITLE, &ThisClass::OnChange)
  ON_CBN_EDITCHANGE(IXTRC_RESULT_ALIAS, &ThisClass::OnChange)
END_MESSAGE_MAP()

#pragma warning( pop )

SynthesUI::ConfirmPage::ConfirmPage(SynthesUI& parent)
  : CPropertyPage(IXTRD_SYNT_NAMES), 
  operation{ mfc::LoadSID(IXTRS_OPLABEL_SYNT) }, parentSht{ parent } {}

void SynthesUI::ConfirmPage::DoDataExchange(CDataExchange* pDX) {
  CPropertyPage::DoDataExchange(pDX);

  ::DDX_Control(pDX, IXTRC_SYNT_FLAG_CTABLE, equationsCtrl.control);
  ::DDX_Control(pDX, IXTRC_RESULT_ALIAS, shortNameCtrl);
  ::DDX_Control(pDX, IXTRC_RESULT_TITLE, fullNameCtrl);

  ::DDX_Text(pDX, IXTRC_FIRST_OP, firstOperand);
  ::DDX_Text(pDX, IXTRC_OP_NAME, operation);
  ::DDX_Text(pDX, IXTRC_SECOND_OP, secondOperand);
  ::DDX_Text(pDX, IXTRC_RESULT_TITLE, parentSht.title);
  ::DDX_Text(pDX, IXTRC_RESULT_ALIAS, parentSht.alias);

  DDV_Names();
}

BOOL SynthesUI::ConfirmPage::OnInitDialog() {
  CPropertyPage::OnInitDialog();

  InitControls();
  equationsCtrl.InitHeader();
  UpdateData(FALSE);
  return TRUE;
}

void SynthesUI::ConfirmPage::InitControls() {
  fullNameCtrl.Init(ui::CstPicker::Mode::TITLE, parentSht.title);
  shortNameCtrl.Init(ui::CstPicker::Mode::ALIAS, parentSht.alias);
}

void SynthesUI::ConfirmPage::OnChange() {
  UpdateData(TRUE);
}

void SynthesUI::ConfirmPage::OnComboChanged() {
  parentSht.SetWizardButtons(PSWIZB_DISABLEDFINISH | PSWIZB_BACK);
}

BOOL SynthesUI::ConfirmPage::OnSetActive() {
  firstOperand = mfc::ToMFC(parentSht.operand1->title);
  secondOperand = mfc::ToMFC(parentSht.operand2->title);
  operation = info::Label(parentSht.opType);
  equationsCtrl.LoadTableSynth(parentSht.equations, *parentSht.operand1, *parentSht.operand2);
  UpdateData(FALSE);
  OnChange();
  return CPropertyPage::OnSetActive();
}

void SynthesUI::ConfirmPage::DDV_Names() {
  if (!fullNameCtrl.IsValid() || !shortNameCtrl.IsValid()) {
    parentSht.SetWizardButtons(PSWIZB_DISABLEDFINISH | PSWIZB_BACK);
  } else {
    parentSht.SetWizardButtons(PSWIZB_FINISH | PSWIZB_BACK);
  }
}

} // namespace xtr::dialog