// XTPSyntaxEditLineMarksManager.cpp : implementation file
//
// This file is a part of the XTREME TOOLKIT PRO MFC class library.
// (c)1998-2012 Codejock Software, All Rights Reserved.
//
// THIS SOURCE FILE IS THE PROPERTY OF CODEJOCK SOFTWARE AND IS NOT TO BE
// RE-DISTRIBUTED BY ANY MEANS WHATSOEVER WITHOUT THE EXPRESSED WRITTEN
// CONSENT OF CODEJOCK SOFTWARE.
//
// THIS SOURCE CODE CAN ONLY BE USED UNDER THE TERMS AND CONDITIONS OUTLINED
// IN THE XTREME SYNTAX EDIT LICENSE AGREEMENT. CODEJOCK SOFTWARE GRANTS TO
// YOU (ONE SOFTWARE DEVELOPER) THE LIMITED RIGHT TO USE THIS SOFTWARE ON A
// SINGLE COMPUTER.
//
// CONTACT INFORMATION:
// support@codejock.com
// http://www.codejock.com
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"

// common includes
#include "Common/XTPSmartPtrInternalT.h"

// syntax editor includes
#include "XTPSyntaxEditLineMarksManager.h"

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

/////////////////////////////////////////////////////////////////////////////
// CXTPSyntaxEditLineMarksManager

IMPLEMENT_DYNAMIC(CXTPSyntaxEditLineMarksManager, CCmdTarget)

////////////////////////////////////////////////////////////////////////////
XTP_EDIT_LMPARAM::~XTP_EDIT_LMPARAM()
{
}

XTP_EDIT_LMPARAM::XTP_EDIT_LMPARAM()
{
	Clear();
}

XTP_EDIT_LMPARAM::XTP_EDIT_LMPARAM(const XTP_EDIT_LMPARAM& rSrc)
{
	*this = rSrc;
}

XTP_EDIT_LMPARAM::XTP_EDIT_LMPARAM(DWORD dwVal)
{
	m_eType = xtpEditLMPT_DWORD;
	m_dwValue = dwVal;
}

XTP_EDIT_LMPARAM::XTP_EDIT_LMPARAM(double dblValue)
{
	m_eType = xtpEditLMPT_double;
	m_dblValue = dblValue;
}

XTP_EDIT_LMPARAM::XTP_EDIT_LMPARAM(void* pPtr)
{
	m_eType = xtpEditLMPT_Ptr;

	*this = pPtr;
}

const XTP_EDIT_LMPARAM& XTP_EDIT_LMPARAM::operator=(const XTP_EDIT_LMPARAM& rSrc)
{
	m_eType = rSrc.m_eType;

	if (m_eType == xtpEditLMPT_Unknown)
	{
		Clear();
	}
	else if (m_eType == xtpEditLMPT_DWORD)
	{
		m_dwValue = rSrc.m_dwValue;
	}
	else if (m_eType == xtpEditLMPT_double)
	{
		m_dblValue = rSrc.m_dblValue;
	}
	else if (m_eType == xtpEditLMPT_Ptr)
	{
		m_Ptr = rSrc.m_Ptr;
	}
	else
	{
		ASSERT(FALSE);
	}

	return *this;
}

const XTP_EDIT_LMPARAM& XTP_EDIT_LMPARAM::operator=(DWORD dwValue)
{
	m_Ptr = NULL;

	m_eType = xtpEditLMPT_DWORD;
	m_dwValue = dwValue;
	return *this;
}

const XTP_EDIT_LMPARAM& XTP_EDIT_LMPARAM::operator=(double dblValue)
{
	m_Ptr = NULL;

	m_eType = xtpEditLMPT_double;
	m_dblValue = dblValue;
	return *this;
}

void XTP_EDIT_LMPARAM::SetPtr(void* pPtr, CXTPSyntaxEditVoidObj::TPFDeleter pfDeleter)
{
	ASSERT(!pPtr || pPtr != m_Ptr);

	m_eType = xtpEditLMPT_Ptr;

	CXTPSyntaxEditVoidObj* pVoidObj = NULL;
	if (pPtr)
	{
		pVoidObj = new CXTPSyntaxEditVoidObj(pPtr, pfDeleter);
	}
	m_Ptr = pVoidObj;
}

