/*
* This file is part of Code::Blocks Studio, an open-source cross-platform IDE
* Copyright (C) 2003  Yiannis An. Mandravellos
*
* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
* Contact e-mail: Yiannis An. Mandravellos <mandrav@codeblocks.org>
* Program URL   : http://www.codeblocks.org
*
* $Id: nativeparser.cpp,v 1.30.2.1 2005/10/25 07:59:01 mandrav Exp $
* $Date: 2005/10/25 07:59:01 $
*/

#include <sdk.h>
#include "nativeparser.h"
#include <manager.h>
#include <configmanager.h>
#include <projectmanager.h>
#include <pluginmanager.h>
#include <messagemanager.h>
#include <editormanager.h>
#include <macrosmanager.h>
#include <customvars.h>
#include <cbeditor.h>
#include <cbproject.h>
#include <cbexception.h>
#include "classbrowser.h"
#include "parser/parser.h"
#include <compilerfactory.h>

#include <cctype>

BEGIN_EVENT_TABLE(NativeParser, wxEvtHandler)
//	EVT_MENU(THREAD_START, NativeParser::OnThreadStart)
//	EVT_MENU(THREAD_END, NativeParser::OnThreadEnd)
	EVT_MENU(PARSER_END, NativeParser::OnParserEnd)
END_EVENT_TABLE()

NativeParser::NativeParser()
	: m_Parsers(1)
{
	//ctor
    m_pClassBrowser = 0L;
}

NativeParser::~NativeParser()
{
	RemoveClassBrowser();
	ClearParsers();
}

void NativeParser::CreateClassBrowser()
{
	if (!m_pClassBrowser)
		m_pClassBrowser = new ClassBrowser(Manager::Get()->GetNotebook(), this);
}

void NativeParser::RemoveClassBrowser(bool appShutDown)
{
    if (!appShutDown && m_pClassBrowser)
    {
        delete m_pClassBrowser;
    }
    m_pClassBrowser = 0L;
}

void NativeParser::RereadParserOptions()
{
	bool needsReparsing = false;
	for (ParsersMap::iterator it = m_Parsers.begin(); it != m_Parsers.end(); ++it)
	{
		Parser* parser = it->second;
		if (parser)
		{
			ParserOptions opts = parser->Options();
			parser->ReadOptions();
			if (opts.followLocalIncludes != parser->Options().followLocalIncludes ||
				opts.followGlobalIncludes != parser->Options().followGlobalIncludes ||
				opts.wantPreprocessor != parser->Options().wantPreprocessor)
			{
				// important options changed... flag for reparsing
				needsReparsing = true;
			}
		}
	}
	if (needsReparsing)
	{
		if (wxMessageBox(_("You changed some class parser options. Do you want to "
						"reparse your projects now, using the new options?"),
						_("Reparse?"),
						wxYES | wxNO | wxICON_QUESTION) == wxYES)
		{
			cbProject* proj = Manager::Get()->GetProjectManager()->GetActiveProject();
			ClearParsers();
			ProjectsArray* projects = Manager::Get()->GetProjectManager()->GetProjects();
			for (unsigned int i = 0; i < projects->GetCount(); ++i)
			{
				AddParser(projects->Item(i));
			}
			if (m_pClassBrowser)
				m_pClassBrowser->SetParser(m_Parsers[proj]);
		}
	}
	if (m_pClassBrowser)
		m_pClassBrowser->Update();
}

void NativeParser::SetClassBrowserProject(cbProject* project)
{
    if (m_pClassBrowser)
		m_pClassBrowser->SetParser(m_Parsers[project]);
}

void NativeParser::SetCBViewMode(const BrowserViewMode& mode)
{
	for (ParsersMap::iterator it = m_Parsers.begin(); it != m_Parsers.end(); ++it)
	{
		Parser* parser = it->second;
		if (parser)
		{
			parser->ClassBrowserOptions().showInheritance = mode == bvmInheritance;
		}
	}
	if (m_pClassBrowser)
		m_pClassBrowser->Update();
}

void NativeParser::ClearParsers()
{
	if (m_pClassBrowser)
		m_pClassBrowser->SetParser(0L);
	for (ParsersMap::iterator it = m_Parsers.begin(); it != m_Parsers.end(); ++it)
	{
		Parser* parser = it->second;
		if (parser)
		{
			delete parser;
        }
	}
	m_Parsers.clear();
}

