/**
 * Copyright (c) 2009-2010 Symbian Foundation and/or its subsidiary(-ies).
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of the License "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 *
 * Initial Contributors:
 * Nokia Corporation - initial contribution.
 * 
 * Contributors:
 * 
 * Description:
 * 
 */

/**
 * Calendar.js
 * 
 * Web Runtime Service API emulation 
 * WRT v1.1
 * 
 */

 
(function(){
	
	var provider = 'Service.Calendar' ,
		Interface = 'IDataSource';

	/**
	 * Calendar service
	 */
	var CalendarService = function(){
		this.GetList 	= __GetList;
		this.Add 		= __Add;
		this.Delete 	= __Delete;
		this.Import 	= __Import;
		this.Export 	= __Export;
		this.Cancel 	= __Cancel;
		this.RequestNotification = __RequestNotification;
	}

	device.implementation.extend(provider, Interface, new CalendarService() );


	/******************************************************/	
	/******************************************************/	
	/******************************************************/	

	var	context = device.implementation.context,
		_t = context._t,
		method = '',
		result = false,
		DBase = null,
		default_calendar = 'C:Calendar';
	
	/**
	 * Calendar: GetList
	 * @param {Object} criteria
	 */
	function __GetList(criteria){
		if ((result = validator.apply('GetList', arguments)) !== false)
			return result; 

		var returnValue = [], 
			match = null,
			filter = criteria.Filter || null;
 
		DBase = context.getData(provider);

		// Type = Calendar
		if (!/CalendarEntry/i.test(criteria.Type)){
			var cals = [default_calendar];
			if (filter && filter.DefaultCalendar === false)
				cals = context.keys(DBase)

			returnValue = cals;

		} else {
		// Type = CalendarEntry
			var cal = default_calendar;
			if (filter && filter.CalendarName)
				cal = filter.CalendarName;
			
			if (!(cal in DBase))
				return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.badCalendar);

			// filter by id or LocalId
			if (filter && (filter.id || filter.LocalId)) {
				var which = filter.id ? 'id' : 'LocalId', 
					id = filter[which];
					
				for (var i in DBase[cal]) {
					if (id == DBase[cal][i][which]) {
						returnValue.push(DBase[cal][i]);
					}
				}
			}
			// filter by type 
			else if (filter && filter.Type && !/IncludeAll/i.test(filter.Type)) {
				for (var i in DBase[cal]) {
					// match on type is case insensitive
					if (filter.Type.toLowerCase() == DBase[cal][i].Type.toLowerCase()) {
						returnValue.push(DBase[cal][i]);
					}
				}
			}
			// unsupported filters 
			else if (filter 
				&& (match = context.keys(filter).join().match(/StartRange|EndRange|SearchText/ig)) ) {
				context.notify(_t('%s:: GetList : filter %s not implemented in preview').arg(provider, match.join()));
			}
			// return everything 
			else {
				returnValue = DBase[cal];
			}
		}

		return context.Result(context.Iterator(returnValue));
	}
			
	/**
	 * Calendar: Add
	 * @param {Object} criteria
	 */
	function __Add(criteria){
		if ((result = validator.apply('Add', arguments)) !== false)
			return result; 
		
		var Item = criteria.Item || false; 
		
		DBase = context.getData(provider);

		// Type = Calendar
		if (!/CalendarEntry/i.test(criteria.Type)){

			if (!Item || !Item.CalendarName)
				return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.missingCalendar);
				
			var cal = Item.CalendarName;
			if (cal in DBase) {
				return error(device.implementation.ERR_ENTRY_EXISTS);
			}

			// @todo: validate calendar name <drive>:<name>
			// return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.badCalendar);
			
			// create new calendar
			device.implementation.loadData(provider, cal, []);
			return error(device.implementation.ERR_SUCCESS);

		} else {
		// Type = CalendarEntry

			if (!Item)
				return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.is_missing, 'Item');
				
			var cal = Item.CalendarName || default_calendar;

			// if calendar doesn't exist and it's the default, create it
			if (!(cal in DBase) && cal == default_calendar)
				device.implementation.loadData(provider, cal, []);
			
			if (!(cal in DBase))
				return error(device.implementation.ERR_NOT_FOUND);

			// update existing item?
			if ('LocalId' in Item) {
				
				if (!Item.LocalId && Item.LocalId !== 0)
					return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.is_invalid, 'LocalId');
				
				if ('InstanceStartTime' in Item)
					context.notify(_t('%s:: Add : InstanceStartTime not implemented in preview.').arg(provider));

				// can't update id
				delete Item.id;
				
				//  search for and update item
				var found = false;
				for (var i in DBase[cal]) {
					var entry = DBase[cal][i];
					if (entry.LocalId == Item.LocalId) {
						context.extend(entry, Item);
						Item.id = entry.id;
						found = true;
						break;
					}
				}
				if (!found)
					return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.is_invalid, 'LocalId');

			} else {
			// add new item
				// generate new id and localId 
				// -- in calendar id's are string!
				Item.id = String(context.getUniqueID());
				Item.LocalId = String(context.getUniqueID());
				DBase[cal].push(Item);
			} 
		} 
		// return success
		return context.Result(Item.id, device.implementation.ERR_SUCCESS);
	}
			

	/**
	 * Calendar: Delete
	 * @param {Object} criteria
	 * @param {function} callback function for async call (optional - valid only for CalendarEntry)
	 */
	function __Delete(criteria, callback){
		
		if ((result = validator.apply('Delete', arguments)) !== false)
			return result; 

		var Data = criteria.Data || false; 
		if (!Data || typeof Data != 'object')
			return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.is_invalid, 'Data');
		
		// only sync call supported for Calendar
		if (!/CalendarEntry/i.test(criteria.Type) && typeof callback == 'function')
			return error(device.implementation.ERR_SERVICE_NOT_SUPPORTED, msg.noAsync);

		DBase = context.getData(provider);
		var cal = Data.CalendarName || default_calendar;			
		if (!(cal in DBase))
			return error(device.implementation.ERR_NOT_FOUND);
		
		// Type = Calendar
		if (!/CalendarEntry/i.test(criteria.Type)) {
			// CalendarName is mandatory
			if (!Data.CalendarName)
				return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.is_missing, 'CalendarName');
						
			DBase[cal] = null;
			delete DBase[cal];
		}
		else {
		// Type = CalendarEntry

			if (!context.keys(Data).join().match(/IdList|LocalIdList|DeleteAll|StartRange|EndRange/ig))
				return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.is_missing, 'Data');

			if (typeof callback == 'function') {
				return context.callAsync(this, arguments.callee, criteria, callback);
			}

			var which = Data.IdList ? 'IdList' : 'LocalIdList',
				idList = Data[which] || false;
				
			if (idList) {
				//  search for and delete items
				for (var id in idList) {
					id = idList[id];
					for (var i in DBase[cal]) {
						if (id == DBase[cal][i][which]) {
							DBase[cal].splice(i, 1);
						}
					}
				}
			}
			else if (Data.DeleteAll && Data.DeleteAll === true){
				delete DBase[cal];
				DBase[cal] = [];
			}

			if (Data.StartRange || Data.EndRange) {
				context.notify(_t('%s:: Delete : StartRange / EndRange not implemented in preview.').arg(provider));
			}
		}
		// return success
		return context.ErrorResult(device.implementation.ERR_SUCCESS);				
	}
			

	/**
	 * Calendar: Import
	 * @param {Object} criteria
	 * @param {function} callback function for async call (optional)
	 */
	function __Import(criteria, callback){

		if ((result = validator.apply('Import', arguments)) !== false)
			return result; 

		var Data = criteria.Data || false; 
		if (!Data)
			return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.is_invalid, 'Data');
		
		if (!Data.FileName)
			return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.is_invalid, 'FileName');

		if (!Data.Format)
			return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.is_invalid, 'Format');

		if (typeof callback == 'function') {
			return context.callAsync(this, arguments.callee, criteria, callback);
		}

		context.notify(_t('%s:: Import : not implemented in preview.').arg(provider));
		var returnValue = context.Iterator([]);
		return context.Result(returnValue, device.implementation.ERR_SUCCESS);		
	}
			

	/**
	 * Calendar: Export
	 * @param {Object} criteria
	 * @param {function} callback function for async call (optional)
	 */
	function __Export(criteria, callback){

		if ((result = validator.apply('Export', arguments)) !== false)
			return result; 

		var Data = criteria.Data || false; 
		if (!Data)
			return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.is_invalid, 'Data');

		if (!Data.Format)
			return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.is_invalid, 'Format');

		if (typeof callback == 'function') {
			return context.callAsync(this, arguments.callee, criteria, callback);
		}
		context.notify(_t('%s:: Export : not implemented in preview.').arg(provider));
		var returnValue = '';
		return context.Result(returnValue, device.implementation.ERR_SUCCESS);		
	}
			

	/**
	 * Calendar: RequestNotification
	 * @param {Object} criteria
	 * @param {function} callback function for async call 
	 */
	function __RequestNotification(criteria, callback){

		if ((result = validator.apply('RequestNotification', arguments)) !== false)
			return result; 

		if (typeof callback != 'function')
			return error(device.implementation.ERR_SERVICE_NOT_SUPPORTED, msg.badAsync);

		context.notify(_t('%s:: RequestNotification : not implemented in preview.').arg(provider));
		var result = context.ErrorResult(device.implementation.ERR_SUCCESS);
		result.TransactionID = 0;
		return result;		
	}
			

	/**
	 * Calendar: Cancel
	 * @param {Object} criteria
	 */
	function __Cancel(criteria){
		method = 'Cancel';
		if (!criteria || !criteria.TransactionID)
			return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.missingTID);
		
		clearTimeout(criteria.TransactionID);
		return context.ErrorResult(device.implementation.ERR_SUCCESS);
	}


	
	/*******************************
	 * helper functions
	 *******************************/
	
	function error(code, msg /*, args...*/){

		var args = ['Calendar',method].concat([].slice.call(arguments,2));
		msg = msg ? _t().arg.apply(msg,args) : undefined;
		return context.ErrorResult(code, msg);
	}

	/**
	 * validate common input arguments
	 * 'this' is string (object) name of calling function
	 * 
	 * @param {arguments} arguments of calling function
	 * @return {Result} Result object if error, false if no error.
	 */
	function validator() {
		method = ''+this;
		var	failed = false,
			criteria = arguments[0] || false;
			
		if (!criteria)
			return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.badType);
			
		if (typeof criteria != 'object' || typeof criteria.Type == 'undefined')
			return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.badType);
		
		var TypeRE = /Import|Export|RequestNotification/i.test(method)
			? /^CalendarEntry$/i
			: /^(Calendar|CalendarEntry)$/i;
		 
		if (!TypeRE.test(criteria.Type)) 
			return error(device.implementation.ERR_INVALID_SERVICE_ARGUMENT, msg.badType);
		
		return failed;
	}

	/** 
	 * error messages
	 * order of %s args: Service name, method name, parameter name 
	 */
	var msg = {
		badType			: '%s : %s : Type is invalid',
		badCalendar		: '%s : %s : CalendarName is invalid',
		missingCalendar	: '%s : %s : CalendarName is missing',
		missingTID 		: '%s : %s : TransactionID is missing',
		badAsync		: '%s : %s : Invalid async parameters',
		noAsync			: '%s : %s : Async not supported',
		is_missing		: '%s : %s : %s is missing',
		is_invalid		: '%s : %s : %s is invalid'
	};
		

}) ();

