// WinMergeScript.cpp : Implementation of CWinMergeScript
#include "stdafx.h"
#include "IgnoreFieldsComma.h"
#include "WinMergeScript.h"
#include "resource.h"
#include <atlstr.h>

/////////////////////////////////////////////////////////////////////////////
// CWinMergeScript

/** 
 * @brief Get the name of the current dll
 */
LPTSTR GetDllFilename(LPTSTR name, int len)
{
	// careful for the last char, the doc does not give this detail
	name[len] = 0;
	GetModuleFileName(_Module.GetModuleInstance(), name, len-1);
	// find last backslash
	TCHAR * lastslash = _tcsrchr(name, '/');
	if (lastslash == 0)		
		lastslash = name;
	else
		lastslash ++;
	TCHAR * lastslash2 = _tcsrchr(lastslash, '\\');
	if (lastslash2 == 0)		
		lastslash2 = name;
	else
		lastslash2 ++;
	if (lastslash2 != name)
		lstrcpy(name, lastslash2);
	return name;
}

CString KeyName()
{
	TCHAR szKeyName[256];
	TCHAR name[256+1];
	GetDllFilename(name, 256);
	lstrcpy(szKeyName, _T("Software\\Thingamahoochie\\WinMerge\\Plugins\\"));
	lstrcat(szKeyName, name);
	return szKeyName;
}

CString RegReadString(const CString& key, const CString& valuename, const CString& defaultValue)
{
	CRegKey reg;
	if (reg.Open(HKEY_CURRENT_USER, key, KEY_READ) == ERROR_SUCCESS)
	{
		TCHAR value[512] = {0};
		DWORD dwSize = sizeof(value) / sizeof(value[0]);
		reg.QueryStringValue(valuename, value, &dwSize);
		return value;
	}
	return defaultValue;
}

int CreateArrayFromRangeString(const TCHAR *rangestr, int (* value)[2])
{
	TCHAR name[256];

	lstrcpy(name, rangestr);

	if (name[0] == 0)
		return 0;

	// first pass : prepare the chunks
	int nValue = 0;
	TCHAR * token = _tcstok(name, _T(",_"));
	while( token != NULL )
	{
		nValue ++;
		/* Get next token: */
		token = _tcstok( NULL, _T(",_") );
	}

	if (value == 0)
		// just return the number of values
		return nValue;

	token = name;
	int i;
	for (i = 0 ; i < nValue ; i++)
	{
		value[i][0] = _tcstol(token, &token, 10);
		while (*token != 0 && !_istdigit(*token))
			token ++;
		if (token[0] == 0)
		{
			value[i][1] = value[i][0];
		}
		else
		{
			value[i][1] = _tcstol(token, &token, 10);
		}
		token = token + _tcslen(token) + 1;
	}

	return nValue;
}

CString CreateRangeStringFromArray(int nExcludedRanges, const int aExcludedRanges[][2])
{
	CString rangestr = _T("");
	for (int i = 0; i < nExcludedRanges; ++i)
	{
		TCHAR value[256];
		if (aExcludedRanges[i][0] > 0 && aExcludedRanges[i][1] > 0)
		{
			if (aExcludedRanges[i][0] == aExcludedRanges[i][1])
			{
				wsprintf(value, _T("%d"), aExcludedRanges[i][0]);
				rangestr += value;
			}
			else
			{
				wsprintf(value, _T("%d-%d"), aExcludedRanges[i][0], aExcludedRanges[i][1]);
				rangestr += value;
			}
			rangestr += _T(",");
		}
	}
	if (!rangestr.IsEmpty())
		rangestr.Delete(rangestr.GetLength() - 1);

	return rangestr;
}

CString GetColumnRangeString()
{
	TCHAR name[256+1];
	GetDllFilename(name, 256);
	CString rangestr;
	TCHAR * token = _tcspbrk(name, _T(",_"));
	if (!token)
		rangestr = RegReadString(KeyName(), _T("ColumnRanges"), _T(""));
	else
		rangestr = token + 1;
	
	int nExcludedRanges = CreateArrayFromRangeString(rangestr, NULL);
	int (* aExcludedRanges)[2] = new int[nExcludedRanges][2];
	if (aExcludedRanges == NULL)
		nExcludedRanges = 0;
	else
		nExcludedRanges = CreateArrayFromRangeString(rangestr, aExcludedRanges);

	rangestr = CreateRangeStringFromArray(nExcludedRanges, aExcludedRanges);

	delete[] aExcludedRanges;

	return rangestr;
}


STDMETHODIMP CWinMergeScript::get_PluginEvent(BSTR *pVal)
{
	*pVal = SysAllocString(L"BUFFER_PREDIFF");
	return S_OK;
}

STDMETHODIMP CWinMergeScript::get_PluginDescription(BSTR *pVal)
{
	*pVal = SysAllocString(L"Ignore some fields - ignored fields list from the plugin name");
	return S_OK;
}

// not used yet
STDMETHODIMP CWinMergeScript::get_PluginFileFilters(BSTR *pVal)
{
	*pVal = SysAllocString(L"\\.csv$");
	return S_OK;
}

// not used yet
STDMETHODIMP CWinMergeScript::get_PluginIsAutomatic(VARIANT_BOOL *pVal)
{
	*pVal = VARIANT_TRUE;
	return S_OK;
}

