/**
* Lingsoft Proofreader - Javascript API
* 
* Javascript API for using Lingsoft Inc Java Spellchecking API.
*
* @version: 1.1
* @date: 14.8.2008
* @author: Jouni Koivuviita - IT Mill Ltd.
* @contact: http://www.itmill.com
* 
* 
* Instructions:
* 
* 1. The HTML-page using this library should use UTF-8 as 
*    character encoding. Optionally, you can specify the
*    encoding of each check that's being sent to the
*    LSProofServlet (a parameter in SpellChecker.check method).
*
* 2. The HTML-page using this library should be located in 
*    the same domain as the LSProofServlet which it calls
*    (restriction in XMLHttpRequest-object, Same Origin Policy).
*    
* TODO add usage example
*/



/**
 * Namespace for LSProof JavaScript-API.
 * Method 'init' must be called before any operation.
 * Both synchronous and asynchronous loading are possible.
 * 
 * @constructor
 */
var LSProof = function(){return new Object();}();

	/**
	 * URL to servlet
	 * 
	 * @member LSProof
	 */
	LSProof.url = null;
	
	/**
	 * Status of initialization
	 * -1 - not initializing, variables not loaded
	 *  0 - initializing, variables not loaded
	 *  1 - initialization ready
	 * 
	 * @private
	 */
	LSProof.status = -1;
	
	/**
	 * ID's for SpellChecker objects (running number).
	 * 
	 * @private
	 */
	LSProof.spellCheckerCount = 0;
	
	/**
	 * Event listeners for init (function references).
	 * 
	 * @private
	 */
	LSProof.listeners = [];
	
	/**
	 * Initializes the LSProof API.
	 * 
	 * @param {String} url Absolute/relative url of the LSPROOFServlet.
	 * @param {Boolean} async (optional) Whether asynchronous loading should be used.
	 * @param {Function} listenerFunc (optional) Reference to a function, which will be called after LSProof is initialized (if asynchronous loading is used). Receives a boolean flag as a parameter, indicating whether the loading was successful (true) or not (false).
	 * 
	 * @return If _synchronous_ loading is used, returns a boolean flag whether the loading was successful. If _asynchronous_ loading is used return true if loading was started successfully.
	 * @type Boolean
	 */
	LSProof.init = function(url, async, scope, listenerFunc) {			
		
		// If already initializing
		if(this.status > -1) {
			
			if(this.status == 1) {					
				listenerFunc.call(scope, true);
				return true;
			} else if(async) {
				this.listeners[this.listeners.length] = [scope, listenerFunc];
				return true;
			} else return false;
				
		} else {
			
			if(url == undefined) {
				alert("Error: No URL provided for LSProof.");
				return false;
			}
			
			this.status = 0; // Initializing
			
			if(url.indexOf("/") !=0 && url.indexOf("http:") != 0) {
				// Relative URL
				var baseHref = window.location.href.substring(0, window.location.href.lastIndexOf("/")+1);
				this.url = baseHref + url;
			} else this.url = url;
			
			this.listeners[this.listeners.length] = [scope, listenerFunc];
							
			// Asynchronous init
			if(async) {
				LSProof.Constants.init(true);
				// Loading started successfully
				return true;
	
			// Synchronous init
			} else {					
				var ret = LSProof.Constants.init();
				if (ret) { 
					// Loading successful
					return true;
				}
				return false;
			}
			
		}
				
	}
	
	/**
	 * @private
	 */
	LSProof.statusChange = function(success) {			
		this.status = success? 1 : -1;
		for(var i=0; i < this.listeners.length; i++) {
			if (this.listeners[i][0] && this.listeners[i][1] && typeof this.listeners[i][1] == "function") {
				this.listeners[i][1].call(this.listeners[i][0], success);
			}
		}
		
	}
	
	/**
	 * Check if LSProof-library is initialized.
	 * 
	 * @return true/false
	 * @type Boolean
	 */
	LSProof.isReady = function() {
		return (this.status == 1);
	}
		
	/**
	 * Get a list of available dictionaries on the server.
	 * 
	 * If these have been fetched once, further calls will be served from cache.
	 * 
	 * @param {Boolean} async (optional) See {@link LSProof.Request#sendRequest}.
	 * @param {Object} scope (optional) See {@link LSProof.Request#sendRequest}.
	 * @param {Function} listenerFunc (optional) See {@link LSProof.Request#sendRequest}.
	 * 
	 * @return Array of dictionaries.
	 * @type Array
	 */
	LSProof.getAvailableDictionaries = function(async, scope, listenerFunc) {
		// Serve from cache
		if(LSProof.availableDictionaries)
			return LSProof.availableDictionaries;
			
		if(async) {
			LSProof._privateCallback = function(response, failed){
				if (!failed) {
					var r = eval("(" + response + ")");
					LSProof.availableDictionaries = r.dictionaries;
					listenerFunc.call(scope, response, failed);
				}
			}
			return LSProof.Request.sendRequest("method=get_dictionaries", true, LSProof, LSProof._privateCallback);
		} else {
			var r = LSProof.Request.sendRequest("method=get_dictionaries");
			r = eval("(" + r + ")");
			LSProof.availableDictionaries = r.dictionaries;
			return LSProof.availableDictionaries;
		}
	}
	LSProof.availableDictionaries = null;
	
	/**
	 * Close LSProof session.
	 */
	LSProof.close = function(async, scope, listenerFunc) {
		return LSProof.Request.sendRequest("method=close_session", async, scope, listenerFunc);
	}





