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

/**
 * Resource Collection tool.
 * Display the resources of a resource collection.
 * @private
 */
Ext.define('Ametys.plugins.explorer.applications.resources.ResourceCollectionTool', {
	extend: 'Ametys.tool.Tool',
	
	inheritableStatics: {
		/**
		 * @property {String} INTERNAL_PREFIX an internal prefix
		 * @private
		 * @readonly
		 */
		INTERNAL_PREFIX: 'resources-app-',
	},
	
	/**
	 * @property {Ext.data.Store} _store The file store
	 * @private
	 */
	/**
	 * @property {Ametys.plugins.explorer.view.ImageThumbnailViewer} _thumbnailView The thumbnail view
	 * @private
	 */
	/**
	 * @property {Ametys.plugins.explorer.view.DetailsViewer} _detailsView The details view
	 * @private
	 */
	/**
	 * @property {Ext.panel.Panel} _mainPanel The application panel.
	 * @private
	 */
	
	
	/**
	 * @property {String[]} _currentFilesId The currently selected file id's.
	 */
	
	constructor: function(config)
	{
		this.callParent(arguments);
		
		// Listening to some bus messages.
		Ametys.message.MessageBus.on(Ametys.message.Message.CREATED, this._onResourceCreated, this);
		Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onResourceDeleted, this);
		Ametys.message.MessageBus.on(Ametys.message.Message.MOVED, this._onResourceMoved, this);
		Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onResourceModified, this);
	},
	
	getMBSelectionInteraction: function() 
	{
		return Ametys.tool.Tool.MB_TYPE_ACTIVE;
	},
	
	createPanel: function() 
	{
		this._store = this._createStore();
		this._thumbnailView = this._createThumbnailView();
		this._detailsView = this._createDetailsView();
		
		this.cardView = Ext.create ('Ext.Panel', {
			layout: 'card',
			activeItem : 0,
			border: false,
			scrollable: true,
			
			items: [this._detailsView, this._thumbnailView]
		});
        
        return this.cardView;
	},
	
	/**
	 * Get the resources store
	 * @return {Ext.data.Store} The store
	 */
	getStore: function ()
	{
		return this._store;
	},
	
	sendCurrentSelection: function()
	{
		var targets, subtargets;
		
		// Sending explorer_collection or resource message depending on the selection.
		var selection = this._getSelection();
		if (selection.length > 0)
		{
			ids = Ext.Array.map(selection, function(resource) {
				return resource.getId();
			});
			
			subtargets = {
				id: Ametys.message.MessageTarget.RESOURCE,
				parameters: { ids: ids }
			};
		}
		
		targets = {
			id: Ametys.message.MessageTarget.EXPLORER_COLLECTION,
			parameters: { ids: [this._parentId] },
			subtargets: subtargets
		};
		
		Ext.create("Ametys.message.Message", {
			type: Ametys.message.Message.SELECTION_CHANGED,
			targets: targets
		});
	},
	
	/**
	 * Get the active selection model
	 * @return {Ext.selection.Model} the selection model
	 * @private
	 */
	_getSelectionModel: function()
	{
		var activeItem = this.cardView.getLayout().getActiveItem();
		return Ext.isFunction(activeItem.getSelectionModel) ? activeItem.getSelectionModel() : null;
	},
	
	/**
	 * Get the selection
	 * @return {Ext.data.Model[]} The selection
	 * @private
	 */
	_getSelection: function()
	{
		var sm = this._getSelectionModel();
		return sm != null ? sm.getSelection() : [];
	},
	
	/**
	 * Creates and returns the details view for resources
	 * @return {Ametys.plugins.explorer.view.DetailsViewer} The view
	 * @private
	 */
	_createDetailsView: function ()
	{
		return Ext.create('Ametys.plugins.explorer.view.DetailsViewer', {
			itemId : this.self.INTERNAL_PREFIX + 'resources-details-view',
			
			stateful: true,
			stateId: this.getId() + "$grid",
			
			store : this.getStore(),
			border: false,
			columns: this._getColumns(),
			
			listeners: {
				activate: {fn: this._onViewActivate, scope: this},
				selectionchange: {fn: this._onSelectFiles, scope: this},
				itemdblclick: {fn: this._onDblClick, scope: this},
			}
		});
	},
	
	/**
	 * Creates and returns the thumbnail view for resources
	 * @return {Ametys.plugins.explorer.view.ImageThumbnailViewer} The view
	 * @private
	 */
	_createThumbnailView: function ()
	{
		return Ext.create('Ametys.plugins.explorer.view.ImageThumbnailViewer', {
			itemId: this.self.INTERNAL_PREFIX + 'resources-icons-view',
			
			store : this.getStore(),
			
			listeners: {
				activate: {fn: this._onViewActivate, scope: this},
				selectionchange: {fn: this._onSelectFiles, scope: this},
				itemdblclick: {fn: this._onDblClick, scope: this},
			}
		});
	},

	/**
	 * @protected
	 * Create the file store.
	 * @return {Ext.data.Store} the configured store
	 */
	_createStore: function()
	{
		return Ext.create('Ext.data.Store', {
			autoDestroy: true,
			model: 'Ametys.plugins.explorer.applications.resources.ResourcesApplication.ResourceEntry',
			proxy: {
				type: 'ametys',
				plugin: this.getPluginName(),
				url: 'files',
				reader: {
					type: 'xml',
					record: '> Node'
				}
			},
			sorters: [{property: 'name'}]
		});
	},
	
	/**
	 * @protected
	 * Create the columns configuration array.
	 * @return {Object[]} An array of column definition object. See {@link Ext.grid.column.Column}
	 */
	_getColumns: function()
	{
		return [
			{text: "{{i18n UITOOL_EXPLORER_APP_RESOURCES_COL_NAME}}", stateId: 'name', width: 250, dataIndex: 'name', hideable: false, renderer: this._renderFilename, scope: this, editor: {
					xtype: 'textfield',
					allowBlank: false,
					selectOnFocus: true
				}
			},
			{text: "{{i18n UITOOL_EXPLORER_APP_RESOURCES_COL_KEYWORDS}}", stateId: 'keywords', width: 100, dataIndex: 'keywords', hidden: true},
			{text: "{{i18n UITOOL_EXPLORER_APP_RESOURCES_COL_LOCATION}}", stateId: 'path', width: 150, dataIndex: 'path',  renderer: this._renderFilePath, scope: this, hidden: true},
			{text: "{{i18n UITOOL_EXPLORER_APP_RESOURCES_COL_DATE}}", stateId: 'lastModified', width: 130, dataIndex: 'lastModified', renderer: Ext.util.Format.dateRenderer('d/m/Y H:i')},
			{text: "{{i18n UITOOL_EXPLORER_APP_RESOURCES_COL_AUTHOR}}", stateId: 'author', width: 200, dataIndex: 'author'},
			{text: "{{i18n UITOOL_EXPLORER_APP_RESOURCES_COL_SIZE}}", stateId: 'size', width: 90, dataIndex: 'size', renderer: Ext.util.Format.fileSize, align: 'right'}
		];
	},
	
	/**
	 * Function to render filename in result grid
	 * @param {Object} value The data value
	 * @param {Object} metaData A collection of metadata about the current cell
	 * @param {Ext.data.Model} record The record
	 * @private
	 */
	_renderFilename: function (value, metaData, record)
	{
		var icon = this.getIcon (value);
		return '<img class="icon" src="' + icon + '"/>' + value;
	},
	
	/**
	 * Function to render path of a file in result grid
	 * @param {Object} value The data value
	 * @param {Object} metadata A collection of metadata about the current cell
	 * @param {Ext.data.Model} record The record
	 * @private
	 */
	_renderFilePath: function(value, metadata, record)
	{
		var index = value.lastIndexOf('/');
		var path = '/' + value.substring(0, index);
		
		return path;
//		return '<a class="location" href="#" onClick="Ametys.plugins.explorer.applications.resources.ResourcesApplication.selectNodeByPath(\'' + this.getExplorer().getTree().getId() + '\', \'' + path + '\'); return(false);">' + path + '</a>'
	},
	
	/**
	 * Get the icon full path of a resource by its name
	 * @param {String} filename The resource name
	 * @return {String} The path to the icon
	 */
	getIcon: function (filename)
	{
		var extension = "unknown";
		var index = filename.lastIndexOf('.');
		if (index > 0)
		{
			extension = filename.substring(index + 1).toLowerCase();
		}
		return Ametys.getPluginDirectPrefix('explorer') + '/icon/' + extension + '.png';
	},
	
	setParams: function (params)
	{
		params.view = params.view || 'details';
		this.callParent(arguments);
		
		var itemIdx = 'images' == this.getParams().view ? 1 : 0;
		this.cardView.getLayout().setActiveItem(itemIdx);
		
		if (!this._parentId || this._parentId != this.getParams().id)
		{
			this._parentId = this.getParams().id;
			this._applicationId = this.getParams().applicationId;
		
			this._detailsView.setApplicationId(this._applicationId);
			this._thumbnailView.setApplicationId(this._applicationId);
			
			this._detailsView.setParentId(this._parentId);
		
			this._updateInfos();
			this._initLoad();
		}
	},
	
	/**
	 * The first auto load
	 */
	_initLoad: function()
	{
		this._load();
	},
	
	/**
	 * Change the tool view.
	 * @param {String} [view=details] the name of the view : details or images
	 */
	changeView: function(view)
	{
		var params = Ext.apply(this.getParams(), {
			view: view || 'details'
		});
		
		this.setParams(params);
	},
	
	/**
	 * Update tool information
	 * @private
	 */
	_updateInfos: function()
	{
		var me = this;
		Ametys.explorer.ExplorerNodeDAO.getExplorerNode(this._parentId, function(explorerNode) {
			
			var text = explorerNode.getText();
			me.setTitle(text);
		});
	},
	
	/**
	 * Load and displays the file in the grid
	 * @param {Object} [loadParams] the load params
	 * @private
	 */
	_load: function(loadParams)
	{
		var loadParams = Ext.apply(loadParams || {}, {id: this._parentId});
		this.getStore().load(loadParams);
	},

	refresh: function(manual)
	{
		this._load({
			callback: this.showUpToDate,
			scope: this
		});
	},
	
	/**
	 * Deletes file entries
	 * @param {String} ids The ids of the entries to delete
	 * @private
	 */
	_deleteEntries: function(ids)
	{
		var store = this.getStore(),
			l = ids.length,
			records = [],
			record;
		
		for (var i = 0; i < l; i++)
		{
			record = store.getById(ids[i]);
			if (record != null)
			{
				records.push(record);
			}
		}
		
		if (records.length > 0)
		{
			var sm = this._getSelectionModel();
			if (sm != null)
			{
				sm.deselect(records);
			}
			
			this.getStore().remove(records);
		}
	},
	
	/**
	 * Listener on {@link Ametys.message.Message#CREATED} message. 
	 * If the tool is concerned by the created object, the view will be refreshed
	 * @param {Ametys.message.Message} message The created message.
	 * @private
	 */
	_onResourceCreated: function (message)
	{
		var resourceTarget = message.getTarget(Ametys.message.MessageTarget.RESOURCE);
		if (resourceTarget)
		{
			// Ensure this tool is concerned by the message.
			if (resourceTarget.getParameters().parentId != this._parentId)
			{
				return;
			}
			
			if (!this.hasFocus())
			{
				this.showOutOfDate();
			}
			else
			{
				// Reload 
				this._load();
			}
		}
	},
	
	/**
	 * Listener on {@link Ametys.message.Message#DELETED} message. 
	 * If the tool is concerned by the deleted object, the corresponding row will be removed
	 * @param {Ametys.message.Message} message The deleted message.
	 * @private
	 */
	_onResourceDeleted: function(message)
	{
		var collectionTarget = message.getTarget(Ametys.message.MessageTarget.EXPLORER_COLLECTION);
		if (collectionTarget)
		{
			if (collectionTarget.getParameters().id != this._parentId)
			{
				return;
			}
			
			// Async tool removal
			var me = this;
			setTimeout(function() {
				Ametys.tool.ToolsManager.removeTool(me);
			}, 0);
			
			// Remove file from navigation history
			Ametys.navhistory.HistoryDAO.removeEntry (this.getId());
		}
			
		var resourceTargets = message.getTargets(Ametys.message.MessageTarget.RESOURCE);
		if (resourceTargets.length > 0)
		{
			// Ensure this tool is concerned by the message.
			if (resourceTargets[0].getParameters().parentId != this._parentId)
			{
				return;
			}
			
			var ids = [];
			for (var i=0; i < resourceTargets.length; i++)
			{
				ids.push(resourceTargets[i].getParameters().id);
			}
			
			this._deleteEntries(ids);
		}
	},
	
	/**
	 * Listener on {@link Ametys.message.Message#MOVED} message. 
	 * If the tool is concerned by the moved object, the new parent node will be refreshed.
	 * @param {Ametys.message.Message} message The moved message.
	 * @private
	 */
	_onResourceMoved: function(message)
	{
		var targets = message.getTargets(Ametys.message.MessageTarget.RESOURCE);
		
		if (targets.length > 0)
		{
			var targetFolderId = targets[0].getParameters().parentId;
			var movedRecords = [], movedRecord;
			
			for (var i = 0; i < targets.length; i++)
			{
				movedRecord = this.getStore().getById(targets[i].getParameters().id);
				if (movedRecord != null)
				{
					movedRecords.push(movedRecord);
				}
			}
			
			if (movedRecords.length == 0 && targetFolderId != this._parentId)
			{
				// Tool is not concerned by this message.
				return;
			}
			
			if (targetFolderId != this._parentId)
			{
				// Remove moved nodes.
				var sm = this._getSelectionModel();
				if (sm != null)
				{
					sm.deselect(movedRecords);
				}
				
				this.getStore().remove(movedRecords);
			}
			else
			{
				// Moved nodes are still in the same folder, but some other nodes might have been added.
				if (targets.length != movedRecords.length)
				{
					this.showOutOfDate();
				}
			}
		}
	},
	
	/**
	 * Listener on {@link Ametys.message.Message#MODIFIED} message. 
	 * If the tool is concerned by the modified object, the node of modified object will be updated.
	 * @param {Ametys.message.Message} message The modified message.
	 * @private
	 */
	_onResourceModified: function(message)
	{
		var collectionTarget = message.getTarget(Ametys.message.MessageTarget.EXPLORER_COLLECTION);
		if (collectionTarget)
		{
			if (collectionTarget.getParameters().id != this._parentId)
			{
				return;
			}
			
			this._updateInfos();
		}
			
		var resourceTargets = message.getTargets(Ametys.message.MessageTarget.RESOURCE);
		if (resourceTargets.length > 0)
		{
			// Ensure this tool is concerned by the message.
			if (resourceTargets[0].getParameters().parentId != this._parentId)
			{
				return;
			}
			
			if (message.getParameters().major)
			{
				this.showOutOfDate();
			}
			else
			{
				for (var i = 0; i < resourceTargets.length; i++)
				{
					this._updateFile(resourceTargets[i].getParameters());
				}
			}
		}
	},
	
	/**
	 * Update a file entry.
	 * Currently only update its name
	 * @param {Object} resourceParams The parameters of a Resource.
	 */
	_updateFile: function(resourceParams)
	{
		var record = this.getStore().getById(resourceParams.id);
		
		record.beginEdit();
		record.set('name', resourceParams.name);
		record.endEdit();
		record.commit();
	},
	
	/**
	 * @private
	 * Listen to the *activate* event of the #_detailsView and #_thumbnailView.
	 * Keep the selection model up to date between both panels.
	 * @param {Ext.Component} activeItem The active component.
	 * @param {Object} eOpts The event option object
	 */
	_onViewActivate: function(activeItem, eOpts)
	{
		function updateSelectionModel(oldModel, newModel)
		{
			var selected = oldModel.getSelection();
			oldModel.deselectAll(true);
			
			selectedIds = Ext.Array.map(selected, function(record) {
				return record.getId();
			});
			
			var toSelect = [];
			var store = newModel.getStore(),
				record;
			
			for (var i = 0; i < selectedIds.length; i++)
			{
				record = store.getById(selectedIds[i]);
				if (record != null)
				{
					toSelect.push(record);
				}
			}
				
			newModel.select(toSelect);
		}
		
		var id = activeItem.getId();
		
		// Update selection model of the active item.
		if (id == this._detailsView.getId())
		{
			updateSelectionModel(this._thumbnailView.getSelectionModel(), this._detailsView.getSelectionModel());
		}
		else if (id == this._thumbnailView.getId())
		{
			updateSelectionModel(this._detailsView.getSelectionModel(), this._thumbnailView.getSelectionModel())
		}
	},
	
	/**
	 * @private
	 * Listen to the 'selectionchange' event of the #_detailsView and #_thumbnailView.
	 * Handle a part of the internal click logic for this component. 
	 * See {@link Ext.selection.Model#event-selectionchange}
	 */
	_onSelectFiles: function (sm, selected, eOpts) 
	{
		var ids = Ext.Array.map(selected, function(record) { return record.getId(); });
		
		if (!this._areTheSame(this._currentFilesId, ids))
		{
			this._currentFilesId = ids;
			this.sendCurrentSelection();
		}
	},
	
	/**
	 * @private
	 * Tells whether files1 and files2 are the same.
	 * @param {String[]} files1 an array of String.
	 * @param {String[]} files2 another array of String.
	 * @return {Boolean} true if files1 and files2 are the same.
	 */
	_areTheSame: function(files1, files2)
	{
		if (files1 == files2)
		{
			return true;
		}

		if (files1 == null || files2 == null || files1.length != files2.length)
		{
			return false;
		}	
		
		for (var i=0; i < files1.length; i++)
		{
			if (files2.indexOf(files1[i]) == -1)
			{
				return false;
			}
		}
		
		return true;
	},
	
	/**
	 * @private
	 * This listener is called when a item of the details view or thumbnail view is 'double-clicked'
	 * Download the file.
	 * @param {Ext.view.View} view The view
	 * @param {Ext.data.Model} record The record that belongs to the item
	 */
	_onDblClick: function(view, record)
	{
		if (record != null)
		{
			Ametys.explorer.resources.actions.File.download(record.getId());
		}
	}
});