const XTP_EDIT_LMPARAM& XTP_EDIT_LMPARAM::operator=(void* pPtr)
{
	SetPtr(pPtr, NULL);
	return *this;
}

XTP_EDIT_LMPARAM::operator DWORD() const
{
	if (m_eType == xtpEditLMPT_DWORD)
	{
		return m_dwValue;
	}
	ASSERT(FALSE);
	return 0;
}

XTP_EDIT_LMPARAM::operator double() const
{
	if (m_eType == xtpEditLMPT_double)
	{
		return m_dblValue;
	}
	else if (m_eType == xtpEditLMPT_DWORD)
	{
		return m_dwValue;
	}
	ASSERT(FALSE);
	return 0;
}

XTP_EDIT_LMPARAM::operator void*() const
{
	return GetPtr();
}

void* XTP_EDIT_LMPARAM::GetPtr() const
{
	if (m_eType == xtpEditLMPT_Ptr)
	{
		return m_Ptr ? m_Ptr->GetPtr() : NULL;
	}
	return NULL;
}

BOOL XTP_EDIT_LMPARAM::IsValid() const
{
	return  m_eType == xtpEditLMPT_DWORD ||
			m_eType == xtpEditLMPT_double ||
			m_eType == xtpEditLMPT_Ptr;
}

void XTP_EDIT_LMPARAM::Clear()
{
	m_eType = xtpEditLMPT_Unknown;

	m_dwValue = 0;
	m_dblValue = 0;
	m_Ptr = NULL;
}

////////////////////////////////////////////////////////////////////////////
//struct XTP_EDIT_LMDATA

XTP_EDIT_LMDATA::~XTP_EDIT_LMDATA()
{
}

XTP_EDIT_LMDATA::XTP_EDIT_LMDATA()
{
	m_nRow = 0;
}

XTP_EDIT_LMDATA::XTP_EDIT_LMDATA(const XTP_EDIT_LMDATA& rSrc)
{
	*this = rSrc;
}

const XTP_EDIT_LMDATA& XTP_EDIT_LMDATA::operator=(const XTP_EDIT_LMDATA& rSrc)
{
	m_nRow = rSrc.m_nRow;
	m_Param = rSrc.m_Param;

	return *this;
}

/////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
CXTPSyntaxEditLineMarksManager::CLineMarksList::~CLineMarksList()
{
	RemoveAll();
}

int CXTPSyntaxEditLineMarksManager::CLineMarksList::FindIndex(const int nKey) const
{
	const int nCount = (int)m_array.GetSize();

	// binary search
	int nLeftIndex = 0, nRightIndex = nCount - 1;
	while (nRightIndex >= nLeftIndex)
	{
		XTP_EDIT_LMDATA* pLeftData = m_array.GetAt(nLeftIndex);
		XTP_EDIT_LMDATA* pRightData = m_array.GetAt(nRightIndex);
		if (!pLeftData || !pRightData)
			break;

		if (nKey == pLeftData->m_nRow)
			return nLeftIndex;

		if (nKey == pRightData->m_nRow)
			return nRightIndex;

		int nMediumIndex = (nRightIndex + nLeftIndex) / 2;

		if (nMediumIndex == nRightIndex || nMediumIndex == nLeftIndex)
			break;

		XTP_EDIT_LMDATA* pMediumData = m_array.GetAt(nMediumIndex);
		if (!pMediumData)
			break;

		if (nKey == pMediumData->m_nRow)
		{
			return nMediumIndex;
		}
		else if (nKey > pMediumData->m_nRow)
		{
			nLeftIndex = nMediumIndex;
		}
		else // if (nKey < pMediumData->m_nRow)
		{
			nRightIndex = nMediumIndex;
		}
	}
	return -1;
}