void NativeParser::AddCompilerDirs(Parser* parser, cbProject* project)
{
	if (!parser)
		return;

    parser->IncludeDirs().Clear();
    wxString base = project->GetBasePath();
    parser->IncludeDirs().Add(base); // add project's base path

    Compiler* compiler = 0;
    // apply compiler global vars
	if (CompilerFactory::Compilers.GetCount() > 0 && CompilerFactory::CompilerIndexOK(project->GetCompilerIndex()))
	{
		compiler = CompilerFactory::Compilers[project->GetCompilerIndex()];
        compiler->GetCustomVars().ApplyVarsToEnvironment();
	}
    // apply project vars
    project->GetCustomVars().ApplyVarsToEnvironment();

    // get project include dirs
    for (unsigned int i = 0; i < project->GetIncludeDirs().GetCount(); ++i)
    {
    	wxString out = project->GetIncludeDirs()[i];
    	Manager::Get()->GetMacrosManager()->ReplaceEnvVars(out);
        wxFileName dir(out);
        wxLogNull ln; // hide the error log about "too many ..", if the relative path is invalid
        if (!dir.IsAbsolute())
            dir.Normalize(wxPATH_NORM_ALL & ~wxPATH_NORM_CASE, base);
        if (dir.IsOk() && parser->IncludeDirs().Index(dir.GetFullPath()) == wxNOT_FOUND)
        {
            parser->IncludeDirs().Add(dir.GetFullPath());
//            Manager::Get()->GetMessageManager()->DebugLog("Parser prj dir: " + dir.GetFullPath());
        }
    }

    // get targets include dirs
    for (int i = 0; i < project->GetBuildTargetsCount(); ++i)
    {
        ProjectBuildTarget* target = project->GetBuildTarget(i);
        if (target)
        {
        	// apply target vars
            target->GetCustomVars().ApplyVarsToEnvironment();
            for (unsigned int ti = 0; ti < target->GetIncludeDirs().GetCount(); ++ti)
            {
                wxString out = target->GetIncludeDirs()[ti];
                Manager::Get()->GetMacrosManager()->ReplaceEnvVars(out);
                wxFileName dir(out);
                wxLogNull ln; // hide the error log about "too many ..", if the relative path is invalid
                if (!dir.IsAbsolute())
                    dir.Normalize(wxPATH_NORM_ALL & ~wxPATH_NORM_CASE, base);
                if (dir.IsOk() && parser->IncludeDirs().Index(dir.GetFullPath()) == wxNOT_FOUND)
                {
                    parser->IncludeDirs().Add(dir.GetFullPath());
//                    Manager::Get()->GetMessageManager()->DebugLog("Parser tgt dir: " + dir.GetFullPath());
                }
            }
        }
    }

    // add compiler include dirs
	if (compiler)
	{
		const wxArrayString& dirs = compiler->GetIncludeDirs();
		for (unsigned int i = 0; i < dirs.GetCount(); ++i)
		{
			//Manager::Get()->GetMessageManager()->Log(mltDevDebug, "Adding %s", dirs[i].c_str());
            wxString out = dirs[i];
            Manager::Get()->GetMacrosManager()->ReplaceEnvVars(out);
            wxFileName dir(out);
            wxLogNull ln; // hide the error log about "too many ..", if the relative path is invalid
            if (!dir.IsAbsolute())
                dir.Normalize(wxPATH_NORM_ALL & ~wxPATH_NORM_CASE, base);
            if (dir.IsOk() && parser->IncludeDirs().Index(dir.GetFullPath()) == wxNOT_FOUND)
            {
                parser->IncludeDirs().Add(dir.GetFullPath());
//                Manager::Get()->GetMessageManager()->DebugLog("Parser cmp dir: " + dir.GetFullPath());
            }
		}
	}
	else
		Manager::Get()->GetMessageManager()->DebugLog(_("No compilers found!"));
}

void NativeParser::AddParser(cbProject* project, bool useCache)
{
	if (!project)
		return;

	// check if we already have a parser for this project
	if (m_Parsers[project])
		return;

	Manager::Get()->GetMessageManager()->DebugLog(_("Start parsing project %s"), project->GetTitle().c_str());
	Parser* parser = new Parser(this);
	m_Parsers[project] = parser;
	m_ParsersFilenames[project] = project->GetFilename();
	AddCompilerDirs(parser, project);
	parser->StartTimer();

    if (useCache && ConfigManager::Get()->Read(_T("/code_completion/use_cache"), 0L) != 0)
    {
        if (LoadCachedData(parser, project))
            return;
    }

    wxArrayString files;

	// parse header files first
	for (int i = 0; i < project->GetFilesCount(); ++i)
	{
		ProjectFile* pf = project->GetFile(i);
		FileType ft = FileTypeOf(pf->relativeFilename);
		if (ft == ftHeader) // only parse header files
		{
			files.Add(pf->file.GetFullPath());
        }
	}
	// next, parse source files
	for (int i = 0; i < project->GetFilesCount(); ++i)
	{
		ProjectFile* pf = project->GetFile(i);
		FileType ft = FileTypeOf(pf->relativeFilename);
		if (ft == ftSource) // only parse source files
		{
			files.Add(pf->file.GetFullPath());
        }
	}
	if (files.IsEmpty())
        Manager::Get()->GetMessageManager()->DebugLog(_("End parsing project %s (no files found?)"), project->GetTitle().c_str());
    else
    {
        parser->BatchParse(files);
    }
}

void NativeParser::RemoveParser(cbProject* project, bool useCache)
{
	// check if we already have a parser for this project
	Parser* parser = m_Parsers[project];
	if (!parser)
		return;

    if (useCache && ConfigManager::Get()->Read(_T("/code_completion/use_cache"), 0L) != 0)
    {
        if (ConfigManager::Get()->Read(_T("/code_completion/update_cache_always"), 0L) != 0 ||
            parser->CacheNeedsUpdate())
        {
            SaveCachedData(parser, m_ParsersFilenames[project]);
        }
    }

	m_Parsers.erase(project);
	m_ParsersFilenames.erase(project);
	if (parser)
		delete parser;
    if (m_pClassBrowser)
		m_pClassBrowser->SetParser(0L);
	Manager::Get()->GetMessageManager()->DebugLog(_("C++ Parser freed"));
}

