var Backbone = require('backbone');
var cdb = require('cartodb.js');
var _ = require('underscore');
var moment = require('moment');
var checkAndBuildOpts = require('../../helpers/required-opts');
var QUERY_TEMPLATE = _.template('SELECT MAX(<%= column %>), MIN(<%= column %>) FROM (<%= table %>) __wrapped');

var STATUS = {
  unavailable: 'unavailable',
  unfetched: 'unfetched',
  fetching: 'fetching',
  fetched: 'fetched'
};

var DEFAULT_MAX_BUCKETS = 367;

var REQUIRED_OPTS = [
  'configModel',
  'querySchemaModel'
];

module.exports = Backbone.Model.extend({
  defaults: {
    status: STATUS.unfetched,
    buckets: []
  },

  initialize: function (attrs, opts) {
    checkAndBuildOpts(opts, REQUIRED_OPTS, this);

    if (!_.has(attrs, 'column')) {
      throw new Error('Column is required');
    }

    this._SQL = new cdb.SQL({
      user: this._configModel.get('user_name'),
      sql_api_template: this._configModel.get('sql_api_template'),
      api_key: this._configModel.get('api_key')
    });

    this._initBinds();

    if (this._querySchemaModel.get('status') === STATUS.fetched) {
      this._queryData();
    }
  },

  _initBinds: function () {
    this.on('change:column', this._queryData, this);
    this.listenTo(this._querySchemaModel, 'change:status', this._onQuerySchemaStatusChanged);
  },

  _onQuerySchemaStatusChanged: function () {
    if (this._querySchemaModel.get('status') === STATUS.fetched) {
      this._queryData();
    }
  },

  _queryData: function () {
    if (this._querySchemaModel.getColumnType(this.get('column')) !== 'date') {
      return;
    }

    var query = QUERY_TEMPLATE({
      column: this.get('column'),
      table: this._querySchemaModel.get('query')
    });

    var callback = {
      success: this._onQuerySuccess.bind(this),
      error: this._onQueryError.bind(this)
    };

    this.set('status', STATUS.fetching);
    this._SQL.execute(query, null, callback);
  },

  _onQuerySuccess: function (data) {
    this.set('status', STATUS.fetched);
    var row = data.rows && data.rows[0];
    var max = row && row.max ? row.max : 0;
    var min = row && row.min ? row.min : 0;
    this._calculateBuckets(max, min);
  },

  _onQueryError: function () {
    this.set('status', STATUS.unavailable);
  },

  _calculateBuckets: function (max, min) {
    var end = moment(max);
    var start = moment(min);

    this.set('buckets', [{
      bins: Math.ceil(end.diff(start, 'minutes', true)) + 1,
      val: 'minute',
      label: 'Minutes'
    }, {
      bins: Math.ceil(end.diff(start, 'hours', true)) + 1,
      val: 'hour',
      label: 'Hours'
    }, {
      bins: Math.ceil(end.diff(start, 'days', true)) + 1,
      val: 'day',
      label: 'Days'
    }, {
      bins: Math.ceil(end.diff(start, 'weeks', true)) + 1,
      val: 'week',
      label: 'Weeks'
    }, {
      bins: Math.ceil(end.diff(start, 'months', true)) + 1,
      val: 'month',
      label: 'Months'
    }, {
      bins: Math.ceil(end.diff(start, 'quarters', true)) + 1,
      val: 'quarter',
      label: 'Quarters'
    }, {
      bins: Math.ceil(end.diff(start, 'years', true)) + 1,
      val: 'year',
      label: 'Years'
    }]);
  },

  getFilteredBuckets: function (max) {
    var limit = max || DEFAULT_MAX_BUCKETS;
    var buckets = _.filter(this.get('buckets'), function (bucket) {
      return bucket.bins <= limit;
    });

    return buckets;
  },

  getPreferredBucket: function (max) {
    var sortedBuckets = _.sortBy(this.getFilteredBuckets(max), 'bins');
    return sortedBuckets.length > 0 ? sortedBuckets[sortedBuckets.length - 1] : {};
  }
});

@@ -2362,32 +2362,56 @@
 n);%0A
-%0A    this.set('
+    var BUCKET_INCREMENT = 1;%0A%0A    var 
 buckets
-',
+ =
  %5B%7B%0A
@@ -2418,34 +2418,24 @@
       bins: 
-Math.ceil(
 end.diff(sta
@@ -2446,37 +2446,32 @@
 'minutes', true)
-) + 1
 ,%0A      val: 'mi
@@ -2517,34 +2517,24 @@
       bins: 
-Math.ceil(
 end.diff(sta
@@ -2543,37 +2543,32 @@
 , 'hours', true)
-) + 1
 ,%0A      val: 'ho
@@ -2610,34 +2610,24 @@
       bins: 
-Math.ceil(
 end.diff(sta
@@ -2635,37 +2635,32 @@
 t, 'days', true)
-) + 1
 ,%0A      val: 'da
@@ -2700,34 +2700,24 @@
       bins: 
-Math.ceil(
 end.diff(sta
@@ -2726,37 +2726,32 @@
 , 'weeks', true)
-) + 1
 ,%0A      val: 'we
@@ -2793,34 +2793,24 @@
       bins: 
-Math.ceil(
 end.diff(sta
@@ -2820,37 +2820,32 @@
  'months', true)
-) + 1
 ,%0A      val: 'mo
@@ -2889,34 +2889,24 @@
       bins: 
-Math.ceil(
 end.diff(sta
@@ -2918,37 +2918,32 @@
 quarters', true)
-) + 1
 ,%0A      val: 'qu
@@ -2999,18 +2999,8 @@
 ns: 
-Math.ceil(
 end.
@@ -3029,13 +3029,8 @@
 rue)
-) + 1
 ,%0A  
@@ -3073,16 +3073,241 @@
 '%0A    %7D%5D
+;%0A%0A    var incrementedBuckets = _.map(buckets, function (bucket) %7B%0A      return _.extend(_.clone(bucket), %7B%0A        bins: Math.ceil(bucket.bins) + BUCKET_INCREMENT%0A      %7D);%0A    %7D);%0A%0A    this.set('buckets', incrementedBuckets
 );%0A  %7D,%0A

Refactor buckets calculation