/***************************************************************************

    supportware
    Copyright (C) 2001-2008 by c.holzheuer
    c.holzheuer@sourceworx.org

    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 <ddformchecker.h>
#include <ddwebpage.h>
#include <ddusermanager.h>
#include <ddmailmanager.h>
#include <ddwebsite.h>


/**
	Default Constructor.
*/

ddformchecker::ddformchecker()
:	_parent( 0 )
{

}


/**
	Standard Constructor.
*/

ddformchecker::ddformchecker( ddformmanager* parent, ddformruleparser& parser, ddconfigsection& rules )
:	_parent( parent )
{
	init_form_rules( parser, rules );
}

/**
	Descructor.
*/


ddformchecker::~ddformchecker()
{
	for( size_t i=0; i<_rules.size(); ++i )
		delete _rules[i];
}


/**
	Baut das interne Regelwerk auf.
*/

bool ddformchecker::init_form_rules( ddformruleparser& parser, ddconfigsection& config )
{
 	set_name( config.get_name() );
	for( int i=0; i<config.size(); ++i )
		if( add_rules( parser, config.key_of(i), config[i] ) < 0 )
			throw runtime_error( "init_form_rules failed: " + config.key_of(i) );
	
	return true;
}


/**
	Fügt die Regeln für ein bestimmtes Feld hinzu.
*/

bool ddformchecker::add_rules( ddformruleparser& parser, const ddstring& key, const ddstring& rulestr )
{
	ddformrule* rule = parser.parse( rulestr );
  if( !rule )
		return false;
	rule->set_name( key );
	_rules.push_back( rule );
	return true;
}
	
	
/**
	Ein Formular überprüfen, die alte Version. deprecated.
	Nachteil:Es wird das item (Formular) geprüft, nicht die ankommenden CGI-Daten,
	das Form muss also schon vorher gesynct worden sein.
 	@return true on success.
*/

bool ddformchecker::check_form( dditem& form, dduserid& user )
{
  // is configuration done?

	return true;
}


/**
	Tested ein Formular, neue Version. Es wird direkt auf den CGI-Daten gearbeitet,
	nicht auf den Formdaten.
	@param form das form
	@param user die userid des nutzers
	@param cgi die daten
	@return true on success.
*/  

void ddformchecker::check_form( dditem& form, dduserid& user, ddwebcgi& cgi )
{
  // is configuration done?
	if( _rules.empty() )
		throw runtime_error( "ddformchecker::check_form: not configured: " + get_name() );
	
  ddstring msgkey;	
	// use all rules
	for( size_t i=0; i<_rules.size(); ++i )
	{
    // rule key == field name i.e. sn_nickname
    ddformrule& rule = *_rules[i];
    // den feld- bzw. regelnamen holen...
    const ddstring& key = rule.get_name();
    
    // Nur wenn das form auch das Feld enthält, dann
    // testen. 
    i = form.index_of( key );
    if( i > -1 )
    {
      // den cgi-wert dazu testen
      if( !rule.apply( cgi[key], msgkey ) )
        _parent->set_error_message( form, key, msgkey );
	  }
	}

}


/***************************************************************************

    supportware
    Copyright (C) 2001-2008 by c.holzheuer
    c.holzheuer@sourceworx.org

    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.

***************************************************************************/


/**
	Default Constructor.
*/

ddformmanager::ddformmanager()
: _website( 0 ), _currentuser( 0 ), 
	_currentcgi( 0 ), _posting_delay( 10 ), _contains_blocked_words( false ),
  _has_error( false )
{

}


/**
	Destructor.
*/

ddformmanager::~ddformmanager()
{

}


/**
	Baut die verschiedenen formchecker aus der config auf.
*/

void ddformmanager::init_form_rules( ddwebsite& mysite )
{
	_website					= &mysite;
	
	ddconfig & config = mysite.get_config();
	
	_errorkey 	   = config["main"].get_entry( "error_key" );
	_errorsymbol   = config["main"].get_entry( "error_symbol" );
	_censorstring  = config["main"].get_entry( "censor_string" );
	_posting_delay = config["main"].get_int( "posting_delay", 10 );
	_messages      = config["err_msg"];
	
	
	// blockliste laden
	ddstring content;
	mysite.fetch_data_entry( DDKEYBLOCKEDWORDS, content );
	_killwords.from_string( content );
	
	// Zensurliste laden
	mysite.fetch_data_entry( DDKEYBADWORDS, content );
	_badwords.from_string( content, false );
	
	ddformruleparser parser( this );
	
	for( int i=0; i<config.size(); ++i )
	{
		
		if( config[i].get_type() != "form" )
			continue;

		const ddstring& key = config[i].get_name();
		_formcheckermap.add_by_key( key, new ddformchecker( this, parser, config[i] ) );
	
	}
}
  
  
/**
	Error-Symbol & Message setzen und mit 'false' 
	abhauen. 
*/