void NativeParser::AddFileToParser(cbProject* project, const wxString& filename)
{
	Parser* parser = m_Parsers[project];
	if (!parser)
		return;
	parser->Parse(filename, true);
}

void NativeParser::RemoveFileFromParser(cbProject* project, const wxString& filename)
{
	Parser* parser = m_Parsers[project];
	if (!parser)
		return;
	parser->RemoveFile(filename);
}

void NativeParser::ForceReparseActiveProject()
{
    cbProject* prj = Manager::Get()->GetProjectManager()->GetActiveProject();
    if (prj)
    {
        RemoveParser(prj, false);
        AddParser(prj, false);
    }
}

cbProject* NativeParser::FindProjectFromParser(Parser* parser)
{
	for (ParsersMap::iterator it = m_Parsers.begin(); it != m_Parsers.end(); ++it)
	{
		if (parser == it->second)
			return it->first;
	}
	return 0L;
}

cbProject* NativeParser::FindProjectFromEditor(cbEditor* editor)
{
	Parser* parser = FindParserFromEditor(editor);
	return FindProjectFromParser(parser);
}

cbProject* NativeParser::FindProjectFromActiveEditor()
{
	EditorManager* edMan = Manager::Get()->GetEditorManager();
    if (!edMan)
    	return 0L;
    cbEditor* ed = edMan->GetBuiltinActiveEditor();
	return FindProjectFromEditor(ed);
}

Parser* NativeParser::FindParserFromEditor(cbEditor* editor)
{
    if (!editor)
    	return 0L;

	ProjectFile* pf = editor->GetProjectFile();
	if (!pf)
		return 0L;
	cbProject* project = pf->project;
	return m_Parsers[project];
}

Parser* NativeParser::FindParserFromActiveEditor()
{
	EditorManager* edMan = Manager::Get()->GetEditorManager();
    if (!edMan)
    	return 0L;
    cbEditor* ed = edMan->GetBuiltinActiveEditor();
	return FindParserFromEditor(ed);
}

Parser* NativeParser::FindParserFromActiveProject()
{
	return FindParserFromProject(Manager::Get()->GetProjectManager()->GetActiveProject());
}

Parser* NativeParser::FindParserFromProject(cbProject* project)
{
    if (!project)
    	return 0L;
	return m_Parsers[project];
}

bool NativeParser::LoadCachedData(Parser* parser, cbProject* project)
{
    if (!parser || !project)
        return false;

    wxFileName projectCache = project->GetFilename();
    projectCache.SetExt(_T("cbCache"));

    wxLogNull ln;
    wxFile f(projectCache.GetFullPath(), wxFile::read);
    if (!f.IsOpened())
        return false;

    // read cache file
    Manager::Get()->GetMessageManager()->DebugLog(_("Using parser's existing cache: %s"), projectCache.GetFullPath().c_str());
    bool ret = false;
    try
    {
        ret = parser->ReadFromCache(&f);
    }
    catch (cbException& ex)
    {
        ex.ShowErrorMessage(true);
        ret = false;
    }
    catch (...)
    {
        // eat it
        wxSafeShowMessage(_("Exception thrown!"),_("ERROR"));
        ret = false;
    }
    DisplayStatus(parser, project);
    return ret;
}

bool NativeParser::SaveCachedData(Parser* parser, const wxString& projectFilename)
{
    if (!parser)
        return false;

    wxFileName projectCache = projectFilename;
    projectCache.SetExt(_T("cbCache"));

    wxLogNull ln;
    wxFile f(projectCache.GetFullPath(), wxFile::write);
    if (!f.IsOpened())
    {
        Manager::Get()->GetMessageManager()->DebugLog(_("Failed updating parser's cache: %s"), projectCache.GetFullPath().c_str());
        return false;
    }

    // write cache file
    Manager::Get()->GetMessageManager()->DebugLog(_("Updating parser's cache: %s"), projectCache.GetFullPath().c_str());
    return parser->WriteToCache(&f);
}

void NativeParser::DisplayStatus(Parser* parser, cbProject* project)
{
    if (!parser || !project)
        return;
    long int tim = parser->GetElapsedTime();
    Manager::Get()->GetMessageManager()->DebugLog(_("Done parsing project %s (%d total parsed files, %d tokens in %d.%d seconds)."),
                    project->GetTitle().c_str(),
                    parser->GetFilesCount(),
                    parser->GetTokens().GetCount(),
                    tim / 1000,
                    tim % 1000);
}

