/*
 *  Copyright 2015 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.
 */

/**
 * Singleton class allowing to add or update Map service into selected page and zone
 * @private
 */
Ext.define('Ametys.plugins.maps.MapServiceActions', {
	singleton: true,
	
	/**
	 * @private
	 * @property {Ametys.window.DialogBox} _box The dialog box for service configuration
	 */
	
	/**
	 * @private
	 * @property {String} _mode the mode, can be 'new' for creation mode, 'edit' for edition mode
	 */
	
	/**
	 * @private
	 * @property {Function} _callback the callback function called after validating the service
	 */
	
	/**
	 * @private
	 * @property {Object} _params the service parameters
	 * @property {String} _params.pageId the page id
	 * @property {String} _params.zoneName the zone's name, required for service creation
	 * @property {String} _params.zoneItemId the id of the zone item, required for service edition
	 * @property  {String} _params.serviceId the id of the service 
	 */
	
	/**
	 * @private
	 * @property {Ametys.form.ConfigurableFormPanel} _form the form used to choose the title and the height of the service
	 */
	
	/**
	 * @private
	 * @property {Ametys.plugins.maps.GMapConfiguration} _gMapConfiguration the configuration of the map. 
	 * It embeds the shared store for the {@link Ametys.plugins.maps.GMapPanel} and {@link Ametys.plugins.maps.MapItemsView} and keeps a reference 
	 * on them in order to be able to call their own methods later on.
	 */
    
    /**
     * @private
     * @property {String[]} _specificMapsParameterNames The names of service's parameters that are specific for the Map 
     */
    _specificMapsParameterNames: ["center", "zoomLevel", "mapTypeId"],
    
    /**
     * @private
     * @property {String} _poisParameterName The name of service's points of interest parameter 
     */
    _poisParameterName: "pointsOfInterest",
	
	/**
	 * Open the dialog box to configure Maps service
	 * @param {Object} params the parameters
	 * @param {String} params.pageId (required) The page id
	 * @param {String} params.zoneName The zone name. Required for service creation
	 * @param {String} params.zoneItemId The zone item id. Required for service edition
	 * @param {String} params.serviceId (required) The service id
	 * @param {Function} callback the callback function
	 * @param {String} mode The mode: 'new' for service creation or 'edit' for service configuration
	 */
	open: function (params, callback, mode)
	{
		var me = this;
		 
		this._params = params;
		this._mode = mode;
		this._callback = callback;
		
		// function called after the form containing the 2 fields title and height is configured
		function configureCallback ()
		{
			me._initBox (params, Ext.bind(me._createMapDialogBox, me));
		}
		
		// configuration of the form
		this._configureForm(params, configureCallback);
	},
	
	/**
	 * @private
	 * Initialize the {@link Ametys.form.ConfigurableFormPanel} used for title and height of the service
     * @param {Object} params the parameters
     * @param {String} params.pageId (required) The page id
     * @param {String} params.zoneName The zone name. Required for service creation
     * @param {String} params.zoneItemId The zone item id. Required for service edition
     * @param {String} params.serviceId (required) The service id
	 * @param {Function} callback the function that will be invoked when the form's configuration process is over
	 */
	_configureForm: function(params, callback)
	{
		// Fetch the API key for google maps and load the script
		Ametys.helper.ChooseLocation.LoadGoogleMaps.getDefaultAPIKey(Ext.bind(this._getAPIKeyCb, this, [params, callback], 1));
	},
	
	/**
	 * @private
	 * Function invoked after retrieving teh Google API key
	 * @param {String} apiKey The API key
	 * @param {Object} params the parameters
     * @param {String} params.pageId (required) The page id
     * @param {String} params.zoneName The zone name. Required for service creation
     * @param {String} params.zoneItemId The zone item id. Required for service edition
     * @param {String} params.serviceId (required) The service id
	 * @param {Function} callback the function that will be invoked when the form's configuration process is over
	 */
	_getAPIKeyCb: function (apiKey, params, callback)
	{
		var me = this;
		Ametys.helper.ChooseLocation.LoadGoogleMaps.loadScript(apiKey, 
            function() 
			{
                Ametys.plugins.web.zone.ZoneItemManager.getServiceParameterDefinitions([params.serviceId, params.pageId, params.zoneItemId, params.zoneName], me._configureFormCb, {scope: me, arguments: [callback]});
			}
	    );
	},
	
	/**
	 * @private
	 * Callback for the form initialization process
	 * @param {Object} response the configuration of the form panel's fields
	 * @param {Object} args the callback arguments
	 * @param {Function} args.callback the callback function
	 */
	_configureFormCb: function(response, args)
	{
        // Separate specific Maps parameters and classic parameters (using ConfigurableFormPanel)
        var separatedDefinitions = this._separateClassicAndSpecificParameterDefinitions(response.parameters.elements);
        
        // Initialize the form for classic parameters
		this._form = Ext.create('Ametys.form.ConfigurableFormPanel', {
			defaultFieldConfig: {
				labelWidth: 80,
				minWidth: 180
			},
            height: 90,
			defaultType: 'textfield',
			listeners: {
				'afterlayout': Ext.bind(this._focusForm, this)
			}
		});
		
		this._form.configure(separatedDefinitions.classicElements);
        this._specificElementDefinitions = separatedDefinitions.specificElements;
        
		if (Ext.isFunction(args[0]))
		{
			args[0]();
		}
	},
    
    /**
     * @private
     * Separate the classic and specific definitions from given definitions
     * The returned {Object} contains 2 elements:
     *   - {Object} classicElements contains all classic parameter definitions
     *   - {Object} specificElements contains all specific Map parameter definitions 
     * @param {Object} allDefinitions all the parameter definitions
     * @return {Object} the separated classic and specific parameter definitions
     */
    _separateClassicAndSpecificParameterDefinitions: function(allDefinitions)
    {
        var me = this;
        var result = {};
        result.classicElements = {};
        result.specificElements = {};
        
        Ext.Object.each(allDefinitions, function(key, value, definitions) {
            if (Ext.Array.contains(me._specificMapsParameterNames, key) || Ext.String.startsWith(key, me._poisParameterName))
            {
                result.specificElements[key] = value;
            }
            else
            {
                result.classicElements[key] = value;
            }
        });
        
        return result;
    },
	
	/**
	 * @private
	 * Focuses the form panel
	 */
	_focusForm: function()
	{
		this._form.focus();
	},
	
	/**
	 * @private
	 * Initialize the dialog box 
	 * @param {Object} params the initial parameters
	 * @param {Function} callback function invoked after the form's initialization process
	 */
	_initBox: function (params, callback)
	{
		if (this._mode == 'edit')
		{
            Ametys.plugins.web.zone.ZoneItemManager.getServiceParameterValues([params.zoneItemId, params.serviceId], this._getServiceParametersCb, {scope: this, arguments: [callback]});
		}
		else
		{
            this._gMapConfiguration = Ext.create ('Ametys.plugins.maps.GMapConfiguration', 
            {
                zoomLevel: this._specificElementDefinitions.zoomLevel['default-value'],
                mapTypeId: google.maps.MapTypeId.ROADMAP,
                center: {
                    lat: this._specificElementDefinitions.center['default-value'].latitude,
                    lng: this._specificElementDefinitions.center['default-value'].longitude
                },
                pois:[]
            });
            
            this._form.setValues(); // setValues must always be called for configurable form panel in order to complete its initialization
        
			if (Ext.isFunction(callback))
			{
				callback();
			}
		}
	},
	
	/**
	 * @private
	 * Callback function invoked after retrieving service parameters in 'edit' mode
	 * @param {Object} response the server response
	 * @param {Object} args the callback arguments
	 * @param {Function} args.callback the callback function invoked 
	 */
	_getServiceParametersCb: function (response, args)
	{
        // Separate specific Maps parameters and classic parameters
        var separatedValues = this._separateClassicAndSpecificParameterValues(response);
        
		// Set classic values for the configurable form panel
        this._form.setValues(separatedValues.classicValues);
		
		// Set specific values for the GMapConfiguration object
        var me = this;
        this._gMapConfiguration = Ext.create ('Ametys.plugins.maps.GMapConfiguration', 
        {
            zoomLevel: parseInt(separatedValues.specificValues.values.zoomLevel),
            mapTypeId: separatedValues.specificValues.values.mapTypeId,
            center: {
                lat: parseFloat(separatedValues.specificValues.values.center.latitude), 
                lng: parseFloat(separatedValues.specificValues.values.center.longitude)
            },
            pois:me._getPoisAsArray(separatedValues.specificValues)
        });
		
		if (Ext.isFunction(args[0]))
		{
			args[0]();
		}
	},
    
    /**
     * @private
     * Separate the classic and specific values from given values
     * The returned {Object} contains 2 elements:
     *   - {Object} classicValues contains all classic parameter values
     *   - {Object} specificValues contains all specific Map parameter values 
     * @param {Object} allValues all the parameter values
     * @return {Object} the separated classic and specific parameter values
     */
    _separateClassicAndSpecificParameterValues: function(allValues)
    {
        var me = this;
        var result = {};
        result.classicValues = {};
        result.classicValues.values = {};
        result.classicValues.repeaters = [];
        result.specificValues = {};
        result.specificValues.values = {};
        result.specificValues.repeaters = [];
        
        // Manage repeaters
        // 1. Find the points of interest repeater
        var pointsOfInterest = Ext.Array.findBy(allValues.repeaters, function(repeater) {
            return me._poisParameterName == repeater.name;
        });
        
        // 2. Add the points of interest repeater to the specific repeaters
        Ext.Array.push(result.specificValues.repeaters, pointsOfInterest);
        
        // 3. Remove it from from the classic repeaters
        var classicRepeaters = Ext.Array.remove(allValues.repeaters, pointsOfInterest);
        
        // 3. if there are other repeaters, add them to the classic values
        if (classicRepeaters.length > 0)
        {
            result.classicValues.repeaters = classicRepeaters;
        }
        
        // Manage values
        Ext.Object.each(allValues.values, function(key, value, values) {
            if (Ext.Array.contains(me._specificMapsParameterNames, key) || Ext.String.startsWith(key, me._poisParameterName))
            {
                result.specificValues.values[key] = value;
            }
            else
            {
                result.classicValues.values[key] = value;
            }
        });
        
        return result;
    },
    
    /**
     * @private
     * Retrieves the points of interest found in the given values as an array
     * @param {Object} values object containing the points of interest values
     * @return {Object[]} the points of interest as an array 
     */
    _getPoisAsArray: function(values)
    {
        var pois = [];
        var poisCount = values.repeaters[0].count;
        
        for (var i = 1; i <= poisCount; i++)
        {
            var poi = {};
            poi.title = values.values[this._poisParameterName + "[" + i + "]/title"];
            poi.description = values.values[this._poisParameterName + "[" + i + "]/description"];
            poi.gtype = values.values[this._poisParameterName + "[" + i + "]/type"];
            
            // marker poi
            if (poi.gtype == 'marker')
            {
                poi.icon = values.values[this._poisParameterName + "[" + i + "]/icon"];
                poi.lat = values.values[this._poisParameterName + "[" + i + "]/point"].latitude;
                poi.lng = values.values[this._poisParameterName + "[" + i + "]/point"].longitude;
            }
            else if (poi.gtype == 'polygon')
            {
                // polygon poi
                poi.color = values.values[this._poisParameterName + "[" + i + "]/color"];
                poi.points = [];
                Ext.Array.each(values.values[this._poisParameterName + "[" + i + "]/points"], function(point)
                {
                    Ext.Array.push(poi.points, {lat: point.latitude, lng: point.longitude});
                });
            }
            
            Ext.Array.push(pois, poi);
        }
        
        return pois;
    },
	
	/**
	 * @private
	 * Create the Maps service dialog box
	 */
	_createMapDialogBox: function ()
	{
		this._box = Ext.create('Ametys.window.DialogBox', {
			title:"{{i18n plugin.web:PLUGINS_WEB_CONTENT_ADDSERVICE_PARAMETERS}}",
			iconCls: 'ametysicon-hammer2',
			
			width: 1000,
			height: 500,
			border: false,
			
			alwaysOnTop: -1, // prioritize the window to the bottom of the z-index stack.
			layout: {
		        type: 'hbox',
		        align: 'stretch'
		    },
			
			items: [
		        {
                    width: 350,
					layout: {
		                type: 'vbox',
		                align: 'stretch'
		            },
					items: [
			  		    this._form,
			  		    this._gMapConfiguration.getMapItemsPanel({flex: 1})
	  		        ]
		        },
		        this._gMapConfiguration.getGMapPanel({flex: 1})
			],
			
			closeAction: 'hide',
			defaultFocus: this._form,
			
			referenceHolder: true,
			defaultButton: 'validate',
			
			buttons : [
				{
					reference: 'validate',
					text :"{{i18n plugin.web:PLUGINS_WEB_ZONE_ADDSERVICEACTION_OK}}",
					handler : this._validate,
					scope: this
				}, 
				{
					text :"{{i18n plugin.web:PLUGINS_WEB_ZONE_ADDSERVICEACTION_CANCEL}}",
					handler : function () { this._box.close() },
					scope: this
				} 
			]
		});
		
		this._box.show();
	},
	
	/**
	 * @private
	 * Validate the dialog box and create or edit the service.
	 */
	_validate: function ()
	{
		// Check form
		var invalidFields = this._form.getInvalidFields();
		if (invalidFields.length > 0)
		{
			Ametys.Msg.show({
				   title: "{{i18n plugin.web:PLUGINS_WEB_CONTENT_ADDSERVICE_PARAMETERS_SAVE}}",
				   msg: "{{i18n plugin.web:PLUGINS_WEB_CONTENT_ADDSERVICE_PARAMETERS_SAVE_INVALIDFIELDS}}" + Ametys.form.SaveHelper.getInvalidFieldsAsReadableList(invalidFields),
				   buttons: Ext.Msg.OK,
				   icon: Ext.MessageBox.ERROR
			});
			return;
		}

		var values = this._form.getJsonValues();
		// get map parameters
		var mapConfiguration = this._gMapConfiguration;
		
		var center = mapConfiguration.getCenter();
		values['center'] = {latitude: center.lat, longitude: center.lng};
		values['zoomLevel'] = mapConfiguration.getZoomLevel();
		values['mapTypeId'] = mapConfiguration.getMapTypeId();
        
        var pois = mapConfiguration.getPoisAsArray();
        values["_pointsOfInterest/size"] = pois.length;
        for (var arrayIndex = 0; arrayIndex < pois.length; arrayIndex++)
        {
            var repeaterIndex = arrayIndex + 1;
            
            // Always put -1, the points of interest can't be re-ordered
            values['_pointsOfInterest[' + repeaterIndex + ']/previous-position'] = -1;
            
            for (var name in pois[arrayIndex])
            {
                if ('gtype' == name)
                {
                    values['pointsOfInterest[' + repeaterIndex + ']/type'] = pois[arrayIndex].gtype;
                }
                else if ('lat' == name || 'lng' == name)
                {
                    values['pointsOfInterest[' + repeaterIndex + ']/point'] = {latitude: pois[arrayIndex].lat, longitude: pois[arrayIndex].lng}
                }
                else if ('points' == name)
                {
                    var points = [];
                    Ext.Array.each(pois[arrayIndex].points, function(point) {
                        Ext.Array.push(points, {latitude: point.lat, longitude: point.lng});
                    });
                    values['pointsOfInterest[' + repeaterIndex + ']/points'] = points;
                }
                else
                {
                    values['pointsOfInterest[' + repeaterIndex + ']/' + name] = pois[arrayIndex][name];
                }
            }
        }
        
        if (this._mode == 'new')
        {
            Ametys.plugins.web.zone.ZoneItemManager.addService([this._params.pageId, this._params.zoneName, this._params.serviceId, values], this._saveParametersCb, {scope: this});
        }
        else
        {
            Ametys.plugins.web.zone.ZoneItemManager.editServiceParameterValues([this._params.zoneItemId, this._params.serviceId, values], this._saveParametersCb, {scope: this});
        }
	},
	
	/**
	 * @private
	 * Callback after saving service
	 * @param {Object} response The server response
	 * @param {Object} args The callback arguments
	 */
	_saveParametersCb: function (response, args)
	{
		var errors = response.error;
		if (!Ext.isEmpty(errors))
		{
			var details = "";
    		for (var error in errors)
    		{
    			details += error + ": " + errors[error].join('; ') + "\n";
    		}
    		
    	    Ametys.log.ErrorDialog.display({
    	        title: "{{i18n plugin.web:PLUGINS_WEB_DAOS_PAGE_CONFIGURESERVICE_FAILED_TITLE}}",
    	        text: "{{i18n plugin.web:PLUGINS_WEB_DAOS_PAGE_CONFIGURESERVICE_FAILED_DESC}}",
    	        details: details,
    	        category: this.self.getName() 
    	    });
		}
		else
		{
			this._box.close();
			
			Ext.create('Ametys.message.Message', {
				type: Ametys.message.Message.MODIFIED, // TODO zoningChanged + selectionchanged ?,
				parameters: {"creation": "service"},
				targets: [{
					id: Ametys.message.MessageTarget.PAGE,
					parameters: {
						ids: [response.id],
						'zone-name': response['zone-name'],
						'zoneitem-id': response['zoneitem-id']
					}
				}]
			});
			
			// Execute callback function passed to #open method
			if (Ext.isFunction(this._callback))
			{
				this._callback(this._params);
			}
		}
	}
});