#include "stdafx.h"

#include "xtr/ui/TextEdit.h"

#include "resource.h"

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

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

#include "xtr/dialog/TerminRefDlg.h"
#include "xtr/dialog/WordRefDlg.h"

namespace xtr::ui {

using viewmodel::HighlightItem;

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

BEGIN_MESSAGE_MAP(TextEdit, BuffRichEdit)
  ON_WM_KEYDOWN()

  ON_CONTROL_REFLECT_EX(EN_CHANGE, &ThisClass::OnTextChange)
  ON_NOTIFY_REFLECT_EX(EN_SELCHANGE, &ThisClass::OnSelChange)
END_MESSAGE_MAP()

#pragma warning( pop )

TextEdit::TextEdit() :
  presenter{ *this } {
  BuffRichEdit::defaultToolTipText = mfc::LoadSID(IXTRS_TEXT_TOOLTIP_DEFAULT);
}

void TextEdit::Initialize(const CString refStr, 
                          doc::RSFacade& targetEditor,
                          BOOL advancedRefs, const BOOL wantReturn) {
  resolveRefs = advancedRefs;
  ignoreReturn = !wantReturn;
  editor = &targetEditor;
  presenter.Initialize(editor->Texts().Context());
  SetRefStr(refStr);
}

void TextEdit::PreSubclassWindow() {
  BuffRichEdit::PreSubclassWindow();
  SetFont(&XTROptions::App().fontText);
}

void TextEdit::SetRefStr(const CString refStr) {
  if (resolveRefs) {
    presenter.SetRefStr(refStr);
  } else {
    SetWindowTextW(refStr);
  }
}

CString TextEdit::GetRefStr() {
  if (resolveRefs) {
    return presenter.GetRefStr();
  } else {
    return GetText();
  }
}

BOOL TextEdit::PreTranslateMessage(MSG* pMsg) {
  if (pMsg->message != WM_KEYDOWN) {
    return BuffRichEdit::PreTranslateMessage(pMsg);
  } else if (const auto key = pMsg->wParam; 
      key != VK_SPACE || !Keyboard::IsPressed(VK_CONTROL)) {
    return BuffRichEdit::PreTranslateMessage(pMsg);
  } else {
    switchLayoutOnCtrl = FALSE;
    const auto cr = ShrinkSelection();
    const auto borderCheck = !IsBorderHL(cr.start) && !IsBorderHL(cr.finish);
    if (borderCheck) {
      if (Keyboard::IsPressed(VK_SHIFT)) {
        RunAddDependWord();
      } else {
        RunAddTerminRef();
      }
    }
    return TRUE;
  }
}

void TextEdit::OnKeyDown(const uint32_t key, const uint32_t nRepCnt, const uint32_t nFlags) {
  if (ProcessKeyDown(key, nRepCnt, nFlags)) {
    switchLayoutOnCtrl = FALSE;
  } else {
    BuffRichEdit::OnKeyDown(key, nRepCnt, nFlags);
  }
}

BOOL TextEdit::ProcessKeyDown(const uint32_t key, const uint32_t nRepCnt, const uint32_t nFlags) {
  if (PreProcessKey(key)) {
    return TRUE;
  } else if (!resolveRefs) {
    return FALSE;
  } else if (ProcessKeyConst(key)) {
    return TRUE;
  } else if ((GetStyle() & ES_READONLY) != 0) {
    return FALSE;
  } else if (ProcessKeyEdit(key)) {
    return TRUE;
  } else if (Keyboard::IsPressed(VK_CONTROL) && key == 'V') {
    const auto start = GetSelectionRange().start;
    BuffRichEdit::OnKeyDown(key, nRepCnt, nFlags);
    ResetHighlight(StrRange{ start, GetSelectionRange().finish });
    return TRUE;
  } else {
    return FALSE;
  }
}

BOOL TextEdit::PreProcessKey(const uint32_t key) {
  switch (key) {
  default: return FALSE;
  case VK_RETURN: {
    return ignoreReturn;
  }
  case 'T': {
    if (Keyboard::IsPressed(VK_CONTROL) && Keyboard::IsPressed(VK_SHIFT)) {
      resolveRefs = !resolveRefs;
      if (!resolveRefs) {
        SetWindowTextW(presenter.GetRefStr());
      } else {
        presenter.SetRefStr(GetText());
      }
      return TRUE;
    } else {
      return FALSE;
    }
  }
  }
}

BOOL TextEdit::ProcessKeyConst(const uint32_t key) {
  switch (key) {
  default: return FALSE;
  case 'C': {
    if (Keyboard::IsPressed(VK_CONTROL) && Keyboard::IsPressed(VK_SHIFT)) {
      CopyWithReferences(GetSelectionRange());
      return TRUE;
    } else {
      return FALSE;
    }
  }
  }
}

BOOL TextEdit::ProcessKeyEdit(const uint32_t key) {
  switch (key) {
  default: return FALSE;
  case VK_BACK: 
  case VK_DELETE: {
    presenter.OnEraseText(GetSelectionRange(), key == VK_BACK);
    return TRUE;
  }
  }
}

void TextEdit::CopyWithReferences(const StrRange range) {
  if (!OpenClipboard()) {
    ::AfxMessageBox(IXTRE_CLIPBOARD_OPEN, MB_ICONERROR);
  } else if (!EmptyClipboard()) {
    ::AfxMessageBox(IXTRE_CLIPBOARD_CLEAR, MB_ICONERROR);
  } else {
    if (auto cpyStr = presenter.GetRefSubstr(range); !cpyStr.IsEmpty()) {      
      if (auto* hGlob = GlobalAlloc(GMEM_FIXED, (wcslen(cpyStr) + 1) * sizeof(wchar_t)); hGlob != nullptr) {
        wcscpy_s(static_cast<wchar_t*>(hGlob), wcslen(cpyStr) + 1, cpyStr);
        if (::SetClipboardData(CF_UNICODETEXT, hGlob) == nullptr) {
          ::AfxMessageBox(IXTRE_CLIPBOARD_COPY, MB_ICONERROR);
          GlobalFree(hGlob);
        }
      }
    }
    CloseClipboard();
  }
}

BOOL TextEdit::OnTextChange() {
  if (resolveRefs) {
    presenter.Invalidate();
  }
  if (const auto sel = GetSelectionRange(); std::empty(sel) && sel.start > 0) {
    ResetHighlight(StrRange{ sel.start - 1, sel.start });
  }
  const auto result = BuffRichEdit::OnTextChange();
  UpdateLock();
  return result;
}

BOOL TextEdit::OnSelChange(NMHDR* pNotifyStruct, LRESULT* result) {
  UpdateLock();
  return BuffRichEdit::OnSelChange(pNotifyStruct, result);
}

void TextEdit::UpdateLock() {
  if (!readOnly) {
    const auto selection = GetSelectionRange();
    BOOL blockUserInput = FALSE;
    if (selection.empty()) {
      blockUserInput = IsBorderHL(selection.start) && !IsBorderHL(selection.start, TRUE);
    } else {
      const auto format = GetSelectionFormat();
      blockUserInput = !(format.dwMask & CFM_BACKCOLOR) || design::IsReference(format.crBackColor);
    }
    if (blockUserInput) {
      LockInput();
    } else {
      UnlockInput();
    }
  }
}

BOOL TextEdit::RunAddTerminRef() {
  const auto pos = GetSubscriptDialogPosition();
  const auto selection = FixSelection();
  dialog::TerminRefDlg dialog(*editor, pos, selection);
  if (dialog.DoModal() != IDOK) {
    return FALSE;
  } else {
    presenter.OnInsertRef(GetSelectionRange(), dialog.CreateRef());
    return TRUE;
  }
}

BOOL TextEdit::RunAddDependWord() {
  const auto pos = GetWordDlgPos();
  const auto selection = FixSelection();
  dialog::WordRefDlg companionDlg(pos, selection);

  FreezeUI();
  const auto result = companionDlg.DoModal();
  UnFreezeUI();

  if (result != IDOK) {
    return FALSE;
  } else {
    presenter.OnInsertRef(GetSelectionRange(), companionDlg.CreateRef());
    return TRUE;
  }
}

RECT TextEdit::GetWordDlgPos() {
  static constexpr auto HORIZONTAL_OFFSET = 150;
  const auto rect = WindowRect(*this);
  const auto x = rect.left + GetCaretPos().x + 5;
  const auto y = rect.top + GetCaretPos().y + 5;

  const auto metrics = TextMetricsOf(*this);
  const auto lineHeight = metrics.tmHeight + metrics.tmExternalLeading;

  RECT dlgRect{};
  dlgRect.left = x;
  dlgRect.right = x + HORIZONTAL_OFFSET;
  dlgRect.top = y;
  dlgRect.bottom = y + lineHeight;
  return dlgRect;
}

CString TextEdit::GenerateToolTipText() {
  if (!resolveRefs) {
    return defaultToolTipText;
  } else if (const auto* ref = presenter.RepresentativeOf(GetSelectionRange());
      ref == nullptr) {
    return defaultToolTipText;
  } else {
    return info::DescriptionOf(*ref);
  }
}

BOOL TextEdit::IsBorderHL(const StrPos position, const BOOL oneSide) {
  if (position < 0 || position > Text().GetLength()) {
    return FALSE;
  } else {
    FreezeUI();

    auto leftResult = FALSE;
    auto rightResult = FALSE;
    if (position != 0) {
      SelectChar(position - 1);
      if (const auto color = GetSelectionFormat().crBackColor; design::IsReference(color)) {
        leftResult = TRUE;
      }
    }
    if (position != Text().GetLength()) {
      SelectChar(position);
      if (const auto color = GetSelectionFormat().crBackColor; design::IsReference(color)) {
        rightResult = TRUE;
      }
    }

    UnFreezeUI();
    return oneSide ? leftResult != rightResult : leftResult || rightResult;
  }
}

StrRange TextEdit::ShrinkSelection(){
  FreezeUI();

  auto selection = GetSelectionRange();
  while (TRUE) {
    if (selection.start == selection.finish || GetSelText()[0] != ' ') {
      break;
    } else {
      ++selection.start;
      SelectRange(selection);
    }
  }
  while (TRUE) {
    if (selection.start == selection.finish) {
      break;
    } else if (const auto txt = GetSelText(); txt[txt.GetLength() - 1] != ' ') {
      break;
    } else {
      --selection.finish;
      SelectRange(selection);
    }
  }

  BuffRichEdit::UnFreezeUI(selection);
  return selection;
}

CString TextEdit::GetText() const {
  return Text();
}

void TextEdit::SetText(const CString text) {
  FreezeUI();
  SetWindowTextW(text);
  UnFreezeUI();
}

void TextEdit::UnFreezeUI() {
  BuffRichEdit::UnFreezeUI();
  if (!IsFrozen()) {
    UpdateLock();
  }
}

std::optional<HighlightItem> TextEdit::NextHighlighted(const StrPos start) {
  FreezeUI();
  const auto result = ScanHighlight(start);
  UnFreezeUI();
  return result;
}

std::optional<HighlightItem> TextEdit::ScanHighlight(const StrPos start) {
  const auto maxLen = Text().GetLength();
  std::optional<COLORREF> hlColor{};
  auto l = -1;
  auto r = -1;
  for (auto curPos = start; curPos < maxLen; ++curPos) {
    SelectChar(curPos);
    const auto color = GetSelectionFormat().crBackColor;
    if (hlColor.has_value() &&
      (color == RGB(255, 255, 255) || color != hlColor)) {
      return HighlightItem{ StrRange{ l, r }, hlColor.value() };
    } else {
      r = curPos + 1;
      if (!hlColor.has_value()) {
        hlColor = color;
        l = curPos;
      }
    }
  }
  if (!hlColor.has_value()) {
    return std::nullopt;
  } else {
    return HighlightItem{ StrRange{ l, r }, hlColor.value() };
  }
}

void TextEdit::ReplaceRange(const StrRange range, const CString replacement) {
  FreezeUI();

  SelectRange(range);
  ReplaceSel(replacement, FALSE);

  BuffRichEdit::UnFreezeUI(StrRange::FromLength(range.start + replacement.GetLength(), 0));
}

void TextEdit::Highlight(const HighlightItem item) {
  auto format = design::CharFormatRE();
  format.dwMask = CFM_BACKCOLOR;
  format.crBackColor = item.color;

  FreezeUI();
  SelectAndFormat(item.rng, format);
  UnFreezeUI();
}

void TextEdit::ResetHighlight(const StrRange hlRange) {
  FreezeUI();
  SelectAndFormat(hlRange, DefaultFormat());
  UnFreezeUI();
}

} // namespace xtr::ui