bool ddformmanager::set_error_message( dditem& form, const ddstring& key, const ddstring& msgkey )
{
	// im zweifel nur den ersten Error anzeigen
	if( !_has_error )
	{
		// neu: Fallunterscheidung zwischem alten und neuen Format
		if( form.index_of( _errorkey ) > -1 )
		{
			// falls das feld fehlt gibts halt nen crash, das gehört jetzt so
			form[ key+"_error" ].from_string( _errorsymbol );
			form[_errorkey].from_string( "<br>"+_messages[msgkey] );  			
		}
		else
		{
			//ddstring err = "<br><center><font color='red'>" + _messages[msgkey] + "</font></center>";
			ddstring err = "<br><font color='red'>" + _messages[msgkey] + "</font>";
			form[ key+"_error" ].from_string( err );			
		}
	
	}
  _has_error = true;
  return false;
}
 
 
/**
	Tested ein Formular.
	@param formname: der zuständige formchecker
	@param form das form
	@param user die userid des nutzers
	@param cgi die daten
	@return true on success.
*/  

bool ddformmanager::check_form( const ddstring& formname, dditem& form, dduserid& user, ddwebcgi& cgi )
{
	
	// alles sperren, ist nicht reentrant, 
	// der usermanager auch nicht
	ddthreadautomutex tmplock( _checkerlock );
	
	int idx = _formcheckermap.index_of( formname );
	if( idx<0 )
		throw runtime_error( "ddformmanager::check_form: no such formchecker: " + formname );
	
	ddformchecker& checker = _formcheckermap.get_entry( idx );
	_currentuser = &user;
	_currentcgi  = &cgi;
	
	// reset
	_contains_blocked_words = false;
	_has_error 					= false;
	
	// form säubern
	form.clear_value();
	form.clear_children();
	
	// form testen
	checker.check_form( form, user, cgi );
		
	// im fehlerfall form wieder füllen
	if( _has_error )
	{
		form.sync( cgi );
	}
	else
	{
		// posting ist ok, also:
		ddstring& body = cgi["sn_msgbody"];
		if( !body.empty() )
		{
			// - body speichern
			user.set_property( "snlastpost", body ) ;
			// -posting time setzen
			user.update_posting_time();
		}
	}
	return !_has_error;
	
}


/**
	Testet, ob zu oft hintereinander gepostet wurde.
*/

bool ddformmanager::check_chain_posting( ddstring& src )
{
	dduserid& uid = *_currentuser;
	// chefs dürfen immer ...
	if( uid.is_admin() )
		return true;		
	// first step: check posting time
	time_t now = ::time(0);
	if( now - uid.get_last_posting_time() < _posting_delay )
		return false;	
	return true;
}


/**
	Testet, ob der gleiche Text schonmal gepostet wurde.
*/

bool ddformmanager::check_repeated_posting( ddstring& src )
{
	dduserid& uid = *_currentuser;
	// chefs dürfen das
	if( uid.is_admin() )
		return true;
	ddstring& body = (*_currentcgi)["sn_msgbody"];
	// von diesem user schon mal den selben sotter gesehen ...
	if( !body.empty() && uid.get_property("snlastpost") == body )
		return false;
	return true;
}


/**
	AGB Akzeptanz testen. Dabei wird auch das 'accepting_terms'
	Flag in der usersession entsprechend gesetzt.
*/

bool ddformmanager::check_accepting_terms( ddstring& src )
{
	bool flag = ( src == "1" );
	_currentuser->set_accepting_terms_flag( flag );
	return flag;
}


/**
	Testet auf "verbotene Worte" also spambot-output wie "viagra cheap" etc.
	Setzt ggf. das Fatal-Error Flag, um anzusagen, das ganze posting still
	verworfen werden soll.
*/

