// 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.

/**
 * Assignment Panel to implement drag'n'drop assignment for set articles
 */
Ext.define('Shopware.apps.ViisonSetArticlesArticleSetArticlesTab.view.detail.AssignmentPanel', {

    extend: 'Ext.panel.Panel',
    alias: 'widget.viison_set_articles_article-view-detail-assignment_panel',
    cls: 'viison-set-articles-tab',
    mixins: [
        'Shopware.apps.ViisonCommonApp.Mixin',
    ],
    viisonSnippetNamespace: 'backend/viison_set_articles_article_set_articles_tab/assignment_panel',

    layout: {
        type: 'hbox',
        align: 'stretch',
    },
    flex: 1,
    style: {
        borderWidth: '0px',
    },
    defaults: {
        border: 0,
    },

    /**
     * @Override
     */
    initComponent: function () {
        this.currentArticleDetailId = 0;
        this.currentPrice = 0;
        this.filterWord = '';
        this.setArticleStores = [];

        this.articleListStore = Ext.create('Shopware.apps.ViisonSetArticlesArticleSetArticlesTab.store.Article', {});
        this.articlePanel = this.createArticlePanel();

        this.setArticlePanel = this.createSetArticlePanel();

        // Add both grid panels as well as a panel containing the assignment buttons
        this.items = [
            this.createArticleContainer(),
            {
                xtype: 'panel',
                width: 32,
                border: 1,
                layout: {
                    type: 'vbox',
                    pack: 'center',
                },
                bodyStyle: {
                    backgroundColor: '#ebedef',
                },
                items: this.createMiddleButtons(),
            },
            this.createSetArticleContainer(),
        ];

        this.on('afterrender', this.onAfterRender, this);
        this.callParent(arguments);
    },

    /**
     * Load stores after tab has been rendered
     */
    onAfterRender: function () {
        this.articleListStore.load();
    },

    /**
     * Creates a container panel containing the left Article grid panel.
     *
     * @return Ext.grid.Panel
     */
    createArticleContainer: function () {
        return {
            xtype: 'panel',
            flex: 1,
            layout: 'fit',
            border: false,
            items: [
                this.articlePanel,
            ],
        };
    },

    /**
     * Creates the grid panel that is used for the left Article side.
     *
     * @return Ext.grid.Panel
     */
    createArticlePanel: function () {
        return Ext.create('Ext.grid.Panel', this.getArticlePanelConfig());
    },

    /**
     * Creates and returns an object containing the configuration for the left Article grid panel.
     *
     * @return Object
     */
    getArticlePanelConfig: function () {
        return {
            cls: 'viison-common--grid has--vertical-lines has--no-borders',
            flex: 1,
            border: false,
            stripeRows: true,
            multiSelect: true,
            hideHeaders: false,
            header: false,
            viewConfig: {
                plugins: {
                    ptype: 'gridviewdragdrop',
                    dragGroup: ('leftGridDDGroup-' + this.id),
                    dropGroup: ('rightGridDDGroup-' + this.id),
                },
                listeners: {
                    /**
                     * Remark: since the D&D plugin does not work properly
                     * the data is transfered manually by using the button-pressed-method
                     *
                     * @param node
                     * @param data
                     * @param overModel
                     * @param dropPosition
                     * @param dropFunction
                     */
                    beforedrop: function (node, data, overModel, dropPosition, dropFunction) {
                        dropFunction.cancelDrop();
                        this.onButtonRemovedPressed();
                    },
                    scope: this,
                },
            },
            columns: this.createArticleColumns(),
            store: this.articleListStore,
            dockedItems: [
                this.createSearchToolbar(this.articleListStore),
                {
                    xtype: 'viison_common_pagination_toolbar-toolbar',
                    store: this.articleListStore,
                },
            ],
        };
    },

    /**
     * @return Ext.grid.column.Column[]
     */
    createArticleColumns: function () {
        return [
            {
                text: this.getViisonSnippet('tab/name'),
                width: '65%',
                dataIndex: 'displayname',
            },
            {
                text: this.getViisonSnippet('tab/ordernumber'),
                width: '20%',
                dataIndex: 'ordernumber',
            },
            {
                text: this.getViisonSnippet('tab/price'),
                width: '15%',
                dataIndex: 'grossprice',
                align: 'right',
                renderer: ViisonCurrencyFormatter.renderer,
            },
        ];
    },

    /**
     * Creates a container panel containing the right set article grid panel.
     *
     * @return Ext.grid.Panel
     */
    createSetArticleContainer: function () {
        return {
            xtype: 'panel',
            flex: 1,
            layout: 'fit',
            border: false,
            items: [
                this.setArticlePanel,
            ],
        };
    },

    /**
     * Creates the grid panel that is used for the right set article side.
     *
     * @return Ext.panel.Panel
     */
    createSetArticlePanel: function () {
        // Right grid: set article store
        this.setArticleGrid = this.createSetArticleGrid();
        this.setArticleGrid.on('edit', function (editor, e) {
            e.record.set('sum'); // force total re-calculation
            e.record.commit();
            // Fire Changing event
            this.sendUpdateSetArticle(this.currentArticleDetailId, false);
        }, this);

        this.setArticlePreviewPanel = Ext.create('Ext.container.Container', {
            style: 'padding: 15px; text-align: right;',
            layout: {
                type: 'vbox',
                align: 'stretch',
            },
            width: '350px',
            height: '175px',
        });

        this.setArticlePricePreview = Ext.create('Ext.container.Container', {
            xtype: 'container',
            layout: {
                type: 'vbox',
                align: 'stretch',
            },
        });

        // Right SetArticlePanel: set articles store and price display
        return new Ext.panel.Panel({
            header: false,
            border: false,
            flex: 1,
            layout: {
                type: 'vbox',
                pack: 'start',
                align: 'stretch',
            },
            items: [
                this.setArticleGrid,
                {
                    // Preview panels, use the viison common tooltip styling
                    xtype: 'container',
                    cls: 'is--viison-common-tooltip',
                    layout: {
                        type: 'hbox',
                        align: 'fit',
                    },
                    items: [
                        this.setArticlePreviewPanel,
                        {
                            xtype: 'container',
                            layout: {
                                type: 'vbox',
                            },
                            items: [
                                {
                                    xtype: 'container',
                                    height: 80,
                                },
                                {
                                    xtype: 'button',
                                    tooltip: this.getViisonSnippet('tab/button/refresh/hint'),
                                    iconCls: 'sprite-arrow-circle-315',
                                    action: 'refreshPreview',
                                    scope: this,
                                    handler: function () {
                                        this.fireEvent('refreshPreview', this, this.setArticleStores[this.currentArticleDetailId], this.currentArticleDetailId);
                                    },
                                },
                            ],
                        },
                        {
                            xtype: 'container',
                            flex: 1,
                        },
                        this.setArticlePricePreview,
                        {
                            xtype: 'container',
                            width: 25,
                        },
                    ],
                },
            ],
            html: '',
        });
    },

    createSetArticleGrid: function () {
        return Ext.create('Ext.grid.Panel', this.getSetArticlePanelConfig());
    },

    /**
     * Creates and returns an object containing the configuration for the right set article grid panel.
     *
     * @return Object
     */
    getSetArticlePanelConfig: function () {
        return {
            cls: 'viison-common--grid has--vertical-lines has--no-borders',
            flex: 1,
            border: false,
            stripeRows: true,
            multiSelect: true,
            hideHeaders: false,
            header: false,
            plugins: [
                Ext.create('Ext.grid.plugin.CellEditing', {
                    clicksToEdit: 1,
                }),
            ],
            viewConfig: {
                plugins: {
                    ptype: 'gridviewdragdrop',
                    dragGroup: ('rightGridDDGroup-' + this.id),
                    dropGroup: ('leftGridDDGroup-' + this.id),
                },
                listeners: {
                    /**
                     * Remark: since the D&D plugin does not work properly
                     * the data is transfered manually by using the button-pressed-method
                     *
                     * @param node
                     * @param data
                     * @param overModel
                     * @param dropPosition
                     * @param dropFunction
                     */
                    beforedrop: function (node, data, overModel, dropPosition, dropFunction) {
                        dropFunction.cancelDrop();
                        this.addArticle(true);
                    },
                    scope: this,
                },
            },
            columns: this.createSetArticleColumns(),
            store: null, // will be assigned when currentArticle-information is loaded (onAfterRender)
        };
    },

    /**
     * Returns all columns for right SetArticle-Store
     * @return Ext.grid.column.Column[]
     */
    createSetArticleColumns: function () {
        return [
            {
                text: this.getViisonSnippet('tab/name'),
                sortable: true,
                width: '30%',
                dataIndex: 'displayname',
            },
            {
                text: this.getViisonSnippet('tab/ordernumber'),
                width: '15%',
                dataIndex: 'ordernumber',
            },
            {
                text: this.getViisonSnippet('tab/instock'),
                width: '7%',
                align: 'right',
                dataIndex: 'instock',
            },
            {
                text: this.getViisonSnippet('tab/available'),
                helpText: this.getViisonSnippet('tab/available-tooltip'),
                width: '6%',
                dataIndex: 'available',
                renderer: this.createBooleanColumnRenderer(),
            },
            {
                text: this.getViisonSnippet('tab/deliverytime'),
                width: '6%',
                align: 'right',
                dataIndex: 'deliverytime',
            },
            {
                text: this.getViisonSnippet('tab/maxpurchase'),
                width: '6%',
                align: 'right',
                dataIndex: 'maxpurchase',
            },
            {
                text: this.getViisonSnippet('tab/amount'),
                sortable: true,
                width: '6%',
                align: 'right',
                dataIndex: 'quantity',
                field: {
                    xtype: 'numberfield',
                    allowBlank: false,
                    minValue: 1,
                    maxValue: 1000000,
                },
            },
            {
                text: this.getViisonSnippet('tab/price'),
                sortable: true,
                width: '10%',
                dataIndex: 'grossprice',
                align: 'right',
                renderer: ViisonCurrencyFormatter.renderer,
            },
            {
                text: this.getViisonSnippet('tab/total'),
                sortable: true,
                width: '10%',
                dataIndex: 'total',
                align: 'right',
                renderer: ViisonCurrencyFormatter.renderer,
            },
            {
                /**
                 * Special column type which provides clickable icons in each row
                 */
                xtype: 'actioncolumn',
                width: '4%',
                items: [
                    {
                        iconCls: 'sprite-inbox',
                        action: 'openArticle',
                        tooltip: this.getViisonSnippet('tab/open-subarticle'),
                        scope: this,
                        /**
                         * Add button handler to fire the openArticle event which is handled in the main controller.
                         */
                        handler: function (view, rowIndex) {
                            var store = view.getStore();
                            var record = store.getAt(rowIndex);

                            this.fireEvent('openArticle', record);
                        },
                        getClass: function (value, metadata, record) {
                            if (!record.get('articleId')) {
                                return 'x-hidden';
                            }

                            return 'sprite-inbox';
                        },
                    },
                ],
            },
        ];
    },

    /**
     * Creates and returns a new toolbar that contains a title on the left and a search
     * field on the right.
     *
     * @return Ext.toolbar.Toolbar
     */
    createSearchToolbar: function () {
        return {
            xtype: 'toolbar',
            dock: 'top',
            ui: 'shopware-ui',
            padding: '8 5 8 0',
            items: [
                '->',
                {
                    xtype: 'textfield',
                    name: 'searchfield',
                    cls: 'searchfield',
                    width: 170,
                    enableKeyEvents: true,
                    emptyText: this.getViisonSnippet('tab/search'),
                    listeners: {
                        buffer: 500,
                        scope: this,
                        change: function (field) {
                            this.filterWord = field.value;
                            this.filterArticleStore();
                        },
                    },
                },
            ],
        };
    },

    /**
     * Creates and returns the two left/right assignment buttons.
     *
     * @return Ext.button.Button[]
     */
    createMiddleButtons: function () {
        return [{
            xtype: 'button',
            cls: Ext.baseCSSPrefix + 'form-itemselector-btn',
            iconCls: Ext.baseCSSPrefix + 'form-itemselector-add',
            tooltip: '',
            navBtn: true,
            margin: '4 4 0 4',
            scope: this,
            handler: function () {
                this.addArticle();
            },
        }, {
            xtype: 'button',
            cls: Ext.baseCSSPrefix + 'form-itemselector-btn',
            iconCls: Ext.baseCSSPrefix + 'form-itemselector-remove',
            tooltip: '',
            navBtn: true,
            margin: '4 4 0 4',
            scope: this,
            handler: function () {
                this.onButtonRemovedPressed();
            },
        }];
    },

    /**
     * Add selected Articles to set article (store).
     *
     * @param updateSetId bool update SetId for all items in store
     */
    addArticle: function (updateSetId) {
        var selection = this.articlePanel.getSelectionModel().getSelection();
        var newItems = [];

        Ext.each(selection, function (item) {
            var newItem = {};
            newItem.id = null;
            newItem.setId = this.currentArticleDetailId;
            newItem.articleDetailId = item.data.articleDetailId;
            newItem.articleId = item.data.articleId;
            newItem.name = item.data.name;
            newItem.ordernumber = item.data.ordernumber;
            newItem.price = item.data.price;
            newItem.additional = item.data.additional;
            newItem.tax = item.data.tax;
            newItem.instock = item.data.instock;
            newItem.deliverytime = item.data.deliverytime;
            newItem.laststock = item.data.laststock;
            newItem.active = item.data.active;
            newItem.quantity = 1;
            newItem.maxpurchase = item.data.maxpurchase;
            newItems.push(newItem);
        }, this);
        // Remove selected articles from left store
        this.articleListStore.remove(selection);

        // Add all items to setArticleStore
        newItems.forEach(function (articleData) {
            this.setArticleStores[this.currentArticleDetailId].add({
                id: articleData.id,
                setId: articleData.setId,
                articleDetailId: articleData.articleDetailId,
                articleId: articleData.articleId,
                name: articleData.name,
                ordernumber: articleData.ordernumber,
                additional: articleData.additional,
                tax: articleData.tax,
                price: articleData.price,
                instock: articleData.instock,
                deliverytime: articleData.deliverytime,
                laststock: articleData.laststock,
                active: articleData.active,
                maxpurchase: articleData.maxpurchase,
                quantity: 1,
            });
        }, this);

        if (updateSetId) {
            this.updateSetIdInSetArticleStore();
        }
        this.sendUpdateSetArticle(this.currentArticleDetailId, true);
    },

    /**
     * Overwrites the set article combination store für die set article with the given setArticleDetailId and refreshes
     * the view. This is necessary to re-calculate the sub (and therefore set) article availability.
     *
     * @param Shopware.apps.ViisonSetArticlesArticleSetArticlesTab.store.SetArticle setArticleStore
     * @param int setArticleDetailId
     */
    updateAndOverwriteSetArticleStore: function (setArticleStore, setArticleDetailId) {
        this.setArticleStores[setArticleDetailId] = setArticleStore;
        this.setArticleGrid.reconfigure(this.setArticleStores[this.currentArticleDetailId]);
        this.updateSetArticlePreviewPanel();
    },

    /**
     * Informs controller of updates set article store, refreshes price information and (optionally) filters
     * ArticleStore.
     *
     * @param setArticleDetailId set article (id) that was updated
     * @param filterArticleStore whether or not update the ArticleStore
     */
    sendUpdateSetArticle: function (setArticleDetailId, filterArticleStore) {
        this.fireEvent(
            'updateSetArticle',
            this.setArticleStores[setArticleDetailId].data.items,
            setArticleDetailId
        );
        this.updateSetArticlePreviewPanel();
        if (filterArticleStore) {
            this.filterArticleStore();
        }
    },

    /**
     * Update all SetArticleStore entries, set SetId to currentArticleDetailId
     */
    updateSetIdInSetArticleStore: function () {
        this.setArticleStores[this.currentArticleDetailId].data.items.forEach(function (articleData) {
            articleData.data.setId = this.currentArticleDetailId;
        }, this);
    },

    /**
     * Remove-Button was pressed: remove all selected sub articles from SetArticlesStore, add them to array and notify
     * controller of removed items.
     */
    onButtonRemovedPressed: function () {
        var sel = this.setArticleGrid.getSelectionModel().getSelection();
        var removeArr = [];
        Ext.each(sel, function (item) {
            this.setArticleStores[this.currentArticleDetailId].remove(item);
            removeArr.push(item);
        }, this);
        this.sendUpdateSetArticle(this.currentArticleDetailId, true);
    },

    /**
     * New set article is selected: update Article information, right store, filter left store and update price info.
     * @param elem
     */
    updateCurrentInformation: function (elem) {
        this.currentArticleDetailId = elem.get('articleDetailId');
        this.currentPrice = elem.get('grossprice');
        this.loadArrayStore(this.currentArticleDetailId);
    },

    /**
     * Change to selected set article. If new, create new store in store array else: update view for set article in
     * array.
     *
     * @param int setid
     */
    loadArrayStore: function (setid) {
        if (!this.setArticleStores[setid]) {
            this.setArticleStores[setid] = Ext.create('Shopware.apps.ViisonSetArticlesArticleSetArticlesTab.store.SetArticle', {});
            this.setArticleStores[setid].getProxy().extraParams = {
                setid: this.currentArticleDetailId,
            };
            this.setArticleStores[setid].load({
                scope: this,
                callback: function () {
                    this.filterArticleStore();
                    this.updateSetArticlePreviewPanel();
                },
            });
        } else {
            this.filterArticleStore();
            this.updateSetArticlePreviewPanel();
        }
        // reconfigure right grid to current set article
        this.setArticleGrid.reconfigure(this.setArticleStores[this.currentArticleDetailId]);
    },

    /**
     * Filter left Article store (ArticleList) by filterword.
     */
    filterArticleStore: function () {
        // Add array filter (IDs) & filterword
        var idArr = [];
        this.setArticleStores[this.currentArticleDetailId].data.items.forEach(function (articleData) {
            idArr.push(articleData.data.articleDetailId);
        });
        var jsonData = Ext.JSON.encode(idArr);

        /**
         * Remark: if the articleListStore has been emptied (all shown articles added), reload to page 1 to avoid empty
         * grid
         */
        if (this.articleListStore.data.items.length === 0) {
            this.articleListStore.currentPage = 1;
        }

        this.articleListStore.getProxy().extraParams = {
            filterarr: jsonData,
            filterword: this.filterWord,
        };
        this.articleListStore.load();
    },

    /**
     * Copies current set article composition to all variants. Overrides any composition that has been saved for other
     * variants.
     */
    copySelectionToAllVariants: function (articleVariantStore) {
        var currentCompositionStore = this.setArticleStores[this.currentArticleDetailId];
        /**
         * Use variant list from articleVariantStore as keys since not all entries in me.setArticleStores have been
         * initialized yet.
         */
        articleVariantStore.forEach(function (articleData) {
            var tempDetailId = articleData.data.articleDetailId;
            // No need to override current composition (because it is active)
            if (tempDetailId === this.currentArticleDetailId) {
                return;
            }

            /**
             * Load store if needed and delete all compositions (otherwise copying the composition would just add new
             * articles) and copy composition
             */
            if (!this.setArticleStores[tempDetailId]) {
                // Load store, then override composition in callback
                this.setArticleStores[tempDetailId] = Ext.create(
                    'Shopware.apps.ViisonSetArticlesArticleSetArticlesTab.store.SetArticle',
                    {}
                );
                this.setArticleStores[tempDetailId].getProxy().extraParams = {
                    setid: tempDetailId,
                };
                this.setArticleStores[tempDetailId].load({
                    scope: this,
                    callback: function (records, operation) {
                        // Fetch setid anew since we lose track of tempDetailId
                        var actualSetId = operation.request.params.setid;
                        this.overrideOneStoreWithComposition(
                            actualSetId, this.setArticleStores[actualSetId], currentCompositionStore
                        );
                    },
                });
            } else {
                // Store is already loaded, override composition
                this.overrideOneStoreWithComposition(
                    tempDetailId, this.setArticleStores[tempDetailId], currentCompositionStore
                );
            }
        }, this);

        // Inform user
        Shopware.Notification.createGrowlMessage(
            this.getViisonSnippet('tab/copy-variant/growl/title'),
            this.getViisonSnippet('tab/copy-variant/growl/message'),
            'ViisonSetArticles'
        );
    },

    /**
     * Clear given targetStore and copy all entries from given newCompositionStore to it.
     * (This basically overwrites the store).
     *
     * @param setArticleDetailId
     * @param targetStore
     * @param newCompositionStore
     */
    overrideOneStoreWithComposition: function (setArticleDetailId, targetStore, newCompositionStore) {
        targetStore.removeAll();
        newCompositionStore.each(function (record) {
            targetStore.add(record.copy());
        });

        // Notify controller of changes
        this.sendUpdateSetArticle(setArticleDetailId, true);
    },

    updateSetArticlePreviewPanel: function () {
        // Update availability preview
        var calculatedInstock = this.reduceStoreByFunctionAndDefaultValue(
            this.setArticleStores[this.currentArticleDetailId],
            function (maxInstock, articleData) {
                return Math.min(maxInstock, Math.floor(articleData.data.instock / articleData.data.quantity));
            },
            Number.POSITIVE_INFINITY,
            '-'
        );
        var calculatedDeliverytime = this.reduceStoreByFunctionAndDefaultValue(
            this.setArticleStores[this.currentArticleDetailId],
            function (maxDeliveryTime, articleData) {
                return Math.max(maxDeliveryTime, articleData.data.deliverytime);
            },
            0,
            '-'
        );
        var calculatedMaxpurchase = this.reduceStoreByFunctionAndDefaultValue(
            this.setArticleStores[this.currentArticleDetailId],
            function (maxPurchase, articleData) {
                return Math.min(maxPurchase, Math.floor(articleData.data.maxpurchase / articleData.data.quantity));
            },
            Number.POSITIVE_INFINITY,
            '-'
        );
        var calculatedWeight = Ext.util.Format.number(
            this.reduceStoreByFunctionAndDefaultValue(
                this.setArticleStores[this.currentArticleDetailId],
                function (sum, articleData) {
                    return sum + (articleData.data.weight * articleData.data.quantity);
                },
                0.0,
                0.0
            )
        );
        this.setArticlePreviewPanel.update(
            this.getSetArticlePreviewAvailabilityTemplate().apply({
                subArticleCount: this.setArticleStores[this.currentArticleDetailId].getCount(),
                inStock: calculatedInstock,
                isAvailable: this.getCalculatedAvailable(this.setArticleStores[this.currentArticleDetailId]),
                deliveryTime: calculatedDeliverytime,
                maxPurchase: calculatedMaxpurchase,
                weight: calculatedWeight,
            })
        );

        // Update pricing preview and calculation
        var totalAmount = this.reduceStoreByFunctionAndDefaultValue(
            this.setArticleStores[this.currentArticleDetailId],
            function (sum, articleData) {
                return sum + articleData.data.total;
            },
            0.0,
            0.0
        );
        var discount = totalAmount - this.currentPrice;
        var discountPercentage = ' - ';
        if (totalAmount > 0) {
            discountPercentage = ((100 * discount) / totalAmount).toFixed(2) + '%';
        }
        var labelString = '(' + discountPercentage + ') ' + ViisonCurrencyFormatter.renderer(discount);

        this.setArticlePricePreview.update(
            this.getSetArticlePricePreviewTemplate().apply({
                total: ViisonCurrencyFormatter.renderer(totalAmount),
                actual: ViisonCurrencyFormatter.renderer(this.currentPrice),
                discount: this.getHighlightedString(labelString, discount),
            })
        );
    },

    /**
     * Returns a string highlighted with html span in RED if given value is less or equal 0
     *
     * @param   string
     * @param   value
     * @returns string
     */
    getHighlightedString: function (string, value) {
        if (value <= 0) {
            return '<span style="color:red">' + string + '</span>';
        }

        return string;
    },

    /**
     * Calculates the set article availability with given SetArticleStore
     *
     * @param setArticleStore
     * @returns string availability (logical AND over all sub articles)
     */
    getCalculatedAvailable: function (setArticleStore) {
        // Return a default character if store is empty
        if (setArticleStore.getCount() === 0) {
            return '-';
        }

        var calculatedAvailability = setArticleStore.data.items.reduce(
            function (isAvailable, articleData) {
                return isAvailable && articleData.data.available;
            },
            true
        );

        if (calculatedAvailability) {
            return this.getViisonSnippet('tab/price-display/available-true');
        }

        return this.getHighlightedString(this.getViisonSnippet('tab/price-display/available-false'), -1);
    },

    /**
     * Reduces a store so a single string by given store, reduction function ans its start value.
     * Returns the given default value is store is empty.
     *
     * @param store
     * @param reductionFunction inline function by which the store is reduced
     * @param startValue start value for the reduction function
     * @param defaultValue returns this default value if store is empty
     * @returns string
     */
    reduceStoreByFunctionAndDefaultValue: function (store, reductionFunction, startValue, defaultValue) {
        if (store.getCount() === 0) {
            return defaultValue;
        }

        return store.data.items.reduce(
            reductionFunction,
            startValue
        );
    },

    /**
     * @param string trueSnippet
     * @return function
     */
    createBooleanColumnRenderer: function () {
        var availableSnippet = this.getViisonSnippet('tab/price-display/available-true');
        var notAvailableSnippet = this.getViisonSnippet('tab/price-display/available-false');

        return function (value) {
            return (value) ? availableSnippet : notAvailableSnippet;
        };
    },

    /**
     * Creates and returns the XTemplate used to render the set article availability preview panel.
     *
     * @returns {Ext.XTemplate}
     */
    getSetArticlePreviewAvailabilityTemplate: function () {
        return new Ext.XTemplate(
            '<tpl for=".">',

            '<div class="is--row has--divider has--padding">',
            '<span class="is--label is--left-aligned-with-fixed-label-width">',
            this.getViisonSnippet('tab/price-display/article-count'),
            '</span>',
            '<span class="is--value is--right-aligned-with-fixed-label-width">{subArticleCount}</span>',
            '</div>',

            '<div class="is--row has--divider has--padding">',
            '<span class="is--label is--left-aligned-with-fixed-label-width">',
            this.getViisonSnippet('tab/price-display/instock'),
            '</span>',
            '<span class="is--value is--right-aligned-with-fixed-label-width">{inStock}</span>',
            '</div>',

            '<div class="is--row has--divider has--padding">',
            '<span class="is--label is--left-aligned-with-fixed-label-width">',
            this.getViisonSnippet('tab/price-display/available'),
            '</span>',
            '<span class="is--value is--right-aligned-with-fixed-label-width">{isAvailable}</span>',
            '</div>',

            '<div class="is--row has--divider has--padding">',
            '<span class="is--label is--left-aligned-with-fixed-label-width">',
            this.getViisonSnippet('tab/price-display/deliverytime'),
            '</span>',
            '<span class="is--value is--right-aligned-with-fixed-label-width">{deliveryTime}</span>',
            '</div>',

            '<div class="is--row has--divider has--padding">',
            '<span class="is--label is--left-aligned-with-fixed-label-width">',
            this.getViisonSnippet('tab/price-display/maxpurchase'),
            '</span>',
            '<span class="is--value is--right-aligned-with-fixed-label-width">{maxPurchase}</span>',
            '</div>',

            '<div class="is--row has--padding">',
            '<span class="is--label is--left-aligned-with-fixed-label-width">',
            this.getViisonSnippet('tab/price-display/weight'),
            '</span>',
            '<span class="is--value is--right-aligned-with-fixed-label-width">{weight}</span>',
            '</div>',

            '</tpl>'
        );
    },

    /**
     * Creates and returns the XTemplate used to render the set article price preview panel.
     *
     * @returns {Ext.XTemplate}
     */
    getSetArticlePricePreviewTemplate: function () {
        return new Ext.XTemplate(
            '<tpl for=".">',

            '<div class="is--price">',
            '<span class="is--label">',
            this.getViisonSnippet('tab/price-display/total'),
            '</span>',
            '<br><span class="is--value">{total}</span>',
            '</div>',

            '<div class="is--price">',
            '<span class="is--label">',
            this.getViisonSnippet('tab/price-display/actual'),
            '</span>',
            '<br><span class="is--value">{actual}</span>',
            '</div>',

            '<div class="is--price">',
            '<span class="is--label has--divider">',
            this.getViisonSnippet('tab/price-display/discount'),
            '</span>',
            '<br><span class="is--value">{discount}</span>',
            '</div>',

            '</tpl>'
        );
    },

});
