/* global ko */
if (typeof jQuery !== 'undefined') {

    var AWPCP = jQuery.AWPCP = jQuery.extend({}, jQuery.AWPCP, AWPCP);

    (function($) {
        /**
         * View model for a Multiple Region Selector.
         */
        $.AWPCP.RegionSelector = function(options, regions) {
            var self = this;

            self.options = $.extend({}, self.defaultOptions, options);
            self.regions = ko.observableArray([]);

            self.count = 0;

            /**
             * Controls whether the Add Region button should
             * be shown ot the user or not.
             */
            self.showAddRegionButton = ko.computed(function() {
                var regions = self.regions();

                if (self.options.disabled) {
                    return false;
                } else {
                    return regions.length < self.options.maxRegions;
                }
            }, this);

            /**
             * Controls whether the Remove Region button should
             * be shown ot the user or not.
             */
            self.showRemoveRegionButton = ko.computed(function() {
                var regions = self.regions();

                if (self.options.disabled) {
                    return false;
                } else {
                    return regions.length > 1;
                }
            }, this);

            /**
             * Populate previously selected regions.
             */
            $.each(regions, function(i, region) {
                self.addRegion( region, self.options.fields );
            });

            // If no regions were included but the selector is configured to
            // accept at least one region, let's add an empry region so that the
            // necessary fields are shown.
            if ( regions.length === 0 && self.options.maxRegions ) {
                self.addEmptyRegion();
            }
        };

        $.extend($.AWPCP.RegionSelector.prototype, {
            defaultOptions: {
                context: 'details',
                maxRegions: 1,
                showTextField: false,
                disabled: false
            },

            getSelectedRegions: function() {
                var self = this,
                    data = [];

                $.each( self.regions(), function( index, region ) {
                    var entry = {};

                    $.each( region.partials(), function( index, partial ) {
                        entry[ partial.type ] = {
                            'type' : partial.type,
                            'label': partial.label(),
                            'param': partial.param(),
                            'options': partial.options(),
                            'selected': partial.selected(),
                            'required': partial.config.required,
                            'alwaysShown': partial.config.alwaysShown,
                            'showTextField': partial.config.showTextField
                        };
                    } );

                    data.push( entry );
                } );

                return data;
            },

            clearSelectedRegions: function() {
                var self = this;

                $.each( self.regions(), function( index, region ) {
                    self.removeRegion( region );
                } );
            },

            addRegion: function(data, fields) {
                var region;

                if (this.regions().length < this.options.maxRegions) {
                    region = new $.AWPCP.Region(this, this.count, data, fields);
                    this.regions.push(region);
                    this.count = this.count + 1;
                }

                return region;
            },

            removeRegion: function(region) {
                this.regions.remove(region);
            },

            /**
             * Show error messages on duplicated regions.
             * Returns true if the value of the given field is the most
             * specific region part of a duplicated region:
             *
             * If USA->Colador->Denver is a duplicated region,
             * this function should return true for the field that
             * holds the "Denver" part.
             */
            checkDuplicatedRegionsForField: function(fieldId, showErrors) {
                var self = this,
                    objects = [], regions = [], subregions = {},

                    targetValue = false, targetRegionIndex = false,
                    fieldValueIsDuplicated = false;

                $.each(self.regions(), function(index, region) {
                    var partials = region.partials(),
                        selected = [],
                        subregion;

                    subregions[region.index] = [];

                    for (var i = 0; i < partials.length; i = i + 1) {
                        if (partials[i].selected()) {
                            selected.push(partials[i].selected());

                            subregion = selected.join('-');
                            subregions[region.index].push(subregion);

                            if (fieldId && partials[i].id === fieldId) {
                                targetValue = subregion;
                                targetRegionIndex = region.index;
                            }
                        }
                    }

                    objects.push(region);
                });

                // check the more specific regions first (the ones with more
                // subregions/partials selected). If there is tie, check the
                // ones that appear first in the page (have lower index).
                objects.sort(function(a, b) {
                    var aRegions = subregions[a.index].length,
                        bRegions = subregions[b.index].length;
                    if (bRegions - aRegions !== 0) {
                        return bRegions - aRegions;
                    } else {
                        return a.index - b.index;
                    }
                });

                $.each(objects, function(index, region) {
                    var values = subregions[region.index], check = true;

                    for (var i = values.length - 1; i >= 0; i = i - 1) {
                        if (check && $.inArray(values[i], regions) !== -1) {
                            if (showErrors) {
                                region.error(AWPCP.l10n('multiple-region-selector', 'duplicated-region'));
                            }

                            if (values[i] === targetValue && region.index === targetRegionIndex) {
                                fieldValueIsDuplicated = true;
                            }

                            break;
                        } else {
                            regions.push(values[i]);
                            region.error(false);
                            check = false;
                        }
                    }
                });

                return fieldValueIsDuplicated;
            },

            checkDuplicatedRegions: function() {
                return this.checkDuplicatedRegionsForField(null, false);
            },

            /**
             * Handler for the Add Region button.
             */
            onAddRegion: function(selector, event) {
                event.preventDefault();
                this.addEmptyRegion();
            },

            /**
             * Add an empty region to the list of regions displayed by the selector.
             */
            addEmptyRegion: function() {
                var region = this.addRegion(
                    this.options.template,
                    this.options.fields
                );

                // Clean selected values.
                region.reset();
            },

            /**
             * Handler for the Remove Region button.
             */
            onRemoveRegion: function() {
                var self = this;
                return function(region, event) {
                    event.preventDefault();
                    self.removeRegion(region);
                };
            },

            getLocalizedText: function(key) {
                return AWPCP.l10n('multiple-region-selector', key);
            }
        });


        /**
         * View model for a Single Region Selector, which allow
         * an user to filter the Regions hierarchy and choose one
         * region.
         */
        $.AWPCP.Region = function(selector, index, region, fields) {
            var self = this, partial, data, lastPartial;

            self.index = index;
            self.selector = selector;
            self.partials = ko.observableArray([]);
            self.error = ko.observable(false);

            $.each(fields, function(i, field) {
                if (region.hasOwnProperty(field)) {
                    data = region[field];

                    partial = new $.AWPCP.RegionPartial(
                        self,
                        data.type,
                        data.label,
                        'regions[' + index + '][' + data.param + ']',
                        data.options,
                        data.selected,
                        {
                            required: data.required,
                            alwaysShown: data.alwaysShown || i === 0,
                            showTextField: selector.options.showTextField
                        }
                    );

                    self.partials.push(partial);

                    if (lastPartial && lastPartial.selected()) {
                        partial.show();
                    }

                    lastPartial = partial;
                }
            });
        };

        jQuery.extend($.AWPCP.Region.prototype, {
            /**
             * Returns ordered array of Partial objects associated to form fields
             * that represent region types that are below in the types hierarchy than
             * the provided type.
             */
            getNextPartials: function(type) {
                var partials = this.partials(),
                    length = partials.length,
                    i;

                for (i = 0; i < length; i=i+1) {
                    if (partials[i].type === type) {
                        return partials.slice(i + 1);
                    }
                }

                return [];
            },

            /**
             * Fetch regions data from the server to populate one of the partial
             * objects.
             */
            getPartialOptions: function(partial, type, parentType, parentValue) {
                var self = this;

                partial.loading(true);

                $.getJSON($.AWPCP.get('ajaxurl'), {
                    action: 'awpcp-get-regions-options',
                    parent_type: parentType,
                    parent: parentValue,
                    type: type,
                    context: self.selector.options.context
                }, function(response) {
                    if (response.status === 'ok') {
                        partial.options(response.options);
                    } else {
                        partial.options([]);
                    }

                    partial.loading(false);
                });
            },

            /**
             * Clear selected region.
             */
            reset: function() {
                $.each(this.partials(), function(i, partial) {
                    partial.selected('');
                });
            },

            selectionChanged: function( type, value, mode ) {
                var next = this.getNextPartials(type),
                    first, hidden, i, n;

                this.selector.checkDuplicatedRegions();

                // return if there are no more fileds to process
                if (0 === next.length) {
                    return;
                }

                if ( 'textfield' === mode ) {
                    next[0].show( true );
                    hidden = next.slice( 1 );

                // fetch new options only if the new value is one of
                // the options
                } else if ( null !== value && undefined !== value && value.length ) {
                    first = next[0];
                    first.selected(undefined);

                    this.getPartialOptions(first, first.type, type, value);

                    first.show(true);

                    // hide all other fields until a new value is selected
                    hidden = next.slice(1);

                // no valid value was selected, hide all fields
                } else {
                    hidden = next;
                }

                n = hidden.length;
                for (i = 0; i < n; i=i+1) {
                    hidden[i].selected(undefined);
                    hidden[i].hide();
                }
            }
        });


        /**
         * View model for a Partial Region (Country, State, County or City).
         */
        $.AWPCP.RegionPartial = function(region, type, label, param, options, selected, config) {
            var self = this;

            self.region = region;
            self.config = jQuery.extend({}, self.defaultConfig, config);

            // HTML id attribute
            self.id = type + Math.random();

            self.type = type;
            self.label = ko.observable(label);
            self.param = ko.observable(param);
            self.options = ko.observableArray(options);
            self.selectedOption = ko.observable(selected);
            self.selectedText = ko.observable(selected);

            self.loading = ko.observable(false);
            self._show = ko.observable(undefined);

            self.caption = ko.computed(function() {
                var caption = AWPCP.l10n('multiple-region-selector', 'select-placeholder').replace('%s', self.label());

                if (caption.lastIndexOf('*') === (caption.length - 1)) {
                    caption = caption.substr(0, caption.length - 1);
                }

                return caption;
            }, self);

            self.showTextField = ko.computed(function() {
                var options = self.options(),
                    loading = self.loading(),
                    show = self._show() || self.config.alwaysShown;

                if (show === true && !loading) {
                    return options.length === 0 && self.config.showTextField;
                } else {
                    return false;
                }
            }, self);

            self.showSelectField = ko.computed(function() {
                var options = self.options(),
                    loading = self.loading(),
                    show = self._show() || self.config.alwaysShown;

                if (show === true && !loading) {
                    return options.length > 0 || !self.config.showTextField;
                } else {
                    return false;
                }
            }, self);

            self.selected = ko.computed({
                read: function() {
                    var showTextField = self.showTextField(),
                        text = self.selectedText(),
                        option = self.selectedOption();
                    return showTextField ? text : option;
                },
                write: function(value) {
                    if (self.showTextField()) {
                        self.selectedText(value);
                    } else {
                        self.selectedOption(value);
                    }
                },
                owner: self
            });

            self.visible = ko.computed(function() {
                var selected = self.selected(),
                    show = self._show() ? true : false,
                    showTextField = self.showTextField();

                if (showTextField) {
                    return true;
                } else {
                    return self.config.alwaysShown || (undefined !== selected) || show;
                }
            });

            // save previous value, so we can decide to notify parent region
            // only when there is a NEW value available
            self.previousValue = null;
            self.selected.subscribe(function(previousValue) {
                self.previousValue = previousValue;
            }, null, 'beforeChange');

            // notify other fields when a new value is selected/provided
            self.selected.subscribe(function(value) {
                if (self.previousValue !== value && (self.showSelectField() || self.showTextField())) {
                    self.region.selectionChanged( self.type, value, self.showSelectField() ? 'dropdown' : 'textfield' );
                }
            });
        };

        jQuery.extend($.AWPCP.RegionPartial.prototype, {
            defaultConfig: {
                showTextField: false,
                alwaysShown: false
            },

            show: function() {
                this._show(true);
            },

            hide: function() {
                this._show(false);
            }
        });

        /**--------------------------------------------------------------------
         * Initialization
         */

        $.fn.MultipleRegionSelector = function( regions ) {
            return $(this).each( function() {
                var selector = $(this),
                    instance,
                    data;

                instance = selector.data( 'RegionSelector' );

                if ( instance ) {
                    return;
                }

                data = $.AWPCP.get( 'multiple-region-selector-' + selector.attr( 'uuid' ) );

                selector.data( 'RegionSelector', new $.AWPCP.RegionSelector(
                    data.options,
                    regions || data.regions
                ) );

                ko.applyBindings( selector.data( 'RegionSelector' ), this );
            } );
        };

        $(function() {
            $( '.awpcp-multiple-region-selector' ).MultipleRegionSelector();
        });

    })(jQuery);
}
