      /*
* Copyright (c) 2003-2024 Rony Shapiro <ronys@pwsafe.org>.
* All rights reserved. Use of the code is allowed under the
* Artistic License 2.0 terms, as specified in the LICENSE file
* distributed with this code, or available from
* http://www.opensource.org/licenses/artistic-license-2.0.php
*/

// FilterDateDlg.cpp : implementation file
//

#include "../stdafx.h"
#include "../GeneralMsgBox.h"
#include "FilterDateDlg.h"
#include "core/itemdata.h"

// CFilterDateDlg dialog

// Allow negative numbers (not supported by style ES_NUMBER)
IMPLEMENT_DYNAMIC(CEditInt, CEdit)

BEGIN_MESSAGE_MAP(CEditInt, CEdit)
  ON_WM_CHAR()
END_MESSAGE_MAP()

void CEditInt::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
  DWORD dw = GetSel();

  switch(nChar) {
    case L'+':
    case L'-':
      if (LOWORD(dw) != 0)
        return;
      break;
    case L'0':
    case L'1':
    case L'2':
    case L'3':
    case L'4':
    case L'5':
    case L'6':
    case L'7':
    case L'8':
    case L'9':
    case L'\b':
      break;
    default:
      return;
  }

  CEdit::OnChar(nChar, nRepCnt, nFlags);
}

IMPLEMENT_DYNAMIC(CFilterDateDlg, CFilterBaseDlg)

CFilterDateDlg::CFilterDateDlg(CWnd* pParent /*=NULL*/)
  : CFilterBaseDlg(CFilterDateDlg::IDD, pParent),
  m_ctime1((time_t)0), m_ctime2((time_t)0),
  m_datetype(0), m_num1(0), m_num2(0),
  m_time_t1(0), m_time_t2(0),
  m_add_present(false), m_ft(FT_INVALID)
{
  time_t now;
  time(&now);
  m_ctime1 = m_ctime2 = CTime(now);
}

CFilterDateDlg::~CFilterDateDlg()
{
}

void CFilterDateDlg::DoDataExchange(CDataExchange* pDX)
{
  CFilterBaseDlg::DoDataExchange(pDX);

  //{{AFX_DATA_MAP(CFilterDateDlg)
  DDX_DateTimeCtrl(pDX, IDC_DATETIMEPICKER1, m_ctime1);
  DDX_DateTimeCtrl(pDX, IDC_DATETIMEPICKER2, m_ctime2);
  DDX_Radio(pDX, IDC_SELECTBYDATETIME, m_datetype);
  DDX_Text(pDX, IDC_INTEGER1, m_num1);
  DDX_Text(pDX, IDC_INTEGER2, m_num2);
  DDX_Control(pDX, IDC_DATERULE, m_cbxRule);
  DDX_Control(pDX, IDC_INTEGER1, (CEdit&)m_edtInteger1);
  DDX_Control(pDX, IDC_INTEGER2, (CEdit&)m_edtInteger2);
  DDX_Control(pDX, IDC_DATETIMEPICKER1, m_dtp1);
  DDX_Control(pDX, IDC_DATETIMEPICKER2, m_dtp2);
  DDX_Control(pDX, IDC_STATIC_AND, m_stcAnd);
  DDX_Control(pDX, IDC_STATIC_AND2, m_stcAnd2);
  DDX_Control(pDX, IDC_STATIC_RELDESC, m_stcRelativeDesc);
  //}}AFX_DATA_MAP

  if (m_datetype == 0) {
    /* Absolute */
    DDV_CheckDateValid(pDX, m_ctime1);
    if (m_rule == PWSMatch::MR_BETWEEN) {
      DDV_CheckDateValid(pDX, m_ctime2);
    }
    DDV_CheckDates(pDX, m_ctime1, m_ctime2);
  } else {
    /* Relative */
    DDV_CheckMinMax(pDX, m_num1);
    DDV_CheckDateValid(pDX, m_num1);
    if (m_rule == PWSMatch::MR_BETWEEN) {
      DDV_CheckMinMax(pDX, m_num2);
      DDV_CheckDateValid(pDX, m_num2);
    }
    DDV_CheckDates(pDX, m_num1, m_num2);
  }
}

BEGIN_MESSAGE_MAP(CFilterDateDlg, CFilterBaseDlg)
  ON_CBN_SELCHANGE(IDC_DATERULE, OnCbnSelchangeDateRule)
  ON_BN_CLICKED(IDOK, &CFilterDateDlg::OnBnClickedOk)
  ON_BN_CLICKED(IDC_SELECTBYDATETIME, OnAbsolute)
  ON_BN_CLICKED(IDC_SELECTBYDAYS, OnRelative)
  ON_NOTIFY(DTN_DATETIMECHANGE, IDC_DATETIMEPICKER1, OnDtnDatetime1Change)
