var sValidChars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZÍÖÜÓŐÚÉÁŰÀÂÆÇÈÊËÎÏÔŒÛÙÑ\'';
var sSpaceChars = ' \r\n\t';
var sOperChars = '=/<>';
var icOperPriority = 4;
var icNotPriority = 5;
var icAndPriority = 6;
var icOrPriority = 7;
//var sTermOperChars = '+-';
var gsSource = '';
var iSourcePos = 0;

// Classes

function Element(sType, sValue, iPos, iPriority) {
	this.sType = sType;
	this.sValue = sValue;
	this.iPos = iPos;
	this.iPriority = iPriority;
}

function Node(sType, sValue, iPos, iPriority) {
	this.sType = sType;
	this.sValue = sValue;
	this.iPos = iPos;
	this.iPriority = iPriority;
	this.aChildren = new Array();
	this.copyTo = copyTo;
}

function copyTo(oNode) {
	oNode.sType = this.sType;
	oNode.sValue = this.sValue;
	oNode.iPos = this.iPos;
	oNode.iPriority = this.iPriority;
	oNode.aChildren = this.aChildren;
}

// Global functions

function initParser(sSource) {
	gsSource = sSource.toUpperCase();
	iSourcePos = 0;
	aoStack = new Array();
}

function getNextChar() {
	if (iSourcePos >= gsSource.length)
		return '\x00';
	else
		return gsSource.charAt(iSourcePos);
}

function getNextElement(bMinusAsNot) {
	var cChar = getNextChar();
	var sType = '', sValue = '';
	var iPos = iSourcePos;
	var iPriority = 0;

	if (sSpaceChars.indexOf(cChar) != -1) {
		sType = 'sp';
		while (sSpaceChars.indexOf(cChar) != -1) {
			sValue += cChar;
			iSourcePos++;
			cChar = getNextChar();
		}
	} else
	if (sValidChars.indexOf(cChar) != -1 || cChar == '*') {
		if (cChar == '*') {
			sValue += cChar;
			sType = 'wp';
			iSourcePos++;
			cChar = getNextChar();
		} else
			sType = 'wd';
			
		while (sValidChars.indexOf(cChar) != -1) {
			sValue += cChar;
			iSourcePos++;
			cChar = getNextChar();
		}
		if (cChar == '*') {
			sValue += cChar;
			sType = 'wp';
			iSourcePos++;
			cChar = getNextChar();
		}
		
		if (sType == 'wd') {
			switch (sValue.toLowerCase()) {
				case 'not':
					iPriority = icNotPriority;
					sType = 'op';
					break;
				case 'and':
					iPriority = icAndPriority;
					sType = 'op';
					break;
				case 'or':
					iPriority = icOrPriority;
					sType = 'op';
					break;
			}
		}

		if (sValue == '*')
			throw new Error('Star can not be used by itself @' +iSourcePos +':1');
		
		if (sType == 'wp' || sType == 'wd')
			sValue = cleanupWord(sValue);
			
		if (sType == 'wd') {
			if (sValue.replace(/[\*0-9]/g, '').length == 0) {
				if (sValue.length != 2 && sValue.length != 4 && sValue.length != 6)
					throw new Error('Numeric values must be 2, 4 or 6 charactes long @' +iSourcePos +':1');
				else
					sValue = ':' +sValue;
			}
	
			if (sValue.match(/^[A-Z][0-9]{4}$/))
				sValue = ':' +sValue;
				
			if (sValue.length < 2)
				throw new Error('Words must be at least 2 characters long @' +iSourcePos +':1');
		}
		
		if (sType == 'wp') {
			if (sValue == '*ING')
				throw new Error('The "*ING" word is denied @' +iSourcePos +':1');
				
			if (sValue.replace(/[\*0-9]/g, '').length == 0)
				throw new Error('Numeric only values cannot be used with star @' +iSourcePos +':1');
		
			if (sValue.replace(/\*/g, '').length < 3)
				throw new Error('Words with star must be at least 3 characters long @' +iSourcePos +':1');
		}
	} else
/*	if (sOperChars.indexOf(cChar) != -1) {
		sType = 'op';
		sValue = cChar;
		iSourcePos++;
		iPriority = icOperPriority;
		cChar = getNextChar();
	} else
	if (sTermOperChars.indexOf(cChar) != -1) {
		if (!bMinusAsNot || (bMinusAsNot && cChar != '-')) {
			sType = 'to';
			sValue = cChar;
		} else {
			sType = 'op';
			sValue = 'not';
			iPriority = icNotPriority;
		}

		iSourcePos++;
		cChar = getNextChar();
	} else*/
	if (cChar == '"') {
		iSourcePos++;
		cChar = getNextChar();
		if (cChar == '*') {
			sValue += cChar;
			sType = 'ep';
			iSourcePos++;
			cChar = getNextChar();
		} else
			sType = 'ex';
		
		while (cChar != '*' && cChar != '"' && cChar != '\x00') {
			sValue += cChar;
			iSourcePos++;
			cChar = getNextChar();
		}
		switch (cChar) {
			case '*':
				sValue += cChar;
				iSourcePos++;
				cChar = getNextChar();
				
				if (cChar != '"')
					throw new Error('Quote missing in the end of expression @' +iSourcePos +':1');
				sType = 'ep';
				
				break;
			case '\x00':
				throw new Error('Unexpected end of expression @' +iSourcePos +':1');
				break;
		}

		if (sValue == '*')
			throw new Error('Star can not be used by itself @' +iSourcePos +':1');

		iSourcePos++;
		cChar = getNextChar();
	} else
	if (cChar == '(') {
		sType = 'bs';
		iSourcePos++;
		cChar = getNextChar();
	} else
	if (cChar == ')') {
		sType = 'be';
		iSourcePos++;
		cChar = getNextChar();
	}

	if (sType == '') {
		if (cChar != '\x00')
			throw new Error('Invalid character \'' +cChar +'\' @' +iSourcePos +':1');
			
		return null;
	} else
		return new Element(sType, sValue, iPos, iPriority);
}

