/*
 *  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 displays the sitemap of a site
 * @private
 */
Ext.define('Ametys.plugins.web.page.tool.SitemapPageTool', {
    extend: 'Ametys.tool.Tool',
    
    statics : {
        /**
         * @private
         * @readonly
         * @property {RegExp} __URL_REGEXP The regexp to match url
         */
        __URL_REGEXP: new RegExp("^(https?:\/\/[^\/]+)(\/.*)?")
    },
    
    /**
     * @private
     * @property {Boolean} true if the displayed page is a page of another site
     */
    _isOutOfSite: false,
    
    /**
     * @private
     * @property {String} _lastZoneName The name of the zone lastly selected. Null if no zone was selected.
     */
    _lastZoneName: null,
    /**
     * @private
     * @property {String} _lastZoneWidth The width of the zone lastly selected. Null if no zone was selected or unknown.
     */
    _lastZoneWidth: null,
    /**
     * @private
     * @property {String} _lastZoneItemId The id of the zone item lastly selected. Null if no zone item was selected.
     */
    _lastZoneItemId: null,
    /**
     * @private
     * @property {String} _lastPageType The type of the page lastly selected.
     */
    _lastPageType: null,
    /**
     * @private
     * @property {Ext.Template} _nodeOrLinkTpl Template for page of type 'node' or 'link'
     */
    _nodeOrLinkTpl: Ext.create('Ext.Template', '<div class="title">',
                                                 '<span class="{iconCls}"></span>',
                                                "{title}",
                                        '</div>',
                                        '<div class="text">{text}</div>'),
                                          
    /**
     * @private
     * @property {String} _baseUrl The url that the iframe should stay on
     */
  
    /**
     * @private
     * @property {String[]} _contentIds The identifiers of contents in this page.
     */

    /**
     * @private
     * @property {Boolean} _ctrlPressedOnLastLink Was the key CTRL pressed, the last time a link was clicked on this page? Read by PageToolFactory
     */
                                                                                  
    /**
     * @private
     * @property {Boolean} _openedAtStartup Was the tool opened during the UI startup
     */
                                          
    constructor: function(config)
    {
        this.callParent(arguments);
        
        this._tooltipDescriptionTpl = new Ext.Template(
                "<u>{{i18n PLUGINS_WEB_TOOL_SITEMAP_SITEMAPPAGETOOL_TOOLTIPTEXT_SITE}}</u> : ",
                "<b>{siteName}</b><br/>",
                "<u>{{i18n PLUGINS_WEB_TOOL_SITEMAP_SITEMAPPAGETOOL_TOOLTIPTEXT_LANG}}</u> : ",
                "<b>{lang}</b>"
        );
        
        Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onModified, this);
    },
    
    createPanel: function ()
    {
        this._container = Ext.create("Ext.Container", { 
            scrollable: true,
            cls: 'empty',
            html: ''
        });
        
        this._iframe = Ext.create("Ext.ux.IFrame", {}); 
        this._iframe.on ('render', this._onIframeRender, this);
        this._iframe.on ('load', this._onIframeLoad, this);
        
        return new Ext.Panel ({
            layout: { 
                type: 'card', 
                deferredRender: false 
            },
            
            cls: 'uitool-pagetool',
            
            dockedItems: [{
                dock: 'top',
                xtype: 'button',
                hidden: true,
                itemId: 'out-of-site',
                ui: 'tool-hintmessage',
                text: "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_OUT_OF_SITE}}",
                handler: this._openOutOfSitePage,
                scope: this
            }, {
                dock: 'top',
                xtype: 'component',
                hidden: true,
                itemId: 'tip',
                ui: 'tool-hintmessage',
                html: "{{i18n PLUGINS_WEB_TOOL_SITEMAP_SITEMAPPAGETOOL_USE}}",
                scope: this
            }],
            
            items: [ 
                    this._container, 
                    this._iframe
            ]
        });
    },
    
    getType: function()
    {
        return Ametys.tool.Tool.TYPE_PAGE;
    },
    
    setParams: function (params)
    {
        this.callParent(arguments);
        
        this._openedAtStartup = !Ametys.tool.ToolsManager.isInitialized();
        
        this._pageId = params['pageId'] || params['id'];
        this._pageMessageType = params['page-message-type'] || Ametys.message.MessageTarget.SITEMAP;

        this.refresh();
    },
    
    getMBSelectionInteraction: function() 
    {
        return Ametys.tool.Tool.MB_TYPE_ACTIVE;
    },
    
    refresh: function()
    {
        this.showRefreshing();
        
        Ametys.web.sitemap.SitemapDAO.getSitemap(this._pageId, Ext.bind(this._finalizeParams, this));
    },
    
    /**
     * @private
     * Finalize refreshment of tool
     * @param {Ametys.web.page.Page} page The page or null if not found
     */
    _finalizeParams: function (page)
    {
        if (page)
        {
            this._openedAtStartup = false;
            
            this._siteName = page.getSiteName();
            
            if (this._siteName != Ametys.getAppParameter('siteName'))
            {
                // Page will be in read-only
                this._isOutOfSite = true;
                this._showOutOfSitePanel();
            }
            
            this._updateUI (page);
            this._updateInfos (page);
            this._updateLastValues(page);
            
            this.showRefreshed();
        }
        else
        {
            if (!this._openedAtStartup) 
            {
                // Page not found
                var details = '';
                var params = this.getParams();
                for (var name in params)
                {
                    details += name + " : " + params[name] + '\n';
                }
                
                Ametys.log.ErrorDialog.display({
                    title: "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_PAGE_NOT_FOUND}}",
                    text: "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_PAGE_NOT_FOUND_ERROR}}",
                    details: details,
                    category: this.self.getName()
                });
            }
            else
            {
                Ametys.notify({
                    title: "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_PAGE_NOT_FOUND}}",
                    description: "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_PAGE_NOT_FOUND_ERROR}}",
                    type: "warn"
                });
                this.getLogger().error("Cannot open unexisting page");
            }
             
            this.close();
        }
    },
    
    /**
     * Show the top hint panel when the page is not a page of current site.
     * @private
     */
    _showOutOfSitePanel: function ()
    {
        this.getContentPanel().down("#out-of-site").show();
    },
    
    /**
     * @private
     * Open a page which is not a page of the current site
     */
    _openOutOfSitePage: function ()
    {
        window.open("../" + this._siteName + "/index.html?uitool=uitool-page,id:'" + this._pageId + "'");
    },
    
    /**
     * Update UI
     * @param {Ametys.web.page.Page} page The page
     * @private
     */
    _updateUI: function (page)
    {
        switch (page.getType().toLowerCase())
        {
            case "node":
                this.getContentPanel().getLayout().setActiveItem(0);
                this.getContentPanel().down("#tip").hide();
                
                var html = this._nodeOrLinkTpl.applyTemplate({
                    iconCls: this.getInitialConfig()['icon-glyph'] + (this.getInitialConfig()['icon-decorator'] ? " " + this.getInitialConfig()['icon-decorator'] : ""), 
                    title: "{{i18n PLUGINS_WEB_TOOL_SITEMAP_SITEMAPPAGETOOL_BLANKPAGE_TITLE}}",
                    text: "{{i18n PLUGINS_WEB_TOOL_SITEMAP_SITEMAPPAGETOOL_BLANKPAGE}}"
                });
                
                this._container.update(html);
                this.setGlyphIcon(this.getInitialConfig()['icon-glyph']);
                this.setIconDecorator(this.getInitialConfig()['icon-decorator']);
                break;
                
            case "container":   
                this.getContentPanel().getLayout().setActiveItem(1); 
                this.getContentPanel().down("#tip").show();
                
                var url = Ametys.getPluginDirectPrefix("web") + "/sitemap/" + page.getSiteName() + "/" + page.getLang() + ".html";
                this._iframe.load(url)
                this._baseUrl = url;
                
                this._contentIds = page.getContentIds();
                this.setGlyphIcon(this.getInitialConfig()['icon-glyph3']);
                this.setIconDecorator(this.getInitialConfig()['icon-decorator3']);
                
                break;
                
            default:
                // Unknow type of page
                Ametys.log.ErrorDialog.display({
                    title: "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_UNKNOWN_PAGE_TYPE_TITLE}}", 
                    text: "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_UNKNOWN_PAGE_TYPE_TEXT}}",
                    details: "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_UNKNOWN_PAGE_TYPE_DETAILS1}}" + page.getType() + "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_UNKNOWN_PAGE_TYPE_DETAILS2}}",
                    category: this.self.getName()
                });

               this.close();
        }
    },
    
    /**
     * Update tool information
     * @param {Ametys.web.page.Page} page The page
     * @private
     */
    _updateInfos: function (page)
    {
        var values = {
                siteName: page.getSiteName(), 
                lang: page.getLang()
        }
        
        this.setTitle("{{i18n PLUGINS_WEB_TOOL_SITEMAP_SITEMAPPAGETOOL_TITLE_PREFIX}}"
                      .replace('{lang}', Ametys.cms.language.LanguageDAO._store.findRecord('name', page.getLang()).get('label')));

        var description = this._tooltipDescriptionTpl.applyTemplate (values);
        this.setDescription(description);
    },
    
    /**
     * Update "last" values, ensuring that the corresponding objects still exist.
     * @param {Ametys.web.page.Page} page The page
     * @private
     */
    _updateLastValues: function(page)
    {
        var zoneExists = false;
        var ziExists = false;
        
        var zones = page.getZones();
        for (var i = 0; i < zones.length; i++)
        {
            if (zones[i].name == this._lastZoneName)
            {
                zoneExists = true;
            }
            
            if (this._lastZoneItemId != null)
            {
                var zoneItems = zones[i].zoneitems;
                for (var j = 0; j < zoneItems.length; j++)
                {
                    if (zoneItems[j].id == this._lastZoneItemId)
                    {
                        ziExists = true;
                    }
                }
            }
        }
        
        this._lastPageType = page.getType();
        
        if (!zoneExists)
        {
            this._lastZoneName = null;
            this._lastZoneWidth = null;
        }
        if (!ziExists)
        {
            this._lastZoneItemId = null;
        }
    },
    
    sendCurrentSelection: function()
    {
        var me = this;
        
        if (!this._isOutOfSite)
        {
            Ext.create("Ametys.message.Message", {
                type: Ametys.message.Message.SELECTION_CHANGED,
                targets: {
                    id: this._pageMessageType,
                    parameters: { 
                        'id': this._pageId,
                        'zone-name': this._lastZoneName,
                        'zoneitem-id': this._lastZoneItemId,
                        'zone-width': this._lastZoneWidth,
                        'errorMessage': this._openedAtStartup ? false : undefined
                    }
                },
                beforeFireCallback: function(message)
                {
                    if (me._lastZoneItemId && message.getTarget("content"))
                    {
                        message.setParameters(Ext.apply({ creation: 'content' }, message.getParameters()));
                    }
                    message.getTargets()[0]
                }
            });
        }
        else
        {
            Ext.create("Ametys.message.Message", {
                type: Ametys.message.Message.SELECTION_CHANGED,
                targets: []
            });
        }        
    },
    
    /**
     * @private
     * Listener called when the iframe is rendered.
     * Protects the iframe by handling links of the internal frame by setting a onclick on every one
     * @param {Ext.ux.IFrame} iframe The iframe
     */
    _onIframeRender: function (iframe)
    {
        this._dropZone = new Ametys.relation.dd.AmetysDropZone(iframe.getEl(), {setAmetysDropZoneInfos: Ext.bind(this.getDropInfo, this)});
    },
    
    /**
     * @private
     * This event is thrown before the beforeDrop event and create the target of the drop operation relation.
     * @param {Object} target The target records of the drop operation.
     * @param {Object} item The default drag data that will be transmitted. You have to add a 'target' item in it: 
     * @param {Object} item.target The target (in the relation way) of the drop operation. A Ametys.relation.RelationPoint config. 
     */ 
    getDropInfo: function(target, item)
    {
        item.target = {
            relationTypes: [Ametys.relation.Relation.MOVE, Ametys.relation.Relation.REFERENCE, Ametys.relation.Relation.COPY], 
            targets: {
                id: this._pageMessageType,
                parameters: { ids: [this._pageId] }
            }
        };
    },
    
    /**
     * @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.web.page.tool.PageTool.__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
        {
            this._iframe.getWin().onfocus = Ext.bind(this._onIframeFocus, this);
            
            this._handleZones();
            this._handleLinks();
        }
    },
    
    /**
     * @private
     * Listener when iframe takes the focus.<br/>
     * Force the focus on tool.
     */
    _onIframeFocus: function ()
    {
        // Force focus on tool
        this.focus();
    },
    
    /**
     * @private
     * Prepare the iframe to have the onZoneSelected method
     */
    _handleZones: function()
    {
        this._iframe.getWin().onZoneSelected = Ext.bind(this._onInternalZoneSelected, this);
    },
    
    /**
     * @private
     * Take care of all existing links in the page to wrap onclick event with #_handleLink
     */
    _handleLinks: function()
    {
        var iframe = this._iframe.getEl().dom;
        var links = this._iframe.getWin().document.getElementsByTagName("a");
        for (var a = 0; a < links.length; a++)
        {
            if (links[a].getAttribute("internal") == null && links[a].href && links[a].href.length > 0)
            {
                if (links[a].onclick)
                {
                    links[a].onclickold = links[a].onclick;
                }
                links[a].onclick = Ext.bind(this._handleLink, this, [links[a]], 0);
            }
        }
    },
    
    /**
     * @private
     * Listener for the internal frame, when a zone is selected
     */
    _onInternalZoneSelected: function(zoneName, zoneItemId, zoneWidth)
    {
        this._iframe.getWin().focus();
        
        if (!this._isOutOfSite)
        {
            this._lastZoneName = zoneName;
            this._lastZoneItemId = zoneItemId;
            this._lastZoneWidth = zoneWidth;
        }
        
        this.sendCurrentSelection();
    },
    
    /**
     * @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
     * @param {Event} event The browser event
     */
    _handleLink: function(link, event)
    {
        this._ctrlPressedOnLastLink = (new Ext.event.Event(event)).ctrlKey;
        
        if (link.onclickold)
        {
            var ret = link.onclickold();
            if (ret === false)
            {
                return false;
            }
        }
        
        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;

        // Pagelink
        if (relHref != null && /^\/([^\/]*)\/([a-z]{2})\/(.*)\.html$/.test(relHref))
        {
            // We have a path but want an id
            
            var siteName = RegExp.$1, 
                lang = RegExp.$2,
                path = RegExp.$3;
            
            Ametys.data.ServerComm.callMethod({
                role: "org.ametys.web.repository.page.SitemapDAO",
                methodName: 'convertPathToId',
                parameters: [siteName, lang, path],
                callback: {
                    handler: this._getPathCb,
                    scope: this,
                    arguments: {
                        siteName: siteName,
                        lang: lang,
                        path: path
                    }
                },
                errorMessage: "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_CONVERTPATHTOID_ERROR}}"
            });
            
            return false;
        }
        
        // Unknown link : open in a new window
        window.open(absHref);
        return false;
    },
    
    /**
     * @private
     * Function called after checking if the page with this path exist in sitemap
     * @param {String} pageId The page id or null if not found
     * @param {Object} args The callback arguments
     */
    _getPathCb: function (pageId, args)
    {
        if (pageId != null)
        {
            // Existing id: we open the page
            Ametys.tool.ToolsManager.openTool ("uitool-page", {id: pageId});
        }
        else
        {
            var detailMsg = "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_CONVERTPATHTOID_ERROR_NOPAGE_SITENAME}}" + args.siteName
                            + "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_CONVERTPATHTOID_ERROR_NOPAGE_LANG}}" + args.lang;
            
            Ametys.log.ErrorDialog.display({
                title: "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_CONVERTPATHTOID_ERROR_NOPAGE_TITLE}}", 
                text: "{{i18n PLUGINS_WEB_TOOL_PAGE_PAGETOOL_CONVERTPATHTOID_ERROR_NOPAGE_TEXT}}",
                details: detailMsg,
                category: this.self.getName()
            });
        }
    },
    
    /**
     * Listener on {@link Ametys.message.Message#MODIFIED} message. If the current page is concerned, the tool will be out-of-date.
     * @param {Ametys.message.Message} message The edited message.
     * @protected
     */
    _onModified: function (message)
    {
        var me = this;
        var pageTargets = message.getTargets(function (target) {return me._pageMessageType == target.getId();});
        
        for (var i=0; i < pageTargets.length; i++)
        {
            if (this._pageId == pageTargets[i].getParameters().id)
            {
                if (pageTargets[i].getParameters()['zone-name'] !== undefined)
                {
                    // The modification concerned of a zone or a zone item of the page
                    var selectionWillChange = false;
                    if (this._lastZoneName != pageTargets[i].getParameters()['zone-name'])
                    {
                        this._lastZoneName = pageTargets[i].getParameters()['zone-name'];
                        selectionWillChange = true;
                    }
                    if (this._lastZoneItemId != pageTargets[i].getParameters()['zoneitem-id'])
                    {
                        this._lastZoneItemId = pageTargets[i].getParameters()['zoneitem-id'];
                        selectionWillChange = true;
                    }
                    
                    if (this._lastPageType != pageTargets[i].getParameters()['type'])
                    {
                        this._lastPageType = pageTargets[i].getParameters()['type'];
                        selectionWillChange = true;
                    }
                    
                    if (selectionWillChange)
                    {
                        this.sendCurrentSelection();
                    }
                }

                this.showOutOfDate();
            }
        }
        
        var contentTargets = message.getTargets(Ametys.message.MessageTarget.CONTENT);
        for (var i=0; i < contentTargets.length; i++)
        {
            if (this._contentIds && Ext.Array.contains (this._contentIds, contentTargets[i].getParameters().id))
            {
                this.showOutOfDate();
            }
        }
    }
});