bool ddformmanager::check_blocked_word( ddstring& src )
{
	if( !_currentuser->is_admin() && _killwords.find_entry_in( src ) )
	{
	  /*
	  // eintrag manipulieren und ...
	  ddwebcgi& cgi = *_currentcgi;
	  cgi["id_group"] = "2";
	  cgi["sn_msgbody"] = "<GEBLOCKT>:\n\n" + cgi["sn_msgbody"];	  
	  // trotzdem noch den evil-marker setzen...
	  */
	  _contains_blocked_words = true;
	}
	// ... still verschwinden	
	return true;
}


/**
	Zensiert böse worte
	@returns true;
*/

bool ddformmanager::check_bad_word( ddstring& src )
{
	// neu: bei admins gruppe 13 nicht zensieren
	bool allowbad = ( _currentuser->is_admin() ) && ( (*_currentcgi)["id_group"] == "13" );
	if( !allowbad )
		get_censored_words().translate( src, _censorstring );	
	return true;
}


/**
	Gibts den nick, version für den pager
*/

bool ddformmanager::check_user_is_member( ddstring& src )
{	
	return ( _website->get_user_manager().index_of( src.as_lower() ) >= 0 );
}


/**
	Gibts den nick schon, version zum posten
*/

bool ddformmanager::check_nickname_exists( ddstring& src )
{	
	// member gehen glatt durch: dieser test wird beim posten aufgerufen
	// und da ist klar das der nick schon existiert. siehe auch 
	// check_new_nickname_exists
	 
	if( _currentuser->is_member() )
		return true;
	return ( _website->get_user_manager().index_of( src.as_lower() ) < 0 );
}


/**
	Gibts den nick schon, version zur anmeldung
*/

bool ddformmanager::check_new_nickname_exists( ddstring& src )
{	
	return ( _website->get_user_manager().index_of( src.as_lower() ) < 0 );
}


/**
	Nutzt den usermanager, um eine nickname auf gültigkeit 
	(sonderzeichen, länge etc.) zu prüfen. Prüft auch,
	ob ein nickname noch frei ist. Postingversion, setzt den
	guestnick.
	
	OBACHT! FIX! HIRNFICK! Warum wird der feldwert geändert?
*/

bool ddformmanager::check_nickname_valid( ddstring& src )
{
	
	if( !_website->get_user_manager().check_nickname_valid( src ) )
		return false;
	
	ddstring& uid = (*_currentcgi)["id_user"];
	// admins dürfen alles
	if( _currentuser->is_sub_admin() )
	{
		// auch admins wollen posten
		if( uid.empty() )
			uid = _currentuser->get_id();	
		return true;
	}
	
	// ab hier: fake nicht erlaubt
	uid = _currentuser->get_id();	
	
	// members must not use wrong names
	if( _currentuser->is_member() )
	{
		(*_currentcgi)["sn_nickname"] = _currentuser->get_nick_name();
		return true;
	}
	bool not_exists = ( _website->get_user_manager().index_of( src.as_lower() ) < 0 );
	if( not_exists )
		_currentuser->set_nick_name( src );	
	return not_exists;
}


/**
	Nutzt den usermanager, um eine nickname auf gültigkeit 
	(sonderzeichen, länge etc.) zu prüfen. Anmeldeversion,
	die den guestnick _nicht_ setzt.	
*/

bool ddformmanager::check_new_nickname_valid( ddstring& src )
{
	return _website->get_user_manager().check_nickname_valid( src );
}


/**
	Testet eine eMail-Adresse auf Gültigkeit. Hier kann man noch blacklists einbauen. 
*/

bool ddformmanager::check_nickmail_valid( ddstring& src )
{
	
	if( src.empty() 
	|| !src.contains( '@' ) 
	|| !src.contains( '.' ) 
	|| src.find_first_of( " *%?&\r\t\n" ) != ddstring::npos )
	{
		return false;
	}
	// korreckte email adressen für gäste speichern
	if( _currentuser->is_guest() )
		_currentuser->set_nick_mail( src );
	
	return true;	

}





/**
	Setzt den mail alert state entsprechend der gültigkeit der 
	email-adresse.
*/

bool ddformmanager::check_mail_alert( ddstring& src )
{
	// hack: 9 wegen 1 (public) und 8 (mailalert)
	bool mailalert = _currentuser->set_mail_alert_flag( src == "9" );
	// ist kein fehler mehr
	//if( mailalert && (*_currentcgi)["sn_nickmail"].empty() )
	//	return false;
	// nochn hack: das feld heisst sn_mailalert und wird jetzt in id_state gespeichert
	(*_currentcgi)["id_state"] = mailalert ? "9" : "1";
	return true;	
}


/**
	Testet, ob das id_state Feld gültig ist.
*/