// Main functions

var aElementArray;
var iElementPos;

function buildElements(bMinusAsNot) {
	var sHTML = '';
	aElementArray = new Array();
	iElementPos = 0;
	for (var i = 0; i < 500; i++) {
		var oElem = getNextElement(bMinusAsNot);

		if (oElem == null)
			break;
		else {
			if (oElem.sType != 'sp') {
				aElementArray.push(oElem);
				sHTML += oElem.sType +': &quot;' +oElem.sValue +'&quot; (' +oElem.iPriority +') @' +oElem.iPos +'<br>';
			}
		}
	}
	return sHTML;
}

var aoStack;
var iLabelIndex;

function doRenderHTML(oNode) {
	var sHTML = '<table border="1" style="border-collapse: collapse" bordercolor="#000000" cellpadding="4"><tr><td colspan="' +oNode.aChildren.length +'" align="center">' +oNode.sValue +'</td></tr>';
	if (oNode.aChildren.length > 0)
		sHTML += '<tr>';
	for (var i = 0; i < oNode.aChildren.length; i++)
		sHTML += '<td>' +doRenderHTML(oNode.aChildren[i]) +'</td>';
	if (oNode.aChildren.length > 0)
		sHTML += '</tr>';
	sHTML += '</table>';
	
	return sHTML;
}

function doRenderQuery(oNode, bHilight) {
	return doRenderQueryCyc(oNode, 255, bHilight);
}

function doRenderQueryCyc(oNode, iPrevPriority, bHilight) {
	sHTML = '';
	switch (oNode.sType) {
		case 'op':
			if (oNode.iPriority > iPrevPriority)
				sHTML += '(';
			var iStart = 0;
/*			if (sOperChars.indexOf(oNode.sValue) != -1) {
				if (bHilight)
					sHTML += '<font color="#808000" style="font-weight:bold">';
				sHTML += oNode.aChildren[0].sValue +oNode.sValue;
				if (bHilight)
					sHTML += '</font>';
					
				iStart = 1;
			}	*/
			for (var i = iStart; i < oNode.aChildren.length; i++) {
				sHTML += doRenderQueryCyc(oNode.aChildren[i], oNode.iPriority, bHilight);
				if (i < oNode.aChildren.length -1) {
/*					if (sOperChars.indexOf(oNode.sValue) != -1)
						sHTML += oNode.sValue;
					else {*/
						if (bHilight)
							sHTML += '<font color="#008080">';
						sHTML += ' ' +oNode.sValue.toLowerCase() +' ';
						if (bHilight)
							sHTML += '</font>';
//					}
				}
			}
			if (oNode.iPriority > iPrevPriority)
				sHTML += ')';
			break;
		case 'to':
			sHTML += oNode.sValue +doRenderQueryCyc(oNode.aChildren[0], oNode.iPriority, bHilight);
			break;
		case 'wd':
		case 'wp':
			if (bHilight)
				sHTML += '<font color="#0040C0" style="font-weight:bold">';
			sHTML += oNode.sValue;
			if (bHilight)
				sHTML += '</font>';
			break;
		case 'ex':
		case 'ep':
			sHTML = '"' +oNode.sValue +'"';
			break;
	}
	return sHTML;
}

