dojo.provide("dojox.date.islamic.Date");

dojo.require("dojo.date");
dojo.requireLocalization("dojo.cldr", "islamic");

dojo.declare("dojox.date.islamic.Date", null, {
	// summary: The component defines the Islamic (Hijri) Calendar Object
	//
	// description:
	//	This module is similar to the Date() object provided by JavaScript
	//
	// example:
	// |	dojo.require("dojox.date.islamic.Date");
	// |
	// |	var date = new dojox.date.islamic.Date();
	// |	document.writeln(date.getFullYear()+'\'+date.getMonth()+'\'+date.getDate());


	_date: 0,
	_month: 0,
	_year: 0,
	_hours: 0,
	_minutes: 0,
	_seconds: 0,
	_milliseconds: 0,
	_day: 0,
	_GREGORIAN_EPOCH : 1721425.5,
	_ISLAMIC_EPOCH : 1948439.5,

	constructor: function(){
		// summary: This is the constructor
		// description:
		//	This function initialize the date object values
		//
		// example:
		// |		var date1 = new dojox.date.islamic.Date();
		// |
		// |		var date2 = new dojox.date.islamic.Date("12\2\1429");
		// |
		// |		var date3 = new dojox.date.islamic.Date(date2);
		// |
		// |		var date4 = new dojox.date.islamic.Date(1429,2,12);

		var len = arguments.length;
		if(!len){// use the current date value, added "" to the similarity to date
			this.fromGregorian(new Date());
		}else if(len == 1){
			var arg0 = arguments[0];
			if(typeof arg0 == "number"){ // this is time "valueof"
				arg0 = new Date(arg0);
			}

			if(arg0 instanceof Date){
				this.fromGregorian(arg0);
			}else if(arg0 == ""){
				// date should be invalid.  Dijit relies on this behavior.
				this._date = new Date(""); //TODO: should this be NaN?  _date is not a Date object
			}else{  // this is Islamic.Date object
				this._year = arg0._year;
				this._month =  arg0._month;
				this._date = arg0._date;
				this._hours = arg0._hours;
				this._minutes = arg0._minutes;
				this._seconds = arg0._seconds;
				this._milliseconds = arg0._milliseconds;
			}
		}else if(len >=3){
			// YYYY MM DD arguments passed, month is from 0-12
			this._year += arguments[0];
			this._month += arguments[1];
			this._date += arguments[2];
			this._hours += arguments[3] || 0;
			this._minutes += arguments[4] || 0;
			this._seconds += arguments[5] || 0;
			this._milliseconds += arguments[6] || 0;
		}
	},

	getDate:function(){
		// summary: This function returns the date value (1 - 30)
		//
		// example:
		// |		var date1 = new dojox.date.islamic.Date();
		// |
		// |		document.writeln(date1.getDate);
		return this._date;
	},
	
	getMonth:function(){
		// summary: This function return the month value ( 0 - 11 )
		//
		// example:
		// |		var date1 = new dojox.date.islamic.Date();
		// |
		// |		document.writeln(date1.getMonth()+1);

		return this._month;
	},

	getFullYear:function(){
		// summary: This function return the Year value
		//
		// example:
		// |		var date1 = new dojox.date.islamic.Date();
		// |
		// |		document.writeln(date1.getFullYear());

		return this._year;
	},
		
	getDay:function(){
		// summary: This function return Week Day value ( 0 - 6 )
		//
		// example:
		// |		var date1 = new dojox.date.islamic.Date();
		// |
		// |		document.writeln(date1.getDay());

		return this.toGregorian().getDay();
	},
		
	getHours:function(){
		//summary: returns the Hour value
		return this._hours;
	},
	
	getMinutes:function(){
		//summary: returns the Minuites value
		return this._minutes;
	},

	getSeconds:function(){
		//summary: returns the seconde value
		return this._seconds;
	},

	getMilliseconds:function(){
		//summary: returns the Milliseconds value
		return this._milliseconds;
	},

	setDate: function(/*number*/date){
		// summary: This function sets the Date
		// example:
		// |		var date1 = new dojox.date.islamic.Date();
		// |		date1.setDate(2);

		date = parseInt(date);

		if(date > 0 && date <= this.getDaysInIslamicMonth(this._month, this._year)){
			this._date = date;
		}else{
			var mdays;
			if(date>0){
				for(mdays = this.getDaysInIslamicMonth(this._month, this._year);
					date > mdays;
						date -= mdays,mdays =this.getDaysInIslamicMonth(this._month, this._year)){
					this._month++;
					if(this._month >= 12){this._year++; this._month -= 12;}
				}

				this._date = date;
			}else{
				for(mdays = this.getDaysInIslamicMonth((this._month-1)>=0 ?(this._month-1) :11 ,((this._month-1)>=0)? this._year: this._year-1);
						date <= 0;
							mdays = this.getDaysInIslamicMonth((this._month-1)>=0 ? (this._month-1) :11,((this._month-1)>=0)? this._year: this._year-1)){
					this._month--;
					if(this._month < 0){this._year--; this._month += 12;}

					date+=mdays;
				}
				this._date = date;
			}
		}
		return this;
	},

	setFullYear:function(/*number*/year){
		// summary: This function set Year
		//
		// example:
		// |		var date1 = new dojox.date.islamic.Date();
		// |		date1.setYear(1429);

		this._year = +year;
	},

	setMonth: function(/*number*/month) {
		// summary: This function set Month
		//
		// example:
		// |		var date1 = new dojox.date.islamic.Date();
		// |		date1.setMonth(2);

		this._year += Math.floor(month / 12);
		if(month > 0){
			this._month = Math.floor(month % 12);
		}else{
			this._month = Math.floor(((month % 12) + 12) % 12);
		}
	},

	setHours:function(){
		//summary: set the Hours
		var hours_arg_no = arguments.length;
		var hours = 0;
		if(hours_arg_no >= 1){
			hours = parseInt(arguments[0]);
		}

		if(hours_arg_no >= 2){
			this._minutes = parseInt(arguments[1]);
		}

		if(hours_arg_no >= 3){
			this._seconds = parseInt(arguments[2]);
		}

		if(hours_arg_no == 4){
			this._milliseconds = parseInt(arguments[3]);
		}

		while(hours >= 24){
			this._date++;
			var mdays = this.getDaysInIslamicMonth(this._month, this._year);
			if(this._date > mdays){
					this._month ++;
					if(this._month >= 12){this._year++; this._month -= 12;}
					this._date -= mdays;
			}
			hours -= 24;
		}
		this._hours = hours;
	},

	setMinutes:function(/*number*/minutes){
		//summary: set the Minutes

		while(minutes >= 60){
			this._hours++;
			if(this._hours >= 24){
				this._date++;
				this._hours -= 24;
				var mdays = this.getDaysInIslamicMonth(this._month, this._year);
				if(this._date > mdays){
						this._month ++;
						if(this._month >= 12){this._year++; this._month -= 12;}
						this._date -= mdays;
				}
			}
			minutes -= 60;
		}
		this._minutes = minutes;
	},
		
		
	setSeconds:function(/*number*/seconds){
		//summary: set Seconds
		while(seconds >= 60){
			this._minutes++;
			if(this._minutes >= 60){
				this._hours++;
				this._minutes -= 60;
				if(this._hours >= 24){
					this._date++;
					this._hours -= 24;
					var mdays = this.getDaysInIslamicMonth(this._month, this._year);
					if(this._date > mdays){
						this._month ++;
						if(this._month >= 12){this._year++; this._month -= 12;}
						this._date -= mdays;
					}
				}
			}
			seconds -= 60;
		}
		this._seconds = seconds;
	},
		
	setMilliseconds:function(/*number*/milliseconds){
		//summary: set the Millisconds
		while(milliseconds >= 1000){
			this.setSeconds++;
			if(this.setSeconds >= 60){
				this._minutes++;
				this.setSeconds -= 60;
				if(this._minutes >= 60){
					this._hours++;
					this._minutes -= 60;
					if(this._hours >= 24){
						this._date++;
						this._hours -= 24;
						var mdays = this.getDaysInIslamicMonth(this._month, this._year);
				if(this._date > mdays){
					this._month ++;
					if(this._month >= 12){this._year++; this._month -= 12;}
					this._date -= mdays;
					}
				}
			}
		}
			milliseconds -= 1000;
		}
		this._milliseconds = milliseconds;
	},
		
		
	toString:function(){
		// summary: This returns a string representation of the date in "DDDD MMMM DD YYYY HH:MM:SS" format
		// example:
		// |		var date1 = new dojox.date.islamic.Date();
		// |		document.writeln(date1.toString());

		//FIXME: TZ/DST issues?
		var x = new Date();
		x.setHours(this._hours);
		x.setMinutes(this._minutes);
		x.setSeconds(this._seconds);
		x.setMilliseconds(this._milliseconds);
		return this._month+" "+ this._date + " " + this._year + " " + x.toTimeString();
	},
		
		
	toGregorian:function(){
		// summary: This returns the equevalent Grogorian date value in Date object
		// example:
		// |		var dateIslamic = new dojox.date.islamic.Date(1429,11,20);
		// |		var dateGregorian = dateIslamic.toGregorian();

		var hYear = this._year;
		var hMonth = this._month;
		var hDate = this._date;
		var julianDay = hDate + Math.ceil(29.5 * hMonth) + (hYear - 1) * 354
						+ Math.floor((3 + (11 * hYear)) / 30) + this._ISLAMIC_EPOCH - 1;

		var wjd = Math.floor(julianDay - 0.5) + 0.5,
			depoch = wjd - this._GREGORIAN_EPOCH,
			quadricent = Math.floor(depoch / 146097),
			dqc = this._mod(depoch, 146097),
			cent = Math.floor(dqc / 36524),
			dcent = this._mod(dqc, 36524),
			quad = Math.floor(dcent / 1461),
			dquad = this._mod(dcent, 1461),
			yindex = Math.floor(dquad / 365),
			year = (quadricent * 400) + (cent * 100) + (quad * 4) + yindex;
		if(!(cent == 4 || yindex == 4)){
			year++;
		}
		
		var gYearStart = this._GREGORIAN_EPOCH + (365 * (year - 1)) + Math.floor((year - 1) / 4)
						- ( Math.floor((year - 1) / 100)) + Math.floor((year - 1) / 400);
						
		var yearday = wjd - gYearStart;
		
		var tjd = (this._GREGORIAN_EPOCH - 1) + (365 * (year - 1)) + Math.floor((year - 1) / 4)
				-( Math.floor((year - 1) / 100)) + Math.floor((year - 1) / 400) + Math.floor( (739 / 12)
				+ ( (dojo.date.isLeapYear(new Date(year,3,1)) ? -1 : -2)) + 1);
			
		var leapadj = ((wjd < tjd ) ? 0 : (dojo.date.isLeapYear(new Date(year,3,1)) ? 1 : 2));
					
		var month = Math.floor((((yearday + leapadj) * 12) + 373) / 367);
		var tjd2 = (this._GREGORIAN_EPOCH - 1) + (365 * (year - 1))
					+ Math.floor((year - 1) / 4) - (Math.floor((year - 1) / 100))
					+ Math.floor((year - 1) / 400) + Math.floor((((367 * month) - 362) / 12)
					+ ((month <= 2) ? 0 : (dojo.date.isLeapYear(new Date(year,month,1)) ? -1 : -2)) + 1);
					
		var day = (wjd - tjd2) + 1;

		var gdate = new Date(year, (month - 1), day, this._hours, this._minutes, this._seconds, this._milliseconds);

		return gdate;
	},

	//TODO: would it make more sense to make this a constructor option? or a static?
	// ported from the Java class com.ibm.icu.util.IslamicCalendar from ICU4J v3.6.1 at http://www.icu-project.org/
	fromGregorian:function(/*Date*/gdate){
		// summary: This function returns the equivalent Islamic Date value for the Gregorian Date
		// example:
		// |		var dateIslamic = new dojox.date.islamic.Date();
		// |		var dateGregorian = new Date(2008,10,12);
		// |		dateIslamic.fromGregorian(dateGregorian);

		var date = new Date(gdate);
		var gYear = date.getFullYear(),
			gMonth = date.getMonth(),
			gDay = date.getDate();
		
		var julianDay = (this._GREGORIAN_EPOCH - 1) + (365 * (gYear - 1)) + Math.floor((gYear - 1) / 4)
					+ (-Math.floor((gYear - 1) / 100)) + Math.floor((gYear - 1) / 400)
					+ Math.floor((((367 * (gMonth+1)) - 362) / 12)
					+ (((gMonth+1) <= 2) ? 0 : (dojo.date.isLeapYear(date) ? -1 : -2)) + gDay);
		julianDay = Math.floor(julianDay) + 0.5;

		var days = julianDay - this._ISLAMIC_EPOCH;
		var hYear  = Math.floor( (30 * days + 10646) / 10631.0 );
		var hMonth = Math.ceil((days - 29 - this._yearStart(hYear)) / 29.5 );
		hMonth = Math.min(hMonth, 11);
		var hDay = Math.ceil(days - this._monthStart(hYear, hMonth)) + 1;

		this._date = hDay;
		this._month = hMonth;
		this._year = hYear;
		this._hours = date.getHours();
		this._minutes = date.getMinutes();
		this._seconds = date.getSeconds();
		this._milliseconds = date.getMilliseconds();
		this._day = date.getDay();
		return this;
	},
	
	valueOf:function(){
		// summary: This function returns The stored time value in milliseconds
		// since midnight, January 1, 1970 UTC

		return this.toGregorian().valueOf();
	},

	// ported from the Java class com.ibm.icu.util.IslamicCalendar from ICU4J v3.6.1 at http://www.icu-project.org/
	_yearStart:function(/*Number*/year){
		//summary: return start of Islamic year
		return (year-1)*354 + Math.floor((3+11*year)/30.0);
	},

	// ported from the Java class com.ibm.icu.util.IslamicCalendar from ICU4J v3.6.1 at http://www.icu-project.org/
	_monthStart:function(/*Number*/year, /*Number*/month){
		//summary: return the start of Islamic Month
		return Math.ceil(29.5*month) +
			(year-1)*354 + Math.floor((3+11*year)/30.0);
	},

	// ported from the Java class com.ibm.icu.util.IslamicCalendar from ICU4J v3.6.1 at http://www.icu-project.org/
	_civilLeapYear:function(/*Number*/year){
		//summary: return Boolean value if Islamic leap year
		return (14 + 11 * year) % 30 < 11;
	},

	// ported from the Java class com.ibm.icu.util.IslamicCalendar from ICU4J v3.6.1 at http://www.icu-project.org/
	getDaysInIslamicMonth:function(/*Number*/month, /*Number*/ year){
		//summary: returns the number of days in the given Islamic Month
		var length = 0;
		length = 29 + ((month+1) % 2);
		if(month == 11 && this._civilLeapYear(year)){
			length++;
		}
		return length;
	},

	_mod:function(a, b){
		return a - (b * Math.floor(a / b));
	}
});

//TODOC
dojox.date.islamic.Date.getDaysInIslamicMonth = function(/*dojox.date.islamic.Date*/month){
	return new dojox.date.islamic.Date().getDaysInIslamicMonth(month.getMonth(),month.getFullYear()); // dojox.date.islamic.Date
};