/**
* Static constants class.
* Check Java API documentation for all possible variables and their names.
* This class gets initialized when {@link LSProof#init} is called.
* 
* @constructor
* @requires LSProof
* @requires LSProof.Request
*/
LSProof.Constants = function(){return new Object()}();
				
		/**
		 * Initialization function, which has to be called before using this library. You don't need to call it explicitly, {@link LSProof#init} does it.
		 * 
		 * @member LSProof.Constants
		 * 
		 * @param {boolean} async (Optional) Whether or not asynchronous callign should be used.
		 * @param {Function} listenerFunc (Optional) If asynchronous loading is used, a listener function should be specified, which will be called after the loading is completed. It will receive a boolean flag as a parameter (ok/failed).
		 * 
		 * @return If synchronous loading is used, a confirmation if the loading was succesful.
		 * @type boolean
		 * 
		 * @private
		 */
		LSProof.Constants.init = function(async) {
			
			var async = (async)? true : false;
					
			var ret = LSProof.Request.sendRequest("method=get_static_variables", async, LSProof.Constants, LSProof.Constants.assignVariables);
			if(!async) return this.assignVariables(ret);
		}
		
		/**
		 * Add variables received from servlet to this class.
		 * 
		 * @private
		 */
		LSProof.Constants.assignVariables = function(responseText, failed) {
			if (failed) {
				LSProof.statusChange(false);
				LSProof.error(LSProof.parseError("Loading of LSProof constants failed.",responseText));
				return false;
			} else {
				if(responseText) var vars = eval('(' + responseText + ')');
				else return false;
				
				for(var o in vars) {
					this[o] = vars[o];
				}				
				
				LSProof.statusChange(true);
				return true;
			}
		}


