/*
  backgrid-select-all
  http://github.com/wyuenho/backgrid

  Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
  Licensed under the MIT @license.
*/
(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(["backbone", "backgrid", "underscore"], factory);
  } else if (typeof exports == "object") {
    // CommonJS
    module.exports = factory(require("backbone"), require("backgrid"), require("underscore"));
  }
  // Browser
  else factory(root.Backbone, root.Backgrid, root._);

}(this, function (Backbone, Backgrid, _) {

  "use strict";

  /**
     Renders a checkbox for row selection.

     @class Backgrid.Extension.SelectRowCell
     @extends Backbone.View
  */
  var SelectRowCell = Backgrid.Extension.SelectRowCell = Backbone.View.extend({

    /** @property */
    className: "select-row-cell",

    /** @property */
    tagName: "td",

    /** @property */
    events: {
      "keydown input[type=checkbox]": "onKeydown",
      "change input[type=checkbox]": "onChange",
      "click input[type=checkbox]": "enterEditMode"
    },

    /**
       Initializer. If the underlying model triggers a `select` event, this cell
       will change its checked value according to the event's `selected` value.

       @param {Object} options
       @param {Backgrid.Column} options.column
       @param {Backbone.Model} options.model
    */
    initialize: function (options) {

      this.column = options.column;
      if (!(this.column instanceof Backgrid.Column)) {
        this.column = new Backgrid.Column(this.column);
      }

      var column = this.column, model = this.model, $el = this.$el;
      this.listenTo(column, "change:renderable", function (column, renderable) {
        $el.toggleClass("renderable", renderable);
      });

      if (Backgrid.callByNeed(column.renderable(), column, model)) $el.addClass("renderable");

      this.listenTo(model, "backgrid:select", function (model, selected) {
        this.checkbox().prop("checked", selected).change();
      });
    },

    /**
       Returns the checkbox.
     */
    checkbox: function () {
      return this.$el.find("input[type=checkbox]");
    },

    /**
       Focuses the checkbox.
    */
    enterEditMode: function () {
      this.checkbox().focus();
    },

    /**
       Unfocuses the checkbox.
    */
    exitEditMode: function () {
      this.checkbox().blur();
    },

    /**
       Process keyboard navigation.
    */
    onKeydown: function (e) {
      var command = new Backgrid.Command(e);
      if (command.passThru()) return true; // skip ahead to `change`
      if (command.cancel()) {
        e.stopPropagation();
        this.checkbox().blur();
      }
      else if (command.save() || command.moveLeft() || command.moveRight() ||
               command.moveUp() || command.moveDown()) {
        e.preventDefault();
        e.stopPropagation();
        this.model.trigger("backgrid:edited", this.model, this.column, command);
      }
    },

    /**
       When the checkbox's value changes, this method will trigger a Backbone
       `backgrid:selected` event with a reference of the model and the
       checkbox's `checked` value.
    */
    onChange: function () {
      var checked = this.checkbox().prop("checked");
      this.$el.parent().toggleClass("selected", checked);
      this.model.trigger("backgrid:selected", this.model, checked);
    },

    /**
       Renders a checkbox in a table cell.
    */
    render: function () {
      this.$el.empty().append('<input tabindex="-1" type="checkbox" />');
      this.delegateEvents();
      return this;
    }

  });

  /**
     Renders a checkbox to select all rows on the current page.

     @class Backgrid.Extension.SelectAllHeaderCell
     @extends Backgrid.Extension.SelectRowCell
  */
  var SelectAllHeaderCell = Backgrid.Extension.SelectAllHeaderCell = SelectRowCell.extend({

    /** @property */
    className: "select-all-header-cell",

    /** @property */
    tagName: "th",

    /**
       Initializer. When this cell's checkbox is checked, a Backbone
       `backgrid:select` event will be triggered for each model for the current
       page in the underlying collection. If a `SelectRowCell` instance exists
       for the rows representing the models, they will check themselves. If any
       of the SelectRowCell instances trigger a Backbone `backgrid:selected`
       event with a `false` value, this cell will uncheck its checkbox. In the
       event of a Backbone `backgrid:refresh` event, which is triggered when the
       body refreshes its rows, which can happen under a number of conditions
       such as paging or the columns were reset, this cell will still remember
       the previously selected models and trigger a Backbone `backgrid:select`
       event on them such that the SelectRowCells can recheck themselves upon
       refreshing.

       @param {Object} options
       @param {Backgrid.Column} options.column
       @param {Backbone.Collection} options.collection
    */
    initialize: function (options) {

      this.column = options.column;
      if (!(this.column instanceof Backgrid.Column)) {
        this.column = new Backgrid.Column(this.column);
      }

      var collection = this.collection;
      var selectedModels = this.selectedModels = {};
      this.listenTo(collection.fullCollection || collection,
                    "backgrid:selected", function (model, selected) {
        if (selected) selectedModels[model.id || model.cid] = 1;
        else {
          delete selectedModels[model.id || model.cid];
          this.checkbox().prop("checked", false);
        }
        if (_.keys(selectedModels).length === (collection.fullCollection|| collection).length) {
          this.checkbox().prop("checked", true);
        }
      });

      this.listenTo(collection.fullCollection || collection, "remove", function (model) {
        delete selectedModels[model.id || model.cid];
        if ((collection.fullCollection || collection).length === 0) {
          this.checkbox().prop("checked", false);
        }
      });

      this.listenTo(collection, "backgrid:refresh", function () {
        if ((collection.fullCollection || collection).length === 0) {
          this.checkbox().prop("checked", false);
        }
        else {
          var checked = this.checkbox().prop("checked");
          for (var i = 0; i < collection.length; i++) {
            var model = collection.at(i);
            if (checked || selectedModels[model.id || model.cid]) {
              model.trigger("backgrid:select", model, true);
            }
          }
        }
      });

      var column = this.column, $el = this.$el;
      this.listenTo(column, "change:renderable", function (column, renderable) {
        $el.toggleClass("renderable", renderable);
      });

      if (Backgrid.callByNeed(column.renderable(), column, collection)) $el.addClass("renderable");
    },

    /**
       Propagates the checked value of this checkbox to all the models of the
       underlying collection by triggering a Backbone `backgrid:select` event on
       the models on the current page, passing each model and the current
       `checked` value of the checkbox in each event.

       A `backgrid:selected` event will also be triggered with the current
       `checked` value on all the models regardless of whether they are on the
       current page.

       This method triggers a 'backgrid:select-all' event on the collection
       afterwards.
    */
    onChange: function () {
      var checked = this.checkbox().prop("checked");

      var collection = this.collection;
      collection.each(function (model) {
        model.trigger("backgrid:select", model, checked);
      });

      if (collection.fullCollection) {
        collection.fullCollection.each(function (model) {
          if (!collection.get(model.cid)) {
            model.trigger("backgrid:selected", model, checked);
          }
        });
      }

      this.collection.trigger("backgrid:select-all", this.collection, checked);
    }

  });

  /**
     Convenient method to retrieve a list of selected models. This method only
     exists when the `SelectAll` extension has been included. Selected models
     are retained across pagination.

     @member Backgrid.Grid
     @return {Array.<Backbone.Model>}
  */
  Backgrid.Grid.prototype.getSelectedModels = function () {
    var selectAllHeaderCell;
    var headerCells = this.header.row.cells;
    for (var i = 0, l = headerCells.length; i < l; i++) {
      var headerCell = headerCells[i];
      if (headerCell instanceof SelectAllHeaderCell) {
        selectAllHeaderCell = headerCell;
        break;
      }
    }

    var result = [];
    if (selectAllHeaderCell) {
      var selectedModels = selectAllHeaderCell.selectedModels;
      var collection = this.collection.fullCollection || this.collection;
      for (var modelId in selectedModels) {
        result.push(collection.get(modelId));
      }
    }

    return result;
  };

  /**
     Convenient method to deselect the selected models. This method is only
     available when the `SelectAll` extension has been included.

     @member Backgrid.Grid
   */
  Backgrid.Grid.prototype.clearSelectedModels = function () {
    var selectedModels = this.getSelectedModels();
    for (var i = 0, l = selectedModels.length; i < l; i++) {
      var model = selectedModels[i];
      model.trigger("backgrid:select", model, false);
    }
  };

}));