int NativeParser::MarkItemsByAI(bool reallyUseAI)
{
    cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
	if (!ed)
		return 0;

	Parser* parser = FindParserFromActiveEditor();
	if (!parser)
		return 0;

	if (!parser->Done())
		Manager::Get()->GetMessageManager()->DebugLog(_("C++ Parser is still parsing files..."));
	else
	{
		// clear all temporary tokens
		parser->ClearTemporaries();
		bool sort = false;

		// parse function's arguments
		wxString _namespace;
		wxString _procedure;
		if (FindFunctionNamespace(ed, &_namespace, &_procedure))
		{
			Token* token = parser->FindTokenByName(_procedure, false, tkFunction);
			if (token)
			{
				 if (!token->m_Args.IsEmpty() && !token->m_Args.Matches(_T("()")))
				{
					wxString buffer = token->m_Args;
					buffer.Remove(0, 1); // remove (
					buffer.RemoveLast(); // remove )
					buffer.Replace(_T(","), _T(";")); // replace commas with semi-colons
					buffer << _T(';'); // aid parser ;)
					Manager::Get()->GetMessageManager()->DebugLog(_("Parsing arguments: \"%s\""), buffer.c_str());
					if (!parser->ParseBuffer(buffer, false))
						Manager::Get()->GetMessageManager()->DebugLog(_("ERROR parsing block:\n%s"), buffer.c_str());
					sort = true;
				}
			}
		}
		else
			Manager::Get()->GetMessageManager()->DebugLog(_("Could not find current function's namespace..."));

		// parse current code block
		int blockStart = FindCurrentBlockStart(ed);
		if (blockStart != -1)
		{
			++blockStart; // skip {
			int blockEnd = ed->GetControl()->GetCurrentPos();
			wxString buffer = ed->GetControl()->GetTextRange(blockStart, blockEnd);
			if (!parser->ParseBuffer(buffer, false))
				Manager::Get()->GetMessageManager()->DebugLog(_("ERROR parsing block:\n%s"), buffer.c_str());
			sort = true;
		}
		else
			Manager::Get()->GetMessageManager()->DebugLog(_("Could not find current block start..."));

		if (sort)
			parser->SortAllTokens();

		// clear previously marked tokens
		const TokensArray& tokens = parser->GetTokens();
		for (unsigned int i = 0; i < tokens.GetCount(); ++i)
		{
			Token* token = tokens[i];
			token->m_Bool = !reallyUseAI;
		}

        if (!reallyUseAI)
            return tokens.GetCount();

        // AI will mark (m_Bool == true) every token we should include in list
        return AI(ed, parser);
	}
	return 0;
}

const wxString& NativeParser::GetCodeCompletionItems()
{
    m_CCItems.Clear();

	Parser* parser = FindParserFromActiveEditor();
	if (!parser)
		return m_CCItems;

	int count = MarkItemsByAI();
	if (count)
	{
		const TokensArray& tokens = parser->GetTokens();
		for (unsigned int i = 0; i < tokens.GetCount(); ++i)
		{
			Token* token = tokens[i];
			if (!token->m_Bool)
				continue; // not marked by AI
			token->m_Bool = false; // reset flag for next run
			if (!m_CCItems.IsEmpty())
				 m_CCItems << _T(";");
			m_CCItems << token->m_Name << token->m_Args;//" " << token->m_Filename << ":" << token->m_Line;
		}
	}

	return m_CCItems;
}

const wxArrayString& NativeParser::GetCallTips()
{
    m_CallTips.Clear();

    cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
	if (!ed)
		return m_CallTips;

	Parser* parser = FindParserFromActiveEditor();
	if (!parser)
		return m_CallTips;

	if (!parser->Done())
		return m_CallTips;

	int line = ed->GetControl()->GetCurrentLine();
	wxString lineText = ed->GetControl()->GetLine(line);
	int end = lineText.Length();
	int nest = 0;
	while (end > 0)
	{
		--end;
		if (lineText.GetChar(end) == ')')
			--nest;
		else if (lineText.GetChar(end) == '(')
		{
			++nest;
			if (nest != 0)
				break;
		}
	}
	if (end == 0)
		return m_CallTips; // no (

	lineText.Remove(end);
	Manager::Get()->GetMessageManager()->DebugLog(_("Sending \"%s\" for call-tip"), lineText.c_str());

	// clear previously marked tokens
	const TokensArray& tokens = parser->GetTokens();
	for (unsigned int i = 0; i < tokens.GetCount(); ++i)
	{
		Token* token = tokens[i];
		token->m_Bool = false;
	}

	// AI will mark (m_Bool == true) every token we should include in list
	if (!AI(ed, parser, lineText, true, true))
		return m_CallTips;

	for (unsigned int i = 0; i < tokens.GetCount(); ++i)
	{
		Token* token = tokens[i];
		if (token->m_Bool && !token->m_Args.Matches(_T("()")))
		{
			m_CallTips.Add(token->m_Args);
			token->m_Bool = false; // reset flag for next run
		}
	}

	return m_CallTips;
}

// helper funcs

