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

/**
 * This tool displays all migrations statuses
 */
Ext.define('Ametys.plugins.admin.migration.MigrationsTool', {
    extend: 'Ametys.tool.Tool',

    /**
     * @property {Ext.grid.Panel} _migrationsGrid The migrations grid
     * @private
     */
     
    /**
     * @private
     * @property {Ext.tree.Panel} _migrationsTree The migrations tree
     */
    
    statics: {
        ICONS: {
            "plugin": "ametysicon-abecedary4",
            "component": "ametysicon-puzzle-piece1",
            "container": "ametysicon-puzzle33",
            
            "past-before": "ametysicon-desktop-archive",
            "past-notdone": "ametysicon-desktop-archive-black",
            "past-done": "ametysicon-arrow-right-curve",
            
            "current": "ametysicon-star129",
            "error": "ametysicon-code-misc-bug",
            "pending": "ametysicon-datetime-clock"
        },
        ICONSINTERNAL: {
            "plugin": "ametysicon-abecedary4",
            "component": "ametysicon-three115",
            "container": "ametysicon-puzzle33",
            
            "past-before": "ametysicon-desktop-archive",
            "past-notdone": "ametysicon-desktop-archive-black",
            "past-done": "ametysicon-arrow-right-curve",
            
            "current": "ametysicon-star129",
            "error": "ametysicon-code-misc-bug",
            "pending": "ametysicon-datetime-clock"
        },        
        TOGGLED: {
            "pending": false,
            "error": false,
            "current": false,
            "past-done": false,
            "past-notdone": false,
            "past-before": true
        },
        TIP: {
            "pending": "{{i18n PLUGINS_ADMIN_TOOL_MIGRATIONS_FILTER_BUTTON_PENDING}}",
            "error": "{{i18n PLUGINS_ADMIN_TOOL_MIGRATIONS_FILTER_BUTTON_ERROR}}",
            "current": "{{i18n PLUGINS_ADMIN_TOOL_MIGRATIONS_FILTER_BUTTON_CURRENT}}",
            "past-done": "{{i18n PLUGINS_ADMIN_TOOL_MIGRATIONS_FILTER_BUTTON_PASTDONE}}",
            "past-notdone": "{{i18n PLUGINS_ADMIN_TOOL_MIGRATIONS_FILTER_BUTTON_PASTNOTDONE}}",
            "past-before": "{{i18n PLUGINS_ADMIN_TOOL_MIGRATIONS_FILTER_BUTTON_PASBEFORE}}"
        },
    },
    
    constructor: function(config)
    {
        this._filter = Ext.Function.createBuffered(this._filter, 200, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onMigrationModifiedOrDeleted, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onMigrationModifiedOrDeleted, this);
        this.callParent(arguments);
    },

    getMBSelectionInteraction: function () 
    {
        return Ametys.tool.Tool.MB_TYPE_ACTIVE;
    },
    
    sendCurrentSelection: function()
    {
        var selection = this._migrationsTree.getSelection();
        
        var targets = [];
        if (selection.length > 0)
        {
            var node = selection[0];
            targets.push(this.getMessageTargetConfiguration(node));
        }
        
        Ext.create('Ametys.message.Message', {
            type: Ametys.message.Message.SELECTION_CHANGED,
            targets: targets
        });
    },
    
    _onMigrationModifiedOrDeleted: function(message)
    {
        var me = this;
        
        var targets = message.getTargets(function(t) { return t.getId() == Ametys.message.MessageTarget.MIGRATION || t.getId() == Ametys.message.MessageTarget.MIGRATION_NODE });
        for (var i=0; i < targets.length; i++)
        {
            var target = targets[i];
            var index = this._migrationsTree.getStore().findBy(function (r) { 
                return r.get('componentId') == target.getParameters()['componentId']
                    && r.get('internal') == target.getParameters()['internal']
                    && r.get('versionListId') == target.getParameters()['versionListId']
                    && (target.getId() == Ametys.message.MessageTarget.MIGRATION_NODE || r.get('component') == target.getParameters()['version']);
            });
            
            var node = this._migrationsTree.getStore().getAt(index);
            var selectPath = node.getPath('component');
            var parentNode = target.getId() == Ametys.message.MessageTarget.MIGRATION_NODE ? node : node.parentNode;
            this._migrationsTree.getStore().load({
                node: parentNode,
                callback: function(records, operation, success) {
                    if (success)
                    {
                        me._migrationsTree.selectPath(selectPath, "component");
                        
                        var parentNodeToUpdate = parentNode;
                        
                        var infosToUpdateOnParents = JSON.parse(operation.getResponse().innerHTML).parentInfos
                        for (var i = infosToUpdateOnParents.length - 1; i >= 0; i--)
                        {
                            var infosToUpdateOnParent = infosToUpdateOnParents[i];
                            Object.keys(infosToUpdateOnParent).forEach(function(k){
                                parentNodeToUpdate.set(k, infosToUpdateOnParent[k]);
                            });
                            
                            parentNodeToUpdate = parentNodeToUpdate.parentNode;
                        }
                    }
                }
            });
        }
    },
    
        /**
     * @private
     * Get the target configuration object for given record
     * @param {Ext.data.Model} record The tree record to convert to its Ametys.message.MessageTarget configuration
     * @return {Object} The configuration to create a Ametys.message.MessageTarget. Can be null, if the record is null or not relevant to be a messagetarget.
     */
    getMessageTargetConfiguration: function (record)
    {
        if (record == null)
        {
            // Empty selection
            return null;
        }
        else
        {
            if (record.get('children') == null)
            {
                return {
                    id: Ametys.message.MessageTarget.MIGRATION,
                    parameters: {
                        componentId: record.get('componentId'),
                        internal: record.get('internal'),
                        versionListId: record.get('versionListId'),
                        version: record.get('component'),
                        type: record.get('type'),
                        comment:  record.get('comment')
                    }
                };
            }
            else
            {
                var params = {
                    type: record.get('type'),
                };
                if (record.get('versionListId'))
                {
                    params.versionListId = record.get('versionListId');
                }
                if (record.get('internal') !== undefined)
                {
                    params.internal = record.get('internal');
                }
                if (record.get('componentId'))
                {
                    params.componentId = record.get('componentId');
                }
                
                return {
                    id: Ametys.message.MessageTarget.MIGRATION_NODE,
                    parameters: params
                };
            }
        }
    },

    createPanel: function () 
    {
        this._createMigrationsTreePanel();
        return this._migrationsTree;
    },

    setParams: function (params)
    {
        this.callParent(arguments);
        this.showOutOfDate();
    },

    refresh: function ()
    {
        this.showRefreshing();
        this._migrationsTree.getStore().load({ 
            callback: function(){
                this._migrationsTree.getRootNode().expand();
                
                var errorRecordIndex = this._migrationsTree.getStore().findBy(function(record) {
                     return record.get('type') == 'error'
                        || record.get('type') == 'current' && record.get('failed') == true;
                 });
                if (errorRecordIndex >= 0)
                {
                    var errorRecord = Ametys.tool.ToolsManager.getFocusedTool()._migrationsTree.getStore().getAt(errorRecordIndex);
                    // autoselect / scroll to error if any
                    this._migrationsTree.getSelectionModel().select([errorRecord]);
                    this._migrationsTree.ensureVisible(errorRecord.getPath());
                }
                this.showRefreshed();
            }, 
            scope: this 
        });
    },
    
    _createMigrationsTreePanel: function ()
    {
        var me = this;
        
        var filterbar = {
            dock: 'top',
            xtype: 'toolbar',
            layout: {
                type: 'hbox',
                align: 'stretch'
            },
            
            border: false,
            defaults : {
                cls: 'ametys',
                labelWidth: 55,
                labelSeparator: ''
            },
            
            items: [{
                // Search input
                xtype: 'textfield',
                itemId: 'search-filter-input',
                cls: 'ametys',
                flex: 1,
                maxWidth: 300,
                emptyText: "{{i18n PLUGINS_ADMIN_TOOL_MIGRATIONS_FILTER_INPUT}}",
                enableKeyEvents: true,
                msgTarget: 'qtip',
                margin: '0 0 0 0',
                listeners: {change: Ext.bind(this._filter, this)}
            }, {
                // Clear filter
                tooltip: "{{i18n PLUGINS_ADMIN_TOOL_MIGRATIONS_FILTER_CLEAR}}",
                handler: Ext.bind (this._clearFilter, this),
                iconCls: 'a-btn-glyph ametysicon-eraser11 size-16',
                margin: '0 0 0 0',
                cls: 'a-btn-light'
            },
            '', {
                // Expand all
                tooltip: "{{i18n PLUGINS_ADMIN_TOOL_MIGRATIONS_FILTER_EXPANDALL}}",
                handler: Ext.bind (this._expandAll, this),
                iconCls: 'a-btn-glyph ametysicon-sign-add size-16',
                margin: '0 0 0 0',
                cls: 'a-btn-light'
            }, {
                // Collapse all
                tooltip: "{{i18n PLUGINS_ADMIN_TOOL_MIGRATIONS_FILTER_COLLAPSEALL}}",
                handler: Ext.bind (this._collapseAll, this),
                iconCls: 'a-btn-glyph ametysicon-sign-minus size-16',
                margin: '0 0 0 0',
                cls: 'a-btn-light'
            },
            '']
        };
        
        Object.keys(Ametys.plugins.admin.migration.MigrationsTool.TOGGLED).forEach(function (v){
            filterbar.items.push({
                iconCls: Ametys.plugins.admin.migration.MigrationsTool.ICONS[v],
                enableToggle: true,
                itemId: 'search-filter-button-type-' + v,
                pressed: Ametys.plugins.admin.migration.MigrationsTool.TOGGLED[v],
                tooltip: Ametys.plugins.admin.migration.MigrationsTool.TIP[v],
                margin: '0 0 0 0',
                cls: 'a-btn-light',
                toggleHandler: function(btn, state) { 
                    Ametys.plugins.admin.migration.MigrationsTool.TOGGLED[v] = state;
                    me._filter(); 
                },
                listeners: {
                    dblclick: {
                        element: 'el',
                        fn: function() {
                            Object.keys(Ametys.plugins.admin.migration.MigrationsTool.TOGGLED).forEach(function (v2){
                                if (v2 != v)
                                {
                                    me._migrationsTree.down("#search-filter-button-type-" + v2).toggle(true);
                                }
                                else
                                {
                                    me._migrationsTree.down("#search-filter-button-type-" + v2).toggle(false);
                                }
                            });
                        }
                    }
                } 
            });
        });
        
        this._migrationsTree = Ext.create('Ext.tree.Panel', {
            
            cls:'uitool-admin-migrations',
            border: false,
            scrollable: true,
//            stateful: true,
            stateId: this.self.getName() + "$grid",
            
            rootVisible: false,
            
            plugins: ['gridfilters'],
            
            viewConfig: {
                getRowClass: function(record) { 
                    return record.get('failed') ? 'error' : 
                        (record.get('warning') ? 'warning' : 
                        (record.get('type') == 'past-before' ? 'past' : 
                        (record.get('type') == 'past-notdone' ? 'past' : 
                        '')));
                } 
            },
            
            columns: [
                {
                    xtype: 'treecolumn',
                    stateId: 'grid-component', 
                    text: "{{i18n PLUGINS_ADMIN_TOOL_MIGRATIONS_COL_COMPONENT}}",
                    dataIndex: 'component',
                    sortable: false,
                    width: 500 
                }, 
                {
                    stateId: 'grid-instant', 
                    text: "{{i18n PLUGINS_ADMIN_TOOL_MIGRATIONS_COL_DATE}}",
                    dataIndex: 'instant',
                    format: Ext.Date.patterns.ShortDateTime, 
                    xtype: 'datecolumn',
                    width: 125,
                    sortable: false
                },
                {
                    stateId: 'grid-comment', 
                    text: "{{i18n PLUGINS_ADMIN_TOOL_MIGRATIONS_COL_COMMENT}}", 
                    dataIndex: 'comment', 
                    sortable: false, 
                    flex: 1,
                    renderer: function(value, metadata, record, rowIndex, colIndex, store, view) {
                      var comment = record.get('comment') || "";
                      var errorComment = record.get('errorComment') || "";
                      
                      if (errorComment)
                      {
                          errorComment = "<i>" + errorComment + "</i>";
                      }
                      
                      function _escape(t)
                      {
                          return (t || "").replaceAll(/(http(?:s)?:\/\/(?:\S)+)/g, "<a target='_blank' href='$1'>$1</a>")
                                  .replaceAll(/([A-Z]{2,10}-[0-9]{1,6})/g, "<a target='_blank' href='https://issues.ametys.org/browse/$1'>$1</a>");
                      }
                      
                      return _escape(comment) + (comment && errorComment ? "<br/>" : "") + _escape(errorComment);
                    }
                }
            ],
            
            // Filter box
            dockedItems: [filterbar],            
            
            store: Ext.create('Ext.data.TreeStore', {
                model: 'Ametys.plugins.admin.migration.MigrationsTool.Migration',
                
                root: {
                    component: 'root',
                    id: 'root',
                    expanded: false
                },  
                
                autoLoad: false,
                
                proxy: {
                    type: 'ametys',
                    role: 'org.ametys.runtime.plugins.admin.migration.MigrationsStatus',
                    methodName: 'getMigrationsStatus',
                    methodArguments: null,
                    reader: {
                        type: 'json'
                    }
                },
                listeners: {
                    beforeload: this._onBeforeLoad,
                    scope: this
                }
            }),
            
            listeners: {
                'selectionchange': Ext.bind(this.sendCurrentSelection, this),
            }
        });
        
        this._filter();

        return this._migrationsTree;
    },
    
    /**
     * @private
     * Appends the path parameter to store's Ajax requests.
     */
    _onBeforeLoad: function(store, operation, eOpts)
    {
        //Passing 'path' as extra parameter during the 'node expand' Ajax call
        operation.setParams( Ext.apply(operation.getParams(), {
            path: operation.node.getPath('component'),
        }));
    },    
    
    /**
     * @private
     * Expand all visible nodes
     */
    _expandAll: function()
    {
        var me = this;
        this._migrationsTree.getRootNode().cascade(function(record) {
            if (record.get('visible') != false)
            {
                me._migrationsTree.expandNode(record);
            }
        });
    },
    
    /**
     * @private
     * Collapse all nodes except plugins
     */
    _collapseAll: function()
    {
        var me = this;
        this._migrationsTree.getRootNode().cascade(function(record) {
            if (id == 'root' || record.get('type') == 'plugin')
            {
                me._migrationsTree.expandNode(record);
            }
            else if (record.get('type') == 'component')
            {
                me._migrationsTree.collapseNode(record);
            }
        });
    },
    
    /**
     * @private
     * Filters queries by input field value.
     */
    _filter: function ()
    {
        var currentSelection = this._migrationsTree.getSelection();
        
        var value = Ext.String.trim(this._migrationsTree.down("#search-filter-input").getValue());
        
        this._regexFilter = value ? new RegExp(value, 'i') : null;
            
        this._migrationsTree.getStore().removeFilter();
        this._migrationsTree.getStore().addFilter({
            filterFn: Ext.bind(this._filterByTextAndChildren, this)
        });
        
        if (currentSelection.length > 0)
        {
            var me = this;
            Ext.Array.each(currentSelection, function (sel) {
                if (me._migrationsTree.getStore().findExact(me._migrationsTree.getStore().getModel().idProperty, sel.getId()) == -1)
                {
                    // The current selection is not visible, clear the selection
                    me._migrationsTree.getSelectionModel().deselect([sel]);
                }
            });
            
            var selection = this._migrationsTree.getSelection();
            if (selection.length > 0)
            {
                this._migrationsTree.ensureVisible(selection[0].getPath());
            }
        }
    },
    
        /**
     * @private
     * Filter function that check if a node in the tree should be visible or not.
     * @param {Ext.data.Model} record The record to check.
     * @return {boolean} True if the record should be visible.
     */
    _filterByTextAndChildren: function (record)
    {
        var toggleVisible = true;
        Object.keys(Ametys.plugins.admin.migration.MigrationsTool.TOGGLED).forEach(function (v){
            if (toggleVisible
                && Ametys.plugins.admin.migration.MigrationsTool.TOGGLED[v]
                && record.get("type") == v)
            {
                toggleVisible = false;
            }
        });
        
        var isVisible = toggleVisible
                    && (this._regexFilter == null 
                        || this._regexFilter.test(record.data.component)
                        || this._regexFilter.test(record.data.comment));
        if (!isVisible)
        {
            // if the record does not match, we check if any child is visible. If at least one is, this record should not be hidden.
            // This is efficient because the data is in the store, and is not loaded in the view.
            for (var i = 0; !isVisible && i < record.childNodes.length; i++) 
            {
                isVisible = this._filterByTextAndChildren(record.childNodes[i]);
            }
        }
        else if (this._regexFilter == null 
                && record.childNodes.length > 0)
        {
            // When no textual search and no visible childnode... finally not visible
            var anyVisible = false;
            for (var i = 0; !anyVisible && i < record.childNodes.length; i++) 
            {
                anyVisible = this._filterByTextAndChildren(record.childNodes[i]);
            }
            if (!anyVisible)
            {
                isVisible = false;
            }
        }
        
        if (isVisible && !this._regexFilter == null)
        {
            this._migrationsTree.expandNode(record);
        }
        
        return isVisible; 
    },  
    
    /**
     * Clear the current filter
     */
    _clearFilter: function()
    {
        this._migrationsTree.down("#search-filter-input").reset();
    }    
});

