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

/**
 * @private
 * This class provides a table of contents for a {@link Ametys.form.ConfigurableFormPanel}
 */
Ext.define('Ametys.form.ConfigurableFormPanel.TableOfContents', {
    extend: 'Ext.panel.Panel',
    
    alias: 'widget.configurable-form-panel.toc',
	
    statics: {
        /**
         * @property {Number} [DEFAULT_NAVIGATION_ITEM_HEIGHT=40] The default height of a navigation item
         * @private
         * @readonly 
         */
    	DEFAULT_NAVIGATION_ITEM_HEIGHT: 40,
        
         /**
         * @property {Number} [NAVIGATION_ITEM_MAX_WIDTH=350] The default max width of table of contents
         * @private
         * @readonly 
         */
        NAVIGATION_ITEM_MAX_WIDTH: 500,
        
        /**
         * @property {Number} [NAVIGATION_ITEM_MIN_WIDTH=350] The default min width of table of contents
         * @private
         * @readonly 
         */
        NAVIGATION_ITEM_MIN_WIDTH: 150,
        
        /**
         * @property {String} [CSS_LAST_BINDED_FIELDSET="last-binded"] A css classname added to the fieldset that was lastly binded with the table of contents
         * @private
         * @readonly
         */
        CSS_LAST_BINDED_FIELDSET: "last-binded"
    },
    
    /**
     * @cfg {Number} [navItemHeight=Ametys.form.ConfigurableFormPanel.TableOfContents#DEFAULT_NAVIGATION_ITEM_HEIGHT] The height of navigation items in the table of contents.
     */
    
    /**
     * @private
     * @property {Object} _navigationMap the mapping of fieldset ids with their corresponding navigation item ids
     */
    
	/**
	 * @private
	 * @property {Ametys.form.ConfigurableFormPanel} _form the configurable form panel instance attached to this table of contents instance
	 */
    
    /**
     * @private
     * @property {String} _currentFieldsetId the id of the highest visible fieldset of the {@link Ametys.form.ConfigurableFormPanel}
     */
    
    /**
     * @private
     * @property {Boolean} _bound Is the scroll currently bound to the table of contents ?
     */
    
    /**
     * @private
     * @property {Boolean} _outOfTabsFieldset true if there is an out of tabs fieldset
     */
    
    /**
     * @private
     * @property {Ext.fx.Anim/Boolean} _animation the current animation if this object has any effects actively running or queued, false otherwise
     */
    
    /**
     * @private
     * @property {Boolean} _scrollingHandled True if the scrolling of the form is currently handled, false otherwise
     */
    
	constructor: function(config)
	{
        config = Ext.applyIf (config, {
            scrollable: 'vertical',
            
            maxWidth: Ametys.form.ConfigurableFormPanel.TableOfContents.NAVIGATION_ITEM_MAX_WIDTH,
            minWidth: Ametys.form.ConfigurableFormPanel.TableOfContents.NAVIGATION_ITEM_MIN_WIDTH,
            
            defaults: {
                xtype: 'button',
                textAlign: 'left',
                border: false,
                enableToggle: true,
                toggleGroup: Ext.id() + '-toc',
                flex: 1,
                minHeight: (config.navItemHeight || Ametys.form.ConfigurableFormPanel.TableOfContents.DEFAULT_NAVIGATION_ITEM_HEIGHT) / 2,
                maxHeight: config.navItemHeight || Ametys.form.ConfigurableFormPanel.TableOfContents.DEFAULT_NAVIGATION_ITEM_HEIGHT
            },
            
            layout: {
                type: 'vbox',
                align: 'stretch'
            },
            border: false,
            shadow: false,
            
            cls: 'a-configurable-form-panel-toc',
            
            header: {
                title: '{{i18n PLUGINS_CORE_UI_CONFIGURABLE_FORM_TABLE_OF_CONTENTS_TITLE}}',
                height: 50
            }
        });
        
		this._navigationMap = {};
		this._bound = true;
		this._scrollingHandled = false;
		
		this.callParent(arguments);
        
        this.form.on('formready', this._initializeListeners, this, {single: true});
	},
	
	/**
	 * Set up the listeners on the scroll and select the current navigation item
	 */
	_initializeListeners: function()
	{
		this.form.getFormContainer().on('afterlayout', this._updateScrollPosition, this, {single: true});
	},
	
	/**
	 * Add a new navigation item to the table of contents
	 * @param {String} label the label of the thumbnail to create
	 * @param {String} fieldsetId the id of the corresponding fieldset
	 */
	addNavigationItem: function(label, fieldsetId)
	{
		var navigationItemId = Ext.id();
		var navigationItemCfg = 
		{
			id: navigationItemId, 
			text : label, 
			handler: Ext.bind(this._scrollToFieldset, this, [fieldsetId], 1)
		};
		
		if (fieldsetId != Ametys.form.ConfigurableFormPanel.OUTOFTAB_FIELDSET_ID)
		{
			// Store the mapping
			this._navigationMap[fieldsetId] = navigationItemId;
			this.add(navigationItemCfg);
		}
		else
		{
			// The out of tabs fieldset goes first
			this._outOfTabsFieldset = true;
			this.insert(0, navigationItemCfg);
		}
	},
	
	/**
	 * Get the navigation item corresponding to the given fieldset id
	 * @param {String} fieldsetId the id of the fieldset
	 * @return {Ext.Component} the navigation item component
	 */
	getNavigationItem: function(fieldsetId)
	{
		if (fieldsetId == Ametys.form.ConfigurableFormPanel.OUTOFTAB_FIELDSET_ID)
		{
			return this.items.get(0);
		}
		else
		{
			return Ext.getCmp(this._navigationMap[fieldsetId]);
		}
	},
	
	/**
	 * @private
	 * Scroll to a fieldset of the attached {@link Ametys.form.ConfigurableFormPanel}
     * @param {Ext.button.Button} button the button corresponding to the fieldset
	 * @param {String} fieldsetId the id of the fieldset
	 */
	_scrollToFieldset: function(button, fieldsetId)
	{
        button.toggle(true);
        
		this._bound = false;
		
		var formContainer = this.form.getFormContainer();
        var lastBinded;
        if (lastBinded = Ext.dom.Query.selectNode("." + Ametys.form.ConfigurableFormPanel.TableOfContents.CSS_LAST_BINDED_FIELDSET, formContainer.getEl().dom))
        {
            Ext.get(lastBinded).removeCls(Ametys.form.ConfigurableFormPanel.TableOfContents.CSS_LAST_BINDED_FIELDSET);
        }
        
		if (fieldsetId == Ametys.form.ConfigurableFormPanel.OUTOFTAB_FIELDSET_ID)
		{
			formContainer.scrollTo(0, 0, {callback: this._bindScroll, scope: this, duration: 500});
		}
		else
		{
    		var fieldset = Ext.getCmp(fieldsetId);
            fieldset.getEl().addCls(Ametys.form.ConfigurableFormPanel.TableOfContents.CSS_LAST_BINDED_FIELDSET);
			
			var newTop = fieldset.getPosition()[1]; 
			var formTop = formContainer.getPosition()[1];
			
			formContainer.scrollBy(0, newTop - formTop, {callback: this._bindScroll, scope: this, duration: 500});
		}
		
		if  (this._currentFieldsetId != fieldsetId)
		{
			this._activateNavigationItem(fieldsetId);
		}
	},
	
	/**
	 * @private
	 * Bind the scroll to the table of contents
	 */
	_bindScroll: function()
	{
		this._bound = true;
	},
	
	/**
	 * @private
	 * Activates the correct navigation item relatively to the current scroll position 
	 */
	_updateScrollPosition: function ()
	{
		// Make sure the scrolling is listened to after the form is displayed
		if (!this._scrollingHandled)
		{
			this._scrollingHandled = true;
            this.form.getFormContainer().mon(this.form.getFormContainer().getEl(), 'scroll', Ext.bind(this._updateScrollPosition, this));
		}
		
		// Wait for the scroll by click on a navigation item to finish before updating the scroll position
		if (!this._bound || this._animation)
		{
			this._animation = this.form.getFormContainer().getActiveAnimation();
			return;
		}

		var withinTabsHeight = 0; // The summed up height of all tabs
		
		Ext.Object.each(this._navigationMap, function(fieldsetId) {
            var field = Ext.getCmp(fieldsetId);
            if (field)
            {
    			var fieldsetHeight = field.getEl().getHeight();
    			withinTabsHeight += fieldsetHeight;
            }
		}, this);
		
		var formContainer = this.form.getFormContainer();
		var e = formContainer.getEl();
		var max = e.dom.scrollHeight - e.getHeight();
		
		var scrollPosition = e.dom.scrollTop;
		var scrollRatio = scrollPosition / max;
		
		var p = scrollRatio * this.form.body.getHeight();
		
		var outOfTabsHeight = 0;
		// Handle the out of tabs fieldset separately
		if (this._outOfTabsFieldset)
		{
			outOfTabsHeight = formContainer.getEl().dom.scrollHeight - withinTabsHeight;
			if (outOfTabsHeight > scrollPosition + p)
			{
				this._activateNavigationItem(Ametys.form.ConfigurableFormPanel.OUTOFTAB_FIELDSET_ID);
				return;
			}
		}
		
		// Within tabs
		var fieldsetIds = Ext.Object.getKeys(this._navigationMap);
        var field = Ext.get(fieldsetIds[0]);
        if (field)
        {
    		var a0 = field.getTop() - outOfTabsHeight;
    		
    		for (var i = 0; i < fieldsetIds.length; i++)
    		{
    			var fieldsetId = fieldsetIds[i];
    			if (i > 0)
    			{
    				last = fieldsetIds[i - 1];
    			}
    			else
    			{
    				last = fieldsetId;
    			}
    			
    			var posY = Ext.get(fieldsetId).getTop() - a0;
    			if (posY >= scrollPosition + p)
    			{
    				this._activateNavigationItem(last);
    				return;
    			}
    		}
    		
    		this._activateNavigationItem(fieldsetIds[fieldsetIds.length - 1]);
        }
	},
	
	/**
	 * @private
	 * Visually activate the current navigation item
	 * @param {String} fieldsetId the id of the fieldset to activate
	 */
	_activateNavigationItem: function(fieldsetId)
	{
		var currentItem = Ext.getCmp(this._navigationMap[fieldsetId]) || this.items.get(0);
		currentItem.toggle(true);
		
		if (this._currentFieldsetId)
		{
			var previousItem = Ext.getCmp(this._navigationMap[this._fieldsetId]) || this.items.get(0);
			
			var previousTop = previousItem.getPosition()[1];
			var currentTop = currentItem.getPosition()[1]; 
			var gap = currentTop - previousTop; 
			
			if (currentItem != this.items.get(0))
			{
				gap = gap > 0 ? gap - this.self.DEFAULT_NAVIGATION_ITEM_HEIGHT : gap + this.self.DEFAULT_NAVIGATION_ITEM_HEIGHT;
			}
			
			this.scrollTo(0, gap);
		}
		
		this._currentFieldsetId = fieldsetId;
	}
});