bool ddformmanager::check_state_valid( ddstring& src )
{
	int flag= src.as_int();
	return (flag > 0 && flag < 64 );	
}


/**
	Testet, ob das id_group Feld gültig ist.
*/

bool ddformmanager::check_group_valid( ddstring& src )
{	
	// chefs dürfen alles
	//if( _currentuser->is_admin() )
	if( _currentuser->is_sub_admin() )		
		return true;
	return _website->is_group_valid( src.as_int() );
}


/***************************************************************************

    supportware
    Copyright (C) 2001-2008 by c.holzheuer
    c.holzheuer@sourceworx.org

    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.

***************************************************************************/


/**
	Default constructor.
*/

ddformrule::ddformrule( bool is_op_and, ddformrule* child1, ddformrule* child2 )
:  _is_op_and( is_op_and ), _child1( child1 ), _child2( child2 )
{

}


/**
	Destructor.
*/

ddformrule::~ddformrule()
{
  if( _child1 )
		delete _child1;
  if( _child2 )
		delete _child2;
}


/**
	Apply this rule: call apply for all child rules
*/

bool ddformrule::apply( ddstring& src, ddstring& message )
{
  if( _is_op_and )
		return _child1->apply( src, message ) && _child2->apply( src, message );
	return _child1->apply( src, message ) || _child2->apply( src, message );

}


void ddformrule::dump( ddstring blank )
{
	if( _child1 )
		_child1->dump( blank + "   " );
  if( _child2 )
		_child2->dump( blank + "   " );
}


/***************************************************************************

    supportware
    Copyright (C) 2001-2008 by c.holzheuer
    c.holzheuer@sourceworx.org

    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.

***************************************************************************/


/**
	Constructor: Erzeugt eine Regel.
	@param call die check function (a memberfunction of ddformchecker) to be called.
	@see ddformchecker
*/

ddformrulecall::ddformrulecall( ddformmanager* parent, ddrulecall call, const ddstring& cmdkey, const ddstring& msgkey, const ddstring& param )
: _parent( parent ), _call( call ), _cmdkey( cmdkey ), _msgkey( msgkey ), _param( param )
{

}


/**
	Wendet eine Regel auf einen Form-Wert an.
	@param src: der form-Wert
	@param msgkey: der Code der Fehlermeldung
	@return true on success
*/

bool ddformrulecall::apply( ddstring& src, ddstring& msgkey )
{
  bool result = (this->*_call)( src );
  if( !result )
		msgkey = _msgkey;
	return result;
}


/**
	Testet, ob der Form-Wert leer ist.
*/

bool ddformrulecall::check_empty( ddstring& src )
{
 	return src.empty();
}


/**
	Testet, ob der Form-Wert nicht leer ist.
*/
  
bool ddformrulecall::check_not_empty( ddstring& src )
{
	return !src.empty();
}


/**
	Testet, ob der Form-Wert eine bestimmte Zeichenkette enthält.
*/
  
bool ddformrulecall::check_contains( ddstring& src )
{
 	return ( src.contains( _param ) );
}


/**
	Testet, ob der Form-Wert mindestens die Vorgabelänge hat.
*/
  
bool ddformrulecall::check_minsize( ddstring& src )
{
	return ( src.size() >= (size_t) _param.as_int() );
}


/**
	Testet, ob der Form-Wert länger die Vorgabe ist.
*/
  
bool ddformrulecall::check_maxsize( ddstring& src )
{
	return ( src.size() <= (size_t) _param.as_int() );
}


/**
	Testet, ob der Form-Wert dem Vorgabeparameter entspricht.
*/

bool ddformrulecall::check_equal( ddstring& src )
{	
	return ( src == _param );	
}


/**
	Testet, ob der Form-Wert ungleich dem Vorgabeparameter ist.
*/

bool ddformrulecall::check_not_equal( ddstring& src )
{
	return ( src != _param );
}


/**
	Testet, ob der Form-Wert kleiner als der Vorgabeparameter ist.
*/

bool ddformrulecall::check_less_than( ddstring& src )
{
	return ( src.as_int() < _param.as_int() );	
}


/**
	Testet, ob der Form-Wert größer als der Vorgabeparameter ist.
*/

bool ddformrulecall::check_greater_than( ddstring& src )
{
	return ( src.as_int() > _param.as_int() );	
}


/**
	Bastelt eine korrecktes reply-subject zusammen: RE: Blablub
*/