END_MESSAGE_MAP()

void AFXAPI CFilterDateDlg::DDV_CheckDateValid(CDataExchange* pDX,
                                               const int &num)
{
  if (m_datetype == 0 /* Absolute */)
    return;

  // Only Expiry dates can be in the past and future
  // All other dates can only be in the past (i.e. created, last accessed etc.)
  if (pDX->m_bSaveAndValidate && m_ft != FT_XTIME && num > 0) {
    CGeneralMsgBox gmb;
    CString cs_text;
    cs_text.LoadString(IDS_INVALIDFUTUREDATE);
    gmb.AfxMessageBox(cs_text);
    pDX->Fail();
  }
}

void AFXAPI CFilterDateDlg::DDV_CheckDateValid(CDataExchange* pDX,
                                               const CTime &ctime)
{
  if (m_datetype == 1 /* Relative */)
    return;

  // Only Expiry dates can be in the past and future
  // All other dates can only be in the past (i.e. created, last accessed etc.)
  if (pDX->m_bSaveAndValidate && m_ft != FT_XTIME && ctime > CTime::GetCurrentTime()) {
    CGeneralMsgBox gmb;
    gmb.AfxMessageBox(IDS_INVALIDFUTUREDATE);
    pDX->Fail();
  }
}

void AFXAPI CFilterDateDlg::DDV_CheckMinMax(CDataExchange* pDX,
                                            const int &num)
{
  if (m_datetype == 0 /* Absolute */)
    return;

  if (pDX->m_bSaveAndValidate) {
    CGeneralMsgBox gmb;
    CString cs_text;
    if (num < -3650) {
      cs_text.Format(IDS_NUMTOOSMALL, -3650);
      gmb.AfxMessageBox(cs_text);
      pDX->Fail();
    }

    if (num > 3650) {
      cs_text.Format(IDS_NUMTOOLARGE, 3650);
      gmb.AfxMessageBox(cs_text);
      pDX->Fail();
    }
  }
}

void AFXAPI CFilterDateDlg::DDV_CheckDates(CDataExchange* pDX,
                                           const CTime &ctime1,  const CTime &ctime2)
{
  if (m_datetype == 1 /* Relative */)
    return;

  if (pDX->m_bSaveAndValidate && m_rule == PWSMatch::MR_BETWEEN && ctime1 >= ctime2) {
    CGeneralMsgBox gmb;
    gmb.AfxMessageBox(IDS_DATE1NOTB4DATE2);
    pDX->Fail();
  }
}

void AFXAPI CFilterDateDlg::DDV_CheckDates(CDataExchange* pDX,
                                           const int &num1, const int &num2)
{
  if (m_datetype == 0 /* Absolute */)
    return;

  CGeneralMsgBox gmb;
  if (pDX->m_bSaveAndValidate) {
    if (m_rule == PWSMatch::MR_BETWEEN && num1 >= num2) {
      gmb.AfxMessageBox(IDS_NUM1NOTLTNUM2);
      pDX->Fail();
      return;
    }

    if (num1 == -3650 && m_rule == PWSMatch::MR_LT) {
      gmb.AfxMessageBox(IDS_CANTBELESSTHANMIN);
      pDX->Fail();
      return;
    }

    if (num1 == 3650 && m_rule == PWSMatch::MR_GT) {
      gmb.AfxMessageBox(IDS_CANTBEGREATERTHANMAX);
      pDX->Fail();
      return;
    }

    if (num1 == 3650 && m_rule == PWSMatch::MR_BETWEEN) {
      gmb.AfxMessageBox(IDS_NUM1CANTBEMAX);
      pDX->Fail();
      return;
    }
  }
}

// CFilterDateDlg message handlers

