'use strict';

var _ = require('lodash/core');

/**
 * A date object to keep Salesforce date literal
 *
 * @class
 * @constructor
 * @see http://www.salesforce.com/us/developer/docs/soql_sosl/Content/sforce_api_calls_soql_select_dateformats.htm
 */
var SfDate = module.exports = function(literal) {
  this._literal = literal;
};

/**
 * Returns literal when converted to string
 *
 * @override
 */
SfDate.prototype.toString =
SfDate.prototype.toJSON = function() { return this._literal; };


/** @private **/
function zeropad(n) { return (n<10 ? "0" : "") + n; }

/**
 * Convert JavaScript date object to ISO8601 Date format (e.g. 2012-10-31)
 *
 * @param {String|Number|Date} date - Input date
 * @returns {SfDate} - Salesforce date literal with ISO8601 date format
 */
SfDate.toDateLiteral = function(date) {
  if (_.isNumber(date)) {
    date = new Date(date);
  } else if (_.isString(date)) {
    date = SfDate.parseDate(date);
  }
  var yy = date.getFullYear();
  var mm = date.getMonth()+1;
  var dd = date.getDate();
  var dstr = [ yy, zeropad(mm), zeropad(dd) ].join("-");
  return new SfDate(dstr);
};

/**
 * Convert JavaScript date object to ISO8601 DateTime format
 * (e.g. 2012-10-31T12:34:56Z)
 *
 * @param {String|Number|Date} date - Input date
 * @returns {SfDate} - Salesforce date literal with ISO8601 datetime format
 */
SfDate.toDateTimeLiteral = function(date) {
  if (_.isNumber(date)) {
    date = new Date(date);
  } else if (_.isString(date)) {
    date = SfDate.parseDate(date);
  }
  var yy = date.getUTCFullYear();
  var mm = date.getUTCMonth()+1;
  var dd = date.getUTCDate();
  var hh = date.getUTCHours();
  var mi = date.getUTCMinutes();
  var ss = date.getUTCSeconds();
  var dtstr =
    [ yy, zeropad(mm), zeropad(dd) ].join("-") + "T" +
    [ zeropad(hh), zeropad(mi), zeropad(ss) ].join(":") + "Z";
  return new SfDate(dtstr);
};

/**
 * Parse IS08601 date(time) formatted string and return date instance
 *
 * @param {String} str
 * @returns {Date}
 */
SfDate.parseDate = function(str) {
  var d = new Date();
  var regexp = /^([\d]{4})-?([\d]{2})-?([\d]{2})(T([\d]{2}):?([\d]{2}):?([\d]{2})(.([\d]{3}))?(Z|([\+\-])([\d]{2}):?([\d]{2})))?$/;
  var m = str.match(regexp);
  if (m) {
    d = new Date(0);
    if (!m[4]) {
      d.setFullYear(parseInt(m[1], 10));
      d.setDate(parseInt(m[3], 10));
      d.setMonth(parseInt(m[2], 10) - 1);
      d.setHours(0);
      d.setMinutes(0);
      d.setSeconds(0);
      d.setMilliseconds(0);
    } else {
      d.setUTCFullYear(parseInt(m[1], 10));
      d.setUTCDate(parseInt(m[3], 10));
      d.setUTCMonth(parseInt(m[2], 10) - 1);
      d.setUTCHours(parseInt(m[5], 10));
      d.setUTCMinutes(parseInt(m[6], 10));
      d.setUTCSeconds(parseInt(m[7], 10));
      d.setUTCMilliseconds(parseInt(m[9] || '0', 10));
      if (m[10] && m[10] !== 'Z') {
        var offset = parseInt(m[12],10) * 60 + parseInt(m[13], 10);
        d.setTime((m[11] === '+' ? -1 : 1) * offset * 60 * 1000 +d.getTime());
      }
    }
    return d;
  } else {
    throw new Error("Invalid date format is specified : " + str);
  }
};

/*
 * Pre-defined Salesforce Date Literals
 */
var SfDateLiterals = {
  YESTERDAY: 1,
  TODAY: 1,
  TOMORROW: 1,
  LAST_WEEK: 1,
  THIS_WEEK: 1,
  NEXT_WEEK: 1,
  LAST_MONTH: 1,
  THIS_MONTH: 1,
  NEXT_MONTH: 1,
  LAST_90_DAYS: 1,
  NEXT_90_DAYS: 1,
  LAST_N_DAYS: 2,
  NEXT_N_DAYS: 2,
  NEXT_N_WEEKS: 2,
  LAST_N_WEEKS: 2,
  NEXT_N_MONTHS: 2,
  LAST_N_MONTHS: 2,
  THIS_QUARTER: 1,
  LAST_QUARTER: 1,
  NEXT_QUARTER: 1,
  NEXT_N_QUARTERS: 2,
  LAST_N_QUARTERS: 2,
  THIS_YEAR: 1,
  LAST_YEAR: 1,
  NEXT_YEAR: 1,
  NEXT_N_YEARS: 2,
  LAST_N_YEARS: 2,
  THIS_FISCAL_QUARTER: 1,
  LAST_FISCAL_QUARTER: 1,
  NEXT_FISCAL_QUARTER: 1,
  NEXT_N_FISCAL_QUARTERS:2,
  LAST_N_FISCAL_QUARTERS:2,
  THIS_FISCAL_YEAR:1,
  LAST_FISCAL_YEAR:1,
  NEXT_FISCAL_YEAR:1,
  NEXT_N_FISCAL_YEARS: 2,
  LAST_N_FISCAL_YEARS: 2
};

for (var literal in SfDateLiterals) {
  var type = SfDateLiterals[literal];
  SfDate[literal] =
   type === 1 ? new SfDate(literal) : createLiteralBuilder(literal);
}

/** @private **/
function createLiteralBuilder(literal) {
  return function(num) { return new SfDate(literal + ":" + num); };
}