int CXTPSyntaxEditLineMarksManager::CLineMarksList::FindLowerIndex(const int nKey) const
{
	const int nCount = (int)m_array.GetSize();

	if (nCount == 0)
		return -1;

	// binary search
	int nLeftIndex = 0, nRightIndex = nCount - 1;
	while (nRightIndex >= nLeftIndex)
	{
		int nMediumIndex = (nRightIndex + nLeftIndex) / 2;

		XTP_EDIT_LMDATA* pMediumData = m_array.GetAt(nMediumIndex);
		if (!pMediumData)
			break;

		XTP_EDIT_LMDATA* pMediumNextData = nMediumIndex + 1 < nCount ?
			m_array.GetAt(nMediumIndex + 1) : NULL;

		if (nKey >= pMediumData->m_nRow &&
			(!pMediumNextData || (pMediumNextData && nKey < pMediumNextData->m_nRow)))
		{
			return nMediumIndex;
		}
		else if (nKey > pMediumData->m_nRow)
		{
			if (nLeftIndex < nMediumIndex)
				nLeftIndex = nMediumIndex;
			else if (nLeftIndex + 1 == nRightIndex)
				nLeftIndex = nRightIndex;
			else
				break;
		}
		else
		{
			if (nRightIndex > nMediumIndex)
				nRightIndex = nMediumIndex;
			else if (nRightIndex - 1 == nLeftIndex)
				nRightIndex = nLeftIndex;
			else
				break;
		}
	}

	return -1;
}

int CXTPSyntaxEditLineMarksManager::CLineMarksList::FindUpperIndex(const int nKey) const
{
	const int nCount = (int)m_array.GetSize();

	if (nCount == 0)
		return -1;

	// binary search
	int nLeftIndex = 0, nRightIndex = nCount - 1;
	while (nRightIndex >= nLeftIndex)
	{
		int nMediumIndex = (nRightIndex + nLeftIndex) / 2;

		XTP_EDIT_LMDATA* pMediumData = m_array.GetAt(nMediumIndex);
		if (!pMediumData)
			break;

		XTP_EDIT_LMDATA* pMediumPrevData = nMediumIndex > 0 ?
			m_array.GetAt(nMediumIndex - 1) : NULL;

		if (nKey <= pMediumData->m_nRow &&
			(!pMediumPrevData || (pMediumPrevData && nKey > pMediumPrevData->m_nRow)))
		{
			return nMediumIndex;
		}
		else if (nKey > pMediumData->m_nRow)
		{
			if (nLeftIndex < nMediumIndex)
				nLeftIndex = nMediumIndex;
			else if (nLeftIndex + 1 == nRightIndex)
				nLeftIndex = nRightIndex;
			else
				break;
		}
		else
		{
			if (nRightIndex > nMediumIndex)
				nRightIndex = nMediumIndex;
			else if (nRightIndex - 1 == nLeftIndex)
				nRightIndex = nLeftIndex;
			else
				break;
		}
	}
	return -1;
}

void CXTPSyntaxEditLineMarksManager::CLineMarksList::Add(const XTP_EDIT_LMDATA& lmData)
{
	int nIndex = FindUpperIndex(lmData.m_nRow);
	if (nIndex < 0)
	{
		m_array.Add(new XTP_EDIT_LMDATA(lmData));
	}
	else
	{
		XTP_EDIT_LMDATA* pOldData = m_array.GetAt(nIndex);
		if (pOldData->m_nRow == lmData.m_nRow)
		{
			// just copy data because the key already exists
			*pOldData = lmData;
		}
		else
		{
			// insert new element for the new key
			m_array.InsertAt(nIndex, new XTP_EDIT_LMDATA(lmData));
		}
	}

}

void CXTPSyntaxEditLineMarksManager::CLineMarksList::Remove(const int nKey)
{
	int nIndex = FindIndex(nKey);
	if (nIndex >= 0)
	{
		XTP_EDIT_LMDATA* pData = m_array.GetAt(nIndex);
		if (pData)
			delete pData;
		m_array.RemoveAt(nIndex);
	}
}

POSITION CXTPSyntaxEditLineMarksManager::CLineMarksList::FindAt(int nKey) const
{
	// find a record with the specified key
	INT_PTR nIndex = FindIndex(nKey);
	return nIndex >= 0 ? (POSITION)(nIndex + 1) : NULL;
}

