/////////////////////////////////////////////////////////////////////////////
// Name:        No names yet.
// Purpose:     Contrib. demo
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     2001/03/28
// RCS-ID:      $Id: classfinder_svc.cpp,v 1.7 2001/04/21 11:44:39 georgetasker Exp $
// Copyright:   (c) Aleksandras Gluchovas (alex@soften.ktu.lt)
// Licence:   	GPL (version 2.0 or later)
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
    #pragma implementation "classfinder_svc.cpp"
    #pragma interface "classfinder_svc.h"
#endif

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include "classfinder_svc.h"
#include "cjparser.h"

#include <string.h>
#include "strlist.h"

#include "wx/tokenzr.h"

/***** Implementation for class wxPMClassFinderService *****/

IMPLEMENT_DYNAMIC_CLASS( wxPMClassFinderService, wxPMSimpleService )

BEGIN_EVENT_TABLE( wxPMClassFinderService, wxPMSimpleService )

	EVT_MENU( ID_DUMP_CLASS_INFO,   wxPMClassFinderService::OnDumpClassInfo )
	EVT_MENU( ID_TEST_EXPR_PARSING, wxPMClassFinderService::OnTestExprParsing )

END_EVENT_TABLE()

wxPMClassFinderService::wxPMClassFinderService()
{
	mpParser = NULL;
	mpTmpBuf = NULL;
}

wxPMClassFinderService::~wxPMClassFinderService()
{
	wxDELETEA(mpTmpBuf);
	wxDELETEA(mpParser);
}

bool wxPMClassFinderService::Start()
{
	mpIndexSvc = (wxPMIndexingService*)wxPMBootSvc().
					FindUniqueServiceInstance( "wxPMIndexingService" );

	if ( !mpIndexSvc )
		return FALSE;

	mpIndexSvc->RegisterIndexHandler( (I_IndexHandler*)this );

	return TRUE; // started ok
}

bool wxPMClassFinderService::Stop()
{
	return TRUE;
}

static wxCommandInfo _gClassFinderCommands[] = {

	wxCommandInfo( -1, "SEPARATOR" ),

	wxCommandInfo( wxPMClassFinderService::ID_DUMP_CLASS_INFO, "&Dump class information", "", "" ),
	wxCommandInfo( wxPMClassFinderService::ID_TEST_EXPR_PARSING, "&Test class information", "", "" ),

	wxCommandInfo( 0, "" ) // terminator
};

static wxPMMergableMenuChain _gClassFinderChains[] = {

	wxPMMergableMenuChain( "/&Tools/", _gClassFinderCommands ),
};

wxPMMergableMenuChain* wxPMClassFinderService::GetMergableMenuChain()
{
	return _gClassFinderChains;
}

/*
 * some higher-level algorithms
 */

// global helpers

static inline bool IsAlpha( char ch )
{
	return ( ch >= 'A' && ch <= 'Z' ) ||
	       ( ch >= 'a' && ch <= 'z' ) ||
	       ch == '_';
}

static inline bool IsAlphaNum( char ch )
{
	return ( ch >= 'A' && ch <= 'Z' ) ||
	       ( ch >= 'a' && ch <= 'z' ) ||
	       ( ch >= '0' && ch <= '9' ) ||
	       ch == '_';
}

static bool IsName( const wxString& token )
{
	if ( !IsAlpha( token[0] ) )
		return FALSE;
		
	for( size_t i = 1; i != token.Length(); ++i )
	{
		char ch = token[i];
		
		if ( !IsAlphaNum( ch ) )
			return FALSE;
	}
	
	return TRUE;	
}

static int FindLeftBrace( StrListT& lst, int pos )
{
	int depth = 1; --pos;
	
	while( depth > 0 && pos >= 0 )
	{
		if ( lst[pos] == ")" )
			++depth;
		else
		if ( lst[pos] == "(" )
			--depth;

		if ( depth == 0 )
			break;

		--pos;
	}
	
	if ( depth != 0 )
		return -1;
		
	return pos;
}

