/////////////////////////////////////////////////////////////////////////////
// Name:        variable substitution in script-templates
// Purpose:     wxBugTracker
// Author:      Aleksandras Gluchovas (mailto:alex@soften.ktu.lt)
// Modified by:
// Created:     20/10/1999
// RCS-ID:      $Id: scripttempl.cpp,v 1.4 2001/03/16 04:37:09 aleksandras Exp $
// Copyright:   (c) Aleksandras Gluchovas
// Licence:   	GPL (version 2.0 or later)
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
    #pragma implementation "minimal.cpp"
    #pragma interface "minimal.cpp"
#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 "scripttempl.h"
#include <ctype.h>

/***** Implementation for class ScriptTemplate *****/

char ScriptTemplate::mVarPrefixChar = '$';

static bool get_next_word( char*& cur )
{
    while( *cur && ( *cur == ' ' || *cur == '\n' || *cur == '\t' ) ) ++cur;

    return (*cur) != '\0';
}

static bool skip_word( char*& cur )
{
    while( *cur && *cur != ' ' && *cur != ScriptTemplate::mVarPrefixChar &&
           *cur !='\n' && *cur != '(' && *cur != ')' ) ++cur;

    return ( *cur != '\0' );
}

bool ScriptTemplate::IsIfExpr( char* cur )
{
    cur += 2;

    if ( !get_next_word( cur ) ) return FALSE;

    return ( toupper( *cur ) == 'I' && toupper( *(cur+1) == 'F' ) );
}

bool ScriptTemplate::ParseIfExpr( char*& cur, wxString& var, 
                                  wxString& value, int& exprType, 
								  bool& isMultiValue,
								  bool& isOrExpr )
{
	isMultiValue = FALSE;
	isOrExpr     = FALSE;

    cur += 2; // skip "$("

    get_next_word( cur );

    if ( !skip_word( cur ) ) return FALSE;

    if ( !get_next_word( cur ) ) return FALSE;

    if ( *cur != ScriptTemplate::mVarPrefixChar || 
		 *(cur+1) != '(' ) return FALSE;

    cur += 2;

    // extract variable of expression

    const char* start = cur;

    if ( !skip_word( cur ) ) return FALSE;

	ConcatString( var, start, size_t(cur-start) );

    if ( !get_next_word( cur ) ) return TRUE;

    if ( *cur != ')' ) return FALSE;

    ++cur;

    if ( !get_next_word( cur ) ) return TRUE;

    // determine type of operator

    if ( *cur == ')' ) 
    {
        exprType = OP_NOT_NULL;
        return TRUE;
    }
	else
	if ( *cur == '!' && 
		 *(cur+1) == '[' &&
		 *(cur+2) == ']' )
	{
        exprType = OP_EXCLUDES;

        cur += 3;
	}
	else
	if ( *cur == '[' &&
		 *(cur+1) == ']' )
	{
        exprType = OP_INCLUDES;

        cur += 2;
	}
	else
    if ( *cur == '!' && *(cur+1) == '=' )
    {
        exprType = OP_NOT_EQUAL;

        cur += 2;
    }
    else
    if ( *cur == '=' )
    {
        exprType = OP_EQUAL;

        ++cur;
    }
    else return FALSE; // error: unknown operator

    // determine value of constant at the "right-hand-side"

    if ( !get_next_word( cur ) ) return TRUE;

	if ( *cur == ScriptTemplate::mVarPrefixChar ) 
	{
		cur += 2; // skip "($"

		char* start = cur;

		while( *cur && *cur != ')' )
			++cur;

		wxString rhsVar;

		ConcatString( rhsVar, start, size_t(cur-start) );

	    char* pszValue = (char*) FindValue( rhsVar );

		if ( pszValue != NULL )

			value = pszValue;

		if ( value.Find( "&" ) != -1 )
		{
			isMultiValue = TRUE;
			isOrExpr = FALSE;
		}
		else
		if ( value.Find( "|" ) != -1 )
		{
			isMultiValue = TRUE;
			isOrExpr = TRUE;
		}

		++cur; // skip ')'

	    if ( !get_next_word( cur ) ) return TRUE;

		if ( *(cur) != ')' ) return FALSE; // closing bracket not found

		return TRUE; // parsed OK
	}

    if ( *cur != '"' ) return FALSE;

    ++cur;

    start = cur;

    while ( *cur && *cur != '"' ) 
	{
		if ( *cur == '&' || *cur == '|' ) 
		{
			isMultiValue = TRUE;
			isOrExpr = *cur == '|';
		}
		++cur;
	}

    if ( !(*cur) ) return FALSE; // error: closing quote not found

	ConcatString( value, start, size_t(cur-start) );

    ++cur; 

    if ( !get_next_word( cur ) ) return TRUE;

    if ( *(cur) != ')' ) return FALSE; // closing bracket not found

    return TRUE; // parsed OK
}