unsigned int NativeParser::FindCCTokenStart(const wxString& line)
{
	int x = line.Length() - 1;
	int nest = 0;

    bool repeat = true;
    while (repeat)
    {
        repeat = false;
        while (x >= 0 && (isalnum(line.GetChar(x)) || line.GetChar(x) == '_'))
            --x;

        if (x > 0 &&
            (line.GetChar(x) == '>' && line.GetChar(x - 1) == '-') ||
            (line.GetChar(x) == ':' && line.GetChar(x - 1) == ':'))
        {
            x -= 2;
            repeat = true;
        }
        else if (x >= 0 && line.GetChar(x) == '.')
        {
            --x;
            repeat = true;
        }

        if (repeat)
        {
            // check for function/cast ()
            if (x >= 0 && line.GetChar(x) == ')')
            {
                ++nest;
                while (--x >= 0 && nest != 0)
                {
                    switch (line.GetChar(x))
                    {
                        case ')': ++nest; break;
                        case '(': --nest; break;
                    }
                }
                if (x > 0 && (isalnum(line.GetChar(x - 1)) || line.GetChar(x - 1) == '_'))
                    --x;
            }
        }
    }
    ++x;

	if (x < 0)
		x = 0;

    while (line.GetChar(x) == ' ' || line.GetChar(x) == '\t')
        ++x;

	//Manager::Get()->GetMessageManager()->DebugLog("Starting at %d \"%s\"", x, line.Mid(x).c_str());
	return x;
}

wxString NativeParser::GetNextCCToken(const wxString& line, unsigned int& startAt)
{
	wxString res;
	int nest = 0;

    if (startAt < line.Length() && line.GetChar(startAt) == '(')
    {
        while (startAt < line.Length() &&
                (line.GetChar(startAt) == '*' ||
                line.GetChar(startAt) == '&' ||
                line.GetChar(startAt) == '('))
        {
            if (line.GetChar(startAt) == '(')
                ++nest;
            ++startAt;
        }
    }

    //Manager::Get()->GetMessageManager()->DebugLog("at %d (%c): res=%s", startAt, line.GetChar(startAt), res.c_str());
    while (startAt < line.Length() && (isalnum(line.GetChar(startAt)) || line.GetChar(startAt) == '_'))
    {
        res << line.GetChar(startAt);
        ++startAt;
    }

    while (nest > 0 && startAt < line.Length())
    {
        if (line.GetChar(startAt) == ')')
            --nest;
        ++startAt;
    }
    //Manager::Get()->GetMessageManager()->DebugLog("Done nest: at %d (%c): res=%s", startAt, line.GetChar(startAt), res.c_str());

    if (startAt < line.Length() && line.GetChar(startAt) == '(')
    {
        ++nest;
        while (startAt < line.Length() - 1 && nest != 0)
        {
            ++startAt;
            switch (line.GetChar(startAt))
            {
                case ')': --nest; break;
                case '(': ++nest; break;
            }
        }
        ++startAt;
    }

    //Manager::Get()->GetMessageManager()->DebugLog("Return at %d (%c): res=%s", startAt, line.GetChar(startAt), res.c_str());
	return res;
}

wxString NativeParser::GetCCToken(wxString& line, ParserTokenType& tokenType)
{
	// line contains a string on the following form:
	// "    char* mychar = SomeNamespace::m_SomeVar.SomeMeth"
	// first we locate the first non-space char starting from the *end*:
	//
	// "    char* mychar = SomeNamespace::m_SomeVar.SomeMeth"
	//                     ^
	// then we remove everything before it.
	// after it, what we do here, is (by this example) return "SomeNamespace"
	// *and* modify line to become:
	// m_SomeVar.SomeMeth
	// so that if we 're called again with the (modified) line,
	// we 'll return "m_SomeVar" and modify line (again) to become:
	// SomeMeth
	// and so on and so forth until we return an empty string...
	// NOTE: if we find () args in our way, we skip them...

	tokenType = pttSearchText;
	if (line.IsEmpty())
		return wxEmptyString;

	unsigned int x = FindCCTokenStart(line);
	wxString res = GetNextCCToken(line, x);
	//Manager::Get()->GetMessageManager()->DebugLog("FindCCTokenStart returned %d \"%s\"", x, line.c_str());
	//Manager::Get()->GetMessageManager()->DebugLog("GetNextCCToken returned %d \"%s\"", x, res.c_str());

	if (x == line.Length())
		line.Clear();
	else
	{
		//Manager::Get()->GetMessageManager()->DebugLog("Left \"%s\"", line.Mid(x).c_str());
		if (line.GetChar(x) == '.')
		{
			tokenType = pttClass;
			line.Remove(0, x + 1);
		}
		else if ((x < line.Length() - 1 && line.GetChar(x) == '-' && line.GetChar(x + 1) == '>') ||
			(x < line.Length() - 1 && line.GetChar(x) == ':' && line.GetChar(x + 1) == ':'))
		{
			if (line.GetChar(x) == ':')
				tokenType = pttNamespace;
			else
				tokenType = pttClass;
			line.Remove(0, x + 2);
		}
		else
			line.Clear();
	}
	return res;
}

