//
// Colorizer.C
//
// written by Michael Riedel <Michael.Riedel@gmx.de>
//

#include <stdlib.h>
#include <qfile.h>
#include <kapp.h>

#include "Colorizer.h"
#include "SyntaxState.h"


    
///////////////////////////////////////////////////////
Colorizer::Colorizer(const char* rules_file)
	: KeywordDict(173)
{
	RuleList.setAutoDelete(true);
	
	if(rules_file && rules_file[0] != 0)
		load(rules_file);
		
	else	// add the minimum: normal state with index 0!
	{
		init(true);
	}
}
///////////////////////////////////////////////////////



///////////////////////////////////////////////////////
Colorizer::~Colorizer()
{
}
///////////////////////////////////////////////////////



///////////////////////////////////////////////////////
inline void Colorizer::insertState(const QString& s)
{
	if(StateArraySize >= StateArray.size())
	{
		if(!StateArray.resize(StateArraySize + 10))
		{
			warning("error: no mem!");
			return;
		}
	}	
		
	SyntaxState* ss = new SyntaxState(s);
	StateArray.insert(StateArraySize++, ss);
}
///////////////////////////////////////////////////////



///////////////////////////////////////////////////////
inline void Colorizer::insertRule(const QString& s)
{
	RegExpSyntaxUnit* rule = new RegExpSyntaxUnit(s, StateArray);
	RuleList.append(rule);
}
///////////////////////////////////////////////////////



///////////////////////////////////////////////////////
inline void Colorizer::insertKeyword(const QString& keyword)
{
    KeywordDict.insert(keyword, new bool(false));
}
///////////////////////////////////////////////////////



///////////////////////////////////////////////////////
void Colorizer::init(bool createNormalState)
{
	StateArraySize = 0;
	StateArray.resize(0);
	
	KeywordCheckIx = -1;
	KeywordStateIx = -1;
	
	if(createNormalState)
		insertState("Normal");
}
///////////////////////////////////////////////////////



#define ST_STATES    0
#define ST_RULES     1
#define ST_KEYWORDS  2
#define ST_SETTINGS  3
#define ST_IGNORE   -1

///////////////////////////////////////////////////////
bool Colorizer::load(const QString& filename)
{
		// clear actual settings:
	init(false);

	int currentState = ST_IGNORE;
	
	
	QRegExp st("states[ \\t]*:", false);
	QRegExp ru("rules[ \\t]*:", false);
	QRegExp ke("keywords[ \\t]*:", false);
	QRegExp se("settings[ \\t]*:", false);

	QFile f(filename);
	if(f.open(IO_ReadOnly))
	{
		QTextStream t(&f);

		while(!t.eof())
		{
			QString s = t.readLine();
			s = s.stripWhiteSpace();
			
			if(s.find(st, 0) == 0)
			{
				currentState = ST_STATES;
				continue; 
			}
			else if(s.find(ru, 0) == 0)
			{
				currentState = ST_RULES;
				continue; 
			}
			else if(s.find(ke, 0) == 0)
			{
				currentState = ST_KEYWORDS;
				continue; 
			}
			else if(s.find(se, 0) == 0)
			{
				currentState = ST_SETTINGS;
				continue; 
			}
			else if(s.isEmpty() || s.find("#") == 0)	// spaces are delīd from s
				continue;

			switch(currentState)
			{
				case ST_STATES:
					insertState(s);
					break;
				case ST_RULES:
					insertRule(s);
					break;
				case ST_KEYWORDS:
					insertKeyword(s);
					break;
				case ST_SETTINGS:
					readSetting(s);
					break;
				case ST_IGNORE:
				default:
					if(!s.isEmpty() && !s.contains("^\\s*#"))
						warning("invalid line <%s> ignored", s.data());
					break;
			}
		}
	}
	return false;
}
///////////////////////////////////////////////////////



///////////////////////////////////////////////////////
bool Colorizer::save(const QString& /*filename*/)
{
	return false;
}
///////////////////////////////////////////////////////



///////////////////////////////////////////////////////
void Colorizer::readSetting(const QString& s)
{
	int eqSignIx = s.find('=');
	if(eqSignIx <= 0)
	{
		warning("setting line <%s> ignored", s.data());
		return;
	}
	
	QString entry = s.left(eqSignIx);
	QString value = s.right(s.length() - eqSignIx - 1);
	
	QRegExp r1("\\s*keywordcheck\\s*", false),
			r2("\\s*keywordstate\\s*", false);
	
	if(entry.find(r1) == 0)
	{
		KeywordCheckIx = findState(StateArray, value);
		if(KeywordCheckIx == 0)		// normal state is invalid as 
			KeywordCheckIx = -1;	// keyword state (performance loss)
	}
	else if(entry.find(r2) == 0)
	{
		KeywordStateIx = findState(StateArray, value);
		if(KeywordStateIx == 0)
			KeywordStateIx = -1;
	}
	else
		warning("setting line <%s> ignored", s.data());

}
///////////////////////////////////////////////////////



///////////////////////////////////////////////////////
inline bool Colorizer::testForKeyword(const QString& test) const
{
    return KeywordDict.find(test);
}
///////////////////////////////////////////////////////



///////////////////////////////////////////////////////
int Colorizer::match(const QString& s, int start, int& len, int& innerState, int& state)
{
    register SyntaxUnit* elem = RuleList.first();
    
    	// Data of found syntax unit:
    SyntaxUnit* foundElem = 0;
    int foundStart = -1, 
        foundLen = -1, 
    	l, l2;
    
        // search the first valid RegExp:
    while(elem)
    {
    	register int st2 = elem->StartMatch(s, start, l2);
    	if(st2 != -1 && (!foundElem || st2 <= foundStart))
    	{
	    	register int st = elem->Match(s, start, l, state);
        	if(st != -1 /*&& st == st2 + l2*/)
			{
/*			
		    	if(!foundElem || st < foundStart)
	    		{
	    			foundElem = elem;
    				foundStart = st2;
    				foundLen = l + l2;
	    		}
		    	else if(foundElem && st2 == foundStart && foundLen < l + l2)
	    		{
    				foundElem = elem;
	    			foundStart = st2;
    				foundLen = l + l2;
    			}
*/
		    	if(!foundElem || st < foundStart)
	    		{
	    			foundElem = elem;
    				foundStart = st;
    				foundLen = l;
	    		}
		    	else if(foundElem && st == foundStart && foundLen < l)
	    		{
    				foundElem = elem;
	    			foundStart = st;
    				foundLen = l;
    			}    			
    		}
		}
	   	elem = RuleList.next();
    }

    if(foundElem)
    {
   		len = foundLen;
   		if(foundElem->innerState() == KeywordCheckIx && 
	    			testForKeyword(s.mid(foundStart, foundLen)))
	        innerState = KeywordStateIx;
    	else
	    	innerState = foundElem->innerState();
	    	
       	state = foundElem->stateAfterwards();
	    return foundStart;
    }
    else
    {
       	return -1;
    }
}
///////////////////////////////////////////////////////

