// *********************************************************************
//    Copyright (c) 2022  Christoph Giesselink
//    Orginially written by : DunnoWho and Jeremy Davis
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// *********************************************************************

// *********************************************************************
// MaskEdit.cpp : implementation file
// *********************************************************************

#include "stdafx.h"
#include "MaskEdit.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CMaskEdit class

IMPLEMENT_DYNAMIC(CMaskEdit, CEdit)

BEGIN_MESSAGE_MAP(CMaskEdit, CEdit)
	//{{AFX_MSG_MAP(CMaskEdit)
	ON_WM_CHAR()
	ON_WM_KEYDOWN()
	ON_WM_LBUTTONUP()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

CMaskEdit::CMaskEdit()
{
	m_bUseMask = FALSE;
	m_strMask = _T("");
	m_strLiteral = _T("");
	m_strValid = _T("");
	m_bMaskKeyInProgress = FALSE;
}

void CMaskEdit::SetMask(LPCTSTR lpMask, LPCTSTR lpLiteral, LPCTSTR lpValid)
{
	m_bUseMask = FALSE;
	if (lpMask == NULL) return;
	m_strMask = lpMask;
	if (m_strMask.IsEmpty()) return;
	if (lpLiteral != NULL)
	{
		m_strLiteral = lpLiteral;
		if (m_strLiteral.GetLength() < m_strMask.GetLength())
		{
			m_strLiteral.Empty();
		}
	}
	else
	{
		m_strLiteral.Empty();
	}
	if (lpValid != NULL)
	{
		m_strValid = lpValid;
	}
	else
	{
		m_strValid.Empty();
	}
	m_bUseMask = TRUE;
}

void CMaskEdit::SendChar(UINT nChar)
{
	m_bMaskKeyInProgress = TRUE;
	SendMessage(WM_CHAR, nChar, 1);
	m_bMaskKeyInProgress = FALSE;
}

void CMaskEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	if (!m_bMaskKeyInProgress && !CheckChar(nChar))
	{
		return;
	}

	if (m_bUseMask)
	{
		CString strWnd;
		GetWindowText(strWnd);

		if (isprint(nChar))
		{
			// si un masque existe, on est en insert mode
			int startPos, endPos;
			GetSel(startPos, endPos);
			SetSel(startPos, endPos+1);
			Clear();
			strWnd.SetAt(endPos, nChar);
		}
		else if (nChar == VK_BACK)
		{
			int startPos, endPos, nLitStart;
			GetSel(startPos, endPos);
			nLitStart = (!m_strLiteral.IsEmpty()) ? m_strLiteral.Find(_T('_')) : 0;
			if ((startPos == endPos) && (startPos > nLitStart) && (startPos <= strWnd.GetLength()))
			{
				char c;
				// get the masked literal representation
				if (!m_strLiteral.IsEmpty())
				{
					// goto next literal
					do
					{
						c = m_strLiteral[--startPos];
					}
					while (startPos > nLitStart && c != _T('_'));
				}
				TRACE("m_strMaskLiteral = [%s](%s)\n", m_strLiteral , strWnd);
				// back space the cursor
				SendMessage(WM_KEYDOWN, VK_LEFT, 0);
				if (!m_strLiteral.IsEmpty())
				{
					// update the char backspacing over
					SendChar(c);
					SendMessage(WM_KEYDOWN, VK_LEFT, 0);
				}
			}
			else // out of range or have more than one char selected
			{
				MessageBeep((UINT)-1);
			}
			return;
		}
	}

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

	if (!m_bMaskKeyInProgress && m_bUseMask && !m_strLiteral.IsEmpty())
	{
		UINT c;

		// position after last literal
		const int nLitEnd = m_strLiteral.ReverseFind(_T('_')) + 1;

		do
		{
			int startPos, endPos;
			GetSel(startPos, endPos);
			c = _T('_');

			// make sure the string is not longer than the mask
			if (endPos < nLitEnd)
			{
				c = m_strLiteral.GetAt(endPos);
				if (c != _T('_')) SendChar(c);
			}
		}
		while (c != _T('_'));
	}
}

