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

/**
 * This class handles the access to node properties.
 * See {@link Ametys.repository.RepositoryNode}.
 */
Ext.define('Ametys.repository.RepositoryDao',
{
    singleton: true,
    
    /**
     * Retrieve a node by its id
     * @param {String} path The node path. Cannot be null.
     * * @param {String} workspaceName the jcr workspace name
     * @param {Function} callback The callback function called when the node is retrieved. Can be null for synchronous mode (not recommended!). Parameters are
     * @param {Ametys.explorer.Resource} callback.node The node retrieved. Can be null if node does not exist.
     * @param {Object} [errorMessage] The error message
     * @return {Ametys.explorer.Resource} The node retrieved if no callback function is specified or null otherwise.
     */
    getNode: function(path, workspaceName, callback, errorMessage)
    {
        if (Ext.isEmpty(path) || Ext.isEmpty(workspaceName))
        {
            callback(null);
            return;
        }
        this._sendRequest([path], workspaceName, Ext.bind(this._getNodeCb, this, [callback], 1), errorMessage);
    },
    
    /**
     * @private
     * Callback function called after #getNode is processed
     * @param {Ametys.explorer.Resource[]} nodes The nodes retrieved
     * @param {Function} callback The callback function called 
     */
    _getNodeCb: function(nodes, callback)
    {
        callback((nodes.length == 0) ? null : nodes[0]);
    },
    
    /**
     * Retrieve nodes by their ids
     * @param {String[]} paths The nodes paths. Cannot be null.
     * @param {String} workspaceName the jcr workspace name
     * @param {Function} callback The callback function called when the node is retrieved. Can be null for synchronous mode (not recommended!). Parameters are
     * @param {Ametys.explorer.Resource} callback.node The node retrieved. Can be null if node does not exist.
     * @param {Object} [errorMessage] The error message
     * @return {Ametys.explorer.Resource} The node retrieved if no callback function is specified or null otherwise.
     */
    getNodes: function(paths, workspaceName, callback, errorMessage)
    {
        if (Ext.isEmpty(paths) || Ext.isEmpty(workspaceName))
        {
            callback([]);
            return;
        }
        this._sendRequest(paths, workspaceName, Ext.bind(this._getNodesCb, this, [callback], 1), errorMessage);
    },
    
    /**
     * @private
     * Callback function called after #getNodes is processed
     * @param {Ametys.explorer.Resource[]} nodes The nodes retrieved
     * @param {Function} callback The callback function called 
     */
    _getNodesCb: function(nodes, callback)
    {
        callback(nodes);
    },
    
    /**
     * Get the repository node store.
     * @return {Ext.data.TreeStore} the repository node store.
     */
    getNodeStore: function()
    {
        var store = Ext.getStore('Ametys.repository.data.RepositoryNodeStore');
        
        if (store == null)
        {
            store = this._createNodeStore();
        }
        
        return store;
    },
    
    /**
     * Create the repository node store.
     * @return {Ext.data.TreeStore} the repository node store.
     */
    _createNodeStore: function()
    {
        return Ext.create('Ext.data.TreeStore', {
            model: 'Ametys.repository.data.RepositoryNode',
            storeId: 'Ametys.repository.data.RepositoryNodeStore',
            proxy: {
                type: 'ametys',
                workspace: 'repository',
                url: 'repository/nodes',
                reader: {
                    type: 'xml',
                    rootProperty: 'repository',
                    record: 'node/node,node/group'
                }
            },
            root: {
                text: "{{i18n workspace.repository:WORKSPACE_REPOSITORY_JCR_ROOT_NODE_TEXT}}",
                iconCls: 'ametysicon-document28',
                id: '/',
                path: '',
                type: 'node',
                expanded: true
            },
            folderSort: true,
            listeners: {
                beforeload: {fn: this.onBeforeLoad, scope: this}
            }
        });
    },
    
    /**
     * Add additional parameters to the Operation before loading the store.
     * @param {Ext.data.TreeStore} store The tree store.
     * @param {Ext.data.Operation} operation The operation object that will be passed to the Proxy to load the Store.
     * @param {Object} eOpts Options added to the addListener
     * @private
     */
    onBeforeLoad: function(store, operation, eOpts)
    {
        var node = operation.node;
        
        // Get the first node which is not a group.
        var jcrNode = node;
        while (jcrNode.isGroup())
        {
            jcrNode = jcrNode.parentNode;
        }
        
        var path = jcrNode.get('path');
        var start = node.get('start');
        var end = node.get('end');
        
        var params = {};
        params.workspaceName = Ametys.repository.RepositoryApp.getCurrentWorkspace() || 'default';
        params.path = path.substring(1);
        
        if (start && end)
        {
            params.start = start;
            params.end = end;
        }
        
        if (jcrNode.get('order') != null)
        {
            params.order = jcrNode.get('order');
        }
        
        operation.setParams(params);
    },
    
    /**
     * @private
     * Send request to server to retrieved nodes
     * @param {String[]} paths The path of nodes to retrieve
     * @param {String} workspaceName the jcr workspace name
     * @param {Function} callback The callback function to be called after server process
     * @param {Object} [errorMessage] The error message
     */
    _sendRequest: function(paths, workspaceName, callback, errorMessage)
    {
        if (errorMessage === undefined)
        {
            errorMessage = "{{i18n plugin.repositoryapp:PLUGINS_REPOSITORYAPP_REPOSITORY_DAO_GET_NODES_ERROR}}";
        }
        
        Ametys.data.ServerComm.callMethod({
            role: 'org.ametys.workspaces.repository.jcr.RepositoryDao', 
            methodName: 'getNodesByPath',
            parameters: [paths, workspaceName],
            callback: {
                handler: this._sendRequestCb,
                scope: this,
                arguments: {callback: callback, silent: errorMessage === false}
            },
            errorMessage: errorMessage
        });
    },
    
    /**
     * @private
     * Callback function called after #_sendRequest is processed
     * @param {Object} result The node retrieval result (found and not found).
     * @param {Object} arguments The callback arguments.
     */
    _sendRequestCb: function(result, arguments)
    {
        var objects = [];
        for (var i=0; i < result.nodes.length; i++)
        {
            objects.push(Ext.create('Ametys.repository.RepositoryNode', result.nodes[i]));
        }
        
        var objectsNotFound = result.notFound;
        if (objectsNotFound.length > 0
            && !arguments.silent) // skip error message
        {
            Ametys.log.ErrorDialog.display({
                title: "{{i18n plugin.repositoryapp:PLUGINS_REPOSITORYAPP_REPOSITORY_DAO_NODES_NOT_FOUND_TITLE}}", 
                text: "{{i18n plugin.repositoryapp:PLUGINS_REPOSITORYAPP_REPOSITORY_DAO_NODES_NOT_FOUND_MESSAGE}}",
                details: "{{i18n plugin.repositoryapp:PLUGINS_REPOSITORYAPP_REPOSITORY_DAO_NODES_NOT_FOUND_DETAILS}}\n" + objectsNotFound.join('\n'),
                category: 'Ametys.repository.RepositoryDao'
            });
        }
        
        if (typeof arguments.callback == 'function')
        {
            arguments.callback(objects);
        }
    }
    
});

