//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//
// copyright            : (C) 2008 by Eran Ifrah
// file name            : stringsearcher.cpp
//
// -------------------------------------------------------------------------
// A
//              _____           _      _     _ _
//             /  __ \         | |    | |   (_) |
//             | /  \/ ___   __| | ___| |    _| |_ ___
//             | |    / _ \ / _  |/ _ \ |   | | __/ _ )
//             | \__/\ (_) | (_| |  __/ |___| | ||  __/
//              \____/\___/ \__,_|\___\_____/_|\__\___|
//
//                                                  F i l e
//
//    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.
//
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
#include "stringsearcher.h"
#include <wx/regex.h>
#include <algorithm>
#include <string>

extern unsigned int UTF8Length(const wchar_t *uptr, unsigned int tlen);

static std::wstring Reverse(const std::wstring &str)
{
	// reverse the string content
	std::wstring tmp;
	tmp.reserve( str.length() );
	
	std::wstring::const_reverse_iterator riter = str.rbegin();
	for(; riter != str.rend(); riter++) {
		tmp += *riter;
	}
	return tmp;
}

wxString StringFindReplacer::GetString(const wxString& input, int from, bool search_up)
{
	if (from < 0) {
		from = 0;
	}

	if ( !search_up ) {

		if (from >= (int)input.Len()) {
			return wxEmptyString;
		}
		return input.Mid((size_t)from);

	} else {
		if (from >= (int)input.Len()) {
			from = (int)input.Len();
		}
		return input.Mid(0, (size_t)from);
	}
}

bool StringFindReplacer::DoRESearch(const wxString& input, int startOffset, const wxString& find_what, size_t flags, int& pos, int& matchLen)
{
	wxString str = GetString(input, startOffset, flags & wxSD_SEARCH_BACKWARD ? true : false);
	if (str.IsEmpty()) {
		return false;
	}

#ifndef __WXMAC__
	int re_flags = wxRE_ADVANCED;
#else
	int re_flags = wxRE_DEFAULT;
#endif
	wxRegEx re;
	bool matchCase = flags & wxSD_MATCHCASE ? true : false;
	if ( !matchCase ) re_flags |= wxRE_ICASE;
	re.Compile(find_what, re_flags);

	// incase we are scanning NOT backwared, set the offset
	if (!( flags & wxSD_SEARCH_BACKWARD )) {
		pos = startOffset;
	}

	if ( re.IsValid() ) {
		if ( flags & wxSD_SEARCH_BACKWARD ) {
			size_t start(0), len(0);
			bool matched(false);

			// get the last match
			while (re.Matches(str)) {
				re.GetMatch(&start, &len);
				pos += start;
				if ( matched ) {
					pos += matchLen;
				}
				matchLen = len;
				matched = true;
				str = str.Mid(start + len);
			}

			if ( matched ) {
				return true;
			}

		} else if ( re.Matches(str)) {
			size_t start, len;
			re.GetMatch(&start, &len);
			pos += start;
			matchLen = len;
			return true;
		}
	}
	return false;
}

bool StringFindReplacer::DoSimpleSearch(const wchar_t* pinput, int startOffset, const wchar_t* pfind_what, size_t flags, int& pos, int& matchLen)
{
#if wxVERSION_NUMBER >= 2900	
	std::wstring input    (pinput);
	std::wstring find_what(pfind_what);
	
	int from = startOffset;
	if (from < 0) {
		from = 0;
	}
	std::wstring str;
	bool search_up = flags & wxSD_SEARCH_BACKWARD;
	if ( !search_up ) {

		if (from >= (int)input.length()) {
			str.clear();
		}
		str = input.substr((size_t)from);

	} else {
		if (from >= (int)input.length() ) {
			from = (int)input.length();
		}
		str = input.substr(0, (size_t)from);
	}
#else
	wxString input(pinput);
	wxString find_what(pfind_what);
	std::wstring str = GetString(pinput, startOffset, flags & wxSD_SEARCH_BACKWARD ? true : false).c_str();
#endif

	size_t init_size = str.length();

	if (str.empty()) {
		return false;
	}

	std::wstring find_str(find_what);
	size_t offset(0);

	// incase we are scanning backwared, revert the strings
	if ( flags & wxSD_SEARCH_BACKWARD ) {
		find_str = Reverse(find_str);
		str = Reverse(str);
	} else {
		offset = startOffset;
	}

	bool matchCase = flags & wxSD_MATCHCASE ? true : false;
	if ( !matchCase ) {
		std::transform(find_str.begin(), find_str.end(), find_str.begin(), towlower);
		std::transform(str.begin(), str.end(), str.begin(), towlower);
	}

	size_t upos = str.find(find_str);

	while ( upos != std::wstring::npos ) {
		if (flags & wxSD_MATCHWHOLEWORD) {
			// full word match
			// test that the characeter at upos - 1 & the character at upos + find_str.Len() are not
			// valid word char [a-zA-Z0-9_]
			if (upos > 1) {
				if(isalpha(str[upos-1]) || (int)str[upos] == (int)'_') {
					// remove the part that already been scanned
					// and search again
					str = str.substr(upos+find_what.length());
					offset += upos+find_what.length();
					upos = str.find(find_str);
					continue;
				}
			}
			int charAfterOff = upos + find_str.length();
			if (charAfterOff < (int)str.length()) {
				if(isalpha(str[charAfterOff]) || (int)str[charAfterOff] == (int)'_') {
					// remove the part that already been scanned
					// and search again
					str = str.substr(upos+find_what.length());
					offset += upos+find_what.length();
					upos = str.find(find_str);
					continue;
				}
			}

			matchLen = (int)find_str.length();
			// mirror the result as well
			if (flags & wxSD_SEARCH_BACKWARD) {
				upos = (init_size - (upos + offset + matchLen));
			} else {
				upos += offset;
			}
			pos = (int)upos;
			return true;
		} else {
			// we got a match
			matchLen = (int)find_str.length();
			if (flags & wxSD_SEARCH_BACKWARD) {
				upos = (init_size - (upos + offset + matchLen));
			} else {
				upos += offset;
			}
			pos = (int)upos;
			return true;
		}
	}
	return false;
}

bool StringFindReplacer::Search(const wchar_t* input, int startOffset, const wchar_t* find_what, size_t flags, int &pos, int &matchLen, int& posInChars, int& matchLenInChars)
{
	// adjust startOffset due to it is in bytes but should be in chars
	int iSO = startOffset;
	while (iSO > 0 && (int)UTF8Length(input, iSO) > startOffset) iSO--;
	startOffset = iSO;

	bool bResult = false;
	if (flags & wxSD_REGULAREXPRESSION) {
		bResult = DoRESearch(input, startOffset, find_what, flags, posInChars, matchLenInChars);
	} else {
		bResult = DoSimpleSearch(input, startOffset, find_what, flags, posInChars, matchLenInChars);
	}
	// correct search Pos and Length owing to non plain ASCII multibyte characters
	if (bResult) {
		pos = UTF8Length(input, posInChars);
		if (flags & wxSD_REGULAREXPRESSION) {
			matchLen = UTF8Length(input, posInChars + matchLenInChars) - pos;
		} else {
			matchLen = UTF8Length(find_what, matchLenInChars);
		}
	}
	return bResult;
}

bool StringFindReplacer::Search(const wchar_t* input, int startOffset, const wchar_t* find_what, size_t flags, int &pos, int &matchLen)
{
	int posInChars(0), matchLenInChars(0);
	return StringFindReplacer::Search(input, startOffset, find_what, flags, pos, matchLen, posInChars, matchLenInChars);
}