bool ScriptTemplate::GetIfExprResult( char* cur )
{
    wxString var, constValue;
    int exprType = 0;
	bool isMultiValue = FALSE, isOrExpr = FALSE;

    ParseIfExpr( cur, var, constValue, exprType, isMultiValue, isOrExpr );

    char* value = (char*) FindValue( var.c_str() );

    if ( exprType == OP_INCLUDES || exprType == OP_EXCLUDES )
    {
		size_t i = 0; wxString valueStr = value;

		while( i < valueStr.length() )
		{
			size_t start = i;

			while( i < valueStr.length() && valueStr[i] != '|' )

				++i;

			wxString valuePart = wxString( valueStr, start, i - start );

			if ( valuePart == constValue )
			{
				if ( exprType == OP_INCLUDES ) return TRUE;

				if ( exprType == OP_EXCLUDES ) return FALSE;
			}

			++i; // skip '|'
		}

		if ( exprType == OP_EXCLUDES ) return TRUE;
								  else return FALSE;
	}
	else
    if ( exprType == OP_EQUAL )
    {
        if ( constValue == "" && value == NULL ) return TRUE;

		if ( isMultiValue && isOrExpr )
		{
			// ugly, but fast...

			size_t i = 0, len = constValue.length(), valueLen = strlen(value);
			bool matchFound = FALSE;

			for(;;)
			{
				size_t start = i;
				matchFound = TRUE;

				while( i < len && constValue[i] != '|' ) 
				{
					if ( constValue[i] != value[i-start] ) matchFound = FALSE;
					++i;
				}

				// lengths should also match 

				if ( i - start != valueLen ) matchFound = FALSE;

				if ( matchFound || i == len ) break;

				++i; // skip '|' and continue comparison
			}

			return matchFound;
		}
		else
			return ( value && strcmp( value, constValue ) == 0 );
    }
    else
    if ( exprType == OP_NOT_EQUAL )
    {
        if ( constValue != "" && value == NULL )
            return TRUE;

		if ( isMultiValue && !isOrExpr )
		{
			// ugly, but fast...

			size_t i = 0, len = constValue.length();
			bool matchFound = FALSE;

			for(;;)
			{
				size_t start = i;
				matchFound = TRUE;

				while( i < len && constValue[i] != '&' ) 
				{
					if ( constValue[i] != value[i-start] )
                        matchFound = FALSE;
					++i;
				}

				if ( matchFound || i == len )
                    break;

				++i; // skip '|' and continue comparison
			}

			return matchFound == FALSE;
		}
		else
			return ( value && strcmp( value, constValue ) != 0 );
    }
    else
        // otherwise `OP_NOT_NULL' operator

        return ( value && *value );
}

bool ScriptTemplate::IsEndIf( char* cur )
{
    cur += 2; // skip "$("

    if ( !get_next_word( cur ) ) return FALSE;

    return ( toupper( *(cur+0) ) == 'E' &&
             toupper( *(cur+1) ) == 'N' &&
             toupper( *(cur+2) ) == 'D' &&
             toupper( *(cur+3) ) == '_' &&
             toupper( *(cur+4) ) == 'I' &&
             toupper( *(cur+5) ) == 'F' );
}