/**
* Static class for handling XMLHttpRequests.
* Both synchronous and asynchronous calls are possible.
* 
* @constructor
* @requires LSProof
*/
LSProof.Request = function(){return new Object()}();
		
		/** 
		 * Low-lever request stamping id sequence.
		 * 
		 * @private
		 * */
		LSProof.Request.lastRequestId = 0;
		
		/**
		 * Get new crossbrowser XMLHttpRequest object.
		 * 
		 * @return New XMLHttpRequest object.
		 * @private
		 */ 
		LSProof.Request.newXHRObj = function() {
			var objType = false;
			try {
				objType = new ActiveXObject('Msxml2.XMLHTTP');
			} catch(e) {
				try {
					objType = new ActiveXObject('Microsoft.XMLHTTP');
				} catch(e) {
					objType = new XMLHttpRequest();
				}
			}
			return objType;
		}
		
		/**
		 * Send a generic AJAX request to LSProofServlet.
		 * 
		 * @param {Sting} pars Parameter for the request (GET protocol).
		 * @param {boolean} async (Optional) Should the request be made asynchronously.
		 * @param {Object} scope (Optional) A reference object, to provide a scope in which listenerFunc should be called. This should be specified, if the request is made asynchronously.
		 * @param {Function} listenerFunc (Optional) A function that will be called after a successful asynchronous request. If the request was OK, the function receives the responseText as a parameter. If the request was not ok, the function receives the statusText and a boolean flag 'true' as parameters. This should be specified, if the request is made asynchronously.
		 * @param {String} encoding (Optional) Request character encoding.
		 *  
		 * @return If listenerFunc is undefined, returns the response of the synchronous request. Otherwise returns an undefined value, and the listener function reseaves the responseText/statusText as a parameter with a boolean flag "failed", if the request failed.
		 */  
		LSProof.Request.sendRequest = function(pars, async, scope, listenerFunc, encoding) {
			
			// Time after which the request should be aborted.
			var timeoutValue = 10000; // Milliseconds
			var timeoutHandle;
			
			var async = (async)? true : false;			
			
			var url = LSProof.url;
			
			var req = this.newXHRObj();
			var requestId = ++this.lastRequestId;
			if(req) {
				LSProof.log("request ("+requestId+"): " + url + " - post data:" + pars);
			
				// Register state change listener
				req.onreadystatechange = function() {
					if(req.readyState == 4) {
						if(timeoutHandle) clearTimeout(timeoutHandle);
						if(async) LSProof.Request.onResponse(req, scope, listenerFunc, requestId);
					}
				}
				
				// Start timeout handler
				if(async) timeoutHandle = setTimeout(function(){ LSProof.Request.onTimeout(req, scope, listenerFunc, requestId); }, timeoutValue);
				
				// Send the request
				req.open("POST", url, async);
				
				if(encoding) req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset="+ encoding);
				else req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
				
				try {
					req.send(LSProof.Request.convertToPost(pars));
				} catch(e) {
					LSProof.error("exception: "+e.message);
				}
				if(!async) return LSProof.Request.onResponse(req, scope, listenerFunc, requestId);
			}
			
		}
		
		/**
		 * Response handler
		 * 
		 * @private
		 */ 
		LSProof.Request.onResponse = function(req, scope, listenerFunc, requestId) {
			LSProof.log("response ("+requestId+"): status=" + req.status);
			if(req.status == 200) {
            	if(listenerFunc) return listenerFunc.call(scope, req.responseText);
				return req.responseText;
			} else {
				if(listenerFunc) return listenerFunc.call(scope, req.responseText, true);
				return req.statusText;
			}
		}
		
		/**
		 * Timeout handler
		 * 
		 * @private
		 */
		LSProof.Request.onTimeout = function(req, scope, listenerFunc,requestId) {
			LSProof.log("timeout ("+requestId+")");
			if(LSProof.Request.inProgress(req)) req.abort();
			if(listenerFunc) listenerFunc.call(scope, "{message:'Request timed out.',cause:null}", true);
		}
		
		/**
		 * Helper method
		 * 
		 * @private
		 */
		LSProof.Request.inProgress = function(req) {
			 switch(req.readyState) {
				case 1, 2, 3:
					return true;
					break;
				default:
					return false;
					break;
			}
		}
		
		/**
		 * Get meta tag content by http-equiv name.
		 * 
		 * @param doc {Object} The document object whose meta tags to search.
		 * @param name {String} Name of the meta info to get.
		 * 
		 * @private
		 */
		LSProof.Request.getHttpEquiv = function(doc, name) {
			var metas = doc.getElementsByTagName("meta");
			for(var i=0; i < metas.length; i++) {
				if(metas[i]["httpEquiv"] == name) return metas[i]["content"];
			}
			return null;
		}
		
		/**
		 * Convert a GET-request parameter string into a POST-request parameter string.
		 * 
		 * @private
		 */
		LSProof.Request.convertToPost = function(pars) {
			var out = encodeURI(pars);
			var out = out.replace(/\+/g,"%2B");
			out = out.replace(/\s/g,"+");
			return out;
		}