static bool AnalyzeExpr( StrListT& lst, StrListT& outLst )
{
	size_t len = lst.GetCount();
	int cur = len - 1;

	if ( lst[cur] == "(" || lst[cur] == "." )
		--cur;
	
	for(;cur > 0;)
	{
		if ( IsName( lst[cur] ) )
		{
			outLst.Insert( lst[cur], 0 );
			--cur;
		}
		else
			break;
			
		if( cur < 0 )
			break;
			
		if ( lst[cur] != "." ) // !
			break;
			
		--cur;
		if ( cur < 0 )
			break;
			
		// expecting preceding braces now
		
		if ( lst[cur] != ")" )
			continue;
			
		int lbrace = FindLeftBrace( lst, cur );
		
		if ( lbrace == -1 )
			break;
			
		if ( lbrace != 0 &&
		     IsName( lst[lbrace-1]  ) )
		{
			cur = lbrace-1;
			continue;
		}
		
		cur = lbrace;
		
		// expecting left-most cast or a name
		
		while( cur < lst.Count() &&
		       lst[cur] == "(" )
			++cur;
			
		if ( cur >= lst.Count() )
			return FALSE; // ERROR?
			
		if ( !IsName( lst[cur] ) )
			return FALSE; // ERORR?
			
		if ( lst[cur+1] == "(" )
			return FALSE; // ERORR? (of rare +/- kind)
			
		outLst.Insert( lst[cur], 0 );
		
		break; // finished!
	}
	
	return TRUE;
}

static const char* _gDelLst = " \n\r\t!@#$%^&*(){}[]<>.,?=-+\";'~/\\";
static unsigned int _gDelLstLen = strlen( _gDelLst );

static wxString GetNextToken( char*& cur )
{
	char* start = cur;

	while( *cur )
	{
		char ch = *cur;

		for( size_t i = 0; i != _gDelLstLen; ++i )
		{
			if ( _gDelLst[i] == ch )
			{
				if ( start == cur )
				{
					++cur;
					return wxString(ch);
				}
				else
				{
					*cur = '\0';
					wxString tok = start;
					*cur = ch;
					return tok;
				}
			}
		}

		++cur;
	}

	return start;
}

static wxString GetQuotedToken( char*& cur, char ch )
{
	char* start = cur;
	++cur; // skip leading quote

	while( *cur )
	{
		if ( *cur == ch )
		
			break;

		if ( *cur == '\\' &&
			 ( *(cur+1) == '"' ||
			   *(cur+1) == '\'' ) 
		   )
			cur += 2;
		else
			++cur;
	}

	wxString tok;

	if ( *cur )
	{
		++cur;
		char ch = *cur;
		*cur = '\0';
		tok = start;
		*cur = ch;
	}
	else
		tok = start;

	return tok;
}

static void TokenizeStatement( const wxString& stmt, StrListT& lst )
{
	char* cur = (char*)(const char*)stmt.c_str();

	while ( *cur )
	{
		wxString tk = GetNextToken( cur );

		if ( tk.Length() == 1 )
		{
			char ch = tk[0u];

			if ( ch == ' '  || 
				 ch == '\n' ||
				 ch == '\r' ||
				 ch == '\t' ) 
			
				 continue;

			// (special hack) convert all '->'s to  '.'s

			if ( ch == '-' &&
				 *cur == '>' )
			{
				cur += 1;
				lst.Add( "." );
				continue;
			}

			if ( ch == '\'' || ch == '"' )
			
				tk = GetQuotedToken( cur, ch );
		}

		lst.Add( tk );
	}
}