BOOL CFilterDateDlg::OnInitDialog()
{
  CFilterBaseDlg::OnInitDialog();

  CString cs_text;

  // NOTE: This ComboBox is NOT sorted by design !
  if (m_cbxRule.GetCount() == 0) {
    if (m_add_present) {
      const PWSMatch::MatchRule mrx[] = {PWSMatch::MR_PRESENT, PWSMatch::MR_NOTPRESENT};

      for (size_t i = 0; i < _countof(mrx); i++) {
        UINT iumsg = PWSMatch::GetRule(mrx[i]);
        cs_text.LoadString(iumsg);
        int iItem = m_cbxRule.AddString(cs_text);
        m_cbxRule.SetItemData(iItem, mrx[i]);
        m_rule2selection[mrx[i]] = iItem;
      }
    }
    const PWSMatch::MatchRule mrx[] = {PWSMatch::MR_EQUALS, PWSMatch::MR_NOTEQUAL,
                                       PWSMatch::MR_BEFORE, PWSMatch::MR_AFTER,
                                       PWSMatch::MR_BETWEEN};

    for (size_t i = 0; i < _countof(mrx); i++) {
      UINT iumsg = PWSMatch::GetRule(mrx[i]);
      cs_text.LoadString(iumsg);
      int iItem = m_cbxRule.AddString(cs_text);
      m_cbxRule.SetItemData(iItem, mrx[i]);
      m_rule2selection[mrx[i]] = iItem;
    }
  }

  wchar_t szBuf[81];       // workspace
  VERIFY(::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SSHORTDATE, szBuf, 80));

  m_dtp1.SetFormat(szBuf);
  m_dtp2.SetFormat(szBuf);

  const CTime dtMin((time_t)0);
  const CTime dtMax(2038, 1, 1, 0, 0, 0, -1);  // time32_t limit
  m_dtp1.SetRange(&dtMin, &dtMax);
  m_dtp2.SetRange(&dtMin, &dtMax);

  int isel = m_rule2selection[(int)m_rule];
  if (isel == -1)
    m_rule = PWSMatch::MR_INVALID;

  BOOL bAbsEnable(FALSE), bAbsEnable1(FALSE), bRelEnable(FALSE), bRelEnable1(FALSE);
  BOOL bSelect(FALSE);
  if (m_rule != PWSMatch::MR_INVALID) {
    m_cbxRule.SetCurSel(isel);

    if (m_time_t1 != 0)
      m_ctime1 = m_time_t1;

    if (m_time_t2 != 0)
      m_ctime2 = m_time_t2;

    bSelect = TRUE;
    switch (m_rule) {
      case PWSMatch::MR_BETWEEN:
        bAbsEnable = bAbsEnable1 = (m_datetype == 0) ? TRUE : FALSE;
        bRelEnable = bRelEnable1 = (m_datetype == 0) ? FALSE : TRUE;
        if (m_datetype == 0 /* Absolute */) {
          if (m_ctime2 < m_ctime1)
            m_ctime2 = m_ctime1;
        } else {
          if (m_num2 < m_num1)
            m_num2 = m_num1;
        }
        break;
      case PWSMatch::MR_PRESENT:
      case PWSMatch::MR_NOTPRESENT:
        bSelect = bAbsEnable = bAbsEnable1 = bRelEnable = bRelEnable1 = FALSE;
        break;
      default:
        if (m_datetype == 0 /* Absolute */) {
          bAbsEnable1 = TRUE;
        } else {
          bRelEnable1 = TRUE;
        }
    }
  } else
    m_cbxRule.SetCurSel(-1);

  GetDlgItem(IDC_SELECTBYDATETIME)->EnableWindow(bSelect);
  GetDlgItem(IDC_SELECTBYDAYS)->EnableWindow(bSelect);
  m_dtp1.EnableWindow(bAbsEnable1);
  m_stcAnd.EnableWindow(bAbsEnable);
  m_dtp2.EnableWindow(bAbsEnable);
  m_edtInteger1.EnableWindow(bRelEnable1);
  m_stcAnd2.EnableWindow(bRelEnable);
  m_edtInteger2.EnableWindow(bRelEnable);
  m_stcRelativeDesc.EnableWindow(bSelect);

  UpdateData(FALSE);

  return TRUE;  // return TRUE unless you set the focus to a control
}

