// this file is part of notepad++
// Copyright (C)2003 Harry <harrybharry@users.sourceforge.net>
// 
// 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.
// 
// Note that the GPL places important restrictions on "derived works", yet
// it does not provide a detailed definition of that term.  To avoid      
// misunderstandings, we consider an application to constitute a          
// "derivative work" for the purpose of this license if it does any of the
// following:                                                             
// 1. Integrates source code from Notepad++.
// 2. Integrates/includes/aggregates Notepad++ into a proprietary executable
//    installer, such as those produced by InstallShield.
// 3. Links to a library or executes a program that does any of the above.
//
// 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., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "precompiledHeaders.h"
#include "SmartHighlighter.h"
#include "ScintillaEditView.h"
#include "FindReplaceDlg.h"

#define MAXLINEHIGHLIGHT 400	//prevent highlighter from doing too much work when a lot is visible

SmartHighlighter::SmartHighlighter(FindReplaceDlg * pFRDlg)
: _pFRDlg(pFRDlg)
{
	//Nothing to do
}

void SmartHighlighter::highlightView(ScintillaEditView * pHighlightView)
{
	//Get selection
	CharacterRange range = pHighlightView->getSelection();

	//Clear marks
	pHighlightView->clearIndicator(SCE_UNIVERSAL_FOUND_STYLE_SMART);

	//If nothing selected, dont mark anything
	if (range.cpMin == range.cpMax)
	{
		return;
	}

	int textlen = range.cpMax - range.cpMin + 1;

	char * text2Find = new char[textlen];
	pHighlightView->getSelectedText(text2Find, textlen, false);	//do not expand selection (false)

	bool valid = true;
	//The word has to consist if wordChars only, and the characters before and after something else
	if (!isQualifiedWord(text2Find))
		valid = false;
	else
	{
		UCHAR c = (UCHAR)pHighlightView->execute(SCI_GETCHARAT, range.cpMax);
		if (c)
		{
			if (isWordChar(char(c)))
				valid = false;
		}
		c = (UCHAR)pHighlightView->execute(SCI_GETCHARAT, range.cpMin-1);
		if (c)
		{
			if (isWordChar(char(c)))
				valid = false;
		}
	}
	if (!valid) {
		delete [] text2Find;
		return;
	}

	// save target locations for other search functions
	int originalStartPos = (int)pHighlightView->execute(SCI_GETTARGETSTART);
	int originalEndPos = (int)pHighlightView->execute(SCI_GETTARGETEND);

	// Get the range of text visible and highlight everything in it
	int firstLine =		(int)pHighlightView->execute(SCI_GETFIRSTVISIBLELINE);
	int nrLines =	min((int)pHighlightView->execute(SCI_LINESONSCREEN), MAXLINEHIGHLIGHT ) + 1;
	int lastLine =		firstLine+nrLines;
	int startPos =		0;//(int)pHighlightView->execute(SCI_POSITIONFROMLINE, firstLine);
	int endPos =		0;//(int)pHighlightView->execute(SCI_POSITIONFROMLINE, lastLine);
	//if (endPos == -1) {	//past EOF
	//	endPos =		(int)pHighlightView->getCurrentDocLen() - 1;
	//}
	int currentLine = firstLine;
	int prevDocLineChecked = -1;	//invalid start

	const NppGUI & nppGUI = NppParameters::getInstance()->getNppGUI();

	FindOption fo;
	fo._isMatchCase = nppGUI._smartHiliteCaseSensitive;
	fo._isWholeWord = true;

	const TCHAR * searchText = NULL;
#ifdef UNICODE
	WcharMbcsConvertor *wmc = WcharMbcsConvertor::getInstance();
	unsigned int cp = pHighlightView->execute(SCI_GETCODEPAGE); 
	const TCHAR * text2FindW = wmc->char2wchar(text2Find, cp);
	searchText = text2FindW;
#else
	searchText = text2Find;
#endif

	for(; currentLine < lastLine; ++currentLine)
	{
		int docLine = (int)pHighlightView->execute(SCI_DOCLINEFROMVISIBLE, currentLine);
		if (docLine == prevDocLineChecked)
			continue;	//still on same line (wordwrap)
		prevDocLineChecked = docLine;
		startPos = (int)pHighlightView->execute(SCI_POSITIONFROMLINE, docLine);
		endPos = (int)pHighlightView->execute(SCI_POSITIONFROMLINE, docLine+1);
		if (endPos == -1) {	//past EOF
			endPos = (int)pHighlightView->getCurrentDocLen() - 1;
			_pFRDlg->processRange(ProcessMarkAll_2, searchText, NULL, startPos, endPos, NULL, &fo);
			break;
		} else {
			_pFRDlg->processRange(ProcessMarkAll_2, searchText, NULL, startPos, endPos, NULL, &fo);
		}
	}

	// restore the original targets to avoid conflicts with the search/replace functions
	pHighlightView->execute(SCI_SETTARGETSTART, originalStartPos);
	pHighlightView->execute(SCI_SETTARGETEND, originalEndPos);
}

bool SmartHighlighter::isQualifiedWord(const char *str) const
{
	for (size_t i = 0, len = strlen(str) ; i < len ; ++i)
	{
		if (!isWordChar(str[i]))
			return false;
	}
	return true;
};

bool SmartHighlighter::isWordChar(char ch) const
{
	if ((UCHAR)ch < 0x20) 
		return false;
	
	switch(ch)
	{
		case ' ':
		case '	':
		case '\n':
		case '\r':
		case '.':
		case ',':
		case '?':
		case ';':
		case ':':
		case '!':
		case '(':
		case ')':
		case '[':
		case ']':
		case '+':
		case '-':
		case '*':
		case '/':
		case '#':
		case '@':
		case '^':
		case '%':
		case '$':
		case '"':
		case '\'':
		case '~':
		case '&':
		case '{':
		case '}':
		case '|':
		case '=':
		case '<':
		case '>':
		case '\\':
			return false;
	}
	return true;
};
