// Copyright 2008 ProjectWizards GmbH. All rights reserved.

//console.log("GanttRuler.js");

var eGanttRulerMode;
if(!eGanttRulerMode) eGanttRulerMode = {
	PlannedAndExpected: 0,
	OnlyPlanned: 1,
	OnlyExpected: 2
}

var GanttRuler = Base.extend({
	constructor: function(rulerDescription, environment, project) {
		this.project = project;
		this.environment = environment;
		this.zoom_factor = 1.25;
		this.defaultStartTime = 8.0;	
		this.defaultEndTime = 17.0;
		this.pixelsPerDay;
		this.rows = [];
		this.startDate = null;
		this.startDateAT;
		this.endDate;	
		this.adjustedStartDate;
		this.adjustedEndDate;
		this.adjustOnlyByExpanding;
		this.ganttRuleMode = eGanttRulerMode.OnlyPlanned;
		this.primaryUnit = eDurationUnit.MEDays;
		this.oldPrimary = this.primaryUnit;
		this.ganttMargin = 0;
		this.limitingActivities = [];
		this.mode = eGanttRulerMode.OnlyExpected;
		if(rulerDescription != null)
			this.createRowsFromDescription(rulerDescription);
		else
			this.createStandardRows();
	},
	
	clone: function(){
		var ruler = new GanttRuler(this.rulerDescription(), this.environment);
	    ruler.project = this.project;						 
		return ruler;
	},
	
	setLimitingActivities: function(activities) {
		this.limitingActivities = activities;
		this.updateStartDate();
		this.updateEndDate();
	},
	
	adjustFromDateToDateOnlyByExpanding: function(start, end, byExpanding) {
		this.adjustOnlyByExpanding = byExpanding;
		if(!this.startDate.isEqual(this._startDateWithAdjustment(start)) || !this.endDate.isEqual(this._endDateWithAdjustment(end))){
			this.adjustedStartDate = new Date(start.getTime());
			this.adjustedEndDate   = new Date(end.getTime());
			this.updateStartDate();
			this.updateEndDate();
			return true;
		}
		return false;
	},

	createStandardRows: function() {
		var description = [{unit:eDurationUnit.MEDays, style:0}, {unit:eDurationUnit.MECalendarWeeks, style:5}];
		this.createRowsFromDescription( { rows: description, pixelsPerDay: 30 });
	},

	createRowsFromDescription: function(description) {
		this.rows = [];
		var rowDescription = description["rows"];
		for(var i=0; i<rowDescription.length; i++){
			this.addRowWithUnitStyle(rowDescription[i]["unit"], rowDescription[i]["style"]);
		}
		this.updatePixelsPerDay();
		this.setPixelsPerDay(description["pixelsPerDay"]);
	},

	rulerDescription: function() {
		var result = {};
		result.rows = [];
		for(var i=0; i<this.rows.length; i++){
			result.rows[i]={unit:this.rows[i].unit, style:this.rows[i].style};
		}			
		result.pixelsPerDay = this.pixelsPerDay;
		return result;	
	},

	addRowWithUnitStyle: function(unit, style) {
		return this.addRowWithUnitStyleWithOrder(unit, style, this.rows.length);
	},

	addRowWithUnitStyleWithOrder: function(unit, style, order) {
		var row = new GanttRulerRow(this, this.environment);
		this.rows.splice(order, 0, row);
		row.setUnit(unit);
		row.setStyle(style);
		this.setPrimaryUnit(this.primaryRow().unit);
		this.updateModificationDate();
		return row;
	},

	rows: function() {
		return this.rows;
	},

	setPrimaryUnit: function(value)	{
		this.setPrimaryUnitAdjustOtherUnits(value, false);
	},

	setPrimaryUnitAdjustOtherUnits: function(unit, adjust) {
		var numRows = this.rows.length;
		if(adjust){
			this.oldPrimary = this.primaryUnit;
			if(unit < this.oldPrimary){
				for(var rowIndex=1; rowIndex<numRows; rowIndex++){
					var row = this.rows[rowIndex];
					row.setUnit(GanttRulerRow.oneUnitSmallerThanUnitIncludingTimes(row.unit));
				}
			}else if(unit > this.oldPrimary){
				for(var rowIndex=1; rowIndex<numRows; rowIndex++){
					var row = this.rows[rowIndex];
					row.setUnit(GanttRulerRow.oneUnitBiggerThanUnit(row.unit));
				}
			}
		}
		if(numRows){
			var row = this.rows[0];
			if(row.unit!=unit)
				row.setUnit(unit);
		}
		this.primaryUnit = unit;
		this.updateStartDate();
		this.updateEndDate();
	},

	setEndDate: function(value)	{
		var changed = (!value && this.endDate) || (!this.endDate && value) || (value && !value.isEqual(this.endDate));
		this.endDate = value;
		if(changed)
			this.updateModificationDate();
	},

	setStartDate: function(value) {
		var changed = (!value && this.startDate) || (!this.startDate && value) || (value && !value.isEqual(this.startDate));
		this.startDate = new Date(value.getTime());
		if(changed)
			this.updateModificationDate();
	},

	setStartDateAT: function(value)	{
		this.startDateAT = value;
	},

	updateModificationDate: function() {
		this.modificationDate = new Date();
	},

	maxDateWithNameInActivities: function(name, activities)	{
		var maxDate = null;
		if(activities){
			for(var i=0; i<activities.length; i++){
				var date = activities[i].valueForKey(name);
				if(date){
					date = new Date(date.getTime());
					if(!maxDate || date.getTime()>maxDate.getTime())
						maxDate = date;
				}
			}
		}
		if(!maxDate)
			maxDate = new Date();
		return maxDate;
	},

	_endDateWithAdjustment: function(adjustment) {
		var result = null;
		if(this.givenEndDate)
			result = new Date(this.givenEndDate.getTime());
		else{
			if(this.mode == eGanttRulerMode.OnlyPlanned)
				result = this.maxDateWithNameInActivities("plannedEndDate", this.limitingActivities);
			else if(this.mode == eGanttRulerMode.OnlyExpected)
				result = this.maxDateWithNameInActivities("expectedEndDate", this.limitingActivities);
			else{
				var maxPlanned = this.maxDateWithNameInActivities("plannedEndDate", this.limitingActivities);
				var maxExpected = this.maxDateWithNameInActivities("expectedEndDate", this.limitingActivities);
				result = maxPlanned.getTime()>maxExpected.getTime() ? maxPlanned : maxExpected;
			}
		}
		
		if(!result)
			result = new Date();
		if(adjustment && (!this.adjustOnlyByExpanding || (result.getTime() < adjustment.getTime())))
			result = adjustment;	
		var primaryRow = this.primaryRow();
		if(primaryRow){
			var start = primaryRow.startOfUnitContainingDateClipAtDate(result, null);
			result = primaryRow.endOfUnitWithStartDateClipAtDate(start, null);
		}
		return result;
	},

	_endDate: function() {
		return this._endDateWithAdjustment(this.adjustedEndDate);
	},


	updateEndDate: function() {
		this.setEndDate(this._endDate());
	},

	minDateWithNameInActivities: function(name, activities)	{
		var minDate = null;
		if(activities){
			for(var i=0; i<activities.length; i++){
				var date = activities[i].valueForKey(name);
				if(date){
					date = new Date(date.getTime());
					if(!minDate || date.getTime()<minDate.getTime())
						minDate = date;
				}
			}
		}
		if(!minDate)
			minDate = new Date();
		return minDate;
	},

	_startDateWithAdjustment: function(adjustment) {
		var result = null;
		if(this.givenStartDate)
			result = new Date(this.givenStartDate.getTime());
		else{
			if(this.mode == eGanttRulerMode.OnlyPlanned)
				result = this.minDateWithNameInActivities("plannedStartDate", this.limitingActivities);
			else if(this.mode == eGanttRulerMode.OnlyExpected)
				result = this.minDateWithNameInActivities("expectedStartDate", this.limitingActivities);
			else{
				var minPlanned = this.minDateWithNameInActivities("plannedStartDate", this.limitingActivities);
				var minExpected = this.minDateWithNameInActivities("expectedStartDate", this.limitingActivities);
				result = minPlanned.getTime()>minExpected.getTime() ? minPlanned : minExpected;
			}
		}
		if(!result)
			result = new Date();
		if(adjustment && (!this.adjustOnlyByExpanding || (result.getTime() > adjustment.getTime())))
			result = adjustment;	
		var primaryRow = this.primaryRow();
		if(primaryRow)
			result = primaryRow.startOfUnitContainingDateClipAtDate(result, null);
		return result;
	},


	_startDate: function() {
		return this._startDateWithAdjustment(this.adjustedStartDate);
	},

	updateStartDate: function()	{
		var newDate = this._startDate();
		this.setStartDate(newDate);
		this.setStartDateAT(newDate.getTime()/1000);
	},

	primaryRow: function() {
		return this.rows.length ? this.rows[0] : null;
	},

	primaryUnitForPixelsPerDay: function(pixels) {
		var unit = this.primaryUnit;
		var newUnit = unit;
		if(unit==eDurationUnit.MEMinutes){
			if(pixels<1600.0)
				newUnit = eDurationUnit.MEHours;
		}else if(unit==eDurationUnit.MEHours){
			if(pixels<100.0)
				newUnit = eDurationUnit.MEDays;
			else if(pixels>=1600.0)
				newUnit = eDurationUnit.MEMinutes;
		}else if(unit==eDurationUnit.MEDays){
			if(pixels<16.0)
				newUnit = eDurationUnit.MECalendarWeeks;
			else if(pixels>=100.0)
				newUnit = eDurationUnit.MEHours;
		}else if(unit==eDurationUnit.MECalendarWeeks){
			if(pixels<5.0)
				newUnit = eDurationUnit.MEMonths;
			else if(pixels>=16.0)
				newUnit = eDurationUnit.MEDays;
		}else if(unit==eDurationUnit.MEMonths){
			if(pixels<0.8)
				newUnit = eDurationUnit.MEQuarterYears;
			else if(pixels>=5.0)
				newUnit = eDurationUnit.MECalendarWeeks;
		}else if(unit==eDurationUnit.MEQuarterYears){
			if(pixels<30.0/(3.0*28.0))
				newUnit = eDurationUnit.MEYears;
			else if(pixels>=0.8)
				newUnit = eDurationUnit.MEMonths;
		}else if(unit==eDurationUnit.MEYears){
			if(pixels>=30.0/(3.0*28.0))
				newUnit = eDurationUnit.MEQuarterYears;
		}
		return newUnit;
	},

	setPixelsPerDay: function(pixels) {
		this.setPrimaryUnitAdjustOtherUnits(this.primaryUnitForPixelsPerDay(pixels), true);
		this.pixelsPerDay = pixels;
	},
	
	updatePixelsPerDay: function() {
		var pixels = GanttRuler.defaultPixelsPerDayForUnit(this.primaryUnit);
		this.setPixelsPerDay(pixels);
	},

	setGanttMargin: function(margin) {
		this.ganttMargin = margin;
	},

	dateToPosRoundClipWorkTime: function(date, round, clip)	{
		var diffSeconds = date ? (date.getTime()/1000.0) - this.startDateAT : this.startDateAT;
		var diffDays = Math.floor(diffSeconds/(3600.0*24.0));
		diffSeconds -= diffDays*3600.0*24.0;
		var dateHour = diffSeconds/3600.0;
		var defaultEndHour = this.defaultEndTime;
		var defaultStartHour = this.defaultStartTime;
		var defaultDayGrossHours = defaultEndHour - defaultStartHour;
		var negative = diffDays<0.0 || diffSeconds<0.0;
		var afterWorkEnd = negative ? clip && 24.0+dateHour >= defaultEndHour : clip && dateHour >= defaultEndHour;
		var beforeWorkStart = negative ? clip && 24.0+dateHour <= defaultStartHour : clip && dateHour <= defaultStartHour;

		if(afterWorkEnd && !negative)
			diffDays ++;
		else if(beforeWorkStart && negative)
			diffDays --;
		var pos = this.ganttMargin + diffDays*this.pixelsPerDay;
		if(!afterWorkEnd && !beforeWorkStart) {
			var dayHours = clip ? defaultDayGrossHours : 24.0;
			var pixelsPerHour = this.pixelsPerDay / dayHours;
			var hours;		
			if(clip)
				hours = negative ? dateHour - defaultEndHour : dateHour - defaultStartHour;
			else
				hours = negative ? 24.0 - dateHour :  dateHour;
			pos += pixelsPerHour * hours;
		}
		if(round)
			pos = Math.floor(pos);		
		return pos;
	},

	dateToPos: function(date) {
		if(!date)
			return 0;
		return this.dateToPosRoundClipWorkTime(date, true, this.primaryUnit!=eDurationUnit.MEHours && this.primaryUnit!=eDurationUnit.MEMinutes);
	},

	dateToPosRound: function(date, round) {
		return this.dateToPosRoundClipWorkTime(date, round, this.primaryUnit!=eDurationUnit.MEHours && this.primaryUnit!=eDurationUnit.MEMinutes);
	},

	posToDatePreferEndDateClipWorkTime: function(pos, preferEndDate,clip) {
		var defaultEndHour = this.defaultEndTime;
		var defaultStartHour = this.defaultStartTime;
		var defaultDayGrossHours = defaultEndHour - defaultStartHour;
		var defaultStartSeconds = defaultStartHour * 3600.0;
		var defaultEndSeconds = defaultEndHour * 3600.0;
		pos -= this.ganttMargin;
		var days = Math.floor(pos/this.pixelsPerDay);
		if(clip && this.primaryUnit==eDurationUnit.MEHours)
			clip = false;
		var dayHours = clip ? defaultDayGrossHours : 24.0;
		var pixelsPerSecond = this.pixelsPerDay / dayHours / 3600.0;
		var seconds = (pos-days*this.pixelsPerDay) / pixelsPerSecond;
		if(clip){
			if(seconds>0)
				seconds += defaultStartSeconds;
			else if(seconds<0){
				days--;
				seconds += defaultEndSeconds;
			}else if(preferEndDate){
				seconds = defaultEndSeconds;
				days--;
			}else
				seconds = defaultStartSeconds;
		}
		if(this.primaryUnit!=eDurationUnit.MEMinutes){
			var minuteModulo = seconds % 60;
			if(minuteModulo){
				if(minuteModulo>30)
					seconds += 60-minuteModulo;
				else 
					seconds -= minuteModulo;
			}
		}
		return new Date((this.startDateAT + 3600.0*24.0*days + seconds)*1000.0);
	},

	posToDate: function(pos) {
		return this.posToDatePreferEndDateClipWorkTime(pos, false, this.primaryUnit!=eDurationUnit.MEHours && this.primaryUnit!=eDurationUnit.MEMinutes);
	},

	posToDatePreferEndDate: function(pos, preferEndDate) {
		return this.posToDatePreferEndDateClipWorkTime(pos, preferEndDate, this.primaryUnit!=eDurationUnit.MEHours && this.primaryUnit!=eDurationUnit.MEMinutes);
	},

	startOfUnitContainingDateClipAtDateExtendIfTooThin: function(unit, aDate, clipDate, extend) {
		var date = new Date(aDate.getTime());
		var start = null;
		date.setUTCSeconds(0,0);
		if(unit==eDurationUnit.MEMinutes){
			var minute = date.getUTCMinutes();
			if(extend){
				var widthOfUnit = this.pixelsPerDay/(24.0*60.0);
				var minWidth = 18.0;
				var numUnits = widthOfUnit<minWidth ? Math.ceil(minWidth / widthOfUnit) : 1;
				if(numUnits>10)
					numUnits = 15;
				else if(numUnits>5)
					numUnits = 10;
				else if(numUnits>1)
					numUnits = 5;
				else
					numUnits = 1;
				minute = Math.floor(minute / numUnits) * numUnits;
			}
			date.setUTCMinutes(minute);
			start = date;
		}else if(unit==eDurationUnit.MEHours){
			var hour = Math.floor(date.getUTCHours());
			if(extend){
				var widthOfUnit = this.pixelsPerDay/24.0;
				var minWidth = 18.0;
				var numUnits = widthOfUnit<minWidth ? Math.ceil(minWidth / widthOfUnit) : 1;
				if(numUnits>4)
					numUnits = 4;
				hour = Math.floor(hour / numUnits) * numUnits;
			}
			date.setUTCHours(hour);
			date.setUTCMinutes(0);
			start = date;
		}else if(unit==eDurationUnit.MEDays){
			date.setUTCHours(0);
			date.setUTCMinutes(0);
			start = date;
		}else if(unit==eDurationUnit.MECalendarWeeks){
			var year = date.getUTCFullYear();
			var cw = date.getCalendarWeekUTC(this.environment);
			if(cw == 1 && date.getUTCMonth() == 11)
				year++;
			else if(cw >= 50 && date.getUTCMonth() == 0)
				year--;
			start = Date.startOfCalendarWeekInYear(cw, year, true, this.environment);
		}
		else if(unit==eDurationUnit.MEMonths){
			date.setUTCDate(1);	//ak utc
			date.setUTCHours(0);
			date.setUTCMinutes(0);
			start = date;
		}else if(unit==eDurationUnit.MEQuarterYears){
			var date2 = new Date(Date.UTC(date.getUTCFullYear(),date.getUTCMonth()));
			var quarterStartMonth = Math.floor(date2.getUTCMonth()/3)*3;
			date2.setUTCMonth(quarterStartMonth);	//ak utc
			date2.setUTCDate(1);	//ak utc
			date2.setUTCHours(0);
			date2.setUTCMinutes(0);
			start = date2;
		}else if(unit==eDurationUnit.MEYears){
			date.setUTCMonth(0);	//ak utc
			date.setUTCDate(1);		//ak utc
			date.setUTCHours(0);
			date.setUTCMinutes(0);
			start = date;
		}if(clipDate){	
			if(start.getTime() < clipDate.getTime())
				start = clipDate;
		}
		return start;
	},

	endOfUnitWithStartDateClipAtDateExtendIfTooThin: function(unit, date, clipDate, extend) {
		var end = null;	
		if(unit==eDurationUnit.MEMinutes){
			var numMinutes;
			if(extend){
				var widthOfUnit = this.pixelsPerDay/(24.0*60.0);
				var minWidth = 18.0;
				numMinutes = widthOfUnit<minWidth ? Math.ceil(minWidth / widthOfUnit) : 1;
				if(numMinutes>10)
					numMinutes = 15;
				else if(numMinutes>5)
					numMinutes = 10;
				else if(numMinutes>1)
					numMinutes = 5;
				else
					numMinutes = 1;
			}else
				numMinutes = 1;
			end = Date.add("n", numMinutes, date, true);
		}else if(unit==eDurationUnit.MEHours){
			var numHours;
			if(extend){
				var widthOfUnit = this.pixelsPerDay/24.0;
				var minWidth = 18.0;
				numHours = widthOfUnit<minWidth ? Math.ceil(minWidth / widthOfUnit) : 1;
				if(numHours>4)
					numHours = 4;
			}
			else
				numHours = 1;
			end = Date.add("h", numHours, date, true);
			end.setUTCMinutes(0);	//ak
			end.setUTCSeconds(0);
		}else if(unit==eDurationUnit.MEDays){
			end = date.nextDay(true);
			end.setUTCHours(0);		//ak
			end.setUTCMinutes(0);
			end.setUTCSeconds(0);
		}else if(unit==eDurationUnit.MECalendarWeeks){
			var cw = date.getCalendarWeekUTC(this.environment);
			var year = date.getUTCFullYear();
			if(cw == 1 && date.getUTCMonth() == 11)
				year++;
			var weekStart = Date.startOfCalendarWeekInYear(cw, year, true, this.environment);
			end = Date.add("d", 7, weekStart, true);
		}else if(unit==eDurationUnit.MEMonths){
			var monthStart = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1));
			end = Date.add("m", 1, monthStart, true);
		}else if(unit==eDurationUnit.MEQuarterYears){
			var comps = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth()));
			var quarterStartMonth =  Math.floor(comps.getUTCMonth()/3)*3;
			comps.setUTCMonth(quarterStartMonth);
			comps.setUTCDate(1);
			end = Date.add("m", 3, comps, true);
		}else if(unit==eDurationUnit.MEYears){
			var comps = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
			comps.setUTCFullYear(comps.getUTCFullYear()+1);
			end = comps;
		}
		if(clipDate){
			if(end.getTime() > clipDate.getTime())
				end = new Date(clipDate.getTime());
		}	
		return end;
	},

	endOfUnitContainingDateClipAtDateExtendIfTooThin: function(unit, date, clipDate, extend) {
		var start = this.startOfUnitContainingDateClipAtDateExtendIfTooThin(unit, date, clipDate, extend);
		if(start.isEqual(date))
			return start;
		return this.endOfUnitWithStartDateClipAtDateExtendIfTooThin(unit, start, clipDate, extend);
	},

	roundPosToUnitWithParts: function(pos, unit, parts)	{
		// Auf den n-ten Teil einer Einheit runden
		var posDate = this.posToDate(pos);
		var extend = this.primaryUnit<=unit;
		var start = this.startOfUnitContainingDateClipAtDateExtendIfTooThin(unit, posDate, null, extend);
		var end = this.endOfUnitWithStartDateClipAtDateExtendIfTooThin(unit, start, null, extend);
		var startPos = this.dateToPosRound(start, false);
		var endPos = this.dateToPosRound(end, false);	
		var deltaPart = (endPos-startPos) / parts;
		var partNum = Math.round((pos - startPos) / deltaPart);
		return startPos + deltaPart * partNum;
	},

	rowsCode: function() {
		var sum = 0;
		for(var i = 0; i<this.rows.length; i++){
			sum += this.rows[i].rowCode();
		}	
		return sum;
	},

	zoomIn: function() {
		var newPixelsPerDay = this.pixelsPerDay*this.zoom_factor;
		this.setPixelsPerDay(newPixelsPerDay);	
	},

	zoomOut: function()	{
		var newPixelsPerDay = this.pixelsPerDay/this.zoom_factor;
		this.setPixelsPerDay(newPixelsPerDay);	
	},

	setGivenStartDate: function(date) {
		this.givenStartDate = new Date(date.getTime());
		this.updateStartDate();
		this.updateEndDate();
	},

	setGivenEndDate: function(date)	{
		this.givenEndDate = new Date(date.getTime());
		this.updateStartDate();
		this.updateEndDate();
	},
	
	numberOfRulerUnits: function(){
		var result = 0;
		var start = this.startDate;
		var endTime = this.endDate.getTime();
		while(start.getTime() <  endTime){
			start =	this.endOfUnitWithStartDateClipAtDateExtendIfTooThin(this.primaryUnit, start, null, true);
			result ++;
		}
		return result;
	}
	
	
}, 
	// Class Interface:
{
	defaultPixelsPerDayForUnit: function(unit) { 		
		var pixels = 30.0;
		if(unit == eDurationUnit.MEMinutes)
			pixels = 1700.0;
		else if(unit == eDurationUnit.MEHours)
			pixels = 160.0;
		else if(unit == eDurationUnit.MEDays)
			pixels = 24.0;
		else if(unit == eDurationUnit.MECalendarWeeks)
			pixels = 6.0;
		else if(unit == eDurationUnit.MEMonths)
			pixels = 2.0;
		else if(unit == eDurationUnit.MEQuarterYears)
			pixels = 1.2*30.0/(3.0*28.0);
		else if(unit == eDurationUnit.MEYears)
			pixels = 30.0/365.0;
		return pixels;
	}
});