/**
 * @extends
 */
Date.superClass = Object.prototype;

/**
 * @type Integer
 */
Date.SECOND = 1000;

/**
 * @type Integer
 */
Date.MINUTE = 0xEA60;

/**
 * @type Integer
 */
Date.HOUR = 0x36EE80;

/**
 * @type Integer
 */
Date.DAY = 0x5265C00;

/**
 * @type Integer
 */
Date.WEEK = 0x240C8400;

/**
 * @type String
 */
Date.TYPE_MONTH = "M";

/**
 * @type String
 */
Date.TYPE_YEAR = "Y";

/**
 * @type String
 */
Date.TYPE_DAY = "D";

/**
 * @type String[]
 */
Date.WEEK_DAY = ["Domingo", "Segunda-feira", "Terça-feira", "Quarta-feira", "Quinta-feira", "Sexta-feira", "Sábado", "Domingo"];

/**
 * @type String[]
 */
Date.SHORT_WEEK_DAY = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab", "Dom"];

/**
 * @type String[]
 */
Date.MONTH_NAME = ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"];

/**
 * @type String[]
 */
Date.SHORT_MONTH_NAME = ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"];

/**
 * @type Integer[]
 */
Date.MONTH_DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

/**
 * Número de milisegundos de diferença entre a representação da data local e UTC
 * 
 * @type Integer
 */
Date.TIME_ZONE_OFFSET = (new Date().getTimezoneOffset() * 60000);

/**
 * Retorna uma versão em String de um Date (dd/mm/yyyy)
 *
 * @addon
 * @type String
 */
Date.prototype.toString = function() {
	return this.format("%d/%m/%Y");
}

/**
 * Verifica se determinada data é final de semana
 *
 * @addon
 * @param {Date} date
 * @type Boolean
 */
Date.prototype.isWeekend = function() {
 	return (this.getDay() == 0 || this.getDay() == 6);
}

/**
 * Retorna o dia da semana
 * 
 * @type String
 */
Date.prototype.getLocaleDay = function() {
	return this.format("%a");
}

/**
 * STR TO DATE - Converte um string no formato DD/MM/YYYY para Date
 *
 * @addon
 * @param {String} strDate
 * @type Date
 */
Date.strToDate = function(strDate) {
 	var valores = strDate.split("/");

    return new Date(valores[1] + '/' + valores[0] + '/' + valores[2]);
}

/**
 * Converte uma data impressa pelo servidor no formato YYYY-MM-DD hh:mm:ss.l para Date
 *
 * @addon
 * @param {String} strDate
 * @type Date
 */
Date.serverStrToDate = function(strDate) {
    return new Date(strDate.split(".").shift().replace(/\-/g, "/"));
}

/**
 * Retorna o numero da semana no ano (ISO 8601) (dynarch)
 * 
 * @type Integer
 */
Date.prototype.getWeekNumber = function() {
	var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
	var DoW = d.getDay();
	d.setDate(d.getDate() - (DoW + 6) % 7 + 3);
	var ms = d.valueOf();
	d.setMonth(0);
	d.setDate(4);
	return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
};

/**
 * Retorna o número do dia no ano
 * 
 * @type Integer
 */
Date.prototype.getDayOfYear = function() {
	var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
	var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
	var time = now - then;
	return Math.floor(time / Date.DAY);
};

/** 
 * Retorna a string da data formatada
 * 
 * <pre>
 * Tokens:
 * "%a" Dia da semana abreviado
 * "%A" Dia da semana completo
 * "%b" Nome do mês abreviado
 * "%B" Nome do mês completo
 * "%C" Século
 * "%d" Dia do mês (01 a 31)
 * "%e" Dia do mês (1 a 31)
 * "%H" Hora do dia (00 a 23)
 * "%i" Hora do dia (01 a 12)
 * "%j" Dia do ano (001 a 366)
 * "%k" Hora do dia (0 a 23)
 * "%l" Hora do dia (1 a 12)
 * "%m" Mês (01 a 12)
 * "%M" Minuto (00 a 59)
 * "%n" Quebra de linha (\n)
 * "%p" Periodo do dia (am/pm)
 * "%P" Periodo do dia (AM/PM)
 * "%s" Segundos
 * "%S" Segundos (00 a 59)
 * "%t" Caracter Tab (\t)
 * "%U" Número da semana no ano (01 a 53)
 * "%u" Dia da semana (1 a 7, 1 = segunda)
 * "%V" Número da semana no ano (01 a 53)
 * "%w" Dia da semana (0 a 6, 0 = domingo)
 * "%W" Número da semana no ano (01 a 53)
 * "%y" Ano sem o século (00 a 99)
 * "%Y" Ano completo
 * "%%" Caracter porcentagem (%)
 * </pre>
 * 
 * @param {String} formato combinação de literais e tokens ("%d/%m/%Y" => "25/12/2007")
 * @type String
 */