void ScriptTemplate::DeleteStrList( wxList& lst )
{
    wxNode* pNode = lst.First();

    while( pNode )
    {
        delete (wxString*)pNode->GetData();

        pNode = pNode->Next();
    }
}

void ScriptTemplate::PreparseScript( const wxString& scriptTempl )
{
    char* cur = (char*)(const char*)mTempl.c_str();
    char* start = cur;
	int nOffsets = 0;

    while( *cur )
    {
        if ( *cur == ScriptTemplate::mVarPrefixChar && 
			 *(cur+1) == '(' )
        {
            char* var = cur;

            // store offset of the variable
            mOfsList.Append( (wxObject*)(cur - start) );

            if ( IsIfExpr( cur ) )
            {
                wxString var,val;int type;

				bool isMultiVal = FALSE, isOrExpr = FALSE;

                if ( !ParseIfExpr( cur, var, val, type, isMultiVal, isOrExpr ) )
                {
					printf( "ERROR: (ScriptTemplate::) while parsing $IF expr at: %s", cur );

                    mOfsList.Clear();
                    return;           // error in $(IF..) expression
                }
            }
            else
            {
                while( *cur && *cur != ')' ) ++cur;

                if ( !(*cur) ) 
                {
                    // end of enclosed variable-name not found, abort..

					printf( "ERROR: (ScriptTemplate::) while parsing variable at: %s", start );

                    mOfsList.Clear(); 

                    return;
                }

                *(cur) = '\0'; // terminate variable name (speeds up things later)
            }

            ++cur; // skip ')'

            // store length of the variable
            mOfsList.Append( (wxObject*)( cur - var ) );
			++nOffsets;
        }
        else ++cur;
    }

	wxNode* pNode = mOfsList.First();
}

ScriptTemplate::ScriptTemplate( const wxString& scriptTempl )

    : mTempl( scriptTempl ),
      mpParent( NULL ),
	  mHash( wxKEY_STRING )
{
    PreparseScript( scriptTempl );
}

ScriptTemplate::ScriptTemplate()

    : mpParent( NULL ),
	  mHash( wxKEY_STRING )
{}

void ScriptTemplate::SetScript( const wxString& scriptTempl )
{
    mHash.Clear();

	const char* cur = (const char*)scriptTempl;
	const char* end = (const char*)scriptTempl + scriptTempl.Length();
	const char* head = cur;

	mTempl = "";

	while( cur < end )
	{
		if ( *cur == '\\' )
		{
			if ( *(cur+1) == 'n' || 
				 *(cur+1) == 't' )
			{
				++cur; // skip '\\'

				ConcatString( mTempl, head, size_t( cur - head ) );

				if ( *cur == 'n' )

					mTempl[ mTempl.Length() - 1 ] = '\n';
				else
					mTempl[ mTempl.Length() - 1 ] = '\t';

				++cur; // skip 'n' or 't'
				head = cur;

				continue;
			}
		}

		++cur;
	}

	if ( cur != head )

		ConcatString( mTempl, head, size_t( cur - head ) );

    PreparseScript( mTempl );
}

void ScriptTemplate::SetParentTemplate( ScriptTemplate* pParent )
{
	mpParent = pParent;
}

ScriptTemplate* ScriptTemplate::GetTopLevelTemplate()
{
	ScriptTemplate* pCur = this;

	while( pCur->mpParent )

		pCur = pCur->mpParent;

	return pCur;
}

char* ScriptTemplate::FindValue( const char* key )
{
	char* val = (char*)mHash.Get( key );

	if ( val ) return val;

	if ( mpParent ) return mpParent->FindValue( key );
			   else return NULL;
}

ScriptTemplate* ScriptTemplate::FindTemplateForValue( const char* key )
{
	char* val = (char*)mHash.Get( key );

	if ( val ) return this;

	if ( mpParent ) return mpParent->FindTemplateForValue( key );
			   else return NULL;
}

