

/*
 * Provides methods to store and retrieve data based on Web Storage API.
 */
sap.ui.define(['jquery.sap.global'],
	function(jQuery) {
	"use strict";

	/**
	 * Check whether the current environment supports JSON.parse and JSON stringify.
	 * @private
	 */
	var bSupportJSON = !!(window.JSON && JSON.parse && JSON.stringify);
	
	/**
	 * Prefix added to all storage keys (typically IDs) passed by the applications
	 * when they are calling state storage methods. The goal of such prefix is to
	 * leave space for saving data (with the same key) also scenarios other than
	 * state saving.
	 * @private
	 */
	var sStateStorageKeyPrefix = "state.key_";

	/**
	 * @interface A Storage API for JavaScript.
	 *
	 * Provides methods to store data on the client using Web Storage API support by the browser. The data
	 * received by this API must be already serialized, in string format. Similarly, the API returns the retrieved
	 * data in serialized string format, so it is the responsibility of the caller to de-serialize it, if applicable.
	 * 
	 * Attention: The Web Storage API stores the data on the client. Therefore do not use this API for confidential information. 
	 * 
	 * One can get access to the 'default' storage by using {@link jQuery.sap.storage} directly
	 * or alternatively via factory functionality available as <code>jQuery.sap.storage(jQuery.sap.storage.Type.session)</code>
	 * returning an object implementing this interface.
	 *
	 * A typical intended usage of this API is the storage of a string representing the state of a control.
	 * In such usage, the data is stored in the browser session, and
	 * the methods to be used are {@link #put} and {@link #get}.
	 * The method {@link #remove} can be used to delete the previously saved state.
	 *
	 * In sake of completeness, the method {@link #clear} is available.
	 * However, it should be called only in very particular situations,
	 * when a global erasing of data is required. If only keys with certain prefix
	 * should be deleted the method {@link #removeAll} should be used.
	 *
	 * @author SAP AG
	 * @version 1.22.5
	 * @since 0.11.0
	 * @public
	 * @name jQuery.sap.storage.Storage
	 */

	/** 
	 * 
	 * Constructor for an instance of jQuery.sap.storage.Storage
	 *
	 * @param {jQuery.sap.storage.Type | Storage} [pStorage=jQuery.sap.storage.Type.session] the type this storage should be of or an Object implementing the typical Storage API for direct usage.
	 * @param {string} [sStorageKeyPrefix='state.key_'] the prefix to use in this storage.
	 * 
	 * @private
	 */
	var fnStorage = /** @lends jQuery.sap.storage.Storage */ function(pStorage, sStorageKeyPrefix){
		var sType = "unknown";
		var sPrefix = sStorageKeyPrefix || sStateStorageKeyPrefix;
		sPrefix += "-";
		var oStorage;

		if(!oStorage || typeof(pStorage) === "string") {
			sType = pStorage || "session";
			try{
				oStorage = window[sType + "Storage"];
			}catch(e){
				oStorage = null;
			}
		} else if(typeof(pStorage) === Object) {
			sType = pStorage.getType ? pStorage.getType() : "unknown";
			oStorage = pStorage;
		}
		var bStorageAvailable = !!oStorage;

		/**
		 * Stores the passed state string in the session, under the key
		 * sStorageKeyPrefix + sId
		 * 
		 * sStorageKeyPrefix is the id prefix defined for the storage instance (@see jQuery.sap#storage)
		 *
		 * @param {string} sId Id for the state to store
		 * @param {string} sStateToStore content to store
		 * @return {boolean} true if the data were successfully stored, false otherwise
		 * @public
		 */
		this.put = function(sId, sStateToStore) {
			//precondition: non-empty sId and available storage feature
			jQuery.sap.assert(typeof sId === "string" && sId, "sId must be a non-empty string");
			jQuery.sap.assert(typeof sStateToStore === "string" || bSupportJSON, "sStateToStore must be string or JSON must be supported");
			if (bStorageAvailable && sId) {
				try {
					oStorage.setItem(sPrefix+sId, bSupportJSON?JSON.stringify(sStateToStore):sStateToStore);
					return true;
				} catch(e) {
					return false;
				}
			} else {
				return false;
			}
		};

		/**
		 * Retrieves the state string stored in the session under the key
		 * sStorageKeyPrefix + sId
		 * 
		 * sStorageKeyPrefix is the id prefix defined for the storage instance (@see jQuery.sap#storage)
		 *
		 * @param {string} sId Id for the state to retrieve
		 * @return {string} the string from the storage, if the retrieval
		 * was successful, and null otherwise
		 * @public
		 */
		this.get = function(sId) {
			//precondition: non-empty sId and available storage feature
			jQuery.sap.assert(typeof sId === "string" && sId, "sId must be a non-empty string");
			if (bStorageAvailable && sId ) {
				try {
					var sItem=oStorage.getItem(sPrefix+sId);
					return bSupportJSON?JSON.parse(sItem):sItem;
				} catch(e) {
					return null;
				}
			} else {
				return null;
			}
		};

		/**
		 * Deletes the state string stored in the session under the key
		 * sStorageKeyPrefix + sId
		 * 
		 * sStorageKeyPrefix is the id prefix defined for the storage instance (@see jQuery.sap#storage)
		 *
		 * @param {string} sId Id for the state to delete
		 * @return {boolean} true if the deletion
		 * was successful or the data doesn't exist under the specified key,
		 * and false if the feature is unavailable or a problem occurred
		 * @public
		 */
		this.remove = function(sId) {
			//precondition: non-empty sId and available storage feature
			jQuery.sap.assert(typeof sId === "string" && sId, "sId must be a non-empty string");
			if (bStorageAvailable && sId) {
				try {
					oStorage.removeItem(sPrefix+sId);
					return true;
				} catch(e) {
					return false;
				}
			} else {
				return false;
			}
		};
		
		/**
		 * Deletes all state strings stored in the session under the key prefix 
		 * sStorageKeyPrefix + sIdPrefix.
		 * 
		 * sStorageKeyPrefix is the id prefix defined for the storage instance (@see jQuery.sap#storage)
		 * 
		 * @param {string} sIdPrefix Id prefix for the states to delete
		 * @return {boolean} true if the deletion
		 * was successful or the data doesn't exist under the specified key,
		 * and false if the feature is unavailable or a problem occurred
		 * @since 1.13.0
		 * @public
		 */
		this.removeAll = function(sIdPrefix) {
			//precondition: available storage feature
			if (bStorageAvailable && oStorage.length && typeof(oStorage.key) === "function") {
				try {
					var len = oStorage.length;
					var aKeysToRemove = [];
					var key, i;
					var p = sPrefix + (sIdPrefix || "");
					for(i=0; i<len; i++){
						key = oStorage.key(i);
						if(key && key.indexOf(p) == 0){
							aKeysToRemove.push(key);
						}
					}
					
					for(i=0; i<aKeysToRemove.length; i++){
						oStorage.removeItem(aKeysToRemove[i]);
					}
					
					return true;
				} catch(e) {
					return false;
				}
			} else {
				return false;
			}
		};

		/**
		 * Deletes all the entries saved in the session (Independent of the current Storage instance!).
		 * 
		 * CAUTION: This method should be called only in very particular situations,
		 * when a global erasing of data is required. Given that the method deletes
		 * the data saved under any ID, it should not be called when managing data
		 * for specific controls.
		 *
		 * @return {boolean} true if execution of removal
		 * was successful or the data to remove doesn't exist,
		 * and false if the feature is unavailable or a problem occurred
		 * @public
		 */
		this.clear = function() {
			//precondition: available storage feature
			if (bStorageAvailable) {
				try {
					oStorage.clear();
					return true;
				} catch(e) {
					return false;
				}
			} else {
				return false;
			}
		};

		/**
		 * Returns the type of the storage.
		 * @returns {jQuery.sap.storage.Type | string} the type of the storage or "unknown"
		 * @public
		 */
		this.getType = function(){
			return sType;
		};
	};


	/**
	 * A map holding instances of different 'standard' storages.
	 * Used to limit number of created storage objects.
	 * @private
	 */
	var mStorages = {};

	/**
	 * Returns a {@link jQuery.sap.storage.Storage Storage} object for a given HTML5 storage (type) and,
	 * as a convenience, provides static functions to access the default (session) storage.
	 *  
	 * When called as a function, it returns an instance of {@link jQuery.sap.storage.Storage}, providing access 
	 * to the storage of the given {@link jQuery.sap.storage.Type} or to the given HTML5 Storage object.
	 * 
	 * The default session storage can be easily accessed with methods {@link jQuery.sap.storage.get}, 
	 * {@link jQuery.sap.storage.put}, {@link jQuery.sap.storage.remove}, {@link jQuery.sap.storage.clear},
	 * {@link jQuery.sap.storage.getType} and {@link jQuery.sap.storage.removeAll}
	 *
	 * @param {jQuery.sap.storage.Type | Storage} 
	 *     oStorage the type specifying the storage to use or an object implementing the browser's Storage API.
	 * @param {string} [sIdPrefix] Prefix used for the Ids. If not set a default prefix is used.    
	 * @returns {jQuery.sap.storage.Storage}
	 * 
	 * @version 1.22.5
	 * @since 0.11.0
	 * @namespace
	 * @public
	 * 
	 * @borrows jQuery.sap.storage.Storage.get as get
	 * @borrows jQuery.sap.storage.Storage.put as put
	 * @borrows jQuery.sap.storage.Storage.remove as remove
	 * @borrows jQuery.sap.storage.Storage.clear as clear
	 * @borrows jQuery.sap.storage.Storage.getType as getType
	 * @borrows jQuery.sap.storage.Storage.removeAll as removeAll
	 * @function
	 */
	jQuery.sap.storage = function(oStorage, sIdPrefix){
		// if nothing or the default was passed in, simply return ourself
		if(!oStorage) {
			oStorage = jQuery.sap.storage.Type.session;
		}

		if(typeof(oStorage) === "string" && jQuery.sap.storage.Type[oStorage]) {
			var sKey = oStorage;
			if(sIdPrefix && sIdPrefix != sStateStorageKeyPrefix){
				sKey = oStorage+"_"+sIdPrefix;
			}
			
			return mStorages[sKey] || (mStorages[sKey] = new fnStorage(oStorage, sIdPrefix));
		}

		// OK, tough but probably good for issue identification. As something was passed in, let's at least ensure our used API is fulfilled.
		jQuery.sap.assert(oStorage instanceof Object && oStorage.clear && oStorage.setItem && oStorage.getItem && oStorage.removeItem, "storage: duck typing the storage");
		return new fnStorage(oStorage, sIdPrefix);
	};

	/**
	 * Enumeration of the storage types supported by {@link jQuery.sap.storage.Storage}
	 * @class
	 * @static
	 * @public
	 * @version 1.22.5
	 * @since 0.11.0
	 */
	jQuery.sap.storage.Type = {
			/**
			 * Indicates usage of the browser's localStorage feature
			 * @public
			 */
			local: "local",
			/**
			 * Indicates usage of the browser's sessionStorage feature
			 * @public
			 */
			session: "session",
			/**
			 * Indicates usage of the browser's globalStorage feature
			 * @public
			 */
			global: "global"
	};

	// ensure the storage constructor applied to our storage object
	fnStorage.apply(jQuery.sap.storage);
	mStorages[jQuery.sap.storage.Type.session] = jQuery.sap.storage;
	
	return jQuery;
	
}, /* bExport= */ false);
