/**
*  BigDumbDev::Configuration
*  (c) 2007 Stephen Brewer
*  www.bigdumbdev.com
* 
*  This code is freely distributable under the MIT License (http://www.opensource.org/licenses/mit-license.php).
*  Additionaly, Steve asks that you let him know if you do use this and provide any feedback on his blog -www.bigdumbdev.com.
* 
*  About
* 
*  BigDumbDev AIR Configuration.  Provides a SQL Lite backed key/value configuration system that can
*  accessed synchronously.  Provides a simple API for persisted key/value configurations.  All of the messy details
*  caused by AIR’s asynchronous DB API is hidden.  When a config is set, it is written to an in-memory cache and then
*  persisted to the DB asynchronously.  The API includes:
*  
*  init(callback, params) : initializes the configuration system. 
*  setConfig(key, value) : adds value into the configuration for key.  Creates the key if it does not already exist.
*  removeConfig(key) : removes the config
*  getConfig(key) : retrieves the value for the specified key
*  getAllConfigs() : returns and array of all the configurations pairs.  Each entry is an array with two elements : [key,value]
* 
********************************************************************************************************************************/

if(typeof BigDumbDev == "undefined"){
	var BigDumbDev = {};	
}

BigDumbDev.Configuration = function(){
	var conn;
	var getStmt;
	var checkStmt;
	var configs = {};
	var initializedCallback = null;
	var keyValuePairs = [];
	var defaults = null;
	var running = false;
	
	var errorHandler=function(event){ 
		throw "Configuration system"
	};
	
	var openHandler=function(event){
		checkTable();
	};
	
	/**
	* Check to see if the config table is already defined.  Try to read from it, if it's successful, then
	* the table exists, otherwise, it doens't
	*/
	var checkTable=function(){
		checkStmt = new air.SQLStatement();
		checkStmt.sqlConnection = conn;
		var sql = "SELECT * FROM config";
		checkStmt.text = sql;
		checkStmt.addEventListener(air.SQLEvent.RESULT, readConfigs);
		checkStmt.addEventListener(air.SQLErrorEvent.ERROR, createTable);
		checkStmt.execute();
	};
	
	var readConfigs=function() 
	{ 
		getStmt = new air.SQLStatement(); 
		getStmt.sqlConnection = conn; 
		var sql =  "select * from config"; 
		getStmt.text = sql; 
		getStmt.addEventListener(air.SQLEvent.RESULT, handleReadConfigs); 
		getStmt.addEventListener(air.SQLErrorEvent.ERROR, errorHandler); 
		getStmt.execute();
	};
	
	var handleReadConfigs = function(event){
		configs = {};
		var results = getStmt.getResult(); 
		if( null != results && null != results.data )
		{
			var numRows = results.data.length; 
			for(var i = 0; i < numRows; i++) 
			{ 
				var result = results.data[i];
				configs[result['config_key']] = result['config_value'];
			}
		}
		notifyReady();
	};
	
	var createTable=function(){
		var createStmt = new air.SQLStatement(); 
		createStmt.sqlConnection = conn; 
		var sql =  "CREATE TABLE IF NOT EXISTS config (" +  
		" config_key TEXT, " +  
		" config_value TEXT " +  
		")"; 
		createStmt.text = sql; 
		createStmt.addEventListener(air.SQLEvent.RESULT, createHandler); 
		createStmt.addEventListener(air.SQLErrorEvent.ERROR, errorHandler); 
		createStmt.execute(); 				
	};
	
	var createHandler=function(event){
		for(var i = 0; i < defaults.length; i++){
			var c = defaults[i];
			BigDumbDev.Configuration.setConfig(c[0], c[1]);
		}
		notifyReady();
	};
	
	var notifyReady=function(){
		if(null != initializedCallback){
			initializedCallback(true);
		}
	};
	
	var commitConfigChanges = function(){
		if( keyValuePairs.length > 0 ){
			running = true;
			var t = keyValuePairs[0];
			var key = t[0];
			var value = t[1];
			var type = t[2];
			if(type == "insert"){
				insert(key, value);
			}
			else if(type == "delete"){
				deleteKey(key);
			}
			else if(type == "update"){
				update(key, value);
			}
		}
		else{
			running = false;
		}
	};
	
	var insert = function(key,value){
		var stmt = new air.SQLStatement();
		stmt.sqlConnection = conn;
		var sql = "insert into config (config_key, config_value) values (:key, :value)";
		stmt.text = sql;
		stmt.parameters[":key"] = key;
		stmt.parameters[":value"] = value;
		stmt.addEventListener(air.SQLEvent.RESULT, commitHandler);
		stmt.addEventListener(air.SQLErrorEvent.ERROR, errorHandler);
		stmt.execute();
	};
	
	var deleteKey = function(key){
		var stmt = new air.SQLStatement();
		stmt.sqlConnection = conn;
		var sql = "delete from config where config_key = :key";
		stmt.text = sql;
		stmt.parameters[":key"] = key;
		
		stmt.addEventListener(air.SQLEvent.RESULT, commitHandler);
		stmt.addEventListener(air.SQLErrorEvent.ERROR, errorHandler);
		stmt.execute();
	};
	
	var update = function(key,value){		
		var stmt = new air.SQLStatement();
		stmt.sqlConnection = conn;
		var sql = "update config set config_value = :value where config_key = :key";
		stmt.text = sql;
		stmt.parameters[":key"] = key;
		stmt.parameters[":value"] = value;
		stmt.addEventListener(air.SQLEvent.RESULT, commitHandler);
		stmt.addEventListener(air.SQLErrorEvent.ERROR, errorHandler);
		stmt.execute();
	};
	
	var commitHandler=function(){
		keyValuePairs.splice(0,1);
		commitConfigChanges();
		
	};
	
	var setDirty=function(){
		if( !running )
		{
			commitConfigChanges();
		}
	};
	
	function escapeHTML (str)
	{
	   var div = document.createElement('div');
	   var text = document.createTextNode(str);
	   div.appendChild(text);
	   return div.innerHTML;
	};
	
	
	return {
		/** 
		* init
		*   callback - a callback function to be called when the config system is initialized
		*   params - (optional) a hash of parameter values.  In this you can specify:
		*   - file_name - the name of the SqlLite file
		*   - default_configs - an array of default key/value pairs.  The config will be
		*     initialized with these values if the Config system has never been updated before.  
		* 
		* Initializes the cache.  Will create the configuration file and table
		* if it does not already exists.  When the config system is initialized,
		* callback will be be called.
		*/
		init : function(callback, params){
			params = params || {};
			var file_name = params.file_name || 'config.db';
			defaults = params.default_configs || [];
			conn = new air.SQLConnection(); 
			conn.addEventListener(air.SQLEvent.OPEN, openHandler); 
			conn.addEventListener(air.SQLErrorEvent.ERROR, errorHandler); 
			var dbFile = window.runtime.flash.filesystem.File.applicationStorageDirectory.resolve(file_name); 
			conn.open(dbFile);
			initializedCallback = callback;
		},
		
		/**
		* setConfig
		* Changes are available immediately from getConfig imediately, though
		* there can be a slight delay before the value is persisted to the database.
		* Note that if you setConfig and then immediately exit the application, the 
		* value may not have been persisted.
		*/
		setConfig : function(key, value){
			key = escapeHTML(key);
			value = escapeHTML(value);
			var isUpdate = false;
			if( undefined != configs[key] )
				isUpdate = true;
			configs[key] = value;
			keyValuePairs.push([key, value, isUpdate ? 'update' : 'insert']);
			setDirty();						
		},
		
		removeConfig : function( key ){
			key = escapeHTML(key);
			configs[key] = undefined;
			keyValuePairs.push([key, null, 'delete']);
			setDirty();
		},
		
		getConfig : function(key){
			value = escapeHTML(value);
			return configs[key];
		},
		
		getAllConfigs : function(){
			var ret = [];
			for( var c in configs ){
				if( configs.hasOwnProperty(c) && undefined != configs[c]){
					ret.push([c, configs[c]]);
				}
			}
			return ret;
		}
	}
}();