bool ddformrulecall::check_is_subject( ddstring& src )
{
	if( src.substr( 0, 4 ) != "RE: " )
		src = "RE: "+src;
	return true;
}

	
/**
	Testet, ob die AGBs akzeptiert wurden.
	Wird delegiert an den Formmanager.
	@see ddformmanager
*/

bool ddformrulecall::check_accepting_terms( ddstring& src )
{
	return _parent->check_accepting_terms( src );
}


/**
	Testet, ob der nickname korrekt ist.
	Wird delegiert an den Formmanager.
	@see ddformmanager
*/

bool ddformrulecall::check_nickname_valid( ddstring& src )
{
	return _parent->check_nickname_valid( src );	
}


/**
	Testet, ob ein User Member ist.
	Wird delegiert an den Formmanager.
	@see ddformmanager
*/

bool ddformrulecall::check_user_is_member( ddstring& src )
{
	return _parent->check_user_is_member( src );	
}


/**
	Testet, ob der nickname korrekt ist.
	Wird delegiert an den Formmanager.
	@see ddformmanager
*/

bool ddformrulecall::check_new_nickname_valid( ddstring& src )
{
	return _parent->check_new_nickname_valid( src );	
}


/**
	Gibts den nick schon, version zum posten
*/

bool ddformrulecall::check_nickname_exists( ddstring& src )
{
	return _parent->check_nickname_exists( src );	
}


/**
	Gibts den nick schon, version zur anmeldung
*/

bool ddformrulecall::check_new_nickname_exists( ddstring& src )
{	
	return _parent->check_new_nickname_exists( src );	
}

/**
	Testet, ob die eMail-Adresse korreckt ist.
	Wird delegiert an den Formmanager.
	@see ddformmanager
*/

bool ddformrulecall::check_nickmail_valid( ddstring& src )
{
	return _parent->check_nickmail_valid( src );	
}


/**
	Testet auf 'blocked words'.
	Wird delegiert an den Formmanager.
	@see ddformmanager
*/

bool ddformrulecall::check_blocked_word( ddstring& src )
{
	return _parent->check_blocked_word( src );
}


/**
	Testet ob zensierte Worte vorhanden sind und löscht diese ggf.
	Wird delegiert an den Formmanager.
	@see ddformmanager
*/

bool ddformrulecall::check_bad_word( ddstring& src )
{
	return _parent->check_bad_word( src );
}


/**
	Testet, ob zu schnell hintereinander gepostet wurde.
	Wird delegiert an den Formmanager.
	@see ddformmanager
*/

bool ddformrulecall::check_chain_posting( ddstring& src )
{
	return _parent->check_chain_posting( src );
}


/**
	Testet, ob mehrmals der gleiche Text gepostet wurde.
	Wird delegiert an den Formmanager.
	@see ddformmanager
*/

bool ddformrulecall::check_repeated_posting( ddstring& src )
{
	return _parent->check_repeated_posting( src );
}


/**
	Testet, ob die Mail-Benachrichtigung korrekt eingetragen ist.
	Wird delegiert an den Formmanager.
	@see ddformmanager
*/

bool ddformrulecall::check_mail_alert( ddstring& src )
{
	return _parent->check_mail_alert( src );
}


/**
	Testet id_state.
	Wird delegiert an den Formmanager.
	@see ddformmanager
*/

bool ddformrulecall::check_state_valid( ddstring& src )
{
	return _parent->check_state_valid( src );
}


/**
	Testet, ob das id_group Feld gültig ist.
	@see ddformmanager
*/

bool ddformrulecall::check_group_valid( ddstring& src )
{
	return _parent->check_group_valid( src );
}

/***************************************************************************

    supportware
    Copyright (C) 2001-2008 by c.holzheuer
    c.holzheuer@sourceworx.org

    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.

***************************************************************************/


/**
	Standard constructor.
*/

ddformruleparser::ddformruleparser( ddformmanager*	parent )
: _parent( parent )
{
	init_call_map();
}


/**
	Destructor.
*/

ddformruleparser::~ddformruleparser()
{

}


/**
	Initialisiert die call-adressen der testroutinen.
*/