/**
* SpellChecker class.
* One session may contain multiple SpellCheckers, which perform checks.
* 
* @constructor
* 
* @param {Function} resultParser (optional) A function which will process the output of check requests. When it's called, it receives a {@link LSProof.CheckRequest} object as a parameter. 
* 
* @requires LSProof
* @requires LSProof.Constants
* @requires LSProof.Request
*/
LSProof.SpellChecker = function(resultParser) {
	
	/**
	 * Instance ID.
	 * 
	 * @private
	 */
	this.id = ++LSProof.spellCheckerCount;
	
	/**
	 * Function that will handle and parse checked requests.
	 * 
	 * @private
	 */
	this.resultParser = (resultParser)? resultParser : null;
		
	/**
	 * Dictionaries to be used with this SpellChecker instance.
	 * Only four possible types are available
	 * (1 = MAIN_DICT, 2 = USER_DICT, 3 = ENHANCED_USER_DICT, 4 = USER_RULES)
	 * 
	 * @private
	 **/	
	this.dictionaries = [[],[],[],[]];

	/**
	 * Send a new spellcheck request.
	 * 
	 * @param {String} inputString Text to spell/grammar check.
	 * @param {Number} textType Type of text (word, sentence, paragraph etc.). For possible values, see {@link LSProof.Constants}.
	 * @param {Boolean} proceedToNextError (optional) (experimental) Whether checking should continue, if the processed length is not the whole input text length. Not fully tested, use cautiously (default is false).
	 * @param {String} inputEncoding (optional) Input text character endocing. Sometimes it is necessary to declare the encoding of the string that's being sent (e.g. if charset is not declared on the HTML-page).
	 * @param {Object} args (optional) Additional arguments that should b passed through to the result parser.
	 * 
	 */
	this.check = function(inputString, textType, proceedToNextError, inputEncoding, args, sync) {
		LSProof.log("check('"+inputString+"',"+textType+","+proceedToNextError+",'"+inputEncoding+"',"+args+")");
		if(this.dictionaries[LSProof.Constants.MAIN_DICT - 1].length > 0) {
			// Only check strings that contain something
			if(inputString.length > 0) {
				var input = new LSProof.CheckRequest(this, inputString, textType, proceedToNextError, args);
			
				var pars = "method=check";
				pars += "&input=" + input.text;
				pars += "&type=" + ((input.textType != undefined)? input.textType : 0);
				pars += "&scid=" + this.id;
				
				var async = (sync)? false : true;
				
				LSProof.Request.sendRequest(pars, async, input, input.parseResult, inputEncoding);
			}
		} else LSProof.error("Main dictionary not specified for this spellchecker, spellchecking not possible.");
	}
	
	/**
	 * Process a checked request.
	 * 
	 * @private
	 */
	this.processCheckedRequest = function(req) {
		if(this.resultParser) this.resultParser(req);
		else LSProof.logVars(req.result);
	}
	
	/**
	 * Add a dictionary to SpellChecker.	 * 
	 * Once the first main dictionary is added, you cannot change the language,
	 * you can only add dictionaries of the same language. If you wish to use
	 * a different language, you should create a new SpellChecker instance.
	 * 
	 * @param {String} dict Name of the dictionary to add. Possible values depend on the LSProofServlet configuration.
	 * @param {Number} type Type of dictionary to add. For possible values, see {@link LSProof.Constants}.
	 */
	this.addDictionary = function(dict, type) {
		this.dictionaries[type-1][this.dictionaries[type-1].length] = dict;
		var pars = "method=add_dictionary&dict=" + dict + "&type=" + type + "&scid=" + this.id;
		LSProof.Request.sendRequest(pars, true, this, this.dictionaryListener);
	}
	
	// TODO not needed necessarily
	/**
	 * @private
	 */
	this.dictionaryListener = function(response, failed) {
		if(failed) {
			LSProof.error(LSProof.parseError("Failed to load dictionary: ", response));
			return false;
		} else return true;
	}
	
	/**
	 * Enable or disable user dictionary (if one is loaded). Will do nothing if no user dictionary is loaded.
	 */
	this.setUserDictionary = function(enabled) {
		if(this.getDictionariesInUse()[LSProof.Constants.USER_DICT-1]) {
			var pars = "method=" + (enabled ? "enable_user_dict" : "disable_user_dict") + "&dicttype=" + LSProof.Constants.USER_DICT + "&scid=" + this.id;
			LSProof.Request.sendRequest(pars, true, this, this.dictionaryListener);
		} else 
			LSProof.warn("Can't enable/disable user dictionary: no user dictionary loaded.")
	}
	
	/**
	 * Returns a boolean array of dictionary types in use with 
	 * this SpellChecker. E.g. [true, false, false, false] means
	 * (normally, check possible dictionary type numbers 
	 * from {@link LSProof.Constants}) this SpellChecker instance 
	 * uses a main dictionary, but not a user dictionary.
	 * 
	 * @return Boolean array of dictionaries in use with this SpellChecker instance.
	 * @type Array 
	 */
	this.getDictionariesInUse = function() {
		var out = new Array();
		for(var i=0; i<this.dictionaries.length; i++) {
			out[i] = this.dictionaries[i].length>0;
		}
		return out;
	}
	
	/**
	 * Get all grammar options for this SpellChecker.
	 *
	 * @param {Boolean} async (optional) See {@link LSProof.Request#sendRequest}.
	 * @param {Object} scope (optional) See {@link LSProof.Request#sendRequest}.
	 * @param {Function} listenerFunc (optional) See {@link LSProof.Request#sendRequest}.
	 *
	 * @return If a _syncronous_ request was made, returns the return value of the request. Otherwise returns undefined.
	 */
	this.getGrammarOptions = function(async, scope, listenerFunc) {
		var pars = "method=get_grammar_options";
		pars += "&scid=" + this.id;
		return LSProof.Request.sendRequest(pars, async, scope, listenerFunc);
	}
	
	/**
	 * Set a SpellChecker option.
	 * 
	 * @param {String} type Type of the option. Possible values are "bool", "int" and "str".
	 * @param {Number} name Name of the option. For possible values, see {@link LSProof.Constants}.
	 * @param {Object} value Value to set.
	 * @param {Number} param (optional) Possible option parameter.
	 * @param {Boolean} async (optional) See {@link LSProof.Request#sendRequest}.
	 * @param {Object} scope (optional) See {@link LSProof.Request#sendRequest}.
	 * @param {Function} listenerFunc (optional) See {@link LSProof.Request#sendRequest}.
	 * 
	 * @return If a _syncronous_ request was made, returns the return value of the request. Otherwise returns undefined.
	 */
	this.setOption = function(type, name, value, param, async, scope, listenerFunc) {
		var pars = "method=set_options_" + type; // bool, int, str
		pars += "&name=" + name;
		pars += "&value=" + value;
		if(param != undefined) pars += "&param=" + param;
		pars += "&scid=" + this.id;
		return LSProof.Request.sendRequest(pars, async, scope, listenerFunc);
	}
	
	/**
	 * Get a SpellChecker option.
	 * 
	 * @param {String} type Type of the option. Possible values are "bool", "int" and "str".
	 * @param {Number} name Name of the option. For possible values, see {@link LSProof.Constants}.
	 * @param {Number} param (optional) Possible option parameter.
	 * @param {Boolean} async (optional) See {@link LSProof.Request#sendRequest}.
	 * @param {Object} scope (optional) See {@link LSProof.Request#sendRequest}.
	 * @param {Function} listenerFunc (optional) See {@link LSProof.Request#sendRequest}.
	 * 
	 * @return If a _syncronous_ request was made, returns the return value of the request. Otherwise returns undefined.
	 */
	this.getOption = function(type, name, param, async, scope, listenerFunc) {
		var pars = "method=get_options_" + type; // int, str
		pars += "&name=" + name;
		if(param) pars += "&param=" + param;
		pars += "&scid=" + this.id;
		return LSProof.Request.sendRequest(pars, async, scope, listenerFunc);
	}
	
	/**
	 * Add a word to a user dictionary.
	 * 
	 * @param {String} dictionaryName Name of the user dictionary (type=USER_DICT) to which the word should be added.
	 * @param {String} word Word to add to the user dictionary.
	 * @param {Boolean} async (optional) See {@link LSProof.Request#sendRequest}.
	 * @param {Object} scope (optional) See {@link LSProof.Request#sendRequest}.
	 * @param {Function} listenerFunc (optional) See {@link LSProof.Request#sendRequest}.
	 */
	this.addWord = function(dictionaryName, word, async, scope, listenerFunc) {
		var i = this.dictionaries[LSProof.Constants.USER_DICT - 1].length; // Number of USER_DICT's
		if (i > 0) {
			do {
				if (this.dictionaries[LSProof.Constants.USER_DICT - 1][i] == dictionaryName) {
					var pars = "method=add_word&dict=" + dictionaryName + "&word=" + word + "&scid=" + this.id;
					LSProof.Request.sendRequest(pars, async, scope, listenerFunc);
					return;
				}
			} while (i--);
		}
		LSProof.log("No user dictionaries found with name '" + dictionaryName + "', while trying to add word '" + word + "'");
	}
	
	/**
	 * Set ignore rule for a grammar error.
	 * 
	 * @param {int} rule Rule number.
	 * @private
	 */
	this.ignoreRule = function(rule) {
		// TODO Not needed right now.
	}

	/**
	 * Initilalize this instance. Gets called automatically.
	 * 
	 * @private
	 */
	this.init = function() {		
		for (var o in LSProof.Constants) {
			this[o] = LSProof.Constants[o];
		}
		return true;
	}
	
	this.init();
	
	return true;
	
};