POSITION CXTPSyntaxEditLineMarksManager::CLineMarksList::FindNext(int nKey) const
{
	INT_PTR nIndex = FindUpperIndex(nKey);
	if (nIndex >= 0)
	{
		return (POSITION)(nIndex + 1);
	}
	return NULL;
}

POSITION CXTPSyntaxEditLineMarksManager::CLineMarksList::FindPrev(int nKey) const
{
	INT_PTR nIndex = FindLowerIndex(nKey);
	if (nIndex >= 0)
	{
		return (POSITION)(nIndex + 1);
	}
	return NULL;
}

void CXTPSyntaxEditLineMarksManager::CLineMarksList::RefreshLineMarks(
			int nRowFrom, int nRowTo, int nRefreshType)
{
	int nDiff = abs(nRowTo - nRowFrom);

	BOOL bDel = (nRefreshType & xtpEditLMRefresh_Delete);
	BOOL bDel_only1 = (nRefreshType & xtpEditLMRefresh_Delete_only1);
	BOOL bDel_only2 = (nRefreshType & xtpEditLMRefresh_Delete_only2);
	BOOL bInsertAt0 = (nRefreshType & xtpEditLMRefresh_InsertAt0);

	if (nRefreshType & xtpEditLMRefresh_Delete)
		nDiff *= -1;

	if (nRefreshType & (xtpEditLMRefresh_Delete_only1 | xtpEditLMRefresh_Delete_only2))
		nDiff = 0;

	int nRowU = max(nRowTo, nRowFrom);
	int nRowL = min(nRowTo, nRowFrom);

	const int nCount = (int)m_array.GetSize();
	CXTPSyntaxEditLineMarkPointersArray newArray;
	newArray.SetSize(0, m_array.GetSize());

	int nPrevDataRow = -1;

	for (int nIndex = 0; nIndex < nCount; nIndex++)
	{
		XTP_EDIT_LMDATA* pData = m_array.GetAt(nIndex);
		if (!pData)
			continue;

		if (pData->m_nRow < nRowL ||
			pData->m_nRow == nRowL && !bDel_only1 && !bInsertAt0)
		{
			// does not affect refresh
			newArray.Add(pData);

			nPrevDataRow = pData->m_nRow;
		}
		else if (bDel && pData->m_nRow < nRowU ||
				 bDel_only1 && pData->m_nRow == nRowL ||
				 bDel_only2 && pData->m_nRow == nRowU)
		{
			// delete line mark
			delete pData;
		}
		else
		{
			// refresh position
			pData->m_nRow += nDiff;

			if (nPrevDataRow == pData->m_nRow)
			{
				// delete line mark
				delete pData;
			}
			else
			{
				newArray.Add(pData);
				nPrevDataRow = pData->m_nRow;
			}
		}
	}
	// update data in the old array
	m_array.RemoveAll();
	m_array.Append(newArray);
}

void CXTPSyntaxEditLineMarksManager::CLineMarksList::RemoveAll()
{
	const int nCount = (int)m_array.GetSize();
	for (int i = 0; i < nCount; i++)
	{
		XTP_EDIT_LMDATA* pData = m_array.GetAt(i);
		if (pData)
			delete pData;
	}

	m_array.RemoveAll();
}

int CXTPSyntaxEditLineMarksManager::CLineMarksList::GetCount() const
{
	return (int)m_array.GetSize();
}

POSITION CXTPSyntaxEditLineMarksManager::CLineMarksList::GetFirstLineMark() const
{
	return BEFORE_START_POSITION;
}

XTP_EDIT_LMDATA* CXTPSyntaxEditLineMarksManager::CLineMarksList::GetNextLineMark(POSITION& pos) const
{
	// Rule: pos == element index + 1  (because 0=NULL and mean no more elements)

	// check for the beginning of the list
	if (pos == BEFORE_START_POSITION)
	{
		pos = (POSITION)1;
	}

	// get data corresponding to the position
	XTP_EDIT_LMDATA* pData = (INT_PTR)pos > (INT_PTR)m_array.GetSize() ? NULL : m_array.GetAt((INT_PTR)pos - 1);

	// increase position
	pos = (POSITION)((INT_PTR)pos + 1);

	// check for the end of the array
	if ((INT_PTR)pos > (INT_PTR)m_array.GetSize() )
	{
		//pos = (POSITION)m_array.GetSize();
		pos = NULL;
	}

	// return the data for the initial position
	return pData;
}

