;(function($) {

// === LOG ====================================	
	
	var log = function() {
		if( window.console && window.console.log ) {
			try{ window.console.log.apply(window.console,arguments); } catch(e) {};
		};
	};
	
	
// === DBWEB ====================================	

	var defaultOptions = {
		url: './dbWeb'
		,onSuccess: function(result,options){}
		,onError: function(result,options){}
	};
	
	var addTable = function(data,options) {
		data.table = options.tableName;
	};

	var addWhereClauses = function(data,options) {
		if( options.whereClauses ) { // whereClauses is an array (can't use field name as key - it could be repeated with different tests)
			data.where = []; // these are actually SQL where constructs
			for(var i=0; i<options.whereClauses.length; i++) {
				data.where.push(options.whereClauses[i]);
			};
		};
	};
	
	var addSelects = function(data,options) {
		if( options.selects ) {
			data.select = []; // list of columns to include in query response
			for(var i=0; i<options.selects.length; i++) {
				data.select.push(options.selects[i]);
			};
		};
	};

	var addGrouping = function(data,options) {
		if( options.groupBys ) {
			data.groupBy = []; // list of columns to group by
			for(var i=0; i<options.groupBys.length; i++) {
				data.groupBy.push(options.groupBys[i]);
			};
		};
	};

	var addSetters = function(data,options) {
		if( options.setters ) {
			data.set = [];
			for( var key in options.setters ) {
				var value = options.setters[key];
				data.set.push(''+key+','+value);
			};
		};
	};
	
	var getCapabilities = function(options_) {
		var options = $.extend({},defaultOptions,options_);

		var data = {};
		
		$.ajax({
			type: 'GET'
			,url: options.url + '/getCapabilities'
			,data: data
			,dataType: 'json'
			,async: true
			,success: onSuccess
			,error: onError
		});

		function onSuccess(data) {
			var result;
			eval('result = '+data+';');

			if( result.error ) {
				options.onError(result.error,options);
				
			} else if( result.capabilities ) {
				options.onSuccess(result.capabilities,options);
				
			} else {
				options.onError({message:'Capabilities not returned'},options);
			};
		};
		
		function onError(xmlHttpRequest, textStatus, errorThrown) {
			options.onError({message:textStatus},options);
		};
	};
	
	var getSchema = function(options_) {
		var options = $.extend({},defaultOptions,options_);

		var data = {};
		
		addTable(data,options);
		
		$.ajax({
			type: 'GET'
			,url: options.url + '/getSchema'
			,data: data
			,dataType: 'json'
			,async: true
			,success: onSuccess
			,error: onError
		});

		function onSuccess(result) {
			if( result.error ) {
				options.onError(result.error,options);
			} else {
				options.onSuccess(result,options);
			};
		};
		
		function onError(xmlHttpRequest, textStatus, errorThrown) {
			options.onError({message:textStatus},options);
		};
	};
	
	var query = function(options_) {
		var options = $.extend({},defaultOptions,options_);

		var data = {};
		
		addTable(data,options);
		addWhereClauses(data,options);
		addSelects(data,options);
		addGrouping(data,options);

		$.ajax({
			type: 'POST'
			,url: options.url + '/query'
			,data: data
			,dataType: 'json'
			,contentType: 'application/x-www-form-urlencoded; charset=utf-8'
			,async: true
			,success: onSuccess
			,error: onError
		});

		function onSuccess(result) {
			if( result.error ) {
				options.onError(result.error,options);
				
			} else if( result.queried ) {
				options.onSuccess(result.queried,options);
				
			} else {
				options.onError({message:'Queried objects not returned'},options);
			};
		};
		
		function onError(xmlHttpRequest, textStatus, errorThrown) {
			options.onError({message:textStatus},options);
		};
	};
	
	var insert = function(options_) {
		var options = $.extend({},defaultOptions,options_);

		var data = {};
		
		addTable(data,options);
		addSetters(data,options);
		
		$.ajax({
			type: 'POST'
			,url: options.url + '/insert'
			,data: data
			,dataType: 'json'
			,contentType: 'application/x-www-form-urlencoded; charset=utf-8'
			,async: true
			,success: onSuccess
			,error: onError
		});

		function onSuccess(result) {
			if( result.error ) {
				options.onError(result.error,options);
				
			} else if( result.inserted ) {
				options.onSuccess(result.inserted,options);
				
			} else {
				options.onError({message:'Inserted objects not returned'},options);
			};
		};
		
		function onError(xmlHttpRequest, textStatus, errorThrown) {
			options.onError({message:textStatus},options);
		};
	};
	
	var update = function(options_) {
		var options = $.extend({},defaultOptions,options_);

		var data = {};
		
		addTable(data,options);
		addWhereClauses(data,options);
		addSetters(data,options);
		
		$.ajax({
			type: 'POST'
			,url: options.url + '/update'
			,data: data
			,dataType: 'json'
			,contentType: 'application/x-www-form-urlencoded; charset=utf-8'
			,async: true
			,success: onSuccess
			,error: onError
		});

		function onSuccess(result) {
			if( result.error ) {
				options.onError(result.error,options);
				
			} else if( result.updated ) {
				options.onSuccess(result.updated,options);
				
			} else {
				options.onError({message:'Updated objects not returned'},options);
			};
		};
		
		function onError(xmlHttpRequest, textStatus, errorThrown) {
			options.onError({message:textStatus},options);
		};
	};
	
	var remove = function(options_) {
		var options = $.extend({},defaultOptions,options_);

		var data = {};
		
		addTable(data,options);
		addWhereClauses(data,options);
		
		$.ajax({
			type: 'POST'
			,url: options.url + '/delete'
			,data: data
			,dataType: 'json'
			,contentType: 'application/x-www-form-urlencoded; charset=utf-8'
			,async: true
			,success: onSuccess
			,error: onError
		});

		function onSuccess(result) {
			if( result.error ) {
				options.onError(result.error,options);
				
			} else {
				options.onSuccess(result,options);
			};
		};
		
		function onError(xmlHttpRequest, textStatus, errorThrown) {
			options.onError({message:textStatus},options);
		};
	};
	
	// where clause: comparison enum values
	var whereComparison_eq = 'eq'; // equal
	var whereComparison_ne = 'ne'; // not equal
	var whereComparison_ge = 'ge'; // greater than or equal
	var whereComparison_le = 'le'; // less than or equal
	var whereComparison_gt = 'gt'; // greater than
	var whereComparison_lt = 'lt'; // less than
	var whereStatus_null   = 'isNull';
	var whereStatus_notNull = 'isNotNull';
	
	var formatWhereClause = function(column, compEnum, value) {
		/*
		 * value may be null in which case, this should be a non comparison where (eg., is null).
		 *
		 * In either case, first two parameters should be strings, one of which is provided by
		 * the enums above...
		 */
		if (null == value) {
			return(column + ',' + compEnum);
		} else {
			return(column + ',' + compEnum + '(' + value + ')');
		};
	};
	
	// select clause: aggregation functions
	var selectAggregator_min = 'min'; // minimum
	var selectAggregator_max = 'max'; // maximum
	var selectAggregator_sum = 'sum'; // summation
	var formatSelectClause = function(column, aggEnum) {
		/*
		 * aggEnum may be null in which case the column name is unmodified (no aggregation requested)
		 *
		 * In either case, first two parameters if provided should be strings,
		 * one of which is provided by the enums above...
		 */
		if (null == aggEnum) {
			return(column);
		} else {
			return(aggEnum + '(' + column + ')');
		};
	};
	
	$.NUNALIIT_DBWEB = {
		// main async data operations
		getCapabilities: getCapabilities
		,getSchema: getSchema
		,query: query
		,insert: insert
		,update: update
		,remove: remove
		
		// enums for formatting where clause entries
		,whereComparison_eq: whereComparison_eq
		,whereComparison_ne: whereComparison_ne
		,whereComparison_ge: whereComparison_ge
		,whereComparison_le: whereComparison_le
		,whereComparison_gt: whereComparison_gt
		,whereComparison_lt: whereComparison_lt
		,whereStatus_null: whereStatus_null
		,whereStatus_notNull: whereStatus_notNull
		
		// enums for formatting select clause entries
		,selectAggregator_min: selectAggregator_min
		,selectAggregator_max: selectAggregator_max
		,selectAggregator_sum: selectAggregator_sum
		
		// formatting helpers
		,formatWhereClause: formatWhereClause
		,formatSelectClause: formatSelectClause
	};
	
	//===========================================================================================
	// dbWebForm
	$.fn.dbWebForm = function(options_) {
		var jqSet = this;

		var options = $.extend({
				installButtons: function(buttons){}
				,data: {}
				,fieldOpts: {}
				,onAlert: function(str){ alert(str); }
			},defaultOptions,options_);

		// Request schema
		var getSchemaOptions = $.extend({},defaultOptions,options_,{
			onSuccess: onGetSchemaSuccess
			,onError: onSchemaError
		});
		getSchema(getSchemaOptions);

		// Query objects if required
		if( options.whereClauses ) {
			var queryOptions = $.extend({},defaultOptions,options_,{
				onSuccess: onQuerySuccess
				,onError: onQueryError
			});
			query(queryOptions);
		};
		
		var data = {};
		
		function onGetSchemaSuccess(schema) {
			log('schema',schema);
			data.schema = schema;
			
			// Check that table can be queried
			if( schema.isQueryAllowed ) {
				// OK
			} else {
				reportError({message:'User does not have privilege to query this data'});
				return;
			}
			
			// In case of update, check that update is allowed
			if( options.whereClauses ) { 
				if( schema.isUpdateAllowed ) {
					// OK
				} else {
					reportError({message:'User does not have privilege to update this data'});
					return;
				};
			} else {
				// In case of insert, check that insert is allowed
				if( schema.isInsertAllowed ) {
					// OK
				} else {
					reportError({message:'User does not have privilege to insert new data'});
					return;
				};
			};
			
			onDataAvailable();
		};
		
		function onSchemaError(cause) {
			error = {
				message: 'Error while retrieving schema'
				,cause: cause
			};
			log('error',error);
			options.onError(error,options);
		};
		
		function onQuerySuccess(queriedObjects) {
			log('queriedObjects',queriedObjects);
			data.queriedObjects = queriedObjects;
			onDataAvailable();
		};
		
		function onQueryError(cause) {
			error = {
				message: 'Error while querying for data'
				,cause: cause
			};
			log('error',error);
			options.onError(error,options);
		};
		
		function reportError(error) {
			if( false ) {
				var elem = jqSet.get(0);
	
				var html = [];
				html.push('<div class="dbweb-error">');
				html.push('Error: ');
				html.push(error.message);
				html.push('</div>');
				
				$(elem).empty();
				$(elem).html( html.join('') );
			} else {
				options.onError(error,options);
			};
		};
		
		function onDataAvailable() {
			if( data.error ) {
				reportError(data.error);
				return;
			};
			
			// A schema is needed
			if( !data.schema ) {
				return;
			};

			// Check if a queried object is required
			if( options.whereClauses ) {
				// A set of selectors were specified. Then, we need to wait for
				// the query request to return
				if( !data.queriedObjects ) {
					return;
				};
				
				// Check returned query request
				if( data.queriedObjects.length < 1 ) {
					reportError({message:'Data can not be edited by current user.'});
					return;
				};
				if( data.queriedObjects.length > 1 ) {
					reportError({message:'Multiple records were returned. Can not proceed with editing.'});
					return;
				};
				
				// Quicker access
				data.queryData = data.queriedObjects[0];
			} else {
				// Insert mode. Use data provided by caller
				data.queryData = options.data;
			};
			
			var elem = $( jqSet.get(0) );

			elem.empty();

			var div = $('<div class="dbweb-form"></div>');
			elem.append(div);
			
			var form = $('<form></form>');
			div.append(form);

			var ul = $('<ul></ul>');
			form.append(ul);

			data.form = {};
			var columns = data.schema.columns;
			for(var loop=0; loop<columns.length; ++loop) {
				var column = columns[loop];
				var columnName = column.column;

				if( column.write ) {
					if( options.fieldOpts[columnName] ) {
						// Special handling specified by caller
						var fieldOpts = options.fieldOpts[columnName];
						
						var mustHide = fieldOpts.hide;
						if( typeof mustHide === 'function' ) {
							mustHide = mustHide();
						};

						if( mustHide ) {
							// Nothing to do
							
						} else if( fieldOpts.choices ) {
							addChoicesInput(form, ul, columnName, fieldOpts);
							
						} else if( fieldOpts.select ) {
							addCallbackInput(form, ul, columnName, fieldOpts);
							
						} else {
							addRegularInput(form, ul, columnName, fieldOpts);
						};
					} else if( 'DATE' == column.type ) {
						addDateInput(form, ul, columnName, {});
					} else {
						// Just regular input
						addRegularInput(form, ul, columnName, {});
					};
				};
			};
			
			// Tell caller to install buttons since form was
			// created
			var buttons = {};

			if( options.whereClauses ) {
				buttons['Save'] = function(saveOptions) {
					updateRecord(data.form,saveOptions);
				};
			} else {
				buttons['Save'] = function(saveOptions) {
					insertRecord(data.form,saveOptions);
				};
			};
			
			// Delete button is available only on update to users
			// allowed to delete
			if( data.schema.isDeleteAllowed && options.whereClauses ) {
				buttons['Delete'] = function(delOptions) {
					delRecord(data.form,delOptions);
				};
			};
			
			options.installButtons(buttons);
		};

		function addRegularInput(formElem, ulElem, columnName, fieldOpts) {
			var value = '';
			var valueAttr = '';
			if( "undefined" != typeof( data.queryData[columnName] ) ) {
				value = data.queryData[columnName];
				valueAttr = ' value="'+value+'"';
			} else if( "undefined" != typeof( fieldOpts.defaultValue ) ) {
				value = fieldOpts.defaultValue;
				valueAttr = ' value="'+value+'"';
			};
			
			var li = $('<li></li>');
			appendLabel(li,columnName);
			
			var input = $('<input class="dbWebFormInput" type="text" name="'+columnName+'"'+valueAttr+'/>');
			li.append(input);
			
			saveInput(columnName, input, value)

			ulElem.append(li);
		};
		
		function addDateInput(formElem, ulElem, columnName, fieldOpts) {
			var value = '';
			var valueAttr = '';
			if( "undefined" != typeof( data.queryData[columnName] ) ) {
				value = data.queryData[columnName];
				valueAttr = ' value="'+value+'"';
			} else if( "undefined" != typeof( fieldOpts.defaultValue ) ) {
				value = fieldOpts.defaultValue;
				valueAttr = ' value="'+value+'"';
			};
			
			var li = $('<li></li>');
			appendLabel(li,columnName);
			
			var input = $('<input class="dbWebFormInput" type="text" name="'+columnName+'"'+valueAttr+'/>');
			input.datepicker({dateFormat: 'yy-mm-dd'});
			li.append(input);
			
			saveInput(columnName, input, value)

			ulElem.append(li);
		};

		function addChoicesInput(formElem, ulElem, columnName, fieldOpts) {
			var value = '';
			if( "undefined" != typeof( data.queryData[columnName] ) ) {
				value = data.queryData[columnName];
			} else if( "undefined" != typeof( fieldOpts.defaultValue ) ) {
				value = fieldOpts.defaultValue;
			};

			var li = $('<li></li>');
			appendLabel(li,columnName);
			
			var input = $('<select class="dbWebFormSelect"></select>');
			for(var loop=0; loop<fieldOpts.choices.length; ++loop) {
				var choice = fieldOpts.choices[loop];
				
				var html = [];
				html.push('<option value="'+choice.value);
				if( value == choice.value ) {
					html.push('" selected="true');
				};
				html.push('">');
				if( choice.label ) {
					html.push(choice.label);
				} else {
					html.push(choice.value);
				};
				html.push('</option>');
				
				input.append( $(html.join('')) );
			};
			li.append(input);
			
			saveInput(columnName, input, value)

			ulElem.append(li);
		};
		
		function addCallbackInput(formElem, ulElem, columnName, fieldOpts) {
			var value = '';
			var valueAttr = '';
			if( "undefined" != typeof( data.queryData[columnName] ) ) {
				value = data.queryData[columnName];
				valueAttr = ' value="'+value+'"';
			} else if( "undefined" != typeof( fieldOpts.defaultValue ) ) {
				value = fieldOpts.defaultValue;
				valueAttr = ' value="'+value+'"';
			};

			var li = $('<li></li>');
			appendLabel(li,columnName);
			
			var input = $('<input class="dbWebFormInput" type="text" name="'+columnName+'"'+valueAttr+'/>');
			li.append(input);
			
			// Add a button to select via the callback
			var button = $('<input class="dbWebFormSelectButton" type="button" value="..."/>');
			button.click(function(evt){
				fieldOpts.select(onSelect);
				return false;
			});
			li.append(button);

			saveInput(columnName, input, value)

			ulElem.append(li);

			function onSelect(value_) {
				input.val(value_);
			};
		};

		function appendLabel(liElem, columnName) {
			var label = $('<label>'+columnName+':</label>');
			liElem.append(label);
		};

		function saveInput(columnName, inputElem, initialValue) {
			inputElem.attr('_initialValue',initialValue);
			data.form[columnName] = inputElem;
		};

		function insertRecord(form, saveOptions) {
			var insertOptions = $.extend({},defaultOptions,options_,saveOptions);
			
			insertOptions.setters = {};
			for(var columnName in form) {
				var input = form[columnName];
				var value = input.val();
				insertOptions.setters[columnName] = value;
			};
			
			insert(insertOptions);
		};
		
		function updateRecord(form, saveOptions) {
			var updateOptions = $.extend({},defaultOptions,options_,saveOptions);
			
			var updating = false;
			updateOptions.setters = {};
			for(var columnName in form) {
				var input = form[columnName];
				var value = input.val();
				var initialValue = input.attr('_initialValue');
				if( value != initialValue ) {
					updateOptions.setters[columnName] = value;
					updating = true;
				};
			};
			
			if( !updating ) {
				options.onAlert('Data left unchanged. No updating required.');
			} else {
				update(updateOptions);
			};
		};
		
		function delRecord(form,delOptions) {
			var removeOptions = $.extend({},defaultOptions,options_,delOptions);
			
			remove(removeOptions);
		};
		
		function updateData(columnName, value) {
			if( data && data.form && data.form[columnName] ) {
				var input = data.form[columnName];
				input.val(value);
			};
		};
		
		// Return an object that can be used to modify the form
		return {
			updateData: updateData 
		};
	};
})(jQuery);