/**
 * CheckRequest object contains all information of one spellchecking request (such as original string, check result etc.).
 * 
 * @constructor
 * 
 * @param {SpellChecker} spellChecker {@link LSProof.SpellChecker} instance, which will handle this CheckRequest after it's checked.
 * @param {String} inputString String from the source, which is to be checked. Can be as long as text as you like.
 * @param {Number} inputType Type of the input text (word, sentence, paragraph etc.). For possible values, see {@link LSProof.Constants}.
 * @param {boolean} proceedToNextError (Optional) Whether or not checking should continue after first error. Defaults to false. (Experimental, may cause unexpected behaviour).
 * @param {Object} args (Optional) Additional data to be passed through result parser.
 */
LSProof.CheckRequest = function(spellChecker, inputString, inputType, proceedToNextError, args) {
	
	/**
	 * SpellChecker instance, who is responsible for this request.
	 */
	this.spellChecker = spellChecker;	
	
	/**
	 * Plain text to be checked.
	 */
	this.text = (inputString)? inputString : "";
	
	/**
	 * Text type (may improve spellchecking speed).
	 */
	this.textType = inputType;
	
	/**
	 * Whether or not checking should continue after first error
	 * 
	 * @private
	 */
	this.proceedToNext = (proceedToNextError)? true : false;
	
	/**
	 * Status of this request.
	 *  0 = not checked
	 *  1 = checked 
	 * -1 = request failed
	 */
	this.status = 0;
	
	/**
	 * Output of the spellcheck (JSON object / server error message).
	 * <br />
	 * <strong>JSON object structure:</strong>
	 *  <ul>
	 * 	<li>status,</li>
	 * 	<li>processedLength,</li>
	 * 	<li>sentenceLength,</li>
	 * 	<li>sentenceStart,</li>
	 * 	<li>errors[] = Array()
	 * 		<ul>
	 * 		<li>status,</li>
	 * 		<li>title,</li>
	 * 		<li>rule,</li>
	 * 		<li>helpText,</li>
	 * 		<li>explanation,</li>
	 * 		<li>length,</li>
	 * 		<li>start,</li>
	 * 		<li>suggestions[] = Array()
	 * 			<ul>
	 * 			<li>action,</li>
	 * 			<li>userText,</li>
	 * 			<li>words[] = Array()
	 * 				<ul>
	 * 				<li>start,<li>
	 * 				<li>length,</li>
	 * 				<li>word</li>
	 * 				</ul>
	 * 			</li>
	 * 			</ul>
	 *		</li>
	 * 		</ul>
	 * 	</li>
	 * 	</ul>
	 */
	this.result = null;
	
	/**
	 * Additional arguments object.
	 */
	this.args = (args)? args : {};
		
	/**
	 * A function that parses the result after a checking request.
	 * 
	 * @private
	 */
	this.parseResult = function(response, failed) {
		if(!failed) {
			/* Checking was performed successfully */
			this.status = 1;
		
			/* Evaluate the JSON object */
			this.result = eval('(' + response + ')');

			/* Let SpellChecker handle this request further */
			this.spellChecker.processCheckedRequest(this);
		
			/* Proceed checking? */
			if(this.proceedToNext && this.text.length > this.result.processedLength) {
				var start = (this.text.substr(start,1) == " ")? this.result.processedLength : this.result.processedLength - 1; // If in between sentences
				spellChecker.check(this.text.substr(start), this.spellChecker.UNKNOWN, start, this.proceedToNext);
			}
		} else {
			this.status = -1;
			LSProof.log("sendCheckRequest failed: " + response);
		}
	}

	/**
	 * Initialize this CheckRequest instance. Gets called automatically.
	 * 
	 * @private
	 */
	this.init = function() {
				
		// TODO If inputType not given, try to determine it from this.text
		if(this.textType == undefined) {
			this.textType = LSProof.Constants.UNKNOWN;
		}
		
	}
	
	this.init();
	
	return true;
	
};