STDMETHODIMP CWinMergeScript::PrediffBufferW(BSTR *pText, INT *pSize, VARIANT_BOOL *pbChanged, VARIANT_BOOL *pbHandled)
{
	WCHAR * pBeginText = *pText;
	long nSize = *pSize;
	WCHAR * pEndText = pBeginText + nSize;

	CString rangestr = GetColumnRangeString();

	int nExcludedRanges = CreateArrayFromRangeString(rangestr, NULL);
	int (* aExcludedRanges)[2] = new int[nExcludedRanges][2];
	if (aExcludedRanges == NULL)
		nExcludedRanges = 0;
	else
		nExcludedRanges = CreateArrayFromRangeString(rangestr, aExcludedRanges);

	if (nExcludedRanges == 0)
	{
		*pbChanged = VARIANT_FALSE;
		*pbHandled = VARIANT_TRUE;
		return S_OK;
	}

	// first column is 1 for user, but is 0 here
	int i;
	for (i = 0 ; i < nExcludedRanges ; i++)
	{
		aExcludedRanges[i][0] --;
		aExcludedRanges[i][1] --;
	}


	WCHAR * pDst = pBeginText;
	WCHAR * columnBegin;

	// change this to change the delimiter between fields
	const WCHAR delimiter = L',';

	const WCHAR quote = RegReadString(_T("Software\\Thingamahoochie\\WinMerge\\Settings"), _T("TableQuoteCharacter"), _T("\""))[0];
	const bool allowNewlinesInQuotes =
		_ttoi(RegReadString(_T("Software\\Thingamahoochie\\WinMerge\\Settings"), _T("TableAllowNewlinesInQuotes"), _T("1"))) != 0;

	// order of the column in the current line
	int iColumn = 0;
	// next excluded column range in the current line
	int nextExcludedRange = 0;

	for (columnBegin = pBeginText; columnBegin < pEndText ; )
	{
		bool inQuote = false;
		// search for the end of the column (columnEnd = first excluded character)
		WCHAR * columnEnd = columnBegin;
		if (allowNewlinesInQuotes)
		{
			while (columnEnd < pEndText &&
				!(!inQuote && (*columnEnd == L'\n' || *columnEnd == L'\r' || *columnEnd == delimiter)))
			{
				if (*columnEnd == quote)
					inQuote = !inQuote;
				columnEnd++;
			}
		}
		else
		{
			while (columnEnd < pEndText &&
				!(*columnEnd == L'\n' || *columnEnd == L'\r' || (!inQuote && *columnEnd == delimiter)))
			{
				if (*columnEnd == quote)
					inQuote = !inQuote;
				columnEnd++;
			}
		}

		// determine the status of this column
		if (nextExcludedRange < nExcludedRanges && iColumn > aExcludedRanges[nextExcludedRange][1])
			nextExcludedRange ++;
		BOOL bIsColumnIncluded = TRUE;
		if (nextExcludedRange < nExcludedRanges && iColumn >= aExcludedRanges[nextExcludedRange][0])
			bIsColumnIncluded = FALSE;

		// copy the characters of included columns
		if (bIsColumnIncluded)
		{
			wcsncpy(pDst, columnBegin, columnEnd - columnBegin);
			pDst += columnEnd - columnBegin;
		}

		// advance the cursor
		columnBegin = columnEnd;

		// keep possible tabulation (only one)
		if (columnBegin < pEndText)
			if (*columnBegin == delimiter)
			{
				// copy the tabulation
				*pDst = delimiter;
				pDst ++;
				// advance the cursor
				columnBegin ++;
				// next column
				iColumn ++;
			}

		// keep possible EOL characters 
		WCHAR * eolEnd = columnBegin;
		while (eolEnd < pEndText && (*eolEnd == L'\n' || *eolEnd == L'\r'))
		{
			eolEnd ++;
		}

		if (eolEnd > columnBegin)
		{
			// copy the EOL characters
			wcsncpy(pDst, columnBegin, eolEnd - columnBegin);
			pDst += eolEnd - columnBegin;
			// advance the cursor
			columnBegin = eolEnd;
			// reset the column counter
			iColumn = 0;
			nextExcludedRange = 0;
		}
	}


	delete [] aExcludedRanges;

	// set the new size
	*pSize = pDst - pBeginText;

	if (*pSize == nSize)
		*pbChanged = VARIANT_FALSE;
	else
		*pbChanged = VARIANT_TRUE;

	*pbHandled = VARIANT_TRUE;
	return S_OK;
}

INT_PTR CALLBACK DlgProc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
	switch(uiMsg) {
	case WM_INITDIALOG:
		SetDlgItemText(hWnd, IDC_EDIT1, GetColumnRangeString());
		return TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK)
		{
			CRegKey	reg;
			if (reg.Create(HKEY_CURRENT_USER, KeyName(), REG_NONE, REG_OPTION_NON_VOLATILE, KEY_WRITE) != ERROR_SUCCESS)
				return FALSE;
			TCHAR value[512] = {0};
			GetDlgItemText(hWnd, IDC_EDIT1, value, sizeof(value)/sizeof(TCHAR));
			reg.SetStringValue(_T("ColumnRanges"), value);
			EndDialog(hWnd, IDOK);
		}
		else if (LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hWnd, IDCANCEL);
		}
		return TRUE;
		break;

	default:
		break;
	}
	return FALSE;
}

STDMETHODIMP CWinMergeScript::ShowSettingsDialog(VARIANT_BOOL *pbHandled)
{
	*pbHandled = (DialogBox(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc) == IDOK);
	return S_OK;
}