XTP_EDIT_LMDATA* CXTPSyntaxEditLineMarksManager::CLineMarksList::GetLineMarkAt(const POSITION pos) const
{
	// Rule: pos == element index + 1  (because 0=NULL and mean no more elements)

	INT_PTR nIndex = (INT_PTR)pos - 1;
	if (nIndex < 0 || nIndex >= (INT_PTR)m_array.GetSize())
	{
		return NULL;
	}
	return m_array.GetAt(nIndex);
}
/////////////////////////////////////////////////////////////////////////////

CXTPSyntaxEditLineMarksManager::CLineMarksListPtr
CXTPSyntaxEditLineMarksManager::CLineMarksListsMap::GetList(LPCTSTR szMarkType) const
{
	CLineMarksListPtr ptrList;
	if (!m_map.Lookup(szMarkType, ptrList))
	{
		return NULL;
	}
	return ptrList;
}

CXTPSyntaxEditLineMarksManager::CLineMarksListPtr
CXTPSyntaxEditLineMarksManager::CLineMarksListsMap::AddList(LPCTSTR szMarkType)
{
	CLineMarksListPtr ptrList(new CLineMarksList());
	m_map.SetAt(szMarkType, ptrList);
	return ptrList;
}

void CXTPSyntaxEditLineMarksManager::CLineMarksListsMap::RefreshLineMarks(
			int nRowFrom, int nRowTo, int nRefreshType)
{
	POSITION pos = m_map.GetStartPosition();
	CString strKey;
	CLineMarksListPtr ptrList;
	while (pos != NULL)
	{
		m_map.GetNextAssoc(pos, strKey, ptrList);

		if (ptrList)
			ptrList->RefreshLineMarks(nRowFrom, nRowTo, nRefreshType);
	}
}
/////////////////////////////////////////////////////////////////////////////
CXTPSyntaxEditLineMarksManager::CXTPSyntaxEditLineMarksManager()
{
}

CXTPSyntaxEditLineMarksManager::~CXTPSyntaxEditLineMarksManager()
{
}

void CXTPSyntaxEditLineMarksManager::AddRemoveLineMark(int nRow, const XTP_EDIT_LINEMARKTYPE lmType,
													  XTP_EDIT_LMPARAM* pParam)
{
	if (HasRowMark(nRow, lmType))
	{
		DeleteLineMark(nRow, lmType);
	}
	else
	{
		SetLineMark(nRow, lmType, pParam);
	}
}

void CXTPSyntaxEditLineMarksManager::SetLineMark(int nRow, const XTP_EDIT_LINEMARKTYPE lmType,
														  XTP_EDIT_LMPARAM* pParam)
{
	// prepare data structure
	XTP_EDIT_LMDATA lmData;
	lmData.m_nRow = nRow;
	if (pParam)
	{
		lmData.m_Param = *pParam;
	}

	// get corresponding list
	CLineMarksListPtr ptrList = m_mapLists.GetList(lmType);
	if (!ptrList)
	{
		// create new list
		ptrList = m_mapLists.AddList(lmType);
		if (!ptrList)
			return; // error happened
	}

	// set value
	ptrList->Add(lmData);
}

void CXTPSyntaxEditLineMarksManager::DeleteLineMark(int nRow, const XTP_EDIT_LINEMARKTYPE lmType)
{
	// get corresponding list
	CLineMarksListPtr ptrList = m_mapLists.GetList(lmType);
	if (ptrList)
	{
		// delete line mark
		ptrList->Remove(nRow);

	}
}

