#include "stdafx.h"

#include "xtr/dialog/TerminRefDlg.h"

#include "resource.h"

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

#include "xtr/ui/UIHelper.h"

namespace xtr::dialog {

using ccl::lang::Morphology;
using ccl::lang::Grammem;
using ccl::lang::Reference;
using ccl::lang::EntityRef;

TerminRefDlg::TerminRefDlg(doc::RSFacade& editor, CPoint position,
                           const CString selection, CWnd* pParent)
  : CDialog(IXTRD_AUTOCOMPLETE, pParent), 
  position{ position }, initialText{ selection }, editor{ editor } {}

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

  ::DDX_Control(pDX, IXTRC_AC_LIST1, singForms);
  ::DDX_Control(pDX, IXTRC_AC_LIST2, plurForms);
  ::DDX_Control(pDX, IXTRC_AC_LABELSLIST, captionList);
}

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

  UpdateContextNameList();

  singForms.SetFont(&XTROptions::App().fontText);
  plurForms.SetFont(&XTROptions::App().fontText);
  captionList.SetFont(&XTROptions::App().fontText);

  if (singForms.GetCount() == 0) {
    ::AfxMessageBox(IXRE_EMPTY_TERMS, MB_ICONERROR);
    OnCancel();
  } else {
    singForms.SetCurSel(0);
    SetPositionAndSize();
    state = ACS_TERMS;
  }
  return TRUE;
}

void TerminRefDlg::UpdateContextNameList() {
  singForms.ResetContent();
  entities.clear();
  const auto inputs = editor.Texts().Context().MatchingTerms(mfc::ToSTL(initialText));
  CString listItem{};
  for (const auto uid : editor.Core().List()) {
    const auto& alias = editor.Core().GetText(uid).alias;
    if (inputs.contains(alias)) {
      const auto& title = inputs.at(alias);
      entities.emplace_back(alias);
      listItem = mfc::ToMFC(alias);
      listItem += L": ";
      listItem += mfc::ToMFC(title);
      singForms.AddString(listItem);
    }
  }
}

BOOL TerminRefDlg::PreTranslateMessage(MSG* pMsg) {
  if (pMsg->hwnd != singForms.GetSafeHwnd() && pMsg->hwnd != plurForms.GetSafeHwnd()) {
    return CDialog::PreTranslateMessage(pMsg);
  }

  const auto selectedSing = pMsg->hwnd == singForms.GetSafeHwnd() ? TRUE : FALSE;
  const auto selection = selectedSing ? singForms.GetCurSel() : plurForms.GetCurSel();
  const auto plurality = selectedSing ? Grammem::sing : Grammem::plur;

  CListBox* selectedCListBox = selectedSing ? &singForms : &plurForms;
  CListBox* unselectedCListBox = selectedSing ? &plurForms : &singForms;

  switch (state) {
  case ACS_TERMS: {
    if (pMsg->message == WM_LBUTTONDBLCLK ||
      (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN)) {
      SelectTermRef();
      return TRUE;
    }
    break;
  }
  case ACS_WORDFORMS: {
    if (pMsg->message == WM_LBUTTONDOWN) {
      unselectedCListBox->SetCurSel(mfc::noItems);
    }
    if (pMsg->message == WM_LBUTTONDBLCLK) {
      SelectTermForm(selection, plurality);
      return TRUE;
    } else if (pMsg->message != WM_KEYDOWN) {
      return CDialog::PreTranslateMessage(pMsg);
    }

    switch (pMsg->wParam) {
    case VK_RETURN:
      SelectTermForm(selection, plurality);
      [[fallthrough]];
    case VK_TAB:
      return TRUE;

    case VK_F2:
      ChangeTermForm(selectedCListBox, plurality);
      return TRUE;

    case VK_LEFT: case VK_RIGHT: {
      const auto selectedLeftKey = pMsg->wParam == VK_LEFT ? TRUE : FALSE;
      if ((selectedSing && !selectedLeftKey) || (!selectedSing && selectedLeftKey)) {
        selectedCListBox->SetCurSel(mfc::noItems);
        unselectedCListBox->SetFocus();
        unselectedCListBox->SetCurSel(selection);
      }
      return TRUE;
    }

    case '1': case '2': case '3': case '4': case '5': case '6': {
      const auto number = static_cast<int>(pMsg->wParam - '1');
      selectedCListBox->SetCurSel(number);
      break;
    }
    }
  }
  }
  return CDialog::PreTranslateMessage(pMsg);
}