void CFilterDateDlg::OnCbnSelchangeDateRule()
{
  int isel = m_cbxRule.GetCurSel();
  m_rule = (PWSMatch::MatchRule)m_cbxRule.GetItemData(isel);

  BOOL bAbsEnable(FALSE), bAbsEnable1(FALSE), bRelEnable(FALSE), bRelEnable1(FALSE);
  BOOL bSelect(TRUE);
  switch (m_rule) {
    case PWSMatch::MR_BETWEEN:
      bAbsEnable = bAbsEnable1 = (m_datetype == 0) ? TRUE : FALSE;
      bRelEnable = bRelEnable1 = (m_datetype == 0) ? FALSE : TRUE;
      if (m_datetype == 0 /* Absolute */) {
        if (m_ctime2 < m_ctime1)
          m_ctime2 = m_ctime1;
      } else {
        if (m_num2 < m_num1)
          m_num2 = m_num1;
      }
      break;
    case PWSMatch::MR_PRESENT:
    case PWSMatch::MR_NOTPRESENT:
      bSelect = bAbsEnable = bAbsEnable1 = bRelEnable = bRelEnable1 = FALSE;
      break;
    default:
      if (m_datetype == 0 /* Absolute */) {
        bAbsEnable1 = TRUE;
      } else {
        bRelEnable1 = TRUE;
      }
  }
  GetDlgItem(IDC_SELECTBYDATETIME)->EnableWindow(bSelect);
  GetDlgItem(IDC_SELECTBYDAYS)->EnableWindow(bSelect);
  m_dtp1.EnableWindow(bAbsEnable1);
  m_stcAnd.EnableWindow(bAbsEnable);
  m_dtp2.EnableWindow(bAbsEnable);
  m_edtInteger1.EnableWindow(bRelEnable1);
  m_stcAnd2.EnableWindow(bRelEnable);
  m_edtInteger2.EnableWindow(bRelEnable);
  m_stcRelativeDesc.EnableWindow(bSelect);

  UpdateData(FALSE);
}

void CFilterDateDlg::OnAbsolute()
{
  m_dtp1.EnableWindow(TRUE);
  m_dtp2.EnableWindow(m_rule == PWSMatch::MR_BETWEEN ? TRUE :FALSE);
  m_stcAnd.EnableWindow(m_rule == PWSMatch::MR_BETWEEN ? TRUE :FALSE);
  m_edtInteger1.EnableWindow(FALSE);
  m_edtInteger2.EnableWindow(FALSE);
  m_stcAnd2.EnableWindow(FALSE);
  m_datetype = 0;  // Absolute
}

void CFilterDateDlg::OnRelative()
{
  m_dtp1.EnableWindow(FALSE);
  m_dtp2.EnableWindow(FALSE);
  m_stcAnd.EnableWindow(FALSE);
  m_edtInteger1.EnableWindow(TRUE);
  m_edtInteger2.EnableWindow(m_rule == PWSMatch::MR_BETWEEN ? TRUE : FALSE);
  m_stcAnd2.EnableWindow(m_rule == PWSMatch::MR_BETWEEN ? TRUE : FALSE);
  m_datetype = 1;  // Relative
}

void CFilterDateDlg::OnCancel()
{
  m_datetype = 0;
  m_num1 = m_num2 = 0;
  m_time_t1 = m_time_t2 = (time_t)0;

  CFilterBaseDlg::OnCancel();
}

void CFilterDateDlg::OnBnClickedOk()
{
  if (UpdateData(TRUE) == FALSE)
    return;

  m_time_t1 = m_time_t2 = (time_t)0;

  if (m_rule == PWSMatch::MR_INVALID) {
    CGeneralMsgBox gmb;
    gmb.AfxMessageBox(IDS_NORULESELECTED);
    return;
  }

  if (m_rule != PWSMatch::MR_PRESENT &&
      m_rule != PWSMatch::MR_NOTPRESENT) {
    if (m_datetype == 0 /* Absolute */) {
      m_time_t1 = (time_t)(CTime(m_ctime1.GetYear(), m_ctime1.GetMonth(), m_ctime1.GetDay(),
                             0, 0, 0).GetTime());

      if (m_rule == PWSMatch::MR_BETWEEN) {
        m_time_t2 = (time_t)(CTime(m_ctime2.GetYear(), m_ctime2.GetMonth(), m_ctime2.GetDay(),
                               0, 0, 0).GetTime());
        if (m_time_t1 >= m_time_t2) {
          CGeneralMsgBox gmb;
          gmb.AfxMessageBox(IDS_DATE1NOTB4DATE2);
          return;
        }
      }
    } else {
      /* Relative */
      if (m_rule != PWSMatch::MR_BETWEEN)
        m_num2 = 0;
    }
  }

  CFilterBaseDlg::OnOK();
}

void CFilterDateDlg::OnDtnDatetime1Change(NMHDR *pNotifyStruct, LRESULT *pLResult)
{
  LPNMDATETIMECHANGE pDTChange = reinterpret_cast<LPNMDATETIMECHANGE>(pNotifyStruct);
  *pLResult = 0;

  if ((pDTChange->dwFlags & GDT_VALID) != GDT_VALID)
    return;

  CTime ct(pDTChange->st);
  if (m_rule == PWSMatch::MR_BETWEEN && m_ctime2 <= ct) {
    m_ctime2 = ct;
    UpdateData(FALSE);
  }
}
