/*
 *  Copyright 2022 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 is a tool for viewing the raw data of a content
 * @private
 */
Ext.define('Ametys.plugins.cms.content.tool.ContentDataTool', {
    extend: "Ametys.tool.Tool",
    
    /**
     * @property {String} _contentId The unique identifier of the content.
     * @private
     */
     
     /**
     * @cfg {String} [view-name="default-edition"] The name of view of content.
     */
    /**
     * @property {String} _viewName The content view name. See #cfg-view-name.
     */
     
     /**
     * @cfg {String} [fallback-view-name="main"] The name of the fallback view of content.
     */
    /**
     * @property {String} _fallbackViewName The content fallback view name. See #cfg-fallback-view-name.
     */
    
    /**
     * @cfg {Boolean} [show-disable-values=true] true to show disable values (default)
     */
    /**
     * @property {Boolean} _showDisableValues true to show disable values (default). See #cfg-show-disable-values.
     */
     
     /**
     * @property {Ext.data.Store} _viewStore The store which contains the views available for the content.
     * @private
     */
     
     /**
     * @property {Ext.form.field.ComboBox} _combo The combobox which enables the choosing of a view.
     * @private
     */
    
     /**
     * @cfg {String} [content-message-type="content"] The message type to send. Defaults to 'content'.
     * A MessageTargetFactory handling the message type and accepting an 'ids' array parameter must exist.
     */
    
    /**
     * @property {String} _contentMessageType The message type to send. See #cfg-content-message-type
     */
    
    /**
     * @private
     * @property {Ext.ux.IFrame} _iframe The iframe object
     */
    
    /**
     * @private
     * @property {Ext.Template} _tooltipDescriptionTpl The template used for tooltip description
     */
    
    /**
     * @private
     * @property {String} _baseUrl The url that the iframe should stay on
     */
    
    /**
     * @cfg {String} showEmptyDataButtonIconCls The separated CSS classes to apply to button to show empty data fields
     */
    showEmptyDataButtonIconCls: 'ametysicon-code-css-border-radius decorator-ametysicon-body-part-eye ',
    
    /**
     * @cfg {String} showEmptyDataButtonTooltip Tooltip of the button to show empty data fields
     */
    showEmptyDataButtonTooltip: "{{i18n PLUGINS_CMS_TOOL_CONTENT_DATA_SHOW_EMPTY_DATA}}",
    
    /**
     * @cfg {String} hideEmptyDataButtonIconCls The separated CSS classes to apply to button to hide empty data fields
     */
    hideEmptyDataButtonIconCls: 'ametysicon-code-css-border-radius decorator-ametysicon-body-part-eye-no',
    /**
     * @cfg {String} showEmptyDataButtonTooltip Tooltip of the button to hide empty data fields
     */
    hideEmptyDataButtonTooltip: "{{i18n PLUGINS_CMS_TOOL_CONTENT_DATA_HIDE_EMPTY_DATA}}",
    
    /**
     * @cfg {String} showTechnicalNamesButtonIconCls The separated CSS classes to apply to button to show the technical names
     */
    showTechnicalNamesButtonIconCls: 'ametysicon-code-html-tag decorator-ametysicon-body-part-eye',
    
    /**
     * @cfg {String} showEmptyDataButtonTooltip Tooltip of the button to show the technical names
     */
    showTechnicalNamesButtonTooltip: "{{i18n PLUGINS_CMS_TOOL_CONTENT_DATA_SHOW_TECHNICAL_NAMES}}",
    
    /**
     * @cfg {String} hideTechnicalNamesButtonIconCls The separated CSS classes to apply to button to hide the technical names
     */
    hideTechnicalNamesButtonIconCls: 'ametysicon-code-html-tag decorator-ametysicon-body-part-eye-no',
    /**
     * @cfg {String} hideTechnicalNamesButtonTooltip Tooltip of the button to hide the technical names
     */
    hideTechnicalNamesButtonTooltip: "{{i18n PLUGINS_CMS_TOOL_CONTENT_DATA_HIDE_TECHNICAL_NAMES}}",
    
    /**
     * @private
     * @property {Boolean} _showEmptyData true to show empty data fields
     */
    _showEmptyData: false,
    
    /**
     * @private
     * @property {Boolean} _showTechnicalNames true to show technical names
     */
    _showTechnicalNames: false,
    
    constructor: function (config)
    {
        this.callParent(arguments);
        
        this._tooltipDescriptionTpl = new Ext.XTemplate(
                '<tpl if="version">',
	                "<u>{{i18n PLUGINS_CMS_TOOL_CONTENT_TOOLTIP_VERSION}}</u> : ",
	                "<b>{version}</b><br/>",
                '</tpl>',
                "<u>{{i18n PLUGINS_CMS_TOOL_CONTENT_TOOLTIP_AUTHOR}}</u> : ",
                "<b>{author}</b><br/>",
                "<u>{{i18n PLUGINS_CMS_TOOL_CONTENT_TOOLTIP_LASTMODIFICATION}}</u> : ",
                "<b>{lastModified}</b>"
        );
        
        
        this._hintTpl = new Ext.XTemplate(
                '{{i18n PLUGINS_CMS_TOOL_CONTENT_DATA_HINT}}',
                "<b>{[Ext.String.escapeHtml(values.title)]}</b>",
                '<tpl if="version">',
                    "<b> ({{i18n PLUGINS_CMS_TOOL_CONTENT_DATA_HINT_VERSION}} {version})</b>",
                '</tpl>'
        );
        
        this._contentMessageType = config['content-message-type'] || Ametys.message.MessageTarget.CONTENT;
        
        Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onModified, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onDeleted, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.ARCHIVED, this._onDeleted, this);
        Ametys.message.MessageBus.on(Ametys.message.Message.UNARCHIVED, this._onDeleted, this);
    },
    
    createPanel: function ()
    {
        this._iframe = Ext.create("Ext.ux.IFrame", {}); 
        
        this._iframe.on ('load', this._onIframeLoad, this);
        
        return Ext.create('Ext.Panel', { 
            cls: 'contentdatatool',
            border: false,
            layout: 'border',
            scrollable: false,
            
            dockedItems: this._getTopPanel(),
            
            items: this._iframe
        });
    },
    
    getType: function()
    {
        return Ametys.tool.Tool.TYPE_CONTENT_DATA;
    },
    
    getMBSelectionInteraction: function() 
    {
        return Ametys.tool.Tool.MB_TYPE_ACTIVE;
    },
    
    /**
     * @protected
     * Get the top panel for hint and actions
     * @return {Ext.Panel} the top panel
     */
    _getTopPanel: function ()
    {
        return Ext.create({
            dock: 'top',
            xtype: 'container',
            layout: {
                type: 'hbox',
                align: 'middle'
            },
            cls: 'top',
            items: [
                {
                    // hint
                    xtype: 'component',
                    itemId: 'hint',
                    cls: 'hint',
                    html: '',
                    flex: 1
                },
                this._createViewsComboBox(),
                {
                    // show/hide empty data
                    xtype: 'button',
                    iconCls: this.showEmptyDataButtonIconCls,
                    tooltip: this.showEmptyDataButtonTooltip,
                    scope: this,
                    enableToggle: true,
                    toggleHandler: this.showEmptyData,
                    cls: 'a-btn-light'
                },
                {
                    // show/hide technical names
                    xtype: 'button',
                    iconCls: this.showTechnicalNamesButtonIconCls,
                    tooltip: this.showTechnicalNamesButtonTooltip,
                    scope: this,
                    enableToggle: true,
                    toggleHandler: this.showTechnicalNames,
                    cls: 'a-btn-light'
                }
            ]
        });
    },
    
    /**
     * Create a combo box for the views
     * @private
     */
    _createViewsComboBox: function()
    {
        this._viewStore = Ext.create('Ext.data.Store', {
            proxy: {
                type: 'ametys',
                role: 'org.ametys.cms.repository.ContentDAO',
                methodName: 'getContentViewsAndAllData',
                methodArguments: ['contentId','includeInternal'],
                reader: {
                    type: 'json'
                }
            },
            
            autoLoad: true,
            sortOnLoad: true,
            sorters: [{property: 'label', direction:'ASC'}],
            
            listeners: {
                load: {fn: this._onLoadViews, scope: this},
                beforeload: {fn: this._onBeforeLoadViews, scope: this}
            }
        });
        
        this._combo = Ext.create('Ext.form.field.ComboBox', {
                cls: 'ametys',
                forceSelection: true,
                editable: false,
                queryMode: 'local',
                allowBlank: false,
                
                store: this._viewStore,
                
                hidden: false,
                valueField: 'name',
                displayField: 'label',
                fieldLabel:"{{i18n CONTENT_OPEN_DATA_SELECT_VIEW_LABEL}}",
                labelWidth: 267,
                labelAlign: "right",
                listeners: {
                    change: {fn: this._onChangeView, scope: this}
                }
            });
            
        return this._combo;
    },
    
    /**
     * Listener called before load form display view combo box
     * @param {Object} store The store
     * @param {Object[]} operation The operation
     * @private
     */
    _onBeforeLoadViews: function(store, operation)
    {
        operation.setParams( Ext.apply(operation.getParams() || {}, {
            contentId: this._contentId,
            includeInternal: true
        }));
    },
    
    /**
     * Listener called after load form display view combo box
     * @param {Object} store The store
     * @param {Object[]} data The data
     * @private
     */
    _onLoadViews: function(store, data)
    {
        if (store.find('name', this._viewName) != -1)
        {
            this._combo.setValue(this._viewName);
        }
        else
        {
            this._combo.setValue(this._fallbackViewName);
        }
    },
    
    /**
     * Listener called on change of the view combo box
     * @param {Object} combo The combo box
     * @param {String} newValue The new value
     * @param {String} oldValue The old value
     * @private
     */
    _onChangeView: function(combo, newValue, oldValue)
    {
        this._viewName = newValue;
        this.showOutOfDate();
    },
    
    setParams: function (params)
    {
        this.callParent(arguments);
        
        this._contentId = params['contentId'] || params['id'];
        this._viewName = params['view-name'] || 'default-edition';
        this._fallbackViewName = params['fallback-view-name'] || 'main';
        this._showDisableValues = params['show-disable-values'] || true;
        this._contentVersion = params['contentVersion'];
        this._versionName = params['versionName'];
        
        // Register the tool on the history tool
        var toolId = this.getFactory().getId();
        var toolParams = this.getParams();

        Ametys.navhistory.HistoryDAO.addEntry({
            id: this.getId(),
            label: this.getTitle(),
            description: this.getDescription(),
            iconGlyph: this.getGlyphIcon(),
            iconSmall: this.getSmallIcon(),
            iconMedium: this.getMediumIcon(),
            iconLarge: this.getLargeIcon(),
            type: Ametys.navhistory.HistoryDAO.TOOL_TYPE,
            action: Ext.bind(Ametys.tool.ToolsManager.openTool, Ametys.tool.ToolsManager, [toolId, toolParams], false)
        });

        this.showOutOfDate();
        
        this._updateInfos();
    },
    
    refresh: function ()
    {
        this.showRefreshing();
        
        var wrappedUrl = this.getWrappedContentUrl ();
        
        this._iframe.load(Ametys.CONTEXT_PATH + wrappedUrl)
        this._baseUrl = Ametys.CONTEXT_PATH + wrappedUrl;
        
        this.showRefreshed();
        
        this._updateInfos();
    },
    
    /**
     * Get the wrapped url for content old revision
     * @returns {String} The wrapped url
     */
    getWrappedContentUrl: function ()
    {
        var appParameters = Ametys.getAppParameters();
        
        var additionParams = '';
        
        additionParams += '&viewName=' + this._viewName;
        additionParams += '&fallbackViewName=' + this._fallbackViewName;
        additionParams += '&showDisableValues=' + this._showDisableValues;
        additionParams += '&lang=' + Ametys.cms.language.LanguageDAO.getCurrentLanguage(); // default rendering language for multilingual content
        additionParams += '&showMissing=' + this._showEmptyData;
        additionParams += '&showTechnicalNames=' + this._showTechnicalNames;
        
        Ext.Object.each(appParameters, function(key, value) {
            additionParams += '&' + key + '=' + encodeURIComponent(value);
        });
        
        return '/_wrapped-content-data' + (this._contentVersion ? '/v_' + this._contentVersion : '') + '.html?contentId=' + this.getContentId() + additionParams;
    },
    
    /**
     * Get the unique identifier of the content.
     * @returns {String} The identifier of the content
     */
    getContentId: function()
    {
        return this._contentId;
    },
    
    /**
     * Show/hide empty data
     * @param {Ext.button.Button} button The button which triggered this method. Parameter not used.
     * @param {Boolean} state If true, show empty data
     * @private
     */
    showEmptyData: function(btn, state)
    {
        this._showEmptyData = state;
        
        if (this._showEmptyData)
        {
            btn.setIconCls(this.hideEmptyDataButtonIconCls);
            btn.setTooltip(this.hideEmptyDataButtonTooltip)
        }
        else
        {
            btn.setIconCls(this.showEmptyDataButtonIconCls);
            btn.setTooltip(this.showEmptyDataButtonTooltip);
        }
                    
        this.refresh();
    },
    
    /**
     * Show/hide technical names
     * @param {Ext.button.Button} button The button which triggered this method. Parameter not used.
     * @param {Boolean} state If true, show technical names
     * @private
     */
    showTechnicalNames: function(btn, state)
    {
        this._showTechnicalNames = state;
        
        if (this._showTechnicalNames)
        {
            btn.setIconCls(this.hideTechnicalNamesButtonIconCls);
            btn.setTooltip(this.hideTechnicalNamesButtonTooltip)
        }
        else
        {
            btn.setIconCls(this.showTechnicalNamesButtonIconCls);
            btn.setTooltip(this.showTechnicalNamesButtonTooltip);
        }
        
        this.refresh();
    },
    
    /**
     * @private
     * Listener called when the iframe is loaded.
     * Protects the iframe by handling links of the internal frame by setting a onclick on every one
     * @param {Ext.ux.IFrame} iframe The iframe
     */
    _onIframeLoad: function (iframe)
    {
        if (window.event && window.event.srcElement.readyState && !/loaded|complete/.test(window.event.srcElement.readyState))
        {
            return;
        }
        
        Ametys.plugins.cms.content.tool.ContentTool.__URL_REGEXP.test(window.location.href);
        
        var win = this._iframe.getWin();
        
        var outside = false;
        try
        {
            win.location.href
        }
        catch (e)
        {
            outside = true;
        }
        
        if (outside || (win.location.href != 'about:blank' && win.location.href.indexOf(RegExp.$1 + this._baseUrl) != 0))
        {
            var outsideUrl = win.location.href;
            // Back to iframe base url
            win.location.href = this._baseUrl;
            // Open ouside url in a new window
            window.open(outsideUrl);
        }
        else
        {
            var links = win.document.getElementsByTagName("a");
            for (var a = 0; a < links.length; a++)
            {
                if (links[a].getAttribute("internal") == null && !links[a].onclick)
                {
                    links[a].onclick = Ext.bind(this._handleLink, this, [links[a]], false);
                }
            }
        }
    },
    
    /**
     * @private
     * Lazy handling of links. Each times a link is clicked, check if we should open it in a window, open another tool...
     * @param {HTMLElement} link The clicked link
     */
    _handleLink: function(link)
    {
        var currentURI = this._iframe.getWin().location.href;
        
        var absHref = link.href;
        var relHref = null;
        if (/^(https?:\/\/[^\/]+)(\/.*)?/.test(absHref) && absHref.indexOf(RegExp.$1 + Ametys.CONTEXT_PATH) == 0)
        {
            relHref = absHref.substring(absHref.indexOf(RegExp.$1 + Ametys.CONTEXT_PATH) + (RegExp.$1 + Ametys.CONTEXT_PATH).length);
        }

        // Internal link
        if (absHref.indexOf(currentURI) == 0) return true;
        
        // JS Link
        if (absHref.indexOf("javascript:") == 0) return true;
        
        // Download link
        if (relHref != null && relHref.indexOf("/plugins/explorer/download/") == 0) return true;

        // Unknown link : open in a new window
        window.open(absHref);
        return false;
    },
    
    /**
     * Update tool information
     * @private
     */
    _updateInfos: function()
    {
        Ametys.data.ServerComm.callMethod({
            role: "org.ametys.cms.repository.ContentDAO", 
            methodName: 'getContentDescription', 
            parameters: [ this.getContentId(), null],
            waitMessage: false,
            cancelCode: "ContentDataTools$updateInfo$" + this.getContentId(),
            callback: {
                handler: this._updateInfosCb,
                scope: this,
                ignoreOnError: false
            }
        });
    },
    
    /**
     * Callback function called after #_updateInfos is processed
     * @param {Object} data The server response
     * @param {String} data.title The title
     * @param {Object} data.lastContributor The last contributor object
     * @param {String} data.lastContributor.fullname The fullname of the last contributor
     * @param {String} data.lastModified The last modified date at 'd/m/Y, H:i' format
     * @param {String} data.iconGlyph A css class to set a glyph
     * @param {String} data.iconDecorator A css class to set a glyph decorator
     * @param {String} data.smallIcon The path to the small (16x16) icon. iconGlyph win if specified
     * @param {String} data.mediumIcon The path to the medium (32x32) icon. iconGlyph win if specified
     * @param {String} data.largeIcon The path to the large (48x48) icon. iconGlyph win if specified
     * @param {Object} args The callback parameters passed to the {@link Ametys.data.ServerComm#send} method
     * @private
     */
    _updateInfosCb: function (data, args)
    {
        this.setTitle(data.title + (this._versionName ? ' ({{i18n PLUGINS_CMS_TOOL_CONTENT_TOOLTIP_VERSION}} ' + this._versionName + ')' : ''));
        
        var values = {title: data.title, version: this._versionName, author: data.lastContributor.fullname, lastModified: Ext.util.Format.date(data.lastModified, 'd/m/Y, H:i')};
        var description = this._tooltipDescriptionTpl.applyTemplate (values);
        
        this.setDescription(description);
        
        // Set title, desc, and icons.
        if (data.iconGlyph)
        {
            this.setGlyphIcon(data.iconGlyph);
            this.setIconDecorator(data.iconDecorator);
        }
        else
        {
            this.setGlyphIcon(null);
            this.setSmallIcon(data.smallIcon);
            this.setMediumIcon(data.mediumIcon);
            this.setLargeIcon(data.largeIcon);
        }
        
        var hint = this._hintTpl.applyTemplate (values);
        this.getContentPanel().down("#hint").update(hint);
    },
    
    sendCurrentSelection: function()
    {
        Ametys.cms.content.ContentDAO.getContent(this.getContentId(), Ext.bind(this._createContentTarget, this));
    },
    
    /**
     * Creates and fires a event of selection on the message bus 
     * @param {Ametys.cms.content.Content} content The concerned content
     * @private
     */
    _createContentTarget: function (content)
    {
        var targetParams = { contents: [content], 'rawData' : true};
        
        if (this._contentVersion)
        {
            targetParams['version'] = this._contentVersion
        }
        
        Ext.create("Ametys.message.Message", {
            type: Ametys.message.Message.SELECTION_CHANGED,
            
            targets: {
                id: this._contentMessageType,
                parameters: targetParams,
                
                subtargets: []
            }
        });
    },
    
    /**
     * Listener on {@link Ametys.message.Message#MODIFIED} message. If the current content is concerned, the tool will be out-of-date.
     * @param {Ametys.message.Message} message The edited message.
     * @protected
     */
    _onModfied: function (message)
    {
        var me = this;
        var contentTargets = message.getTargets(function (target) {return me._contentMessageType == target.getId();});
        
        for (var i=0; i < contentTargets.length; i++)
        {
            if (this._contentId == contentTargets[i].getParameters().id)
            {
                this.showOutOfDate();
            }
        }
    },
    
    /**
     * Listener on {@link Ametys.message.Message#DELETED} message. If the current content is concerned, the tool will be closed.
     * @param {Ametys.message.Message}  message The deleted message.
     * @protected
     */
    _onDeleted: function (message)
    {
        var me = this;
        var contentTargets = message.getTargets(function (target) {return me._contentMessageType == target.getId();});
        
        for (var i=0; i < contentTargets.length; i++)
        {
            if (this._contentId == contentTargets[i].getParameters().id)
            {
                this.close();
            }
        }
    },
    
});