/*
 *  Copyright 2015 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 widget to define a filter based on available metadata.
 */
Ext.define('Ametys.cms.form.widget.SelectMetadataToFilterBy', {
    extend: 'Ametys.form.AbstractFieldsWrapper',
    
    /**
     * @cfg {String} [metadataSetName=main] The desired metadataset name
     */
    metadataSetName: 'main',
    
    /**
     * @cfg {String} [contentTypesField] The relative path the content types
     *      field, to allow the list of metadata to be updated given the
     *      value of the content types field
     */
    /**
     * @private
     * @property {String} _contentTypesFieldName The property related to {@link #cfg-contentTypesField}
     */
    /**
     * @cfg {String} [metadataSetField] The relative path the content field, to
     *      allow the list of metadata to be updated given the value of the
     *      metadata set field
     */
    /**
     * @private
     * @property {String} _metadataSetFieldName The property related to {@link #cfg-metadataSetField}
     */
    
    /**
     * @private
     * @property {Object} _filters The available filters.
     */
    _filters: {
        NONE: '',
        EQUAL: 'ft-eq',
        DIFFERENT: 'ft-neq',
        STARTSWITH: 'ft-sw',
        ENDSWITH: 'ft-ew',
        CONTAINS: 'ft-co',
        NOTCONTAINS: 'ft-nco',
        EMPTY: 'ft-em',
        NONEMPTY: 'ft-nem',
        LESSTHAN: 'ft-lt',
        GREATERTHAN: 'ft-gt',
        PAST: 'ft-past',
        FUTURE: 'ft-future',
        TRUE: 'ft-true',
        FALSE: 'ft-false'
    },
    
    /**
     * @private
     * @property {Ext.data.Store} _filterStore The local store holding current filter for each metadata
     */
    
    initComponent: function() 
    {
        this.cls = 'x-form-selectmetadatafilter-widget';
        
        this._contentTypesFieldName = this.contentTypesField || null;
        this._metadataSetFieldName = this.metadataSetField || null;
        
        this._filterStore = Ext.create('Ext.data.ArrayStore', this.getFilterStoreCfg());
        
        this.items = [{
            xtype: 'container',
            itemId: 'container',
            width: '100%',
            maxHeight: 200,
            scrollable: true,
            bodyPadding: '7 7 0 7',
            defaults : {
                cls: 'ametys',
                labelAlign: 'left',
                labelPad: 5,
                labelSeparator: '',
                msgTarget: 'side',
                anchor: '100%'
            }
        }];
        
        this.callParent();
        
        this.form.onRelativeFieldsChange([this._contentTypesFieldName, this._metadataSetFieldName], this, this._relativeFieldChange);
    },
    
    /**
     * Get the store configuration
     * @param {Object} config The current configuration object
     * @return {Object} The store configuration
     */
    getFilterStoreCfg: function(config)
    {
        return {
            type: 'store', // Ext.data.Store
            proxy: {
                type: 'ametys',
                plugin: 'cms',
                url: 'common-attributes.json',
                reader: {
                    type: 'json',
                    rootProperty: 'attributes'
                },
                extraParams: {
                    acceptedTypes: ['DATE', 'DATETIME'], // FIXME currently only available for date and datetime metadata
                    withLastValidation: true,
                    withFullLabel: true
                }
            },
            fields: [
                 {name: 'id', mapping: 'name'},
                 {name: 'name', mapping: 'name'},
                 {name: 'label', mapping: 'label',},
                 {name: 'fullLabel', mapping: 'fullLabel', type: 'string'},
                 {name: 'type', mapping: 'type'}
            ],
            sortOnLoad: true,
            sorters: [{property: 'label', direction:'ASC'}],
            listeners: {
                beforeload: {fn: this._onFilterStoreBeforeLoad, scope: this},
                load: {fn: this._onFilterStoreLoad, scope: this}
            }
        };
    },
    
    /**
     * Listener called when the value of a relative field changes
     */
    _relativeFieldChange: function()
    {
        this._filterStore.load();
    },
    
    /**
     * Set the request parameters before loading the store.
     * @param {Ext.data.Store} store The store.
     * @param {Ext.data.operation.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to load the Store.
     * @private
     */
    _onFilterStoreBeforeLoad: function(store, operation)
    {
        var relativeFields = this.form.getRelativeFields([this._contentTypesFieldName, this._metadataSetFieldName], this),
            contentTypesField = relativeFields[0],
            metadataSetField = relativeFields[1];
        
        var params = operation.getParams() || {};
        operation.setParams(Ext.apply(params, {
            ids: contentTypesField ? contentTypesField.getValue() : null,
            viewName: metadataSetField ? metadataSetField.getValue() : this.metadataSetName
        }));
    },
    
    /**
     * Set the value after the store load
     * @param {Ext.data.Store} store The store.
     * @param {Ext.data.Model[]} records The loaded records.
     * @private
     */
    _onFilterStoreLoad: function(store, records)
    {
        var form = this.getComponent('container'),
            fieldsToKeep = [], recordsToInsert = [];
        
        var addedOrRemoved = false,
            field, name; 
        
        // Supsend layouts between field addition/removal
        Ext.suspendLayouts();
        
        this._filterStore.each(function(record) {
            name = record.get('name');
            field = form.getComponent(name);
            if (!field)
            {
                recordsToInsert.push(record);
                addedOrRemoved = true;
            }
            fieldsToKeep.push(name);
        }, this);
        
        var fieldsToRemove = form.items.filterBy(function(field) {
            return !Ext.Array.contains(fieldsToKeep, field.getItemId());
        });
        
        addedOrRemoved = addedOrRemoved || fieldsToRemove.getCount() > 0;
        
        fieldsToRemove.each(function(field) {
            form.remove(field, true);
        });
        
        // Iterates in sort order to keep field sorted.
        var index;
        Ext.Array.forEach(recordsToInsert, function(record) {
            index = this._filterStore.indexOf(record);
            form.insert(index, this._getFilterComboboxCfg(record));
        }, this);
        
        // Resume layouts
        Ext.resumeLayouts(addedOrRemoved);
        
        if (this._valuesToSet)
        {
            var valuesSet = [];
            
            Ext.Object.each(this._valuesToSet, function(key, value) {
                field = form.getComponent(key);
                
                if (field)
                {
                    field.setValue(value);
                    valuesSet.push(key);
                }
            });
            
            Ext.Array.forEach(valuesSet, function(name) {
                delete this._valuesToSet[name]; // remove from the values to set
            }, this);
        }
    },
    
    /**
     * Get the configuration of a filter combobox
     * @param {Ext.data.Model} metadata The metadata corresponding to the filter to create
     */
    _getFilterComboboxCfg: function(metadata)
    {
        return {
            xtype: 'combobox',
            name: metadata.get('name'),
            itemId: metadata.get('name'),
            fieldLabel: metadata.get('label'),
            editable: false,
            forceSelection: true,
            valueField: 'name',
            displayField: 'label',
            queryMode: 'local',
            value: '',
            store: {
                type: 'array', // Ext.data.ArrayStore
                fields: ['name', 'label'],
                data: this._getLocalFilterData(metadata.get('type'))
            }
        };
    },
    
    /**
     * Retrieves filter data for a given metadata type
     * @param {String} type The metadata type
     */
    _getLocalFilterData: function(type)
    {
        switch (type.toLowerCase())
        {
            case 'string': 
                return this._createComboboxFilterData(
                    ['NONE', 'EQUAL', 'DIFFERENT', 'STARTSWITH', 'ENDSWITH', 'CONTAINS', 'NOTCONTAINS']
                );
            case 'double':
            case 'long':
                return this._createComboboxFilterData(
                    ['NONE', 'LESSTHAN', 'GREATERTHAN']
                );
            case 'date': 
            case 'datetime': 
                return this._createComboboxFilterData(
                    ['NONE', 'PAST', 'FUTURE']
                );
            case 'boolean':
                return this._createComboboxFilterData(
                    ['NONE', 'TRUE', 'FALSE']
                );
            default:
            // ie.
            // case 'rich-text':
            // case 'binary':
            // case 'password':
                return this._createComboboxFilterData(
                    ['NONE', 'EMPTY', 'NONEMPTY']
                );
        }
    },
    
    /**
     * Create the desired filter data given an array of filter names
     * @param {String[]} filterNames ordered array of filter name
     * @return {String[][]} A bidimensionnal array that can be used as data for a local combobox store
     */
    _createComboboxFilterData: function(filterNames)
    {
        return Ext.Array.map(filterNames, function(name) {
            return [this._filters[name], this._getFilterLabel(name)];
        }, this);
    },
    
    /**
     * @private
     * Get a label associated to a filter value
     * @param {String} filterName A filter name such as 'NONE', 'EQUAL'...
     * @return {String} The label of an existing filter name or undefined
     */
    _getFilterLabel: function(filterName)
    {
        switch (filterName)
        {
            case 'NONE':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_NONE}}";
            case 'EQUAL':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_EQUAL}}";
            case 'DIFFERENT':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_DIFFERENT}}";
            case 'STARTSWITH':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_STARTSWITH}}";
            case 'ENDSWITH':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_ENDSWITH}}";
            case 'CONTAINS':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_CONTAINS}}";
            case 'NOTCONTAINS':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_NOTCONTAINS}}";
            case 'EMPTY':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_EMPTY}}";
            case 'NONEMPTY':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_NONEMPTY}}";
            case 'LESSTHAN':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_LESSTHAN}}";
            case 'GREATERTHAN':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_GREATERTHAN}}";
            case 'PAST':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_PAST}}";
            case 'FUTURE':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_FUTURE}}";
            case 'TRUE':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_TRUE}}";
            case 'FALSE':
                return "{{i18n PLUGINS_CMS_WIDGET_SELECTMETADATATOFILTERBY_FILTER_FALSE}}";
            default:
                return undefined;
        }
    },
    
    getValue: function()
    {
        return Ext.JSON.encode(this.getValuesAsObject());
    },
    
    /**
     * Returns the current value of the widget as an object.
     * @return {Object/Object[]} Each object represents an entry. Object keys are: 'name', the metadata name and 'filter', the filter name
     */
    getValuesAsObject: function ()
    {
        var form = this.getComponent('container'),
            values = {}, value, record;
        
        form.items.each(function(field) {
            value = field.getValue();
            if (value)
            {
                record = this._filterStore.findRecord('name', field.getItemId());
                if (record && record.get('type') == 'STRING')
                {
                    // lowercase for string filters
                    value += '_LC';
                }
                
                values[field.getName()] = value;
            }
        }, this);
        
        return values;
    },
    
    setValue: function (value)
    {
        // retrieves the value object
        value = value || null;
        if (value)
        {
            if (Ext.isString(value))
            {
                value = Ext.JSON.decode(value);
            }
            
            // value should be an object at this point
            if (!Ext.isObject(value))
            {
                value = null;
            }
        }
        
        if (value)
        {
            var form = this.getComponent('container'),
                field;
            
            Ext.Object.each(value, function(key, value) {
                field = form.getComponent(key);
                
                if (Ext.String.endsWith(value, '_LC'))
                {
                    value = value.slice(0, -3); // remove the last 3 characters, ie. the '_LC' part.
                }
                
                if (field)
                {
                    field.setValue(value);
                }
                else
                {
                    // Field might not currently exists. The value will be set
                    // once the corresponding field is created
                    this._valuesToSet = this._valuesToSet || {};
                    this._valuesToSet[key] = value;
                }
            }, this);
        }
    },
    
    getSubmitData: function() {
        var data = null;
            
        if (!this.disabled && this.submitValue)
        {
            data = {};
            data[this.getName()] = this.getValue();
        }
        
        return data;
    },
    
    getErrors: function (value) 
    {
        var form = this.getComponent('container'), 
            errors = this.callParent(arguments);
        
        form.items.each(function(field) {
            errors = errors.concat(field.getErrors());
        });

        return errors;
    }
});