spContext* wxPMClassFinderService::DetectLastContext( SourceEditingContextInfo& ctx )
{
	bool isMethod = ctx.mEditedText[ ctx.mEditedText.Length()-1 ] == '(';

	StrListT tokens, identifiers;
	TokenizeStatement( ctx.mEditedText, tokens );
	AnalyzeExpr( tokens, identifiers );

	if ( identifiers.Count() == 0 )
		return NULL;

	// find out the type of left-most identifier

	wxString first = identifiers[0];
	wxString curType = first;

	size_t i = 0;
	for( ; i != ctx.mCtxIdentifiers.GetCount(); ++i )
	{
		if ( ctx.mCtxIdentifiers[i] == first )
		{
			curType = ctx.mCtxTypes[i];
			break;
		}
	}

	// (if the above loop didn't find the match, then
	//  the name of the identifier is assumed as name of the type)

	int j;
	for( j = 1; j < identifiers.Count(); ++j )
	{
		spClass* curClass = FindClass( curType );

		if ( !curClass )
			return NULL;

		wxString nextId = identifiers[j];

		// try to find this identifier among members
		// of the class or superclasses of curClass

		spContext* found = FindMember( curClass, nextId, TRUE );

		if ( !found )
			return NULL;

		if ( j == identifiers.Count() - 1 )
			return found;

		if ( found->GetType() == SP_CTX_OPERATION )
		
			curType = ((spOperation*)found)->mRetType;
		else
		if ( found->GetType() == SP_CTX_ATTRIBUTE )

			curType = ((spAttribute*)found)->mType;

		delete found;

		// next iteration goes deeper towards right-hand-side of expression
	}

	return FindClass( curType );
}

spClass* wxPMClassFinderService::FindClass( const wxString& name )
{
	ItemTable& it = mpIndexSvc->GetItemTable();

	if ( it.FindByName( name, "", SP_CTX_CLASS, FALSE ) )
	
		return ReadClass();

	return NULL;
}

spContext* wxPMClassFinderService::FindMember( const spClass* cl,
											   const wxString& name,
											   bool searchSuperClasses )
{
	size_t i = 0;
	for( ; i != cl->mMembers.Count(); ++i )
	{
		spContext* ctx = cl->mMembers[i];

		if ( ctx->mName == name )
		
			return ctx;
	}

	if ( searchSuperClasses )
	{
		for( i = 0; i != cl->mSuperClassNames.GetCount(); ++i )
		{
			spClass* super = FindClass( cl->mSuperClassNames[i] );

			if ( super )
			{
				// recursive call
				spContext* found = FindMember( super, name );

				if ( found )

					return found;
			}
		}
	}

	delete cl; // !!!
	return NULL;
}

void wxPMClassFinderService::DumpAllItems( wxTextOutputStream& stm )
{
	ItemTable& it = mpIndexSvc->GetItemTable();

	int types[] = { SP_CTX_CLASS, SP_CTX_OPERATION };

	for( int i = 0; i != 2; ++i )
	{
		if ( it.FindByType( types[i] ) )
		{
			int type = it.GetType();

			while( it.Ok() && it.GetType() == types[i] )
			{
				spContext* ctx = ReadContext();

				if ( ctx )
				{
					ctx->Dump( stm, wxEmptyString );
					delete ctx;
				}

				stm << "\n";
				it.Next();
			}

			it.End();
		}
	}
}

void wxPMClassFinderService::DumpAllItemsToFile( const wxString& fname )
{
	wxFFileOutputStream fs( fname );
	wxTextOutputStream ts( fs );

  	DumpAllItems( ts );
}

/*** implementation of I_IndexHandler interface ***/

const char* wxPMClassFinderService::GetIndexHandlerName()
{
	return "C++-Class-Info-Handler";
}

bool wxPMClassFinderService::OnClearIndexItems( int indexId )
{
	ItemTable& it = mpIndexSvc->GetItemTable();

	it.DeleteItemsByType( SP_CTX_CLASS, indexId );
	it.DeleteItemsByType( SP_CTX_OPERATION, indexId );
	it.DeleteItemsByType( SP_CTX_ATTRIBUTE, indexId );

	return TRUE;
}

bool wxPMClassFinderService::OnBeginBuild( int indexId )
{
	mTmpBufLen = 10000;
	mpTmpBuf = new char[mTmpBufLen+1];

	mpParser = new CJSourceParser();

	return TRUE; // nothing
}