function doCleanStopwords(oNode, aStopWords) {
	var bResult = true;
	switch (oNode.sType) {
		case 'op':
			bResult = false;
			
			for (var i = oNode.aChildren.length -1; i >= 0; i--) {
				var bRes = doCleanStopwords(oNode.aChildren[i], aStopWords);
				if (!bRes)
					oNode.aChildren.splice(i, 1);
				bResult |= bRes;
			}
			
			if (oNode.aChildren.length == 1) {
//				if (sOperChars.indexOf(oNode.sValue) == -1)
					oNode.aChildren[0].copyTo(oNode);
/*				else
					throw new Error('Field \'' +oNode.aChildren[0].sValue +'\' can not be used on stopword @' 
						+oNode.aChildren[0].iPos +':' +oNode.aChildren[0].sValue.length);*/
			}
			
			break;
		case 'to': // This always results acceptable words, doesent matter what is below, but - cant be used on stopwords
			var bTemp = false;
			
			for (var i = oNode.aChildren.length -1; i >= 0; i--)
				bTemp |= doCleanStopwords(oNode.aChildren[i], aStopWords);

			if (oNode.sValue == '-' && !bTemp)
				throw new Error('Operator \'-\' can not be used on stopwords @' +oNode.iPos
					 +':' +oNode.sValue.length);
		
			break;
		case 'wd':
		case 'wp':
		case 'ex':
		case 'ep':
			var sWord = oNode.sValue.replace(/\*/g, '');
			for (var i = 0; i < aStopWords.length; i++)
				if (aStopWords[i] == sWord) {
					bResult = false;
					break;
				}
			
			break;
	}
	return bResult;
}

function checkParentheses(sText) {
	var iParenthesesDepth = 0;
	for (var i = 0; i < sText.length; i++)
		switch (sText.charAt(i)) {
			case '(':
				iParenthesesDepth++;
				break;
			case ')':
				if (iParenthesesDepth == 0)
					throw new Error('No starter parentheses for closer parentheses @' +i +':1');
			
				iParenthesesDepth--;
				break;
		}
		
	if (iParenthesesDepth != 0)
		throw new Error('No closer parentheses for a starter parentheses @' +i +':1');
}

function insertAnd(iPos) {
//	var oElement = new Element('op', 'AND', iPos, icAndPriority);
	var oElement = new Element('op', 'OR', iPos, icOrPriority);
	aElementArray.push(null);
	for (var i = aElementArray.length -1; i > iElementPos; i--)
		aElementArray[i] = aElementArray[i -1];
	aElementArray[iElementPos] = oElement;
}

function parseItems(iPrevPriority, bMergeOps) {
	var iWordCount = 0;
	while (iElementPos < aElementArray.length) {
		oElement = aElementArray[iElementPos];
			
		switch (oElement.sType) {
			case 'ex':
			case 'ep':
			case 'wd':
			case 'wp':
//					alert(iWordCount +': ' +oElement.sValue);
			
				if (iWordCount == 0) {
					aoStack.push(new Node(oElement.sType, oElement.sValue, oElement.iPos, oElement.iPriority));
					iElementPos++;
				} else
					insertAnd(oElement.iPos);

				iWordCount++;
				
				break;
			case 'bs': // (
				if (iWordCount == 0) {
					iElementPos++;
					parseItems(255, bMergeOps);
					iElementPos++;
				} else
					insertAnd(oElement.iPos);

				iWordCount++;
				
				break;
			case 'be': // )
				return;
			case 'to': // +, -
				if (iWordCount == 0) {
					var oTempNode = new Node(oElement.sType, oElement.sValue, oElement.iPos, oElement.iPriority);
					
					iElementPos++;
					parseItems(0, bMergeOps);

					if (aoStack.length == 0)
						throw new Error('Not enough parameters for operator \'' +oTempNode.sValue +'\' @' +oTempNode.iPos  +':' +oTempNode.sValue.length);

					oTempNode.aChildren.push(aoStack.pop());
					aoStack.push(oTempNode);
				} else
					insertAnd(oElement.iPos);

				iWordCount++;

				break;
			case 'op': // operators
				var oTempNode = new Node(oElement.sType, oElement.sValue, oElement.iPos, oElement.iPriority);
				
				//alert(oElement.sValue +': ' +oElement.iPriority +', ' +iPrevPriority);
				
				if (oElement.iPriority > iPrevPriority) 
					return;
				else {
					iElementPos++;
					parseItems(oElement.iPriority, bMergeOps);
					
					if (aoStack.length < 2)
						throw new Error('Not enough parameters for operator \'' +oTempNode.sValue +'\' @' +oTempNode.iPos +':' +oTempNode.sValue.length);

					var oTemp2 = aoStack.pop(); // For swap order
					var oTemp1 = aoStack.pop();
					
					if (bMergeOps) {
						if (oTemp1.sType == 'op' && oTemp1.sValue == oTempNode.sValue) {
							for (var i = 0; i < oTemp1.aChildren.length; i++)
								oTempNode.aChildren.push(oTemp1.aChildren[i]);
						} else
							oTempNode.aChildren.push(oTemp1);

						if (oTemp2.sType == 'op' && oTemp2.sValue == oTempNode.sValue) {
							for (var i = 0; i < oTemp2.aChildren.length; i++)
								oTempNode.aChildren.push(oTemp2.aChildren[i]);
						} else
							oTempNode.aChildren.push(oTemp2);
					} else {
						oTempNode.aChildren.push(oTemp1);
						oTempNode.aChildren.push(oTemp2);
					}
						
					aoStack.push(oTempNode);
				}
				
				iWordCount++;

				break;
			default:
				iElementPos++;
				break;
		}
	}
	
	return;
}