/**
 * Perform a full document check on a single textarea element.
 * 
 * @param {String} elementId ID of the textarea.
 * @param {Function} callbackFunction (optional) User callback function.
 * @param debug
 */
LSProof.checkInWindow = function(elementId, callbackFunction, debug) {
	
	var e = document.getElementById(elementId);
	if (!e) return;

	// Open window for spellchecking
	if (e.value) {
		var w = window.open('check.html?lang='+e.getAttribute("lang"),'lsproof_check','border=0,statusbar=0,width=600,height=420');			
		this.window = w;
		
		// Create callback handler
		var cb = function(saved,text) {
		
			// Set the text back to input
			if (saved) {
				e.value = text;
			}
			
			// Invoke the user callback if given
			if (callbackFunction) {
				callbackFunction(saved,text);
			}
		};
		
		// When window is loaded we start the spellcheck
		if (document.all) {
			var checkCalled = false;
			w.document.onreadystatechange = function() {
				if (!checkCalled) {
					try {						
						w.lsproofCheck(e.value, cb, debug);
						checkCalled = true;
					} catch (ex) {						
						checkCalled = false;
					}
				}
			}
		} else {	
			var onloadCallback = function() {
				w.lsproofCheck(e.value, cb, debug);
			};
			w.onload = onloadCallback;
		}
		
		// Focus the window
		w.focus();
	}
}





/**
 * (Convinience class, not necessarily needed when using LSProof JS API)
 * LSProof Preferences object for different editor instances.
 * 
 * Initialize one of these to conveniently load the whole library and 
 * get a working set of different spellcheckers into use.
 */