bool wxPMClassFinderService::OnIndexFile( const wxPMFileInfo& file, int fileId, int indexId )
{
	wxFile fl( file.GetPath() );
	if ( !fl.IsOpened() )
		return FALSE;

	int len = fl.Length();

	if ( len > mTmpBufLen )
	{
		delete [] mpTmpBuf;
		mTmpBufLen = len;
		mpTmpBuf = new char[ mTmpBufLen + 1 ];
	}

	char* buf = mpTmpBuf;

	// this must be fast, yeahh...!
	int n = fl.Read( buf, len );

	buf[n] = '\0'; 

	spFile* fileCtx = mpParser->Parse( buf, buf + n, FALSE );

	if ( !fileCtx )
		return FALSE;

	//mpParser->ResolveLineNumbers( fileCtx, buf, buf + n );

	ItemTable& it = mpIndexSvc->GetItemTable();
	it.SetIndexId( indexId );
	it.SetFileId( fileId );

	WriteParsedFile( fileCtx );

	return TRUE;
}

bool wxPMClassFinderService::OnEndBuild( int indexId )
{
	wxDELETEA(mpTmpBuf);
	wxDELETE(mpParser);

	return TRUE; // nothing
}

// command handlers 

void wxPMClassFinderService::OnDumpClassInfo( wxCommandEvent& event )
{
	DumpAllItemsToFile( "d:\\sample\\dump.txt" );
}

void wxPMClassFinderService::OnTestExprParsing( wxCommandEvent& event )
{
	SourceEditingContextInfo ctx;
	ctx.mEditedText = "{ z.getFirst()->mtd1(";
	ctx.mCtxTypes.Add( "Zero" );
	ctx.mCtxIdentifiers.Add( "z" );

	spContext* found = DetectLastContext( ctx );
	// TBD::
}

/*** protected methods ***/

void wxPMClassFinderService::WriteParsedFile( spFile* file )
{
	MMemberListT& lst = file->GetMembers();

	for( size_t i = 0; i != lst.Count(); ++i )
	{
		spContext* ctx = lst[i];

		switch( ctx->GetType() )
		{
			case SP_CTX_CLASS : 
				WriteParsedClass( (spClass*)ctx ); 
				break;

			case SP_CTX_OPERATION : 
				WriteParsedOperation( (spOperation*)ctx ); 
				break;

			default: break;
		}
	}
}

void wxPMClassFinderService::WriteParsedOperation( spOperation* op )
{
	mTBuf.Clear();
	mTBuf.WriteShort( op->mFlags );
	mTBuf.WriteString( op->GetArgListDigest() );
	WriteOpProperties( mTBuf, op );

	ItemTable& it = mpIndexSvc->GetItemTable();

	it.SetName( op->mName );
	it.SetContext( op->mScope );
	it.SetDateStr( mTBuf.GetContent() );
	it.SetType( SP_CTX_OPERATION );
	it.Add();
}

void wxPMClassFinderService::WriteParsedClass( spClass* cl )
{
	mTBuf.Clear();

	mTBuf.WriteShort( cl->mFlags );
	mTBuf.WriteByte( cl->mSuperClassNames.GetCount() );
	
	size_t i = 0;
	for( ; i != cl->mSuperClassNames.GetCount(); ++i )
	
		mTBuf.WriteString( cl->mSuperClassNames[i] );
	
	mTBuf.WriteString( cl->mTemplateTypes );

	mTBuf.WriteShort( cl->CountMembers( SP_CTX_OPERATION ) );

	for( i = 0; i != cl->mMembers.Count(); ++i )
	{
		spContext* ctx = cl->mMembers[i];

		if ( ctx->GetType() == SP_CTX_OPERATION )
		{
			spOperation* op = (spOperation*)ctx;

			mTBuf.WriteShort( op->mFlags );
			mTBuf.WriteString( op->mName );
			WriteOpProperties( mTBuf, op );
		}
	}

	mTBuf.WriteShort( cl->CountMembers( SP_CTX_ATTRIBUTE ) );

	for( i = 0; i != cl->mMembers.Count(); ++i )
	{
		spContext* ctx = cl->mMembers[i];

		if ( ctx->GetType() == SP_CTX_ATTRIBUTE )
		{
			spAttribute* attr = (spAttribute*)ctx;

			mTBuf.WriteShort( attr->mFlags );
			mTBuf.WriteString( attr->mName );
			mTBuf.WriteString( attr->mType );
			mTBuf.WriteString( attr->mInitVal );
		}
	}

	ItemTable& it = mpIndexSvc->GetItemTable();

	it.SetName( cl->mName );
	it.SetContext( "" );
	it.SetDateStr( mTBuf.GetContent() );
	it.SetType( SP_CTX_CLASS );
	it.Add();
}