function doParse(sText, bMinusAsNot, bMergeOps, bStopword, aStopWords) {
	sText = replaceQuotesWithAnds(sText);	
	checkParentheses(sText);
	initParser(sText);
	buildElements(bMinusAsNot);
	parseItems(255, bMergeOps);
	if (aoStack.length == 0)
		throw new Error('Query is empty');
	var oRoot = aoStack[0];
	if (bStopword && !doCleanStopwords(oRoot, aStopWords))
		throw new Error('Query unacceptable because it contains only stopwords');
	return oRoot;
}

// XSLT search functions

var iResultIndex;

function doExecSearch(oNode) {
    var sFilter = doCollectWordsCyc(oNode);
    var sFilter = sFilter.substr(0, sFilter.length -1);
    var sOp = '';
    iResultIndex = 1;
    var iLevel = 0;
    while (true) {
    	if (oNode.sType == 'wd' || oNode.sType == 'wp' || oNode.sType == 'ex' || oNode.sType == 'ep')
    		break;
    		
    	if (sOp != '')
    		sOp += ';';

    	sOp += doExecSearchCyc(oNode);
    	
	    var sCopy = doCollectWordsCyc(oNode);
	    if (sCopy != '')
		    sOp += 'C(' +sCopy.substr(0, sCopy.length -1) +')';
		    
		iLevel++;
	}
    
    sURL = '&xsl0=xslt/wordfilter.xsl&filter=' +sFilter +'&op=' +sOp;
    for (var i = 0; i < iLevel; i++)
    	sURL += '&xsl' +(i +1) +'=xslt/opfilter.xsl';

	return sURL;
}

function doCollectWordsCyc(oNode) {
	sWords = '';
	switch (oNode.sType) {
		case 'op':
			for (var i = 0; i < oNode.aChildren.length; i++)
				sWords += doCollectWordsCyc(oNode.aChildren[i]);
			break;
		case 'wd':
		case 'wp':
		case 'ex':
		case 'ep':
			if (oNode.sValue.charAt(0) == '#')
				oNode.sValue = 'R' +oNode.sValue.substr(1);
			else
				sWords += oNode.sValue +',';
			break;
	}
	return sWords;
}

//'A(ABDOMINAL,CORSETS,R1)A(BELT*,PURPOSES,R2);O(R1,R2,R3)';
function doExecSearchCyc(oNode) {
	sQuery = '';
	switch (oNode.sType) {
		case 'op':
			var oNode1 = oNode.aChildren[0];
			var oNode2 = oNode.aChildren[1];
			
			var bOk = (oNode1.sType == 'wd' || oNode1.sType == 'wp' || oNode1.sType == 'ex' || oNode1.sType == 'ep')
				&& (oNode2.sType == 'wd' || oNode2.sType == 'wp' || oNode2.sType == 'ex' || oNode2.sType == 'ep');
				
			if (bOk) {
				sQuery += oNode.sValue.charAt(0) +'(' +oNode1.sValue +',' +oNode2.sValue +',R' +iResultIndex +')';
				oNode.sType = 'wd';
				oNode.sValue = '#' +iResultIndex;
				iResultIndex++;
			} else 	
				for (var i = 0; i < oNode.aChildren.length; i++)
					sQuery += doExecSearchCyc(oNode.aChildren[i]);

			break;
	}
	return sQuery;
}

function replaceQuotesWithAnds(sText) {
	var aSplits = sText.split('"');
	var i = 1;
	while (i < aSplits.length) {
		aSplits[i] = aSplits[i].replace(/ /mig, ' and ');
		i += 2;
	}
	sText = '';
	for (i = 0; i < aSplits.length; i++)
		sText += aSplits[i];
	return sText;
}
