/*
 *  Copyright 2023 Anyware Services
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * The temporay users DAO
 * @private
 */
Ext.define('Ametys.plugins.web.tempusers.TempUsersDAO', {
	singleton: true,
	
	constructor: function(config)
 	{
		/**
		 * @callable
		 * @member Ametys.plugins.web.tempusers.TempUsersDAO
		 * @method getTempUsersProperties 
		 * Gets temporary users information.
		 * This calls the method 'getTempUsersInformation' of the server component 'org.ametys.web.usermanagement.UserSignupManager'.
		 * @param {Object[]} parameters The parameters to transmit to the server method
 		 * @param {String[]} parameters.emails The emails of the temporary users to retrieve.
         * @param {String[]} parameters.siteName The site name of the temporary users to retrieve.
		 * @param {Function} callback The function to call when the java process is over. Use options.scope for the scope. 
		 * @param {Object[]} callback.tempusers The temporary users' properties. Null on error (please note that when an error occured, the callback may not be called depending on the value of errorMessage).
         * @param {String[]} callback.unknownTempusers The unknown temporay users
		 * @param {Object} callback.arguments Other arguments specified in option.arguments                 
		 * @param {Object} [options] Advanced options for the call.
		 * @param {Boolean/String/Object} [options.errorMessage] Display an error message. See Ametys.data.ServerComm#callMethod errorMessage.
		 * @param {Boolean/String/Object} [options.waitMessage] Display a waiting message. See Ametys.data.ServerComm#callMethod waitMessage.
		 * @param {Number} [options.scope] This parameter is the scope used to call the callback. Moreover is the given class is a mixin of Ametys.data.ServerCaller, its methods Ametys.data.ServerCaller#beforeServerCall and Ametys.data.ServerCaller#afterServerCall will be used so see their documentation to look for additional options (such a refreshing on Ametys.ribbon.element.ui.ButtonController#beforeServerCall).
		 * @param {Number} [options.priority] The message priority. See Ametys.data.ServerComm#callMethod for more information on the priority. PRIORITY_SYNCHRONOUS cannot be used here.
		 * @param {String} [options.cancelCode] Cancel similar unachieved read operations. See Ametys.data.ServerComm#callMethod cancelCode.
		 * @param {Object} [options.arguments] Additional arguments set in the callback.arguments parameter.                  
		 * @param {Boolean} [options.ignoreCallbackOnError] If the server throws an exception, should the callback beeing called with a null parameter. See Ametys.data.ServerComm#callMethod ignoreOnError.
		 */
		this.addCallables(
			{
			    role: "org.ametys.web.usermanagement.UserSignupManager",
				methodName: "getTempUsersProperties",
	     		callback: {
	         		handler: Ext.emptyFn
	     		},
				waitMessage: true,
				errorMessage: {
				    msg: "{{i18n PLUGINS_WEB_DAO_TEMPUSERS_REQUEST_ERROR}}",
				    category: Ext.getClassName(this)
				}
			}
		);
 	},
 	
    /**
     * Get a temporary user by email (for current site) asynchronously
     * @param {String} email The email of the temporary user to get. Cannot be null.
     * @param {String} siteName The site name of the temporary user to get. Cannot be null.
     * @param {Function} callback The method that will be called 
     * @param {Ametys.plugins.web.tempusers.TempUser} callback.tempuser The retrieved temporary user. Can be empty.
     */
    getTempUser: function (email, callback)
    {
        if (Ext.isEmpty(id))
        {
            callback(null);
            return;
        }
        this._getTempUsers ([email], Ext.bind(this._getTempUserCb, this, [callback], 1));
    },
    
    /**
     * @private
     * Called after #getTempUser to finally call the callback argument
     * @param {Ametys.plugins.web.tempusers.TempUser[]} tempusers The retrieved temporary users. Can be empty.
     * @param {Function} callback The function to call back
     * @param {Ametys.plugins.web.tempusers.TempUser} callback.tempuser The temporary user. Can be empty.
     */
    _getTempUserCb: function (tempusers, callback)
    {
        callback((tempusers.length == 0) ? null : tempusers[0]);
    },
    
    /**
     * Get several temporay users by their emails (for current site) asynchronously
     * @param {String[]} emails The emails of the temporary users to gets. Cannot be null or empty.
     * @param {Function} callback The method that will be called 
     * @param {Ametys.plugins.web.tempusers.TempUser[]} callback.tempusers The retrieved tempusers. Can be empty.
     */
    getTempUsers: function (emails, callback)
    {
        if (Ext.isEmpty(emails))
        {
            callback([]);
            return;
        }
        this._getTempUsers (emails, Ext.bind(this._getTempUsersCb, this, [callback], 1));
    },
    
    /**
     * @private
     * Called after #getTempUsers to finally call the callback argument
     * @param {Ametys.plugins.web.tempusers.TempUser[]} tempusers The retrieved tempusers. Can be empty.
     * @param {Function} callback The function to call back
     * @param {Ametys.plugins.web.tempusers.TempUser[]} callback.tempusers The retrieved tempusers. Can be empty.
     */
    _getTempUsersCb: function (tempusers, callback)
    {
        callback(tempusers);
    },
        
	/**
	 * Retrieve temporary users from the cache, or by doing a server request.
	 * @param {String[]} emails The emails
	 * @param {Function} callback The callback function called after retrieving the temporary users. Has the following parameters: 
	 * @param {Ametys.plugins.web.tempusers.TempUser[]} callback.tempusers An array of retrieved temporary users. Can be empty.
	 * @param {String[]} callback.unknownTempusers Array of unknown emails among the requested list.
	 * @param {Object} [scope=window] The callback scope, default to window. Can be null.
	 * @param {Boolean} [displayErrors=false] Set to true to display error dialog box if unknown temp users are reported.
	 * @private
	 */
	_getTempUsers: function(emails, callback, scope, displayErrors)
	{
		this._initCache();
		
		emails = Ext.Array.from(emails);
		
		var resolved = {}, unresolved = [], tempuser;
		
		Ext.Array.each(emails, function(email) {
			tempuser = this._getFromCache(email);
			if (tempuser)
			{
				resolved[id] = tempuser;
			}
			else if (!Ext.Array.contains(this._resolving, email))
			{
				this._resolving.push(email);
				unresolved.push(email);
			}
		}, this);
		
		if (unresolved.length > 0)
		{
			this.getTempUsersProperties([emails, Ametys.getAppParameter('siteName')], this._sendGetTempUserRequestCb, {scope: this, arguments: [callback, scope, displayErrors || false]});
			
		}
		else
		{
			callback.call(scope || window, Ext.Object.getValues(resolved), []);
		}
	},
	
	/**
	 * Callback function called after #getTempUsersProperties is processed
	 * @param {Object} response The response object.
	 * @param {Array} args The callback arguments.
	 * @param {Array} params The parameters.
	 * @private
	 */
	_sendGetTempUserRequestCb: function (response, args, params)
	{
		var callback = args[0] || Ext.emptyFn;
		var scope = args[1] || window;
		var displayErrors = args[3] || false;
		
		var tempusers = [];
		var unknownTempusers = response.unknownTempusers || [];
		
		// Populate cache.
		Ext.Array.forEach(response.tempusers, function(data) {
			var tempuser = Ext.create ('Ametys.plugins.web.tempusers.TempUser', data);
			tempusers.push(tempuser);
			this._addToCache(data.email, tempuser);
		}, this);
		
		if (displayErrors && unknownTempusers.length > 0)
		{
			Ametys.log.ErrorDialog.display({
				title: "{{i18n PLUGINS_WEB_DAO_TEMP_USER_NOT_FOUND_ERROR_TITLE}}",
				text: "{{i18n PLUGINS_WEB_DAO_TEMP_USER_NOT_FOUND_ERROR_MSG}}", 
				details: unknownTempusers.join(", "),
				category: this.self.getName()
			});
		}
        
        if (typeof callback == 'function')
        {
            callback (tempusers, unknownTempusers);
        }
	},
	
	/**
	 * @property {Object} _cache The DAO cache. Keys are temporary users' email, Values are an object representing the temporary user data.
	 * @private
	 */
	_cache: null,
	/**
	 * @property {Object} _cacheExpirations The object holding the expiration times for each entry in the cache
	 * @private
	 */
	_cacheExpirations : null,
	/**
	 * @property {String[]} _resolving Array of the requested temporay users that are currently being resolved on the server.
	 * Used to avoid making several requests for the same temporay user in a short period of time.
	 * @private
	 */
	_resolving: null,

	/**
	 * Update a temporay user in the cache. Nothing will be done if the entry is not present in the cache.
	 * This must be done when some data of a temporary user are modified.
	 * @param {String} email the email
	 * @param {Object} updatedData The properties to update for this temporay user.
	 */
	localUpdate: function(email, updatedData)
	{
		this._initCache();
		
		var tempuser = this._getFromCache(email);
		if (tempuser)
		{
			tempuser.setProperties(updatedData);
			this._addToCache(email, tempuser);
		}
	},
	
	/**
	 * Remove a temporay user in the cache.
	 * This must be used to invalidate an entry in the cache.
	 * Typically it must be done when a temporary user have just been removed on the server.
	 * @param {String} email the email
	 */
	localRemove: function(email)
	{
		this._initCache();
			
		delete this._cache[email];
		delete this._cacheExpirations[email];
		delete this._resolving[email];
		
	},
	
    /**
     * Cache initialization
     * @private
     */
    _initCache: function()
    {
        if (this._cache == null)
        {
            this._cache = {};
            this._cacheExpirations = {};
            this._resolving = [];
        }
    },
    
    /**
     * Cache invalidation
     */
    invalidateCache: function()
    {
        this._cache = null;
        this._initCache();
    },
    
	/**
	 * Internal method to put an entry in the cache.
	 * @private
	 */
	_addToCache: function(email, tempuser)
	{
		this._cache[email] = tempuser;
		this._cacheExpirations[email] = Ext.Date.add(new Date(), Ext.Date.MINUTE, 15);
		
		Ext.Array.remove(this._resolving, email);
	},
	
	/**
	 * Internal method to retrieve an entry in the cache.
	 * @private
	 */
	_getFromCache: function(email)
	{
		if (this._cacheExpirations[email] && new Date() <= this._cacheExpirations[email])
		{
			return this._cache[email];
		}
		else
		{
			this.localRemove(email);
			return null;
		}
	}
});