void ddformruleparser::init_call_map()
{
	_calls["contains"]       			= &ddformrulecall::check_contains;
	_calls["empty"]          			= &ddformrulecall::check_empty;
	_calls["not_empty"]      			=	&ddformrulecall::check_not_empty;
	_calls["user_is_member"] 			= &ddformrulecall::check_user_is_member;
	_calls["nickname_valid"] 			= &ddformrulecall::check_nickname_valid;
	_calls["new_nickname_valid"] 	= &ddformrulecall::check_new_nickname_valid;
	_calls["nickname_exists"]			= &ddformrulecall::check_nickname_exists;				
	_calls["new_nickname_exists"]	= &ddformrulecall::check_new_nickname_exists;				
	_calls["nickmail_valid"] 			= &ddformrulecall::check_nickmail_valid;
	_calls["minsize"] 			 			= &ddformrulecall::check_minsize;	
	_calls["maxsize"] 			 			= &ddformrulecall::check_maxsize;	
	_calls["equal"]		 			 			= &ddformrulecall::check_equal;	
	_calls["not_equal"]			 			= &ddformrulecall::check_not_equal;	
	_calls["less"]		 			 			= &ddformrulecall::check_less_than;	
	_calls["greater"]			 	 			= &ddformrulecall::check_greater_than;	
	_calls["accepting_terms"]			= &ddformrulecall::check_accepting_terms;
	_calls["blocked_word"]  			= &ddformrulecall::check_blocked_word;
	_calls["bad_word"]  					= &ddformrulecall::check_bad_word;
	_calls["chain_posting"]   		= &ddformrulecall::check_chain_posting;	
	_calls["repeated_posting"]		= &ddformrulecall::check_repeated_posting;	
	_calls["is_subject"]      		= &ddformrulecall::check_is_subject;
	_calls["mail_alert"]					= &ddformrulecall::check_mail_alert;
	_calls["state_valid"]					= &ddformrulecall::check_state_valid;
	_calls["group_valid"]     		= &ddformrulecall::check_group_valid;
}


/**
	
*/

void ddformruleparser::split( const ddstring& src, const ddstring& sep )
{
	size_t beg=0, end=0, step=0;
  _rules.clear();
	// ein trennzeichen, ein feld (ala otti)
	while ( beg < src.size() )
	{
		end = src.find_first_of( sep, beg );
    if( end == ddstring::npos )
			end = src.size();
    step = end-beg;
    if( step )
    {
      ddstring tmp1 = src.substr( beg, step );
      tmp1.trim();
      if( !tmp1.empty() )
				_rules.add( tmp1 );
		}
		if( end != src.size() )
     	_rules.add( src.substr( end, 1 ) );
		beg = end + 1;
	}
}


/**

*/

ddformrule* ddformruleparser::parse( const ddstring& rulestr )
{
  split( rulestr, "()|&" );  
  _idx = 0;
  ddformrule* base = 0;
	base = parse_expression();
	return base;
}


/**
	
*/

ddformrule* ddformruleparser::parse_expression()
{  
	ddformrule* result = parse_term();
	while( _idx < _rules.size() )
	{
    if( _rules[_idx] != "|" )
			break;
    _idx++;
		result = new ddformrule( false, result, parse_term() );
	}
	return result;
}


/**
	
*/

ddformrule* ddformruleparser::parse_term()
{
	ddformrule* result = parse_primary();
	while( _idx < _rules.size() )
	{
    if( _rules[_idx] != "&" )
			break;
    _idx++;
		result = new ddformrule( true, result, parse_primary() );
	}
	return result;	
}


/**
	
*/

ddformrule* ddformruleparser::parse_primary()
{
  // parse embraced expressions
  ddstring& token = _rules[_idx++];
	if( token == "(" )
	{
		ddformrule* exp = parse_expression();
    if( _rules[_idx++] != ")" )
			throw runtime_error("ddformruleparser::parse_primary: ')' expected! " );
		return exp;
	}

  // parse calls
  ddstring cmd = token;
  ddstring param, msgkey;

  // find message key
  size_t colon = cmd.rfind( ':' );
  if( colon != ddstring::npos )
  {		
		msgkey = cmd.substr( colon+1 );
    cmd = cmd.substr( 0, colon );
	}

  // find param  
	size_t brac1 = cmd.rfind( '[' );
	size_t brac2 = cmd.rfind( ']' );
	if( brac1 != ddstring::npos && brac2 != ddstring::npos )
	{		
		param = cmd.substr( brac1+1, brac2-brac1-1 );
		cmd = cmd.substr( 0, brac1 );
	}
  ddrulecall ptr = _calls[cmd];
  if( !ptr )
		throw runtime_error( "ddformruleparser::parse_primary: cmd not found: " + cmd );

  return new ddformrulecall( _parent, ptr, cmd, msgkey, param );

}