void TerminRefDlg::SelectTermRef() {
  cstName = entities.at(singForms.GetCurSel());
  const auto* chosenTermin = editor.Texts().Context().At(cstName);
  if (chosenTermin == nullptr) {
    ::AfxMessageBox(IXTRE_TERMIN_INDEX, MB_ICONEXCLAMATION);
    return;
  }
  singForms.ResetContent();
  plurForms.ResetContent();
  captionList.ResetContent();

  for (uint8_t i = 0; i < ssize(ccl::lang::ALL_CASES); ++i) {
    const auto rcase = ccl::lang::ALL_CASES.at(i);
    singForms.AddString(mfc::ToMFC(chosenTermin->GetForm(Morphology{ Grammem::sing, rcase })));
    plurForms.AddString(mfc::ToMFC(chosenTermin->GetForm(Morphology{ Grammem::plur, rcase })));
    captionList.AddString(info::DescriptionOf(rcase));
  }

  singForms.SetCurSel(0);
  singForms.SetFocus();

  SetPositionAndSize(TRUE);
  state = ACS_WORDFORMS;
}

void TerminRefDlg::SelectTermForm(const int32_t selection, const Grammem plurality) {
  termForm = Morphology{ ccl::lang::ALL_CASES.at(selection), plurality };
  OnOK();
}

void TerminRefDlg::ChangeTermForm(CListBox* selectedCListBox, const Grammem plurality) {
  const auto* termin = editor.Texts().Context().At(cstName);
  if (termin == nullptr) {
    ::AfxMessageBox(IXTRE_TERMIN_INDEX, MB_ICONERROR);
    return;
  }
  const auto curSel = selectedCListBox->GetCurSel();
  const auto rcase = ccl::lang::ALL_CASES.at(curSel);
  const auto form = Morphology{ plurality, rcase };
  const auto& curCstTermForm = termin->GetForm(form);

  const auto listRect = ui::WindowRect(*selectedCListBox);
  CRect dlgRect{};
  selectedCListBox->GetItemRect(curSel, &dlgRect);
  dlgRect.left += listRect.left;
  dlgRect.right += listRect.left;
  dlgRect.top += listRect.top;
  dlgRect.bottom += listRect.top + 4; // 2bx border from both sides

  TermFormDlg renameDlg{ mfc::ToMFC(curCstTermForm), dlgRect };
  if (renameDlg.DoModal() != IDOK) {
    return;
  } 
  auto newCstTermForm = mfc::ToSTL(renameDlg.termFormText);
  if (curCstTermForm == newCstTermForm) {
    return;
  }

  const auto uid = editor.Core().FindAlias(cstName).value();
  editor.SetTermFormFor(uid, newCstTermForm, form);
  if (curSel != 0) {
    selectedCListBox->DeleteString(curSel);
    selectedCListBox->InsertString(curSel, mfc::ToMFC(termin->GetForm(form)));
  } else {
    selectedCListBox->ResetContent();
    for (auto i = 0; i < ssize(ccl::lang::ALL_CASES); ++i) {
      selectedCListBox->AddString(mfc::ToMFC(termin->GetForm(Morphology{ plurality, ccl::lang::ALL_CASES.at(i) })));
    }
  }
  selectedCListBox->SetCurSel(curSel);
  SetPositionAndSize(TRUE);
}

