/*
 *  Copyright 2013 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 provides a orderable tag field.<br>
 * The items of the list are not orderable.
 * 
 * To set a simple local store : 
 * 
 *     var field = Ext.create('Ametys.form.OrderableTagField', {
 *         multiSelect: true,
 *         field: ['id', 'label'],
 *         data: [
 *             {id: 0, label: 'Battlestar Galactica'},
 *             {id: 1, label: 'Doctor Who'},
 *             {id: 2, label: 'Farscape'},
 *             {id: 3, label: 'Firefly'},
 *             {id: 4, label: 'Star Trek'},
 *             {id: 5, label: 'Star Wars: Christmas Special'}
 *         ]
 *     });
 *     
 */
Ext.define('Ametys.form.widget.OrderableTagField', {
    extend: 'Ext.form.field.Tag',
    
    xtype: 'orderable-tagfield',
   
    canDisplayComparisons: true,
      
    /**
     * @cfg {Boolean|String} [orderable=true] true if the items can be orderable by drag&drop
     */
    
    /**
     * @cfg {Boolean} [naturalOrder=false] True to sort drop down list by natural order. By default alphabetical order is applied to the store.
     */
    
    /**
     * @cfg {Boolean} [anyMatch=true] True to allow matching of the typed characters at any position in the valueField's value.
     */
    
    /**
     * @cfg {String} noResultText The text when there is no result found.
     */
    noResultText: "{{i18n PLUGINS_CORE_UI_FORM_TAG_FIELD_NO_RESULT}}",
    
    constructor: function(config)
    {
        var storeCfg = {
            fields: config.fields || ([ 'value', {name: 'text', type: 'string'}]),
            data: config.data || []
        };
            
        config.naturalOrder = Ext.isBoolean(config.naturalOrder) ? config.naturalOrder : config.naturalOrder == 'true';
        if (!config.naturalOrder)
        {
            storeCfg.sorters = [{property: config.displayField || 'text', direction:'ASC'}]; // default order
        }
        
        config.anyMatch = Ext.isBoolean(config.anyMatch) ? config.anyMatch : config.anyMatch != "false"; // default to true
        config.orderable = Ext.isBoolean(config.orderable) ? config.orderable : config.orderable != "false"; // default to true
        
        config = Ext.applyIf(config, {
            cls: 'ametys',
            
            mode: 'local',
            queryMode: 'local',
            
            encodeSubmitValue: false,
            typeAhead: true,
            triggerAction: 'all',
            enableKeyEvents: true,
            
            store: new Ext.data.ArrayStore(storeCfg),
            
            displayField: 'text',
            valueField: 'value',
            
            allowBlank: this.allowBlank,
            multiSelect: config.multiSelect,
            
            listConfig: {
                emptyText: '<span class="x-tagfield-noresult-text">' + this.noResultText + '<span>'
            }
        });
        
        this.callParent(arguments);
    },
    
    /**
     * @protected
     * Convert a value to be comparable
     */
    _convertToComparableValue: function(item)
    {
        if (!item)
        {
            return "";
        }
        else if (Ext.isString(item))
        {
            return item;
        }
        else
        {
            return item[this.valueField]; // this is a guess since value are never records
        } 
    }, 
    
    /**
      * When used in readonly mode, settting the comparison value will display ins/del tags
      * @param {String} otherValue The value to compare the current value with
      * @param {boolean} base When true, the value to compare is a base version (old) ; when false it is a future value
      */
    setComparisonValue: function(otherValue, base)
    {
         if (base)
         {
             this._baseValue = otherValue || null;
             this._futureValue = undefined;
         }
         else
         {
             this._baseValue = undefined;
             this._futureValue = otherValue || null;
         }
         this.updateValue();
    },
    
    _updateComparisonRendering: function()
    {
        if (!this.bodyEl)
        {
            return;
        }
        
        if (this._baseValue !== undefined || this._futureValue !== undefined)
        {
            var base = this._baseValue !== undefined;
            
            var comparison = Ametys.form.widget.Comparison.compareCanonicalValues(this.getValue(), base ? this._baseValue : this._futureValue, base, Ext.bind(this._convertToComparableValue, this));
            for (var c = 1; c <= comparison.length; c++)
            {
                var elt = this.bodyEl.query('.x-tagfield-item:nth-child(' + c + ')', false, true);
                if (elt == null)
                {
                    // not really rendered
                    return;
                }
                elt.removeCls(["ametys-tagfield-new", "ametys-tagfield-old", "ametys-tagfield-mod"]);
                switch (comparison[c-1])
                {
                    case "added": elt.addCls("ametys-tagfield-new"); break;
                    case "moved": elt.addCls("ametys-tagfield-mod"); break;
                    case "deleted": elt.addCls("ametys-tagfield-old"); break;
                    default:
                    case "none": break;
                }
            }
        }
    },
    
    afterRender: function()
    {
        this.callParent(arguments);
        
        this._updateComparisonRendering();

        if (this.multiSelect && this.orderable)
        {
            var me = this,
                ddGroup = 'ametys-box-select-' + me.getId();

            new Ext.dd.DragZone(me.listWrapper, { 
                ddGroup: ddGroup,
                
                getDragData: function(e) 
                {
                    var sourceEl = e.getTarget(".x-tagfield-item", 10), d;
                    if (sourceEl) 
                    {
                        d = sourceEl.cloneNode(true);
                        d.id = Ext.id();
                        return (me.dragData = {
                                sourceEl: sourceEl,
                                repairXY: Ext.fly(sourceEl).getXY(),
                                ddel: d,
                                rec: me.getRecordByListItemNode(sourceEl)
                        });
                    }               
                },
                getRepairXY: function() 
                {
                    return me.dragData.repairXY;
                }
            });
            
            new Ext.dd.DropZone(me.listWrapper, { 
                ddGroup: ddGroup,
                
                getTargetFromEvent: function(e) 
                {
                    return e.getTarget('.x-tagfield-item') || e.getTarget('.x-tagfield-input') || e.getTarget('.x-tagfield-list');
                },
                
                onNodeEnter : function(target, dd, e, data)
                {
                    var t = Ext.fly(target);
                    var r = t.getRegion();
                    
                    if (t.hasCls('x-tagfield-item') && e.getX() > r.left + (r.right - r.left) / 2)
                    {
                        t.removeCls('x-tagfield-target-hoverbefore');
                        t.addCls('x-tagfield-target-hoverafter');
                    }
                    else
                    {
                        t.addCls('x-tagfield-target-hoverbefore');
                        t.removeCls('x-tagfield-target-hoverafter');
                    }
                },
                
                onNodeOut : function(target, dd, e, data)
                {
                    Ext.fly(target).removeCls(['x-tagfield-target-hoverbefore', 'x-tagfield-target-hoverafter']);
                },
                
                onNodeOver : function(target, dd, e, data)
                {
                    this.onNodeEnter(target, dd, e, data);
                    
                    return Ext.dd.DropZone.prototype.dropAllowed;
                },
                
                onNodeDrop : function(target, dd, e, data)
                {
                    var targetRecord;
                    var t = Ext.get(target);
                    if (t.hasCls("x-tagfield-item"))
                    {
                        targetRecord = me.getRecordByListItemNode(target)
                    }
                    
                    var currentValue = me.getValue();

                    var movedValue = data.rec.get(me.valueField);
                    var newPosition = targetRecord != null ? currentValue.indexOf(targetRecord.get(me.valueField)) : currentValue.length;
                    var currentPosition = currentValue.indexOf(movedValue);
                    
                    currentValue = Ext.Array.remove(currentValue, movedValue);
                    newPosition += (newPosition <= currentPosition ? 0 : -1) + (newPosition != currentPosition && t.hasCls('x-tagfield-target-hoverafter') ? 1 : 0);
                    currentValue = Ext.Array.insert(currentValue, newPosition, [movedValue]);
                    
                    // This is to avoid setValue to be pointless
                    me.suspendEvents(false);
                    me.setValue(null);
                    me.resumeEvents();
                    
                    me.setValue(currentValue);
                    
                    return true;
                }
            });
        }
    }
    
});