// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

/**
 * By default, checkboxes in ExtJS cannot have a validator. We add a validate function
 * to checkboxes so that it is possible to add a validator function to checkboxes. This
 * feature is used below in the allowOnlyIfGroupFilledValidator method.
 *
 * @returns boolean
 */
Ext.form.Checkbox.prototype.validate = function () {
    var validationResult = true;

    if (this.validator) {
        validationResult = this.validator();

        if (validationResult === true) {
            Ext.form.Field.prototype.clearInvalid.call(this); // use implementation from Field as function is deactivated for Checkbox
            if (this.boxLabelEl) {
                this.boxLabelEl.removeCls('x-form-invalid-field');
                this.boxLabelEl.dom.removeAttribute('data-errorqtip');
            }
        } else {
            Ext.form.Field.prototype.markInvalid.call(this, validationResult);  // use implementation from Field as function is deactivated for Checkbox
            if (this.boxLabelEl) {
                this.boxLabelEl.addCls('x-form-invalid-field');
                this.boxLabelEl.dom.setAttribute('data-errorqtip', validationResult);
            }
        }
    }

    return validationResult === true;
};

/**
 * The dispatch service provider configuration form panel, which is displayed in the window that is
 * accessible via the button in the plugin description in the plugin manager.
 */
// {namespace name="backend/viison_shipping_common_config/config"}
Ext.define('Shopware.apps.ViisonShippingCommonConfig.view.Panel', {

    /**
     * Extend from the standard ExtJS 4.
     */
    extend: 'Ext.form.Panel',

    xtype: 'viison-shipping-common-config-panel',

    commonSnippets: {
        countryNames: {
            AT: '{s name="countryNames/AT"}{/s}',
            BE: '{s name="countryNames/BE"}{/s}',
            BG: '{s name="countryNames/BG"}{/s}',
            CH: '{s name="countryNames/CH"}{/s}',
            CY: '{s name="countryNames/CY"}{/s}',
            CZ: '{s name="countryNames/CZ"}{/s}',
            DE: '{s name="countryNames/DE"}{/s}',
            DK: '{s name="countryNames/DK"}{/s}',
            EE: '{s name="countryNames/EE"}{/s}',
            ES: '{s name="countryNames/ES"}{/s}',
            FI: '{s name="countryNames/FI"}{/s}',
            FR: '{s name="countryNames/FR"}{/s}',
            GB: '{s name="countryNames/GB"}{/s}',
            GR: '{s name="countryNames/GR"}{/s}',
            HU: '{s name="countryNames/HU"}{/s}',
            HR: '{s name="countryNames/HR"}{/s}',
            IE: '{s name="countryNames/IE"}{/s}',
            IT: '{s name="countryNames/IT"}{/s}',
            LT: '{s name="countryNames/LT"}{/s}',
            LU: '{s name="countryNames/LU"}{/s}',
            LV: '{s name="countryNames/LV"}{/s}',
            NL: '{s name="countryNames/NL"}{/s}',
            PT: '{s name="countryNames/PT"}{/s}',
            PL: '{s name="countryNames/PL"}{/s}',
            RO: '{s name="countryNames/RO"}{/s}',
            SE: '{s name="countryNames/SE"}{/s}',
            SI: '{s name="countryNames/SI"}{/s}',
            SK: '{s name="countryNames/SK"}{/s}',
        },

        items: {
            salutation: {
                title: '{s name="panel/salutation/title"}{/s}',
                label: '{s name="panel/salutation/label"}{/s}',
                help: '{s name="panel/salutation/help"}{/s}',
            },
            cashOnDeliveryPaymentMeans: {
                title: '{s name="panel/cash_on_delivery_payment_means/title"}{/s}',
                label: '{s name="panel/cash_on_delivery_payment_means/label"}{/s}',
                help: '{s name="panel/cash_on_delivery_payment_means/help"}{/s}',
            },
            printerRowFormat: {
                title: '{s name="panel/items/printer_row_format/title"}{/s}',
                label: '{s name="panel/items/printer_row_format/label"}{/s}',
                help: '{s name="panel/items/printer_row_format/help"}{/s}',
            },
        },
    },

    /**
     * Activates the config fields that are defined and used in most Adapters
     * Adapters can easily override this property's
     *
     * LEGEND:
     *  activate - if the fields should be rendered on the UI, (every adapter can override this field)
     *  insertAfterSection - set name of the section where we want to render the fields, default is at the end of the panel
     *  createFunctionCallback - function to get/create the fields
     *  snippets - Snippets to be used in the component
     */
    sharedConfigFields: {},

    paymentStore: null,

    /**
     * The default initializer
     */
    initComponent: function () {
        // Create store for incoterms
        this.incotermData = [
            ['-'],
            ['EXW'],
            ['FCA'],
            ['CPT'],
            ['CIP'],
            ['DAT'],
            ['DAP'],
            ['DDP'],
        ];

        // Create shared config fields
        this.sharedConfigFields = {
            salutationSection: {
                activate: true,
                insertAfterSection: null,
                createFunctionCallback: this.getSalutationSection,
                snippets: this.commonSnippets.items.salutation,
            },
            cashOnDeliveryPaymentMeansSection: {
                activate: false,
                insertAfterSection: null,
                createFunctionCallback: this.getCashOnDeliveryPaymentsMeans,
                snippets: this.commonSnippets.items.cashOnDeliveryPaymentMeans,
            },
        };

        // Load/Create Shopware payment means store
        this.paymentStore = (Ext.getStore('base.Payment') || Ext.create('Shopware.apps.Base.store.Payment')).load();

        this.callParent(arguments);

        // On every setValues() call on the form the originalValue of fields is updated. This is needed to detect
        // changes properly.
        this.getForm().trackResetOnLoad = true;
    },

    /**
     * @param { Ext.data.Model } shop
     * @returns { Array }
     */
    getConfigItemsForShop: function (shop) {
        // Combines shared config fields across Adapters with specific adapter fields
        var configItems = this.concatConfigFieldsItemsWithCommonItems(this.createItems(shop));

        return this.postProcessItems(shop, configItems);
    },

    /**
     * Add custom listener and modify the name of the items.
     *
     * @param { Shopware.apps.Base.model.Shop } shop
     * @param { Array } items
     * @returns { Array }
     */
    postProcessItems: function (shop, items) {
        var me = this;

        // Prefix the item names with the shopId. The shop id and the original name
        // are also added as properties to the individual items for convenience.
        var prefixedItems = [];
        Ext.each(items, function (item) {
            item = Ext.applyIf({
                shopId: shop.getId(),
            }, item);
            if (item.name) {
                // if a change listener already exists execute both functions and do not overwrite it
                if (!item.listeners) {
                    item.listeners = {};
                }
                if (item.listeners.change) {
                    var scope = item.listeners.scope;
                    var oldChangeFunction = item.listeners.change;
                    delete item.listeners.scope;
                    item.listeners.change = function(field, newValue, oldValue) {
                        var oldChangeFunctionWithScope = oldChangeFunction.bind(scope || this);
                        var fieldChangeFunction = me.fieldChange.bind(this);
                        oldChangeFunctionWithScope(field, newValue, oldValue);
                        fieldChangeFunction(field, newValue, oldValue);
                    };
                } else {
                    item.listeners.change = me.fieldChange;
                }
                // if the value of an item is that of a dummy secret reset the field when focusing it
                item.listeners.focus = function (field) {
                    if (field.value  === '************') {
                        field.setValue('');
                    }
                };
                // if restores the dummy secret if there was no change in the field
                item.listeners.blur = function (field) {
                    if (field.value === '' && field.originalValue  === '************') {
                        field.setValue(field.originalValue);
                    }
                };
                item = Ext.applyIf({
                    origName: item.name,
                    name: me.makeShopQualifiedFieldName(shop.getId(), item.name),
                }, item);
            }
            prefixedItems.push(item);
        });

        return prefixedItems;
    },

    /**
     * Generates a field name for a config field that is qualified with the shop name.
     * @param shopId number - the shop id
     * @param name string - the field name
     * @returns string the field name, qualified with the shop id
     */
    makeShopQualifiedFieldName: function (shopId, name) {
        return 'values[' + shopId + '][' + name + ']';
    },

    /**
     * @param shopStore
     */
    initForm: function (shopStore) {
        this.shopStore = shopStore;

        this.add(this.getItems());
        this.doLayout();
    },

    /**
     * Creates the items that make up the panel.
     * @return { Array }
     */
    getItems: function () {
        var items = [];

        // Create the combo box, attached to the states data store
        this.shopCombobox = Ext.create('Ext.form.field.ComboBox', {
            fieldLabel: '{s name="panel/shop_select/label"}{/s}',
            store: this.shopStore,
            queryMode: 'local',
            displayField: 'name',
            valueField: 'id',
            name: 'shopCombobox',
            editable: false,
            listeners: {
                change: function (combobox, newValue) {
                    var shop = combobox.getStore().getById(newValue);
                    this.fireEvent('shopComboboxChange', this, shop);
                },
                beforeselect: function(shopCombobox, newlySelectedShop) {
                    return this.fireEvent('beforeShopComboboxSelect', this, shopCombobox, newlySelectedShop);
                },
                scope: this,
            },
        });
        items.push({
            xtype: 'toolbar',
            dock: 'top',
            ui: 'shopware-ui',
            cls: 'shopware-toolbar',
            style: {
                borderBottom: '1px solid #a4b5c0 !important',
            },
            items: [
                this.shopCombobox,
            ],
        });

        this.configValuePanel = Ext.create('Ext.panel.Panel', {
            bodyStyle: 'background-color: transparent !important',
            border: false,
            layout: 'fit',
            name: 'configValuePanel',
            items: [],
        });
        items.push(this.configValuePanel);

        return items;
    },

    /**
     * @returns { boolean }
     */
    hasModifiedItems: function () {
        // Exclude the first field as that is the shopCombobox
        var dirtyFields = this.getForm().getFields().items.slice(1).filter(function (field) {
            return field.isDirty();
        });

        return dirtyFields.length > 0;
    },

    /**
     * Combine common config fields and 'Feature' fields. This fields are created from ShippingCommon directly.
     *
     * @param { Array } items Unique Config data
     * @returns { Array } Array combined with common and unique Adapter config fields
     */
    concatConfigFieldsItemsWithCommonItems: function (items) {
        var me = this;
        var sharedConfigFields;
        var configField;

        if (!this.sharedConfigFields) return items;

        // Shared config fields data is initialized in the InitComponent
        sharedConfigFields = this.sharedConfigFields;
        Object.keys(sharedConfigFields).forEach(function (key) {
            if (sharedConfigFields[key].activate) {
                configField = sharedConfigFields[key];
                if (configField.insertAfterSection) {
                    // ** Case if we want to insert the fields after some existing config field

                    // By default 'this' will be bind to the object,
                    // Bind 'me' as 'this' so the function can access the whole class data
                    // Also by sending the snippets we are ensuring that the function is 'Pure',
                    // And so that the logic handling is only triggered from this function
                    var configFieldsArray = (configField.createFunctionCallback.bind(me))(configField.snippets || null);

                    // Get the index after the field we want to insert
                    var indexOfItem = items.map(function (item) { return item.name; }).indexOf(configField.insertAfterSection) + 1;
                    items = items.concat(configFieldsArray, items.splice(indexOfItem, items.length - 1));
                } else {
                    // ** Default, insert fields at the end of the config
                    items = items.concat((configField.createFunctionCallback.bind(me))(configField.snippets || null));
                }
            }
        });

        return items;
    },

    /**
     * Get printer formats
     *
     * @param snippets The Provided snippets, use default ones if not provided
     * @param printerFormats The Provided printer formats, use default ones if not provided
     * @param string dbName The name used for the element naming
     * @return Array
     */
    getPrinterRowFormatSection: function (snippets, printerFormats, dbName) {
        var items = [];

        // Use Default snippets if not provided
        snippets = (snippets) || this.commonSnippets.items.printerRowFormat;

        var title = snippets.title;
        var label = snippets.label;
        var help = snippets.help;

        items.push({
            xtype: 'container',
            margin: '15 0 10 0',
            items: [{
                xtype: 'label',
                text: title,
            }],
        }, {
            xtype: 'combobox',
            name: dbName || 'printerRowFormat',
            fieldLabel: label,
            helpText: help,
            editable: false,
            valueField: 'value',
            displayField: 'text',
            store: Ext.create('Ext.data.SimpleStore', {
                fields: [
                    'text', 'value',
                ],
                data: printerFormats || [
                    ['A6', 'A6'],
                    ['A5', 'A5'],
                ],
            }),
        });

        return items;
    },

    /**
     * Creates and gets the salutation section
     * @returns Array Salutation fields
     */
    getSalutationSection: function (snippets) {
        var items = [];

        var title = '';
        var label = '';
        var help = '';

        if (snippets) {
            title = snippets.title;
            label = snippets.label;
            help = snippets.help;
        }

        items.push({
            xtype: 'container',
            margin: '15 0 10 0',
            items: [{
                xtype: 'label',
                text: title,
            }],
        }, {
            xtype: 'base-element-boolean',
            name: 'isSalutationRequired',
            fieldLabel: label,
            helpText: help,
            checked: true,
        });

        return items;
    },

    /**
     * Create and get fields for payment means used for cash on delivery.
     * These payment means trigger automatically the CashOnDelivery comobox in Order view.
     *
     * @param object snippets, contains title, label and help text
     * @returns Array Of ExtJs fields
     */
    getCashOnDeliveryPaymentsMeans: function (snippets) {
        var items = [];

        items.push({
            xtype: 'container',
            margin: '15 0 10 0',
            items: [{
                xtype: 'label',
                text: snippets.title,
            }],
        }, {
            xtype: 'combobox',
            name: 'cashOnDeliveryPaymentMeansIds',
            fieldLabel: snippets.label,
            helpText: snippets.help,
            valueField: 'id',
            displayField: 'description',
            queryMode: 'local',
            mode: 'local',
            editable: false,
            multiSelect: true,
            store: this.paymentStore,
        });

        return items;
    },

    /**
     * Returns if a field exists that belongs to the given group that is not blank.
     *
     * @param form The form to which the fields belong.
     * @param group The name of the group.
     * @param shopId The shop id.
     * @return boolean
     */
    groupHasNonBlankValues: function (form, group, shopId) {
        var fields = form.query('[group="' + group + '"][shopId="' + shopId + '"]');

        var hasValues = false;

        Ext.each(fields, function (field) {
            if (!this.isFieldBlank(field)) {
                hasValues = true;

                // break loop
                return false;
            }
        }, this);

        return hasValues;
    },

    /**
     * Returns if all fields that belong to the given group are non-blank.
     *
     * @param form The form to which the fields belong.
     * @param group The name of the group.
     * @param shopId The shop id.
     * @return boolean
     */
    groupCompletelyFilled: function (form, group, shopId) {
        var fields = form.query('[group="' + group + '"][shopId="' + shopId + '"]');

        var returnValue = true;

        Ext.each(fields, function (field) {
            if (this.isFieldBlank(field)) {
                returnValue = false;

                // break loop
                return false;
            }
        }, this);

        return returnValue;
    },

    /**
     * Validator function that can be applied to fields. It makes sure that either
     * all or none of the fields that belong to the corresponding group are blank.
     * Group associations can be set using the group property of fields.
     * Dependencies between groups (if one group is filled out, also another group
     * is needed) can be expressed in the groupDependencies object.
     * Fields that belong to a group can be marked as optional by setting their
     * property optionalGroupField to true.
     *
     * @param value
     * @return true|string True in case of success otherwise an error message string that is displayed in the tooltip.
     */
    groupValidator: function (value) {
        var field = this;
        var configPanel = field.up('viison-shipping-common-config-panel');
        var form = field.up('form');

        if (!field.hasOwnProperty('group')) {
            throw new Error('groupValidator(): Group attribute missing for field \'' + field.name + '\'');
        }

        // if there is a field in this group that is not blank, this field must not be blank either
        var allowBlank = !configPanel.groupHasNonBlankValues(form, field.group, field.shopId);

        if (!configPanel.isFieldBlank(field) || field.optionalGroupField) {
            return true;
        } else if (allowBlank) {
            var result = true;
            Ext.each(configPanel.groupDependencies[field.group], function (group) {
                if (configPanel.groupHasNonBlankValues(form, group, field.shopId)) {
                    result = configPanel.snippets.groupDependencyValidationFailed;

                    // exit loop
                    return false;
                }
            });

            return result;
        }

        return configPanel.snippets.groupValidationFailed;
    },

    /**
     * Listener function that is applied to the 'change' event of all fields.
     * Makes sure that all fields that belong to a group are validated if one
     * of its fields is validated.
     * Allows the same mechanism between two individual fields by setting the
     * influencesValidationOf property of one field.
     *
     * @param field
     * @param newValue
     * @param oldValue
     * @param eOpts
     */
    fieldChange: function (field, newValue, oldValue, eOpts) {
        if (field.group) {
            var fields = field.up('form').query('[group="' + field.group + '"][shopId="' + field.shopId + '"]');
            Ext.each(fields, function (otherField) {
                if (otherField.name === field.name) {
                    return;
                }
                otherField.validate();
            });
        }
        if (field.influencesValidationOf) {
            Ext.each(field.influencesValidationOf, function (name) {
                field.up('form').down('[origName="' + name + '"][shopId="' + field.shopId + '"]').validate();
            });
        }
    },

    /**
     * Checks if a field is blank.
     *
     * @param field
     * @return bool
     */
    isFieldBlank: function (field) {
        return field.getValue() === null || field.getValue() === '' || (field.emptyText && field.getValue() === field.emptyText);
    },

    /**
     * Validator function that can be applied to fields. The field this
     * validator function is applied to should also have a checkboxName
     * property. Validation will fail if the given checkbox is checked
     * and the field is blank.
     *
     * @param value
     * @return true|string True in case of success otherwise an error message string that is displayed in the tooltip.
     */
    disallowBlankIfCheckedValidator: function (value) {
        var field = this;
        var configPanel = field.up('viison-shipping-common-config-panel');

        if (!field.hasOwnProperty('checkboxName')) {
            throw new Error('disallowBlankIfCheckedValidator(): checkboxName attribute missing for field \'' + field.name + '\'');
        }

        var checkBox = field.up('form').down('[origName="' + field.checkboxName + '"][shopId="' + field.shopId + '"]');
        var checked = checkBox.getValue();

        var success = !(checked && configPanel.isFieldBlank(field));

        if (success) {
            return true;
        }

        return configPanel.snippets.disallowBlankIfCheckedValidationFailed;
    },

    /**
     * Validator function that can be applied to checkboxes. The field this validator function
     * is applied to should also have a referencedGroup property. Checks if the referenced group
     * is filled and only allows to check the checkbox if the group is completely filled.
     *
     * @param value
     * @return true|string True in case of success otherwise an error message string that is displayed in the tooltip.
     */
    allowOnlyIfGroupFilledValidator: function (value) {
        var field = this;
        var configPanel = field.up('viison-shipping-common-config-panel');
        var form = field.up('form');

        if (!field.hasOwnProperty('referencedGroup')) {
            throw new Error('allowOnlyIfGroupFilledValidator(): referencedGroup attribute missing for field \'' + field.name + '\'');
        }

        var groupFilled = configPanel.groupCompletelyFilled(form, field.referencedGroup, field.shopId);

        var checked = field.getValue();

        if (!checked) {
            return true;
        }

        if (groupFilled) {
            return true;
        }

        return field.validationErrorMessage;
    },

});