ScriptTemplate::~ScriptTemplate()
{
    // nothing
}

void ScriptTemplate::SetName( const wxString& name )
{
	mName = name;
}

const wxString& ScriptTemplate::GetName()
{
	return mName;
}

// prepare for the next substitution pass

void ScriptTemplate::Reset()
{
    mHash.Clear();
}

void ScriptTemplate::SetVar( const wxString& name, const wxString& value )
{
    // NOTE:: given 'name' and 'value' strings should NOT be deleted
    //        until SubstitueVariables() is called

    char* key = (char*)(const char*)(name.c_str());
    char* val = (char*)(const char*)(value.c_str());

    void* v = (void*) FindValue( key );

	// the old value is overwritten!

    if ( v ) 
        mHash.Delete( key );

    mHash.Put( key, (wxObject*) val );
}

wxString ScriptTemplate::SubstitueVariables()
{
    char* start = (char*)(const char*)mTempl.c_str();

    wxString result;

    size_t curOfs = 0;

    wxNode* pNode = mOfsList.First();

    while( pNode )
    {
        size_t varOfs = (int)pNode->Data();
        size_t varLen = (int)pNode->Next()->Data();

        if ( curOfs < varOfs )
        
			// FIXME:: slow!!! (mallocs, etc)
            //result.ConcatSelf( size_t(varOfs - curOfs), mTempl.c_str() + curOfs );
			ConcatString( result, mTempl.c_str() + curOfs, size_t(varOfs - curOfs) );


        if ( IsIfExpr( start + varOfs ) )
        {
            if ( GetIfExprResult( start + varOfs ) == FALSE )
            {
                // skip content, until the matching $(END_IF) is found

                int level = 1;

                pNode = pNode->Next()->Next();

                size_t varOfs1;
                size_t varLen1;

                while( pNode && level > 0 )
                {
                    varOfs1 = (int)pNode->Data();
                    varLen1 = (int)pNode->Next()->Data();

                    if ( IsIfExpr( start + varOfs1 ) ) 
                        
                        ++level;

                    if ( IsEndIf( start + varOfs1 ) ) 
                        
                        --level;

                    if ( level > 0 )

                        pNode = pNode->Next()->Next();
                }

                if ( level > 0 ) 
                {
                    Reset();    // error: matching $(END_IF) not found
                    return "";
                }

                varOfs = varOfs1;
                varLen = varLen1;
            }
        }
		else
		if ( *( start + varOfs + 2 ) == '@' )
        {
			result += HandleSpecialSubstitution( start + varOfs + 3, *this );
		}
		else
        {
            char* value = (char*) FindValue( start + varOfs + 2 );

            if ( value != NULL ) result += value;
       }

        curOfs = varOfs + varLen;

        pNode = pNode->Next()->Next(); // skip offset and length
    }

    // append reminder

    if ( curOfs < mTempl.length() )

		// FIXME:: slow!!! (mallocs, etc)
        //result.ConcatSelf( mTempl.length() - curOfs, start + curOfs );
		ConcatString( result, start + curOfs, mTempl.length() - curOfs );
		

    Reset(); // reset value-hashes automaticaly after each substitution

    return result;
}

wxString ScriptTemplate::HandleSpecialSubstitution( char* at, ScriptTemplate& templ )

{ 
	// pass request up the chain with a hope that there will be subclasses
	// which  could handle this substitution with special ("custom") syntax

	if ( mpParent ) return mpParent->HandleSpecialSubstitution( at, templ );
	
	// error otherwise

	return "ERROR_SUBSTITUTING_SPECIAL_SYNTAX!";
}

/*** static methods ***/

void ScriptTemplate::SetVarPrefixChar( char ch )
{
	mVarPrefixChar = ch;
}

char ScriptTemplate::GetVarPrefixChar()
{
	return mVarPrefixChar;
}