int NativeParser::AI(cbEditor* editor, Parser* parser, const wxString& lineText, bool noPartialMatch, bool caseSensitive)
{
	int count = 0;
    int pos = editor->GetControl()->GetCurrentPos();
	m_EditorStartWord = editor->GetControl()->WordStartPosition(pos, true);
	m_EditorEndWord = pos;//editor->GetControl()->WordEndPosition(pos, true);
	int line = editor->GetControl()->GetCurrentLine();

	wxString searchtext;
	//Manager::Get()->GetMessageManager()->DebugLog("********* START **********");

	Token* parentToken = 0L;
	ParserTokenType tokenType;
	wxString actual;
	int col;
	wxString tabwidth;
	tabwidth.Pad(editor->GetControl()->GetTabWidth(), ' ');
	if (lineText.IsEmpty())
	{
		actual = editor->GetControl()->GetLine(line);
		col = editor->GetControl()->GetColumn(pos);
		// replace tabs in line by equal-count spaces because col is in spaces!
		actual.Replace(_T("\t"), tabwidth);
		actual.Remove(col);
		actual.Trim();
	}
	else
	{
		actual = lineText;
		col = actual.Length() - 1;
	}
	Manager::Get()->GetMessageManager()->DebugLog(_("Doing AI for '%s':"), actual.c_str());

	// find current function's namespace
	wxString procName;
	wxString scopeName;
	FindFunctionNamespace(editor, &scopeName, &procName);
	Token* scopeToken = 0L;
	if (!scopeName.IsEmpty())
		scopeToken = parser->FindTokenByName(scopeName, false, tkNamespace | tkClass);

	while (1)
	{
//		wxString bef = actual;
		wxString tok = GetCCToken(actual, tokenType);
//		wxString aft = actual;
//		Manager::Get()->GetMessageManager()->DebugLog("before='%s', token='%s', after='%s', namespace=%d", bef.c_str(), tok.c_str(), aft.c_str(), tokenType);
		Manager::Get()->GetMessageManager()->DebugLog(_("tok='%s'"), tok.c_str());
		if (tok.IsEmpty())
			break;
		if (actual.IsEmpty() && tokenType == pttSearchText)
		{
			searchtext = tok;
			break;
		}

		if (tokenType == pttNamespace)
		{
			//parentToken = parser->FindTokenByName(tok);
			parentToken = parser->FindChildTokenByName(parentToken, tok, true);
			if (!parentToken)
				parentToken = parser->FindChildTokenByName(scopeToken, tok, true); // try local scope
			Manager::Get()->GetMessageManager()->DebugLog(_("Namespace '%s'"), parentToken ? parentToken->m_Name.c_str() : _("unknown"));
			if (!parentToken)
			{
				Manager::Get()->GetMessageManager()->DebugLog(_("Bailing out: namespace not found"));
				return 0; // fail deliberately
			}
		}
		else
		{
			Token* token = 0L;
			// try #1 - special case
			if (tok.Matches(_T("this"))) // <-- special case
			{
                token = scopeToken;
                parentToken = scopeToken;
			}
			// try #2 - function's class member
			if (!token)
			{
                Manager::Get()->GetMessageManager()->DebugLog(_("Looking for %s under %s"), tok.c_str(), scopeToken ? scopeToken->m_Name.c_str() : _("Unknown"));
				token = parser->FindChildTokenByName(scopeToken, tok, true); // try local scope
            }
            // try #3 - everything else
            if (!token)
            {
                Manager::Get()->GetMessageManager()->DebugLog(_("Looking for %s under %s"), tok.c_str(), parentToken ? parentToken->m_Name.c_str() : _("Unknown"));
                token = parser->FindChildTokenByName(parentToken, tok, true);
            }
            // NOTE: Now that #2 is checked before #3, class member supersedes similarly named
            //       function argument (if any). But if we put #3 before #2, we 'll be checking
            //       global tokens first, which is not what we want...

			if (token)
                Manager::Get()->GetMessageManager()->DebugLog(_("Token found %s, type '%s'"), token->m_Name.c_str(), token->m_ActualType.c_str());
			if (token && !token->m_ActualType.IsEmpty())
			{
				Manager::Get()->GetMessageManager()->DebugLog(_("actual type is %s"), token->m_ActualType.c_str());
                wxString srch = token->m_ActualType;
				int colon = srch.Find(_T("::"));
				if (colon != -1)
				{
                    // type has namespace; break it down and search for it
                    while (!srch.IsEmpty())
                    {
                        parentToken = parser->FindChildTokenByName(parentToken, srch.Left(colon), true);
                        if (!parentToken)
                            break;
                        Manager::Get()->GetMessageManager()->DebugLog(_("searching under %s"), parentToken->m_DisplayName.c_str());
                        srch.Remove(0, colon + 2); // plus two to compensate for "::"
                        colon = srch.Find(_T("::"));
                        if (colon == -1)
                            colon = wxSTRING_MAXLEN;
                    }
				}
				else
				{
                    Manager::Get()->GetMessageManager()->DebugLog(_("Locating %s"), token->m_ActualType.c_str());
                    parentToken = parser->FindTokenByName(token->m_ActualType, true, tkClass | tkNamespace | tkEnum);
                    if (!parentToken) // one more, global, try (might be under a namespace)
                        parentToken = parser->FindTokenByName(token->m_ActualType, false, tkClass | tkNamespace | tkEnum);
                }
			}
			Manager::Get()->GetMessageManager()->DebugLog(_("Class '%s'"), parentToken ? parentToken->m_Name.c_str() : _("unknown"));

			if (!parentToken)
			{
				Manager::Get()->GetMessageManager()->DebugLog(_("Bailing out: class not found"));
				return 0; // fail deliberately
			}
		}
		//Manager::Get()->GetMessageManager()->Log(mltDevDebug, "parentToken=0x%8.8x", parentToken);
	}

    if (noPartialMatch && searchtext.IsEmpty())
        return 0;

	bool bCaseSensitive = parser->Options().caseSensitive || caseSensitive;
	if (!bCaseSensitive)
		searchtext.MakeLower();

	Manager::Get()->GetMessageManager()->DebugLog(_("Scope='%s'"), scopeToken ? scopeToken->m_Name.c_str() : _("Unknown"));
	if (parentToken)
	{
		Manager::Get()->GetMessageManager()->DebugLog(_("Final parent: '%s' (count=%d, search=%s)"), parentToken->m_Name.c_str(), parentToken->m_Children.GetCount(), searchtext.c_str());
		for (unsigned int i = 0; i < parentToken->m_Children.GetCount(); ++i)
		{
			Token* token = parentToken->m_Children[i];
			if (token->m_IsOperator)
				continue; // ignore operators

			wxString name = token->m_Name;
			if (!bCaseSensitive)
				name.MakeLower();

			bool textCondition;
            if (noPartialMatch)
                textCondition = searchtext.IsEmpty() ? false : name.Matches(searchtext);
            else
            {
                if (lineText.IsEmpty())
                    textCondition = searchtext.IsEmpty() ? true : name.StartsWith(searchtext);
                else
                    textCondition = searchtext.IsEmpty() ? true : name.Matches(searchtext);
            }

            if (token->m_TokenKind == tkEnum)
            {
                // enums children (enumerators), are added by default
                for (unsigned int i2 = 0; i2 < token->m_Children.GetCount(); ++i2)
                {
                    Token* ctok = token->m_Children[i2];
                    ctok->m_Bool = true;
                    ++count;
                }
            }

			// scope-based member access :)
			// display public members
			// private/protected are displayed only if in same scope...
			bool scopeCondition =
						// we can access public members
						token->m_Scope == tsPublic ||
						// we can access private/protected members of current scope only
						scopeToken == parentToken;

			token->m_Bool = textCondition && scopeCondition;// &&
//							token->m_TokenKind != tkConstructor && // ignore constructors
//							token->m_TokenKind != tkDestructor; // and destructors too
			if (token->m_Bool)
				++count;
		}
		// look for inheritance
		count += DoInheritanceAI(parentToken, scopeToken, searchtext, bCaseSensitive);
	}
	else
	{
		//Manager::Get()->GetMessageManager()->Log(mltDevDebug, "no token");
		// just globals and current function's parent class members here...
		Manager::Get()->GetMessageManager()->DebugLog(_("Procedure of class '%s' (0x%8.8x), name='%s'"), scopeName.c_str(), scopeToken, procName.c_str());

		for (unsigned int i = 0; i < parser->GetTokens().GetCount(); ++i)
		{
			Token* token = parser->GetTokens()[i];
			if (token->m_IsOperator)
				continue; // ignore operators

			wxString name = token->m_Name;
			if (!bCaseSensitive)
				name.MakeLower();

			bool textCondition;
			if (!noPartialMatch && lineText.IsEmpty())
				textCondition = searchtext.IsEmpty() ? true : name.StartsWith(searchtext);
			else
				textCondition = searchtext.IsEmpty() ? false : name.Matches(searchtext);

			token->m_Bool = textCondition &&
							(!token->m_pParent || // globals
							token->m_TokenKind == tkEnumerator || // enumerators
							token->m_pParent == scopeToken || // child of procToken
							!lineText.IsEmpty()); // locals too
			if (token->m_Bool)
				++count;
		}
		// look for inheritance
		count += DoInheritanceAI(scopeToken, scopeToken, searchtext, bCaseSensitive);
	}
	return count;
}