LSProof.Preferences = function() {
		
		// SpellChecker instances to be used with one editor instance (one spellchecker instance per language)
		// Language name is used as the key.
		this.spellCheckers = new Object();
		
		// Currently selected language
		this.selectedLanguage = undefined;
		
		// Preferred language on init
		this.preferredLanguage = undefined;
		
		// Is spellchecking active (LSPROOF native library dependant)
		this.USE_SPELLER = false;
		
		// Is grammar checking active (LSPROOF native library dependant)
		this.USE_GRAMMAR = false;
		
		// Is user dictionary enabled
		this.userDictDisabled = false;
		
		// Is grammar dictionary already loaded?
		this.grammarLoaded = false;
		
		// Grammar options object
		this.grammarOptions = null;
		
		// Is SURE rules dictionary loaded?
		this.userRulesLoaded = false;
		
		// Mark SURE rules
		this.useSURE = false;
		
		// Ignore caps (LSPROOF native library dependant)
		this.IGNORE_CAPS = false;
		
		// Ignore digits (LSPROOF native library dependant)
		this.IGNORE_DIGITS = false;
		
		// Check for repeated words (LSPROOF native library dependant)
		this.CHECK_REPEATED_WORDS = false;
		
		// Function that will parse check results from server
		// (Can be null, LSProof default handler will then be used, which is a simple alert)
		this.resultParser = null;
		
		
		/*
		 * Select a language for this preference instance
		 * Use three letter acronyms (e.g. "fin", "swe", "eng") provided by the server.
		 */
		this.selectLanguage = function(lang) {
			if (!this.isDictionary(lang)) {
				LSProof.warn("No such language available: " + lang);
				return;
			}
			
			this.selectedLanguage = lang;
			
			if(!this.spellCheckers[lang]) {
				this.spellCheckers[lang] = new LSProof.SpellChecker(this.resultParser);
				this.spellCheckers[lang].addDictionary(lang, LSProof.Constants.MAIN_DICT);
			}
			if(this.grammarLoaded && this.canUseGrammar()) {
				this.spellCheckers[lang].addDictionary(lang+"_gr", LSProof.Constants.MAIN_DICT);
				this.USE_GRAMMAR = this.loadedFromCookie? this.USE_GRAMMAR : true;
			} else {
				this.USE_GRAMMAR = false;
				this.grammarLoaded = false;
			}
			if(!this.userDictDisabled && this.canUseUserDict()) {
				this.spellCheckers[lang].addDictionary(lang+"_user", LSProof.Constants.USER_DICT);
			}
			if(this.userRulesLoaded && this.canUseUserRules()) {
				this.spellCheckers[lang].addDictionary(lang+"_sure", LSProof.Constants.USER_RULES);
				this.userRulesLoaded = true;
				this.useSURE = this.loadedFromCookie? this.useSURE : true;
			} else {
				this.useSURE = false;
				this.userRulesLoaded = false;
			}
			this.grammarOptions = null;
		};
		
		this.isDictionary = function(lang) {
			for(var i=0; i < this.dictionaries.length; i++) {
				if(this.dictionaries[i].language == lang) 
					return true;
			}
			return false;
		};
		
		this.canUseGrammar = function() {
			for(var i=0; i < this.dictionaries.length; i++) {
				if(this.dictionaries[i].language == this.selectedLanguage+"_gr" 
						&& this.dictionaries[i].type == LSProof.Constants.MAIN_DICT 
						&& this.dictionaries[i].category == "grammar") 
					return true;
			}
			return false;
		};
		
		this.canUseUserDict = function() {
			for(var i=0; i < this.dictionaries.length; i++) {
				if(this.dictionaries[i].language == this.selectedLanguage+"_user" 
						&& this.dictionaries[i].type == LSProof.Constants.USER_DICT 
						&& this.dictionaries[i].category == "user") 
					return true;
			}
			return false;
		};
		
		this.canUseUserRules = function() {
			if(!this.canUseGrammar())
				return false;
			for(var i=0; i < this.dictionaries.length; i++) {
				if(this.dictionaries[i].language == this.selectedLanguage+"_sure" 
						&& this.dictionaries[i].type == LSProof.Constants.USER_RULES 
						&& this.dictionaries[i].category == "sure") 
					return true;
			}
			return false;
		};
		
		/*
		 * Initializes all preferences. Starts the loading of the LSProof Ajax API as well.
		 */
		this.init = function(url, preferredLanguage, resultParser) {
			
			// Set preferred language
			this.preferredLanguage = preferredLanguage;
			
			// Set parser
			this.resultParser = resultParser;
			
			// Initialize LSProof Ajax API
			var _self = this;
			if(!LSProof.init(url, true, _self, function(success) {
				if(success) {
					// Blocking (synchronous) call
					this.dictionaries = LSProof.getAvailableDictionaries();
					this.selectFirstDictionary();
				} else
					LSProof.error("LSProof Ajax API initialization failed.");
			})) {
				LSProof.error("LSProof Ajax API initialization failed.");
			}
		};
		
		this.selectFirstDictionary = function() {
			// Load grammar/sure dictionary instantly if preferred
			if(this.preferredLanguage.indexOf("_sure") > 0 && this.isDictionary(this.preferredLanguage)) {
				this.grammarLoaded = true;
				this.userRulesLoaded = true;
			}
			else if(this.preferredLanguage.indexOf("_gr") > 0 && this.isDictionary(this.preferredLanguage)) {
				this.grammarLoaded = true;
			}
			
			if(!this.selectedLanguage && !this.isDictionary(this.selectedLanguage)) {
				var lang;
				for(var i=0; i < LSProof.availableDictionaries.length; i++) {
					var dict = LSProof.availableDictionaries[i];
					if(dict.type == LSProof.Constants.MAIN_DICT && dict.category == "speller") {
						if(lang == undefined || this.preferredLanguage.indexOf(dict.language) == 0)
							lang = dict.language;
						this.USE_SPELLER = true;
						if(this.preferredLanguage.indexOf(dict.language) == 0)
							break;
					}
				}
				
				// Load the selected language
				this.selectLanguage(lang);
			} else {				
				// Load previous language (probably loaded from cookie)
				this.selectLanguage(this.selectedLanguage);
				this.USE_SPELLER = true;
				LSProof.log("Previously saved language loaded '"+this.selectedLanguage+"'");
			}
			
			// Set settings on server side
			var opts = new Array("USE_SPELLER","USE_GRAMMAR","IGNORE_CAPS","IGNORE_DIGITS","CHECK_REPEATED_WORDS");
			for(var i=0; i < opts.length; i++) {
				if(opts[i] == "USE_GRAMMAR" && !this.canUseGrammar()) {
					this["USE_GRAMMAR"] = false;
				}
				// Asynchronous
				this.spellCheckers[this.selectedLanguage].setOption("bool", LSProof.Constants[opts[i]], this[opts[i]], null, true);
			}
		};
		
}