Ext.define('Ametys.plugins.admin.migration.MigrationsTool.Migration', {
    extend: 'Ext.data.TreeModel',
    fields: [
        { name: 'id' },
        { name: 'component' },
        { name: 'componentId' },
        { name: 'versionListId' },
        { name: 'comment' },
        { name: 'errorComment' },
        { name: 'failed', type: 'boolean' },
        { name: 'internal', type: 'boolean' },
        { name: 'warning', type: 'boolean' },
        { name: 'instant', type: 'date' },
        { name: 'type' },
        { 
            name: 'iconCls', 
            calculate: function(data)
            {
                return Ametys.plugins.admin.migration.MigrationsTool['ICONS' + (data.internal === true ? 'INTERNAL' : '')][data.type];
            } 
        },
        { 
            name: 'expanded', 
            type: 'boolean',
            mapping: function(data) {
                return (data.expanded == true) || (data.failed == true);
            } 
        },
        { name: 'current', type: 'boolean' },
        { name: 'children' },
        { 
            name: 'leaf', 
            type: 'boolean',
            depends: ['children', 'component'], 
            convert: function (value, record) {
                return record.data.id != 'root' && (!record.data.children || record.data.children.length == 0);
            }
        }
    ]
});

Ext.define("Ametys.message.AdminMigrationsMessageTarget",
    {
        override: "Ametys.message.MessageTarget",
        statics: 
        {
            /**
             * @member Ametys.message.MessageTarget
             * @readonly
             * @property {String} MIGRATION The target of the message is an Ametys migration
             *  @property {String} MIGRATION.componentId The id of the migration component
             *  @property {String} MIGRATION.versionListId The id of the version list
             *  @property {String} MIGRATION.version The version number
             *  @property {Boolean} MIGRATION.internal Is internal
             *  @property {String} MIGRATION.comment The version comment
             */
            MIGRATION: "migration",
            /**
             * @member Ametys.message.MessageTarget
             * @readonly
             * @property {String} MIGRATION_NODE The target of the message is an Ametys migration wrapper (plugin, component or container)
             * @property {String} [MIGRATION.componentId] The id of the migration component if relevant
             * @property {Boolean} [MIGRATION.internal] Is internal if relevant
             * @property {String} [MIGRATION.versionListId] The id of the version list if relevant
             */
            MIGRATION_NODE: "migration-node"
        }
    }
);