int NativeParser::DoInheritanceAI(Token* parentToken, Token* scopeToken, const wxString& searchText, bool caseSensitive)
{
	int count = 0;
	if (!parentToken)
		return 0;
	Manager::Get()->GetMessageManager()->DebugLog(_("Checking inheritance of %s"), parentToken->m_Name.c_str());
	Manager::Get()->GetMessageManager()->DebugLog(_("- Has %d ancestor(s)"), parentToken->m_Ancestors.GetCount());
	for (unsigned int i = 0; i < parentToken->m_Ancestors.GetCount(); ++i)
	{
		Token* ancestor = parentToken->m_Ancestors[i];
		Manager::Get()->GetMessageManager()->DebugLog(_("- Checking ancestor %s"), ancestor->m_Name.c_str());
		int bak = count;
		for (unsigned int x = 0; x < ancestor->m_Children.GetCount(); ++x)
		{
			Token* token = ancestor->m_Children[x];
			wxString name = token->m_Name;
			if (!caseSensitive)
				name.MakeLower();
			bool textCondition = searchText.IsEmpty() ? true : name.StartsWith(searchText);
			//Manager::Get()->GetMessageManager()->DebugLog("- [%s] %s: %s", searchText.c_str(), name.c_str(), textCondition ? "match" : "no match");
			token->m_Bool = textCondition &&
							(token->m_Scope == tsPublic || (scopeToken && scopeToken->InheritsFrom(ancestor)));
			if (token->m_Bool)
				count++;
		}
		Manager::Get()->GetMessageManager()->DebugLog(_("- %d matches"), count - bak);
		count += DoInheritanceAI(ancestor, scopeToken, searchText, caseSensitive);
	}
	return count;
}