/**
 * Model for repository node.
 * @private
 */
Ext.define('Ametys.repository.data.RepositoryNode', {
    extend: 'Ext.data.TreeModel',
    fields: [
        {name: 'text', mapping: '@name'},
        {name: 'name', mapping: '@name'},
        {name: 'parentPath', mapping: '@parentPath'},
        {name: 'nodePath', mapping: '@path'},   // Only on groups, indicates the path of the "real" node.
        {name: 'type', mapping: '@type'},
        {name: 'index', mapping: '@index'},
        {name: 'hasOrderableChildNodes', mapping: '@hasOrderableChildNodes', type: 'boolean'},
        {name: 'order'},
        {name: 'start', mapping: '@start'},
        {name: 'end', mapping: '@end'},
        {name: 'isNew', mapping: '@isNew', type: 'boolean'},
        {name: 'isModified', mapping: '@isModified', type: 'boolean'},
        {name: 'loaded', mapping: '@noChild', type: 'boolean' },
        {
            name: 'path',
            calculate: function(data) {
                if (data.id == '/') {
                    return '';
                }
                else if (data.type == 'group') {
                    // Useless for groups but calculate anyway.
                    return data.nodePath + '/' + data.name;
                }
                else if (data.parentPath == '/') {
                    return '/' + data.name;
                }
                else {
                    return data.parentPath + '/' + data.name;
                }
            }
        },
        {
            name: 'iconCls',
            depends: ['type', 'isNew', 'isModified'],
            calculate: function(data) 
            {
                if (data.type == 'group') {
                    return 'a-tree-glyph ametysicon-file98';
                }
                else if (data.root) {
                    return 'a-tree-glyph ametysicon-document28';
                }
                else if (data.isNew || data.isModified) {
                    return 'a-tree-glyph ametysicon-document112 decorator-ametysicon-clock56';
                }
                else {
                    return 'a-tree-glyph ametysicon-document112';
                }
            }
        },
        {
            name: 'cls',
            calculate: function(data) {
                if (data.isNew || data.isModified) {
                    return 'jcr-tree-node-new';
                }
                return '';
            }
        }
    ],
    
    /**
     * Test if the tree node is a JCR node (and not a node group).
     * @return {Boolean} True if the tree node is a real JCR node, false if it's a group.
     */
    isNode: function()
    {
        return this.get('type') == 'node';
    },
    
    /**
     * Test if the tree node is a node group.
     * @return {Boolean} True if the tree node is a node group, false if it's a real JCR node.
     */
    isGroup: function()
    {
        return this.get('type') == 'group';
    }
});