Date.prototype.format = function (format) {
	var m = this.getMonth();
	var d = this.getDate();
	var y = this.getFullYear();
	var wn = this.getWeekNumber();
	var w = this.getDay();
	var s = {};
	var hr = this.getHours();
	var pm = (hr >= 12);
	var ir = (pm) ? (hr - 12) : hr;
	var dy = this.getDayOfYear();
	if (ir == 0) ir = 12;
	var min = this.getMinutes();
	var sec = this.getSeconds();

	s["%a"] = Date.SHORT_WEEK_DAY[w];
	s["%A"] = Date.WEEK_DAY[w];
	s["%b"] = Date.SHORT_MONTH_NAME[m];
	s["%B"] = Date.MONTH_NAME[m];
	s["%C"] = 1 + Math.floor(y / 100);
	s["%d"] = (d < 10) ? ("0" + d) : d;
	s["%e"] = d;
	s["%H"] = (hr < 10) ? ("0" + hr) : hr;
	s["%I"] = (ir < 10) ? ("0" + ir) : ir;
	s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy;
	s["%k"] = hr;
	s["%l"] = ir;
	s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m);
	s["%M"] = (min < 10) ? ("0" + min) : min;
	s["%n"] = "\n";
	s["%p"] = pm ? "PM" : "AM";
	s["%P"] = pm ? "pm" : "am";
	s["%s"] = Math.floor(this.getTime() / 1000);
	s["%S"] = (sec < 10) ? ("0" + sec) : sec;
	s["%t"] = "\t";
	s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
	s["%u"] = w + 1;
	s["%w"] = w;
	s["%y"] = ('' + y).substr(2, 2);
	s["%Y"] = y;
	s["%%"] = "%";

	var re = /%./g;
	format = String(format);

	var a = format.match(re);

	for (var i = 0; i < a.length; i++) {
		var tmp = s[a[i]];
		if (tmp) {
			re = new RegExp(a[i], 'g');
			format = format.replace(re, tmp);
		}
	}

	return format;
};

/**
 * Retorna o numero do último dia do mês da data
 * 
 * @type Integer
 */
Date.prototype.getMonthMaxDay = function() {
	var month = this.getMonth();
	return (
		month === 1 && (this.getFullYear() % 4) === 0 
		? 29
		: Date.MONTH_DAYS[month]
	);
}


/**
 * Retorna uma string JSON representando a data
 * 
 * @see http://www.json.org/json.js
 * @return {String}
 */
Date.prototype.toJSONString = function () {
    function f(n) {
        return n < 10 ? '0' + n : n;
    }

    return '"' + this.getFullYear() + '-' +
            f(this.getMonth() + 1) + '-' +
            f(this.getDate()) + 'T' +
            f(this.getHours()) + ':' +
            f(this.getMinutes()) + ':' +
            f(this.getSeconds()) + '"';
};

/**
 * Indica se uma data esta entre duas determinadas datas (inclusivo)
 * 
 * @param {Date} startDate
 * @param {Date} endDate
 * @return {Boolean}
 */
Date.prototype.between = function(startDate, endDate) {
    if (startDate > endDate) {
        var swapTemp = endDate;
        endDate = startDate;
        startDate = swapTemp;
    }
    return (startDate <= this && endDate >= this);
}

/**
 * Indica se esta data está após a data passada
 * 
 * @param {Date} date
 * @return {Boolean}
 */
Date.prototype.after = function(date) {
    return (this > date);
}

/**
 * Indica se esta data está anterior a data passada
 * 
 * @param {Date} date
 * @return {Boolean}
 */
Date.prototype.before = function(date) {
    return this < date;
}

/**
 * Verifica a equalidade entre esta data e uma outra data
 * 
 * @param {Date} date
 * @return {Boolean}
 */
Date.prototype.equals = function(date) {
    return Date.superClass.equals.call(this, date) || (date instanceof Date && date.getTime() == this.getTime());
}

/**
 * Retorna um clone da data
 * 
 * @return {Date}
 */
Date.prototype.clone = function() {
    return (new Date(this.getTime()));
}

/**
 * Adiciona um determinado número de tempo a data 
 * 
 * @param {String} type
 * @param {Number} number
 * @return {Date}
 * @throws {TypeError}
 */
Date.prototype.roll = function(type, number) {
   if (type === Date.TYPE_DAY) {
       this.setTime(this.getTime() + (number * Date.DAY));
   } else if (type === Date.TYPE_MONTH || type === Date.TYPE_YEAR) {
       var n = Math.abs(number);
       var add = number >= 0 ? 1 : -1;
       for (var x = 1; x <= n; x++) {
           var day = this.getDate();
           this.setDate(1);
           if (type == Date.TYPE_MONTH) {
               this.setMonth(this.getMonth() + add);
           } else {
               this.setFullYear(this.getFullYear() + add);
           }
           if (day > this.getMonthMaxDay()) {
               this.setDate(this.getMonthMaxDay());
           } else {
               this.setDate(day);
           }
       }
   } else {
       throw new TypeError('Date#roll: invalid type');
   }

   return this;
};


/**
 * Retorna a diferença entre esta e outra data segundo o tipo indicado
 * 
 * @param {String} type
 * @param {Date} dateEnd
 * @return {Number}
 * @throws {TypeError}
 */
Date.prototype.diff = function(type, dateEnd) {
   if (type == Date.TYPE_DAY) {
       return Math.floor((dateEnd.getTime() - this.getTime()) / Date.DAY);
   } else if (type === Date.TYPE_MONTH) {
       var years = (dateEnd.getFullYear() - this.getFullYear()) * 12;
       if (dateEnd.getMonth() >= this.getMonth()) {
           return years + (dateEnd.getMonth() - this.getMonth());
       } else {
           return years - (this.getMonth() - dateEnd.getMonth());
       }
   } else if (type === Date.TYPE_YEAR) {
       return dateEnd.getFullYear() - this.getFullYear();
   } else {
       throw new TypeError('Date#diff: invalid type');
   }
}; 