bool NativeParser::SkipWhitespaceForward(cbEditor* editor, int& pos)
{
    if (!editor)
        return false;
    wxChar ch = editor->GetControl()->GetCharAt(pos);
    int len = editor->GetControl()->GetLength() - 1;
    if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')
    {
        while (pos < len && (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'))
        {
            ++pos;
            ch = editor->GetControl()->GetCharAt(pos);
        }
        return true;
    }
    return false;
}

bool NativeParser::SkipWhitespaceBackward(cbEditor* editor, int& pos)
{
    if (!editor)
        return false;
    wxChar ch = editor->GetControl()->GetCharAt(pos);
    if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')
    {
        while (pos > 0 && (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'))
        {
            --pos;
            ch = editor->GetControl()->GetCharAt(pos);
        }
        return true;
    }
    return false;
}

int NativeParser::FindCurrentBlockStart(cbEditor* editor)
{
	int pos = -1;
	int line = editor->GetControl()->GetCurrentLine();
	while (line >= 0)
	{
		int level = editor->GetControl()->GetFoldLevel(line);
		if ((level & wxSCI_FOLDLEVELHEADERFLAG) &&
			(wxSCI_FOLDLEVELBASE == (level & wxSCI_FOLDLEVELNUMBERMASK)))
		{
			// we 're at block start (maybe '{')
			wxString lineStr = editor->GetControl()->GetLine(line);
			pos = lineStr.Find(_T("{"));
			if (pos != wxNOT_FOUND)
			{
				pos += editor->GetControl()->PositionFromLine(line);
				break;
			}
		}
		--line;
	}
	return pos;
}

bool NativeParser::FindFunctionNamespace(cbEditor* editor, wxString* nameSpace, wxString* procName)
{
	if (!nameSpace && !procName)
		return false;

	int posOf = FindCurrentBlockStart(editor);
	if (posOf != wxNOT_FOUND)
	{
		// look for :: right before procname and procargs
		// if we find a }, we failed (probably no class member this function)...
		bool done = false;
		int nest = 0;
		bool passedArgs = false;
		bool hasNS = false;
		while (posOf > 0)
		{
			--posOf;
			char ch = editor->GetControl()->GetCharAt(posOf);
			switch (ch)
			{
				case ')' : --nest; passedArgs = false; break;
                case '(' :
                    ++nest;
                    passedArgs = nest == 0;
                    if (passedArgs)
                    {
                        --posOf;
                        SkipWhitespaceBackward(editor, posOf);
                    }
                    break;
			}
			if (passedArgs)
			{
                if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ':')
                {
                    int bkp = posOf;
                    SkipWhitespaceBackward(editor, posOf);
                    done = true;
                    hasNS = ch == ':' && editor->GetControl()->GetCharAt(posOf - 1) == ':';
                    posOf = bkp;
                }
            }
			if (done || ch == '}' || ch == ';')
				break;
		}
        Manager::Get()->GetMessageManager()->DebugLog(_("Pos=%d"), posOf);
		if (done)
		{
			if (procName)
			{
				int procEnd = editor->GetControl()->WordEndPosition(posOf + 1, true);
				*procName = editor->GetControl()->GetTextRange(posOf + 1, procEnd);
			}
			if (nameSpace && hasNS)
			{
                nameSpace->Clear();
				posOf -= 2;
				int scopeStart = editor->GetControl()->WordStartPosition(posOf, true);
				*nameSpace = editor->GetControl()->GetTextRange(scopeStart, posOf + 1);
			}
            Manager::Get()->GetMessageManager()->DebugLog(_("NS: '%s', PROC: '%s'"), nameSpace ? nameSpace->c_str() : _T(""), procName ? procName->c_str() : _T(""));
			return true;
		}
	}
	else
        Manager::Get()->GetMessageManager()->DebugLog(_("Can't find block start."));
	return false;
}

// events

void NativeParser::OnThreadStart(wxCommandEvent& event)
{
//	 nothing for now
}

void NativeParser::OnThreadEnd(wxCommandEvent& event)
{
//	 nothing for now
}


void NativeParser::OnParserEnd(wxCommandEvent& event)
{
	Parser* parser = (Parser*)event.GetClientData();
	if (parser)// && parser->Done())
	{
		cbProject* project = FindProjectFromParser(parser);
		if (project)
            DisplayStatus(parser, project);
		else
			Manager::Get()->GetMessageManager()->DebugLog(_("Parser aborted (project closed)."));

		if (project == Manager::Get()->GetProjectManager()->GetActiveProject())
		{
            Manager::Get()->GetMessageManager()->DebugLog(_("Updating class browser..."));
			if (m_pClassBrowser)
			{
				m_pClassBrowser->SetParser(parser);
				m_pClassBrowser->Update();
			}
            Manager::Get()->GetMessageManager()->DebugLog(_("Class browser updated."));
		}
	}
}


syntax highlighted by Code2HTML, v. 0.9.1