spContext* wxPMClassFinderService::ReadContext()
{
	ItemTable& it = mpIndexSvc->GetItemTable();

	if ( it.GetType() == SP_CTX_CLASS )

		return ReadClass();
	else
	if ( it.GetType() == SP_CTX_OPERATION )

		return ReadOperation();
	else
		return NULL;
}

spClass* wxPMClassFinderService::ReadClass()
{
	ItemTable& it = mpIndexSvc->GetItemTable();
	spClass* cl = new spClass();

	cl->mName = it.GetName();
	mTBuf.SetContent( it.GetDataStr() );

	cl->mFlags = mTBuf.ReadShort();
	size_t n = (size_t)mTBuf.ReadByte();

	size_t i = 0; wxString s;

	for( ; i != n; ++i )
	{
		s = mTBuf.ReadString();
		cl->mSuperClassNames.Add( s );
	}

	cl->mTemplateTypes = mTBuf.ReadString();

	n = (size_t)mTBuf.ReadShort();

	for( i = 0; i != n; ++i )
	{
		spOperation* op = new spOperation();

		op->mFlags = mTBuf.ReadShort();
		op->mName = mTBuf.ReadString();
		ReadOpProperties( mTBuf, op );

		cl->AddMember( op );
	}

	n = (size_t)mTBuf.ReadShort();

	for( i = 0; i != n; ++i )
	{
		spAttribute* at = new spAttribute();

		at->mFlags   = mTBuf.ReadShort();
		at->mName    = mTBuf.ReadString();
		at->mType    = mTBuf.ReadString();
		at->mInitVal = mTBuf.ReadString();

		cl->AddMember( at );
	}

	return cl;
}

spOperation* wxPMClassFinderService::ReadOperation()
{
	ItemTable& it = mpIndexSvc->GetItemTable();
	spOperation* op = new spOperation();


	op->mName = it.GetName();
	op->mScope = it.GetContext();

	mTBuf.SetContent( it.GetDataStr() );
	op->mFlags = mTBuf.ReadShort();
	op->mArgsDigest = mTBuf.ReadString();

	ReadOpProperties( mTBuf, op );

	return op;
}

void wxPMClassFinderService::WriteOpProperties( TextBuffer& buf, spOperation* op )
{
	buf.WriteString( op->mRetType );
	buf.WriteByte( op->mParams.Count() );

	for( size_t i = 0; i != op->mParams.Count(); ++i )
	{
		spParameter* param = op->mParams[i];

		buf.WriteString( param->mName );
		buf.WriteString( param->mType );
		buf.WriteString( param->mInitVal );
	}
}

void wxPMClassFinderService::ReadOpProperties( TextBuffer& buf, spOperation* op )
{
	op->mRetType = buf.ReadString();
	size_t n = (size_t)buf.ReadByte();

	for( size_t i = 0; i != n; ++i )
	{
		spParameter* param = new spParameter();

		param->mName    = buf.ReadString();
		param->mType    = buf.ReadString();
		param->mInitVal = buf.ReadString();

		op->mParams.Add( param );
	}
}

/***** Implementation for class TextBuffer *****/

// some helpers

static char _gConvBuf[5];

#define BITS_TO_HEX( x ) ( x >= 10 ? (char)((int)'A' + x - 10 ) \
								   : (char)((int)'0' + x ) )

#define HEX_TO_BITS( c ) ( c >= 'A' ? 10 + c - 'A' \
									: c - '0' )