BOOL CMaskEdit::CheckChar(UINT nChar)
{
	UINT c;

	// do not use mask
	if (!m_bUseMask) return TRUE;

	// control character, OK
	if (!isprint(nChar)) return TRUE;

	// unselect all selections, if any
	int startPos, endPos;
	GetSel(startPos, endPos);
	SetSel(-1, 0);
	SetSel(startPos, startPos);

	// check the key against the mask
	GetSel(startPos, endPos);

	// make sure the string is not longer than the mask
	if (endPos >= m_strMask.GetLength())
	{
		MessageBeep((UINT)-1);
		return FALSE;
	}

	// check to see if a literal is in this position
	c = _T('_');
	if (!m_strLiteral.IsEmpty())
	{
		c = m_strLiteral.GetAt(endPos);
	}

	if (c != _T('_'))
	{
		// not on a literal position
		MessageBeep((UINT)-1);
		return FALSE;
	}

	BOOL doit = FALSE;

	// have validation string then check for valid string character
	if (!m_strValid.IsEmpty() && m_strValid.Find(nChar) == -1)
	{
		MessageBeep((UINT)-1);
		return FALSE;	// invalid character
	}

	// check the key against the mask
	c = m_strMask.GetAt(endPos);
	switch (c)
	{
	case _T('0'):	// digit only
		doit = _istdigit(nChar);
		break;
	case _T('9'):	// digit or space
		doit = _istdigit(nChar) || nChar == VK_SPACE;
		break;
	case _T('#'):	// digit or space or '+' or '-'
		doit = _istdigit(nChar) || nChar == VK_SPACE || nChar == VK_ADD || nChar == VK_SUBTRACT;
		break;
	case _T('L'):	// alpha only
		doit = _istalpha(nChar);
		break;
	case _T('?'):	// alpha or space
		doit = _istalpha(nChar) || nChar == VK_SPACE;
		break;
	case _T('A'):	// alpha numeric only
		doit = _istalnum(nChar);
		break;
	case _T('a'):	// alpha numeric or space
		doit = _istalnum(nChar) || nChar == VK_SPACE;
		break;
	case _T('&'):	// all print character only
		doit = _istprint(nChar);
		break;
	}

	if (!doit)
	{
		MessageBeep((UINT)-1);
	}
	return doit;
}

void CMaskEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	// si un masque existe, tester les touches spéciales
	if (m_bUseMask)
	{
		switch (nChar)
		{
		case VK_DELETE:
		case VK_INSERT: return;

		case VK_HOME:
			if (!m_strLiteral.IsEmpty())
			{
				int nPos = m_strLiteral.Find(_T('_'));
				CEdit::SetSel(nPos,nPos);
				return;
			}
			break;
		case VK_END:
			if (!m_strLiteral.IsEmpty())
			{
				int nPos = m_strLiteral.ReverseFind(_T('_')) + 1;
				CEdit::SetSel(nPos,nPos);
				return;
			}
			break;
		case VK_UP:
		case VK_LEFT:
			if (!m_strLiteral.IsEmpty())
			{
				int nStart, nEnd;
				CEdit::GetSel(nStart, nEnd);

				int nPos = m_strLiteral.Left(nStart).ReverseFind(_T('_'));
				if (nPos != -1)
				{
					CEdit::SetSel(nPos,nPos);
				}
				return;
			}
			break;
		case VK_DOWN:
		case VK_RIGHT:
			if (!m_strLiteral.IsEmpty())
			{
				int nStart, nEnd;
				CEdit::GetSel(nStart, nEnd);

				int nPos = m_strLiteral.Find(_T('_'),nStart+1);
				if (nPos == -1)
				{
					nPos = m_strLiteral.ReverseFind(_T('_')) + 1;
				}
				CEdit::SetSel(nPos,nPos);
				return;
			}
			break;
		}
	}
	CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
}

void CMaskEdit::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: Add your message handler code here and/or call default
	if (!m_strLiteral.IsEmpty())
	{
		int nStart, nEnd;
		CEdit::GetSel(nStart, nEnd);

		int nLitStart = m_strLiteral.Left(nStart).ReverseFind(_T('_'));
		if (nLitStart == -1)
		{
			nLitStart = m_strLiteral.Find(_T('_'));
		}

		int nLitEnd = m_strLiteral.Find(_T('_'),nStart+1);
		if (nLitEnd == -1)
		{
			nLitEnd = m_strLiteral.ReverseFind(_T('_')) + 1;
		}

		// outside literals
		if (nStart <= nLitStart)
		{
			CEdit::SetSel(nLitStart, nLitStart);
		}
		else if (nEnd > nLitEnd)
		{
			CEdit::SetSel(nLitEnd, nLitEnd);
		}
		// not on a literal
		else if (m_strLiteral[nStart] != _T('_'))
		{
			// goto next literal
			nStart = nEnd = m_strLiteral.Find(_T('_'),nStart + 1);
			CEdit::SetSel(nStart, nEnd);
		}

	}
	else
	{
		CEdit::SetSel(0, -1);
	}

	CEdit::OnLButtonUp(nFlags, point);
}