BOOL CXTPSyntaxEditLineMarksManager::HasRowMark(int nRow, const XTP_EDIT_LINEMARKTYPE& lmType,
												XTP_EDIT_LMPARAM* pParam) const
{
	// get corresponding list
	CLineMarksListPtr ptrList = m_mapLists.GetList(lmType);
	if (ptrList)
	{
		// Find line data
		POSITION pos = ptrList->FindAt(nRow);
		XTP_EDIT_LMDATA* pData = ptrList->GetLineMarkAt(pos);
		if (pData)
		{
			ASSERT(pData->m_nRow == nRow);
			if (pParam)
			{
				*pParam = pData->m_Param;
			}
			return TRUE;
		}
	}

	return FALSE;
}

POSITION CXTPSyntaxEditLineMarksManager::GetLastLineMark(const XTP_EDIT_LINEMARKTYPE lmType) const
{
	int nRow = INT_MAX - 1;
	return FindPrevLineMark(nRow, lmType);
}

POSITION CXTPSyntaxEditLineMarksManager::FindPrevLineMark(int& nRow, const XTP_EDIT_LINEMARKTYPE lmType) const
{
	int nPrevRow = -1;
	POSITION posPrev = NULL;

	// get corresponding list
	CLineMarksListPtr ptrList = m_mapLists.GetList(lmType);
	if (ptrList)
	{
		// Find line data
		posPrev = ptrList->FindPrev(nRow);
		XTP_EDIT_LMDATA* pData = ptrList->GetLineMarkAt(posPrev);
		if (pData)
		{
			nPrevRow = pData->m_nRow;
		}
	}

	nRow = nPrevRow;
	return posPrev;
}

POSITION CXTPSyntaxEditLineMarksManager::FindNextLineMark(int& nRow, const XTP_EDIT_LINEMARKTYPE lmType) const
{
	int nNextRow = INT_MAX;
	POSITION posNext = NULL;

	// get corresponding list
	CLineMarksListPtr ptrList = m_mapLists.GetList(lmType);
	if (ptrList)
	{
		// Find line data
		posNext = ptrList->FindNext(nRow);
		XTP_EDIT_LMDATA* pData = ptrList->GetLineMarkAt(posNext);
		if (pData)
		{
			nNextRow = pData->m_nRow;
		}
	}

	if (nNextRow == INT_MAX)
	{
		nNextRow = -1;
	}

	nRow = nNextRow;
	return posNext;
}

POSITION CXTPSyntaxEditLineMarksManager::GetFirstLineMark(const XTP_EDIT_LINEMARKTYPE lmType) const
{
	// get corresponding list
	CLineMarksListPtr ptrList = m_mapLists.GetList(lmType);
	return ptrList ? ptrList->GetFirstLineMark() : NULL;
}

XTP_EDIT_LMDATA* CXTPSyntaxEditLineMarksManager::GetNextLineMark(POSITION& pos, const XTP_EDIT_LINEMARKTYPE lmType) const
{
	CLineMarksListPtr ptrList = m_mapLists.GetList(lmType);
	return ptrList ? ptrList->GetNextLineMark(pos) : NULL;
}

void CXTPSyntaxEditLineMarksManager::RefreshLineMarks(int nRowFrom, int nRowTo, int nRefreshType)
{
	m_mapLists.RefreshLineMarks(nRowFrom, nRowTo, nRefreshType);
}

void CXTPSyntaxEditLineMarksManager::RemoveAll(const XTP_EDIT_LINEMARKTYPE lmType)
{
	// get corresponding list
	CLineMarksListPtr ptrList = m_mapLists.GetList(lmType);
	if (ptrList)
		ptrList->RemoveAll();
}

XTP_EDIT_LMDATA* CXTPSyntaxEditLineMarksManager::GetLineMarkAt(const POSITION pos, const XTP_EDIT_LINEMARKTYPE lmType) const
{
	// get corresponding list
	CLineMarksListPtr ptrList = m_mapLists.GetList(lmType);
	return ptrList ? ptrList->GetLineMarkAt(pos) : NULL;
}

int CXTPSyntaxEditLineMarksManager::GetCount(const XTP_EDIT_LINEMARKTYPE lmType) const
{
	// get corresponding list
	CLineMarksListPtr ptrList = m_mapLists.GetList(lmType);
	return ptrList ? ptrList->GetCount() : 0;
}