void TerminRefDlg::SetPositionAndSize(BOOL fitBoth) {
  static constexpr auto widthMax = 250;
  static constexpr auto TEXT_BORDER = 5;

  auto h = 0;
  auto width1 = 0;
  auto width2 = 0;
  auto width3 = 0;
  CString str{};
  CSize sz{};

  auto* pDC1 = singForms.GetDC();
  pDC1->SelectObject(singForms.GetFont());

  auto* pDC2 = plurForms.GetDC();
  pDC2->SelectObject(plurForms.GetFont());

  auto* pDC3 = captionList.GetDC();
  pDC3->SelectObject(captionList.GetFont());

  for (auto i = 0; i < singForms.GetCount(); ++i) {
    if (i < 10) {
      h += singForms.GetItemHeight(i);
    }

    singForms.GetText(i, str);
    sz = pDC1->GetTextExtent(str);
    if (sz.cx > width1) {
      width1 = sz.cx;
    }
    if (fitBoth) {
      plurForms.GetText(i, str);
      sz = pDC2->GetTextExtent(str);
      if (sz.cx > width2) {
        width2 = sz.cx;
      }
      captionList.GetText(i, str);
      sz = pDC3->GetTextExtent(str);
      if (sz.cx > width3) {
        width3 = sz.cx;
      }
    }
  }
  singForms.ReleaseDC(pDC1);
  plurForms.ReleaseDC(pDC2);

  width1 = std::min(width1 + TEXT_BORDER, widthMax) + GetSystemMetrics(SM_CXVSCROLL);
  width2 = std::min(width2 + TEXT_BORDER, widthMax) + GetSystemMetrics(SM_CXVSCROLL);
  width3 = std::min(width3 + TEXT_BORDER, widthMax) + GetSystemMetrics(SM_CXVSCROLL);

  h += 4; // 2px border from both sides

  if (!fitBoth) {
    SetWindowPos(nullptr, position.x, position.y, width1, h, SWP_SHOWWINDOW);
    singForms.SetWindowPos(&wndTop, 0, 0, width1, h, SWP_SHOWWINDOW);
    singForms.SetColumnWidth(width1);
    plurForms.SetWindowPos(&wndTop, 0, 0, 0, 0, SWP_HIDEWINDOW);
    captionList.SetWindowPos(&wndTop, 0, 0, 0, 0, SWP_HIDEWINDOW);
  } else {
    SetWindowPos(nullptr, position.x, position.y, width1 + width2 + width3 - 2, h, SWP_SHOWWINDOW);
    singForms.SetWindowPos(&wndTop, 0, 0, width1, h, SWP_SHOWWINDOW);
    singForms.SetColumnWidth(width1);
    plurForms.SetWindowPos(&wndTop, width1 - 2, 0, width2, h, SWP_SHOWWINDOW);
    plurForms.SetColumnWidth(width2);
    captionList.SetWindowPos(&wndTop, width1 + width2 - 2, 0, width3, h, SWP_SHOWWINDOW);
  }
}

Reference TerminRefDlg::CreateRef() const {
  return Reference{ EntityRef{ cstName, termForm } };
}

TermFormDlg::TermFormDlg(const CString initText, RECT position, CWnd* pParent)
  : CDialog(IXTRD_CST_CHANGE_TERM_FORM, pParent), termFormText{ initText }, position{ position } {}

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

  textControl.SetSel(0, 1);
  SetPositionAndSize();
  return TRUE;
}

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

  ::DDX_Control(pDX, IXTRC_CST_TERM_FORM_EDIT, textControl);
  ::DDX_Text(pDX, IXTRC_CST_TERM_FORM_EDIT, termFormText);
}

BOOL TermFormDlg::PreTranslateMessage(MSG* pMsg) {
  if (pMsg->message == WM_KEYDOWN &&
      pMsg->hwnd == textControl.GetSafeHwnd() &&
      pMsg->wParam == VK_RETURN) {
    OnOK();
    return TRUE;
  } else {
    return CDialog::PreTranslateMessage(pMsg);
  }
}

void TermFormDlg::SetPositionAndSize() {
  SetWindowPos(nullptr, position.left, position.top,
               position.right - position.left,
               position.bottom - position.top,
               SWP_SHOWWINDOW);

  textControl.SetWindowPos(&wndTop, 0, 0,
                           position.right - position.left,
                           position.bottom - position.top,
                           SWP_SHOWWINDOW);
}

} // namespace xtr::dialog