/** 
 * Initializes the LSProof client-side log textbox into give HTML element.
 * 
 * @param {HTMLElement} el HTML element in which the debug log should be inserted. Replaces the previous content of the element.
 */
LSProof.initLog = function(el) {
	if (el) {
		el.innerHTML = '<h2>LSProof debug Log</h2>'
					+ '	<div id="lsproof_log_box" style="width: 100%; height: 25em; border: 1px solid grey; background: #fff; overflow: auto; color: #333;"></div>'
					+ '	<a href="" onclick="document.getElementById(\'lsproof_log_box\').value=\'\';return false;">[Clear log]</a>'
					+ ' <a href="?debug=false">[Disable debug]</a>'
		this.log("Log intialized at "+ new Date());
	}
}

/** 
 * Log a message to LSProof client-side log.
 * 
 * @param {String} message Text to print into the log.
 */
LSProof.log = function(message) {
	if((typeof console == 'undefined') && (typeof loadFirebugConsole != 'undefined'))
		loadFirebugConsole();
	if(typeof console != 'undefined') {
		console.log(message);
		return;
	}
	var el = document.getElementById("lsproof_log_box");	
	if(el) {
		el.innerHTML = el.innerHTML + "<div>" + message + "</div>";
		el.scrollTop = el.scrollHeight - el.clientHeight;
	}
}

/** 
 * Log an info message to LSProof client-side log.
 * 
 * @param {String} message Text to print into the log.
 */
LSProof.info = function(message) {
	if((typeof console == 'undefined') && (typeof loadFirebugConsole != 'undefined'))
		loadFirebugConsole();
	if(typeof console != 'undefined') {
		console.info(message);
		return;
	}
	var el = document.getElementById("lsproof_log_box");	
	if(el) {
		el.innerHTML = el.innerHTML + "<div style='color: blue;'>" + message + "</div>";
		el.scrollTop = el.scrollHeight - el.clientHeight;
	}
}

/** 
 * Log a warning to LSProof client-side log.
 * 
 * @param {String} warning Text to print into the log.
 */
LSProof.warn = function(message) {
	if((typeof console == 'undefined') && (typeof loadFirebugConsole != 'undefined'))
		loadFirebugConsole();
	if(typeof console != 'undefined') {
		console.warn(message);
		return;
	}
	var el = document.getElementById("lsproof_log_box");	
	if(el) {
		el.innerHTML = el.innerHTML + "<div style='color: orange;'>" + message + "</div>";
		el.scrollTop = el.scrollHeight - el.clientHeight;
	}
}

/** 
 * Log an error to LSProof client-side log.
 * 
 * @param {String} error Text to print into the log.
 */
LSProof.error = function(message) {
	if((typeof console == 'undefined') && (typeof loadFirebugConsole != 'undefined'))
		loadFirebugConsole();
	if(typeof console != 'undefined') {
		console.error(message);
		return;
	}
	var el = document.getElementById("lsproof_log_box");	
	if(el) {
		el.innerHTML = el.innerHTML + "<div style='color: red;'>" + message + "</div>";
		el.scrollTop = el.scrollHeight - el.clientHeight;
	}
}

/** Parse and return server error response. 
 *
 * @param title Title for the error message.
 * @param responseText HTTP response text returned from server.
 * 
 * @private
 *
 */
LSProof.parseError = function(title, responseText) {
		var vars = {message: null, cause: null };
		if (responseText)  {
			vars = eval('(' + responseText + ')');
		}
		var msg = title;
		if (vars.message) {
			msg += "\n\nError: "+vars.message;
			if (vars.cause) {
				msg += "\nCaused by: "+vars.cause;					
			}
		}
		return msg;		
}

/** 
 * Log all properties of an object into LSProof client-side log.
 * 
 * @param {Object} obj Object which variables should be printed into the log.
 */
LSProof.logVars = function(obj) {
	var out="";
	for(o in obj) out += o + ": " + obj[o] + "\n";
	LSProof.log(out);
}