void byte_to_hex( int byte, char* buf )
{
	int p1 = byte & 0xF;
	int p2 = ( byte >> 4 ) & 0xF;

	buf[ 1 ] = BITS_TO_HEX( p1 );
	buf[ 0 ] = BITS_TO_HEX( p2 );
}

void short_to_hex( int s, char* buf )
{
	byte_to_hex( s & 0xFF, buf + 2 );
	byte_to_hex( ( s >> 8 ) & 0xFF, buf );
}

int hex_to_byte( char* hex )
{
	char c1 = hex[0], c2 = hex[1];

	int p1 = HEX_TO_BITS( c1 );
	int p2 = HEX_TO_BITS( c2 );

	return ( ( p1 << 4 ) & 0xF0 ) | p2;
}

int hex_to_short( char* hex )
{
	int p1 = hex_to_byte( hex );
	int p2 = hex_to_byte( hex + 2 );

	return ( ( p1 << 8 ) & 0xFF00 ) | p2;
}

TextBuffer::TextBuffer()

	: mpBuf( NULL ),
	  mLen(0),
	  mBufLen(0)
{}

TextBuffer::~TextBuffer()
{
	CleanUp();
}

void TextBuffer::SetContent( const wxString& text )
{
	CleanUp();
	mReadHead = 0;

	int len = text.Length() + 1;
	mpBuf = new char[ len ];

	memcpy( mpBuf, text.c_str(), len - 1 );
	mpBuf[ len - 1 ] = '\0';

	mLen = len - 1;
	mBufLen = len;
}

const char* TextBuffer::GetContent()
{
	mpBuf[mLen] = '\0';
	return mpBuf;
}

void TextBuffer::WriteString( const wxString& s )
{
	size_t len = s.Length();

	if ( len > 255 )
	{
		int avoidCompilerWarning = 0;
		wxASSERT(avoidCompilerWarning); // FOR NOW::
	}
	
	Resize( mLen + 1 + len );

	mpBuf[mLen] = len + 1; // add one to avoid zero value (terminator)
	
	memcpy( mpBuf + mLen + 1, s.c_str(), len );

	mLen += len + 1;
}

void TextBuffer::WriteShort( int s )
{
	Resize( mLen + 4 );

	short_to_hex( s, mpBuf + mLen );

	mLen += 4;
}

void TextBuffer::WriteByte( int byte )
{
	Resize( mLen + 1 );

	if ( byte >= 255 )
	{
		int avoidCompilerWarning = 0;
		wxASSERT(avoidCompilerWarning); // FOR NOW::
	}

	mpBuf[mLen] = (char)(byte + 1);

	++mLen;
}

int TextBuffer::ReadShort()
{
	wxASSERT( mReadHead < mLen ); 

	int s = hex_to_short( mpBuf + mReadHead );

	mReadHead += 4;

	return s;
}

int TextBuffer::ReadByte()
{
	wxASSERT( mReadHead < mLen );

	int b = mpBuf[ mReadHead ] - 1; // remove '\0' padding

	++mReadHead;

	return b;
}

wxString TextBuffer::ReadString()
{
	wxASSERT( mReadHead < mLen );

	int len = mpBuf[ mReadHead ] - 1;
	++mReadHead;

	char tmp = mpBuf[ mReadHead + len ];
	mpBuf[ mReadHead + len ] = '\0';

	wxString s = mpBuf + mReadHead;

	mpBuf[ mReadHead + len ] = tmp;

	mReadHead += len;

	return s;
}

void TextBuffer::CleanUp()
{
	if ( mpBuf )
	{
		wxDELETEA(mpBuf);
		mLen = 0;
		mBufLen = 0;
	}
}

void TextBuffer::Clear()
{
	if ( !mpBuf )
		SetContent( "" );

	mLen = 0;
}

void TextBuffer::Resize( int newLen )
{
	if ( mBufLen >= newLen )
		return;

	mBufLen = ( newLen / 16 ) * 32 + 32;

	char* newBuf = new char[ mBufLen ];

	memcpy( newBuf, mpBuf, mLen );

	delete [] mpBuf;

	mpBuf = newBuf;
}
