function UserQuery_ParseTokens(userSearch)
{
	var pos = 0;
	var parseOK = true;
	var lastParsedToken = null;

	userSearch = userSearch.toLowerCase();
	userSearch = userSearch.replace(/'/g,'"');

	while (userSearch && (userSearch.length > 0))
	{
		// The first search line does not handle internation characters, wheres the second one does
		// pos = userSearch.search(/\s*(\w+\*)|(\w+)|(["][^"]*["])|([\(\)])\s*/);
		pos = userSearch.search(/\s*([A-Za-z0-9_\u00C0-\u00FF]+\*)|([A-Za-z0-9_\u00C0-\u00FF]+)|(["][^"]*["])|([\(\)])\s*/);

		var tokenStr = "";
		if (pos >= 0)
		{
			userSearch = RegExp.rightContext;
			if (RegExp.$1.length > 0)
			 tokenStr = '"' + RegExp.$1 + '"';
			else if (RegExp.$2.length > 0)
			 tokenStr = RegExp.$2;
			else if (RegExp.$3.length > 0)
			 tokenStr = RegExp.$3;
			else if (RegExp.$4.length > 0)
			 tokenStr = RegExp.$4;
		}
		if (tokenStr.length > 0)
		{
			
			var token = this.GetToken(tokenStr);
			
			if ( lastParsedToken && (lastParsedToken.type == UserQuery.NOISEWORD) &&
				(token.type & UserQuery.BINARYOP) )
			{
				// skip this token since it joins a noise word
			}
			else if (token.type == UserQuery.NOISEWORD)
			{
				this.UnrollExpression(UserQuery.OPERATOR);
				lastParsedToken = token;
			}
			else
			{
				if (token.type == UserQuery.USERITEM)
				{
						
					if (this.GetLastToken() && (this.GetLastToken().type & UserQuery.EXPRESSION))
					{
						this.InsertDefaultOperator();
					}
				}
				else if ((token.type == UserQuery.NOTOP) && this.GetLastToken() && (this.GetLastToken().type & UserQuery.EXPRESSION))
				{
					this.AddToken(this.builtIn("and"));
				}
			
				this.AddToken(token);
				lastParsedToken = token;
			}
		}
		else
		{
			if (userSearch.search(/\S/) >= 0)
			{
				parseOK = false;
				this.error = "Caractères invalides : '" + userSearch + "'";
				break;
			}
			else
				userSearch = "";
		}
		
	}
	return parseOK;
}

function UserQuery_InsertDefaultOperator(token)
{
	this.tokens[this.tokens.length] = this.builtIn("and");
}

function UserQuery_AddToken(token)
{
	this.tokens[this.tokens.length] = token;
}

function UserQuery_GetToken(tokenStr)
{
	var token = null;
	
	token = this.builtIn(tokenStr);
	
	if (token==null)
	{
		token = new Object();
		token.type = UserQuery.USERITEM;		
		token.value = tokenStr;
	}
	return token;
}

function UserQuery_AddBuiltIn(key,value,type)
{
	var token = new Object();
	token.type = type;
	token.value = value;
	this.builtIn.Add(key,token);
}

// Attempts to validate for the following grammar
// Expression : USERITEM | USERITEM OROP Expression | UNARYOP USERITEM
function UserQuery_Validate()
{
	var valid = true;
	var lastItemOK = false;
	var nextItem = UserQuery.USERITEM | UserQuery.LEFTPAREN | UserQuery.NOTOP;
	var balance = 0;
	
	if (this.tokens.length == 0)
	{
		valid = false;
		this.error = "Valeur de recherche vide.";
	}
	else
		{
		for (var tokIndex=0; tokIndex < this.tokens.length;tokIndex++)
		{
			if ((this.tokens[tokIndex].type & nextItem) != 0)
			{
				switch (this.tokens[tokIndex].type)
				{
					case (UserQuery.USERITEM):
						nextItem = UserQuery.BINARYOP |UserQuery.RIGHTPAREN;
						lastItemOK = true;
						break;
					case (UserQuery.ANDOP):
						nextItem = UserQuery.USERITEM | UserQuery.NOTOP | UserQuery.LEFTPAREN;
						lastItemOK = false;
						break;
					case (UserQuery.NEAROP):
						nextItem = UserQuery.USERITEM;
						lastItemOK = false;
					case (UserQuery.OROP):
						nextItem = UserQuery.USERITEM | UserQuery.LEFTPAREN;
						lastItemOK = false;
						break;
					case (UserQuery.NOTOP):
						nextItem = UserQuery.USERITEM | UserQuery.LEFTPAREN;
						lastItemOK = false;
						break;
					case (UserQuery.LEFTPAREN):
						balance++;
						nextItem = UserQuery.USERITEM;
						lastItemOK = false;
						break;
					case (UserQuery.RIGHTPAREN):
						balance--;
						nextItem = UserQuery.OROP | UserQuery.ANDOP;
						if (balance > 0)
							lastItemOK = false;
						else
							lastItemOK = true;
						break;
				}
				if (balance < 0)
				{
					valid = false;
					this.error = "Mismatched parenthesis";
					break;
				}
			}
			else
			{
				valid = false;
				this.error = "Mot ou caractère inattendu : " + this.tokens[tokIndex].value;
				break;
			}
				
		}
	
		if (balance != 0)
		{
			valid = false;
			this.error = "Parenthèses mal formatées";
		}
		else if (valid && !lastItemOK)
		{
			valid = false;
			this.error = "Fin de valeur de recherche attendue après : " + this.tokens[this.tokens.length-1].value;
	
		}
	}
	return valid;
}

// This function searches back until it reaches the first uType token truncating any
// tokens that follow it
function UserQuery_UnrollExpression(uType)
{
	var newLength = this.tokens.length;
	
	for (var i=this.tokens.length; i > 0 ; i--)
	{
		if ( (this.tokens[i-1].type & uType) != 0)
		{
			newLength = i-1;
		}
		else
			break;
		
	}

	this.tokens.length = newLength;
}


function UserQuery_GetMSSQLSearchStr()
{
	var searchStr = this.tokens[0].value;
	
	for (var i=1;i<this.tokens.length;i++)
	{
		searchStr += " " + this.tokens[i].value;
	}
	
	return searchStr;
}

function UserQuery_GetLastToken()
{
	var lastToken = null;
	
	if (this.tokens.length > 0)
		lastToken = this.tokens[this.tokens.length-1];
	
	return lastToken;
}

function UserQuery()
{
	UserQuery_ClassInitialize();

	this.tokens = new Array();
	this.builtIn = new ActiveXObject("Scripting.Dictionary");
	this.error = "";
	this.InsertDefaultOperator = UserQuery_InsertDefaultOperator;
	this.ParseTokens = UserQuery_ParseTokens;
	this.AddToken = UserQuery_AddToken;
	this.AddBuiltIn = UserQuery_AddBuiltIn;
	this.GetToken = UserQuery_GetToken;
	this.Validate = UserQuery_Validate;
	this.GetMSSQLSearchStr = UserQuery_GetMSSQLSearchStr;
	this.UnrollExpression = UserQuery_UnrollExpression;
	this.GetLastToken = UserQuery_GetLastToken;
	
	this.AddBuiltIn("and","AND",UserQuery.ANDOP);
	this.AddBuiltIn("or","OR",UserQuery.OROP);	
	this.AddBuiltIn("near","NEAR",UserQuery.NEAROP);	
	this.AddBuiltIn("not","NOT",UserQuery.NOTOP);	
	this.AddBuiltIn("(","(",UserQuery.LEFTPAREN);	
	this.AddBuiltIn(")",")",UserQuery.RIGHTPAREN);
	
	return this;
}

function UserQuery_ClassInitialize()
{
	if (!UserQuery.ClassInit)
	{
		UserQuery.ClassInit = true;
		
		UserQuery.USERITEM = 1;
		UserQuery.ANDOP = 2;
		UserQuery.OROP = 4;
		UserQuery.NOTOP = 8;
		UserQuery.LEFTPAREN = 16;
		UserQuery.RIGHTPAREN = 32;
		UserQuery.NEAROP = 64;
		UserQuery.NOISEWORD = 128;

		// Derived logical types (for comparison only)
		UserQuery.OPERATOR = UserQuery.ANDOP|UserQuery.OROP|UserQuery.NOTOP|UserQuery.NEAROP;
		UserQuery.BINARYOP = UserQuery.ANDOP|UserQuery.OROP|UserQuery.NEAROP;
		UserQuery.EXPRESSION = UserQuery.RIGHTPAREN|UserQuery.USERITEM;
	}
}

// For VBScript use
function NewUserQuery()
{
	return new UserQuery();
}

