/*jshint multistr:true */

this.recline = this.recline || {};
this.recline.View = this.recline.View || {};

(function($, my) {
  "use strict";
// ## (Data) Grid Dataset View
//
// Provides a tabular view on a Dataset.
//
// Initialize it with a `recline.Model.Dataset`.
my.Grid = Backbone.View.extend({
  tagName:  "div",
  className: "recline-grid-container",

  initialize: function(modelEtc) {
    var self = this;
    _.bindAll(this, 'render', 'onHorizontalScroll');
    this.listenTo(this.model.records, 'add reset remove', this.render);
    this.tempState = {};
    var state = _.extend({
        hiddenFields: []
      }, modelEtc.state
    ); 
    this.state = new recline.Model.ObjectState(state);
  },

  events: {
    // does not work here so done at end of render function
    // 'scroll .recline-grid tbody': 'onHorizontalScroll'
  },

  // ======================================================
  // Column and row menus

  setColumnSort: function(order) {
    var sort = [{}];
    sort[0][this.tempState.currentColumn] = {order: order};
    this.model.query({sort: sort});
  },
  
  hideColumn: function() {
    var hiddenFields = this.state.get('hiddenFields');
    hiddenFields.push(this.tempState.currentColumn);
    this.state.set({hiddenFields: hiddenFields});
    // change event not being triggered (because it is an array?) so trigger manually
    this.state.trigger('change');
    this.render();
  },
  
  showColumn: function(e) {
    var hiddenFields = _.without(this.state.get('hiddenFields'), $(e.target).data('column'));
    this.state.set({hiddenFields: hiddenFields});
    this.render();
  },

  onHorizontalScroll: function(e) {
    var currentScroll = $(e.target).scrollLeft();
    this.$el.find('.recline-grid thead tr').scrollLeft(currentScroll);
  },

  // ======================================================
  // #### Templating
  template: ' \
    <div class="table-container"> \
    <table class="recline-grid table-striped table-condensed" cellspacing="0"> \
      <thead class="fixed-header"> \
        <tr> \
          {{#fields}} \
            <th class="column-header {{#hidden}}hidden{{/hidden}}" data-field="{{id}}" style="width: {{width}}px; max-width: {{width}}px; min-width: {{width}}px;" title="{{label}}"> \
              <span class="column-header-name">{{label}}</span> \
            </th> \
          {{/fields}} \
          <th class="last-header" style="width: {{lastHeaderWidth}}px; max-width: {{lastHeaderWidth}}px; min-width: {{lastHeaderWidth}}px; padding: 0; margin: 0;"></th> \
        </tr> \
      </thead> \
      <tbody class="scroll-content"></tbody> \
    </table> \
    </div> \
  ',

  toTemplateJSON: function() {
    var self = this; 
    var modelData = this.model.toJSON();
    modelData.notEmpty = ( this.fields.length > 0 );
    // TODO: move this sort of thing into a toTemplateJSON method on Dataset?
    modelData.fields = this.fields.map(function(field) {
      return field.toJSON();
    });
    // last header width = scroll bar - border (2px) */
    modelData.lastHeaderWidth = this.scrollbarDimensions.width - 2;
    return modelData;
  },
  render: function() {
    var self = this;
    this.fields = new recline.Model.FieldList(this.model.fields.filter(function(field) {
      return _.indexOf(self.state.get('hiddenFields'), field.id) == -1;
    }));

    this.scrollbarDimensions = this.scrollbarDimensions || this._scrollbarSize(); // skip measurement if already have dimensions
    var numFields = this.fields.length;
    // compute field widths (-20 for first menu col + 10px for padding on each col and finally 16px for the scrollbar)
    var fullWidth = self.$el.width() - 20 - 10 * numFields - this.scrollbarDimensions.width;
    var width = parseInt(Math.max(50, fullWidth / numFields), 10);
    // if columns extend outside viewport then remainder is 0 
    var remainder = Math.max(fullWidth - numFields * width,0);
    this.fields.each(function(field, idx) {
      // add the remainder to the first field width so we make up full col
      if (idx === 0) {
        field.set({width: width+remainder});
      } else {
        field.set({width: width});
      }
    });
    var htmls = Mustache.render(this.template, this.toTemplateJSON());
    this.$el.html(htmls);
    this.model.records.forEach(function(doc) {
      var tr = $('<tr />');
      self.$el.find('tbody').append(tr);
      var newView = new my.GridRow({
          model: doc,
          el: tr,
          fields: self.fields
        });
      newView.render();
    });
    // hide extra header col if no scrollbar to avoid unsightly overhang
    var $tbody = this.$el.find('tbody')[0];
    if ($tbody.scrollHeight <= $tbody.offsetHeight) {
      this.$el.find('th.last-header').hide();
    }
    this.$el.find('.recline-grid').toggleClass('no-hidden', (self.state.get('hiddenFields').length === 0));
    this.$el.find('.recline-grid tbody').scroll(this.onHorizontalScroll);
    return this;
  },

  // ### _scrollbarSize
  // 
  // Measure width of a vertical scrollbar and height of a horizontal scrollbar.
  //
  // @return: { width: pixelWidth, height: pixelHeight }
  _scrollbarSize: function() {
    var $c = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body");
    var dim = { width: $c.width() - $c[0].clientWidth + 1, height: $c.height() - $c[0].clientHeight };
    $c.remove();
    return dim;
  }
});

// ## GridRow View for rendering an individual record.
//
// Since we want this to update in place it is up to creator to provider the element to attach to.
//
// In addition you *must* pass in a FieldList in the constructor options. This should be list of fields for the Grid.
//
// Example:
//
// <pre>
// var row = new GridRow({
//   model: dataset-record,
//     el: dom-element,
//     fields: mydatasets.fields // a FieldList object
//   });
// </pre>
my.GridRow = Backbone.View.extend({
  initialize: function(initData) {
    _.bindAll(this, 'render');
    this._fields = initData.fields;
    this.listenTo(this.model, 'change', this.render);
  },

  template: ' \
      {{#cells}} \
      <td data-field="{{field}}" style="width: {{width}}px; max-width: {{width}}px; min-width: {{width}}px;"> \
        <div class="data-table-cell-content"> \
          <a href="javascript:{}" class="data-table-cell-edit" title="Edit this cell">&nbsp;</a> \
          <div class="data-table-cell-value">{{{value}}}</div> \
        </div> \
      </td> \
      {{/cells}} \
    ',
  events: {
    'click .data-table-cell-edit': 'onEditClick',
    'click .data-table-cell-editor .okButton': 'onEditorOK',
    'click .data-table-cell-editor .cancelButton': 'onEditorCancel'
  },
  
  toTemplateJSON: function() {
    var self = this;
    var doc = this.model;
    var cellData = this._fields.map(function(field) {
      return {
        field: field.id,
        width: field.get('width'),
        value: doc.getFieldValue(field)
      };
    });
    return { id: this.id, cells: cellData };
  },

  render: function() {
    this.$el.attr('data-id', this.model.id);
    var html = Mustache.render(this.template, this.toTemplateJSON());
    this.$el.html(html);
    return this;
  },

  // ===================
  // Cell Editor methods

  cellEditorTemplate: ' \
    <div class="menu-container data-table-cell-editor"> \
      <textarea class="data-table-cell-editor-editor" bind="textarea">{{value}}</textarea> \
      <div id="data-table-cell-editor-actions"> \
        <div class="data-table-cell-editor-action"> \
          <button class="okButton btn primary">Update</button> \
          <button class="cancelButton btn danger">Cancel</button> \
        </div> \
      </div> \
    </div> \
  ',

  onEditClick: function(e) {
    var editing = this.$el.find('.data-table-cell-editor-editor');
    if (editing.length > 0) {
      editing.parents('.data-table-cell-value').html(editing.text()).siblings('.data-table-cell-edit').removeClass("hidden");
    }
    $(e.target).addClass("hidden");
    var cell = $(e.target).siblings('.data-table-cell-value');
    cell.data("previousContents", cell.text());
    var templated = Mustache.render(this.cellEditorTemplate, {value: cell.text()});
    cell.html(templated);
  },

  onEditorOK: function(e) {
    var self = this;
    var cell = $(e.target);
    var rowId = cell.parents('tr').attr('data-id');
    var field = cell.parents('td').attr('data-field');
    var newValue = cell.parents('.data-table-cell-editor').find('.data-table-cell-editor-editor').val();
    var newData = {};
    newData[field] = newValue;
    this.model.set(newData);
    this.trigger('recline:flash', {message: "Updating row...", loader: true});
    this.model.save().then(function(response) {
        this.trigger('recline:flash', {message: "Row updated successfully", category: 'success'});
      })
      .fail(function() {
        this.trigger('recline:flash', {
          message: 'Error saving row',
          category: 'error',
          persist: true
        });
      });
  },

  onEditorCancel: function(e) {
    var cell = $(e.target).parents('.data-table-cell-value');
    cell.html(cell.data('previousContents')).siblings('.data-table-cell-edit').removeClass("hidden");
  }
});

})(jQuery, recline.View);
