// Backbone.Picky, v0.2.2
// Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
// Copyright (c)2013 Boris Kozorovitzky.
// Distributed under MIT license
// http://github.com/BorisKozo/backbone.picky

var Picky = (function (Backbone, _) {

    var Picky = {};
    Backbone.Picky = Picky;

    // Picky.SingleSelect
    // ------------------
    // A single-select mixin for Backbone.Collection, allowing a single
    // model to be selected within a collection. Selection of another
    // model within the collection causes the previous model to be
    // deselected.

    Picky.SingleSelect = function () {
    };

    Picky.SingleSelect.mixInto = function (collection, options) {
        _.extend(collection, singleSelect);
        if (options && options.selectOnAdd) {
            collection.on("add", function (model, collection) {
                collection.select(model);
            });
        }

        if (options && options.selectOnRemove) {
            collection.on("remove", function (model, collection, collectionOptions) {
                var index = collectionOptions.index,
                    prevIndex = Math.max(index - 1, 0),
                    nextIndex = Math.min(index, collection.length - 1);
                if (collection.selected === model) {
                    if (_.isFunction(options.selectOnRemove)) {
                        options.selectOnRemove(model, collection, collectionOptions);
                        return;
                    }

                    if (collection.length === 0) {
                        return;
                    }

                    // Select the model before the previously selected model
                    if (options.selectOnRemove === "prev") {
                        collection.at(prevIndex).select();
                        return;
                    }

                    // Select the model before the previously selected model
                    if (options.selectOnRemove === "next") {
                        collection.at(nextIndex).select();
                        return;
                    }

                }
            });
        } else {
            collection.on("remove", function (model, collection) {
                if (collection.selected === model) {
                    delete collection.selected;
                }
            });
        }

        collection.on("reset", function (collection) {
            collection.refreshSelection();
        });

        updateCollectionSelectionSingleSelect(collection);
    };

   var singleSelect = {

        // Select a model, deselecting any previously
        // select model
        select: function (model) {
            if (model && this.selected === model) { return; }

            if (!this.some(function (innerModel) {
                return innerModel === model;
            })) {
                return;
            }

            this.deselect();

            this.selected = model;
            this.selected.select();
            this.trigger("collection:selected", model);
        },

        // Deselect a model, resulting in no model
        // being selected
        deselect: function (model) {
            var selected;

            if (!this.selected) { return; }

            model = model || this.selected;
            if (this.selected !== model) { return; }

            selected = this.selected;
            delete this.selected;
            this.trigger("collection:deselected", selected);
            selected.deselect();
        },

        //Finds the first selected model in the collection and selects it,
        //deselects all other models
        refreshSelection: function () {
            updateCollectionSelectionSingleSelect(this);
        }

    };

    // Picky.MultiSelect
    // -----------------
    // A mult-select mixin for Backbone.Collection, allowing a collection to
    // have multiple items selected, including `selectAll` and `deselectAll`
    // capabilities.

    Picky.MultiSelect = function () {
    };

    Picky.MultiSelect.mixInto = function (collection, options) {


        _.extend(collection, multiSelect);
        collection.selected = {};
        if (options && options.selectOnAdd) {
            collection.on("add", function (model, collection) {
                collection.select(model);
            });
        }

        collection.on("remove", function (model, collection) {
            if (collection.selected && collection.selected.hasOwnProperty(model.cid)) {
                delete collection.selected[model.cid];
                calculateSelectedLength(collection);
            }
        });


        collection.on("reset", function (collection) {
            collection.refreshSelection();
        });


        updateCollectionSelectionMultiSelect(collection);
    };

    var multiSelect = {

        // Select a specified model, make sure the
        // model knows it's selected, and hold on to
        // the selected model.
        select: function (model) {
            if (this.selected[model.cid]) { return; }
            if (!this.some(function (innerModel) {
                return innerModel === model;
            })) { return; }

            this.selected[model.cid] = model;
            model.select();
            calculateSelectedLength(this);
        },

        // Deselect a specified model, make sure the
        // model knows it has been deselected, and remove
        // the model from the selected list.
        deselect: function (model) {
            if (!this.selected[model.cid]) { return; }

            delete this.selected[model.cid];
            model.deselect();
            calculateSelectedLength(this);
        },

        // Select all models in this collection
        selectAll: function () {
            this.each(function (model) { model.select(); });
            calculateSelectedLength(this);
        },

        // Deselect all models in this collection
        deselectAll: function () {
            if (this.selectedLength === 0) { return; }

            this.each(function (model) { model.deselect(); });
            calculateSelectedLength(this);
        },

        // Toggle select all / none. If some are selected, it
        // will select all. If all are selected, it will select 
        // none. If none are selected, it will select all.
        toggleSelectAll: function () {
            if (this.selectedLength === this.length) {
                this.deselectAll();
            } else {
                this.selectAll();
            }
        },

        // Updates the internal data structure of the collection to the selected state of models
        // in the collection. Triggers selected:none, selected:some, or selected:all event.
        refreshSelection: function () {
            updateCollectionSelectionMultiSelect(this);
        },

        // returns an array of all the selected models
        getSelected: function () {
            var modelCid, result = [];
            for (modelCid in this.selected) {
                if (this.selected.hasOwnProperty(modelCid)) {
                    result.push(this.selected[modelCid]);
                }
            }

            return result;
        }
    };

    // Picky.Selectable
    // ----------------
    // A selectable mixin for Backbone.Model, allowing a model to be selected,
    // enabling it to work with Picky.MultiSelect or on it's own

    Picky.Selectable = function () {
    };

    Picky.Selectable.mixInto = function (model) {
        _.extend(model, selectable);
    };

    var selectable = {

        // Select this model, and tell our
        // collection that we're selected
        select: function (options) {
            if (this.selected) { return; }

            if (this.collection && _.isFunction(this.collection.select)) {
                this.collection.select(this);
            }

            this.selected = true;

            if (options && options.silent) { return; }

            this.trigger("model:selected");

        },

        // Deselect this model, and tell our
        // collection that we're deselected
        deselect: function (options) {
            if (!this.selected) { return; }

            if (this.collection && _.isFunction(this.collection.deselect)) {
                this.collection.deselect(this);
            }

            this.selected = false;

            if (options && options.silent) { return; }

            this.trigger("model:deselected");

        },

        // Change selected to the opposite of what
        // it currently is
        toggleSelected: function (options) {
            if (this.selected) {
                this.deselect(options);
            } else {
                this.select(options);
            }
        },

        // Change the selection to the given value
        changeSelected: function (value, options) {
            if (value) {
                this.select(options);
            } else {
                this.deselect(options);
            }
        }

    };

    // Helper Methods
    // --------------

    // Calculate the number of selected items in a collection
    // and update the collection with that length. Trigger events
    // from the collection based on the number of selected items.
    var calculateSelectedLength = function (collection) {
        collection.selectedLength = _.size(collection.selected);

        var selectedLength = collection.selectedLength,
         length = collection.length;

        if (selectedLength === length) {
            collection.trigger("collection:selected:all", collection);
            return;
        }

        if (selectedLength === 0) {
            collection.trigger("collection:selected:none", collection);
            return;
        }

        if (selectedLength > 0 && selectedLength < length) {
            collection.trigger("collection:selected:some", collection);
            return;
        }
    },

    // Update the state of selected metadata on the collection based on 
    // the items contained in the collection
    updateCollectionSelectionSingleSelect = function (collection) {
        delete collection.selected;
        collection.each(function (model) {
            if (model.selected) {
                if (collection.selected) {
                    model.deselect({ silent: true });
                } else {
                    collection.selected = model;
                }
            }
        });
        if (collection.selected) {
            collection.trigger("collection:selected", collection.selected);
        }
    },


    // Update the state of selected metadata on the collection based on 
    // the items contained in the collection
    updateCollectionSelectionMultiSelect = function (collection) {
        collection.selected = {};
        collection.each(function (model) {
            if (model.selected) {
                collection.selected[model.cid] = model;
            }
        });
        calculateSelectedLength(collection);
    };


    return Picky;
})(Backbone, _);