var MyUniversityRangeSlider = Class.create();
Object.extend(MyUniversityRangeSlider.prototype, Control.Slider.prototype);
Class.superrise(MyUniversityRangeSlider.prototype, ['initialize', 'setSpan']);
Object.extend(MyUniversityRangeSlider.prototype, {
	initialize: function(handle, track, options){
		this.super_initialize.apply(this, arguments);
		Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
	},
	setSpan: function(span, range) {
	  if(this.isVertical()) {
		this.super_setSpan.apply(this, arguments);
	  } else {
	    //this hack, to draw stan include current pos
		//Math.floor(x/2)*2 - to be sure that value is even
		var x =  parseInt(this.translateToPx(range.start));
		span.style.left = Math.floor(x/2)*2+'px';
		var w = parseInt(this.translateToPx(range.end - range.start + this.range.start + 1));
		span.style.width = Math.floor((w-2)/2)*2+'px';
	  }
	}
});
/* Table that each cell selectable. Used to specify holidays. */
var MonthExceptionsTable = Class.create();
MonthExceptionsTable.prototype = {
	initialize: function(table){
		this.table = $(table);
		this.cells = $A(this.table.rows[0].cells);
	}
}
/* Class that manage selection of multiple cells */
var TableSelectionManager = Class.create();
TableSelectionManager.prototype = {
	disabled: false,
	/*
	* @param holder element that contains all [rows]
	* @param rows array of table cells array
	* @options {disabled:bool, onRemoved: f, onAdded: f, onBeforeRemoveSelection: f }
	*/
	initialize: function(holder, rows, selectionData, options){
		Object.extend(this, options || {});//add listeners, and options

		this.holder = $(holder);
		this.rows = rows;
		this.cells = rows.flatten();

		this.active = false;//is selecting now?
		this.preselected = [];//elements that have 'pre-selected' class

		if( !this.disabled ){
			Event.observe(this.holder, "mousedown", this.startDrag.bind(this));
			Event.observe(document, "mousemove", this.updateDrag.bind(this));
			Event.observe(document, "mouseup", this.endDrag.bind(this));
		}

		this.cumulativeRowsCount = this.rows.inject([0], function(acc, row){ acc.push(acc.last() + row.length); return acc; });
		this.initSelection(selectionData);
	},
	initSelection: function(selectionData){
		selectionData.each(function(sel){
			var start = this.cumulativeRowsCount[sel.start_row] + sel.start_col;
			var end = this.cumulativeRowsCount[sel.end_row] + sel.end_col;
			var r = $R(start, end);
			r.id = sel.id;
			this.addSelection(r, false);
		}.bind(this));
	},
	getRowColByIndex: function(index){
		for(var i = 0, n = this.cumulativeRowsCount.length; i < n; i++){
			if( index < this.cumulativeRowsCount[i] ){
				return [i-1, index - this.cumulativeRowsCount[i-1] ];
			}
		}
	},
	addSelection: function(range, init){
		//init external properties
		if (init) {
			var startRC = this.getRowColByIndex(range.start);
			range.start_row = startRC[0];
			range.start_col = startRC[1];
			var endRC = this.getRowColByIndex(range.end);
			range.end_row = endRC[0];
			range.end_col = endRC[1];
		}
		//init cells
		for(var i = range.start; i <= range.end; i++){
			Element.addClassName(this.cells[i], 'selected');
			this.cells[i].selectionObj = range;
		}
	},
	removeSelection: function(range){
		for(var i = range.start; i <= range.end; i++){
			Element.removeClassName(this.cells[i], 'selected');
			this.cells[i].selectionObj = null;
		}
	},
	startDrag: function(event){
		if(Event.isLeftClick(event)) {
			var element = Event.element(event);
			if( element.nodeName != "TD" )
				return;
			var pointer  = [Event.pointerX(event), Event.pointerY(event)];
			var isOurCell = this.cells.any( function(cell){ return Position.within(cell, pointer[0], pointer[1] ) } );
			if( ! isOurCell ) return;
			if( element.hasClassName('selected') ){//remove selection
				var range = element.selectionObj;
				if( this.onBeforeRemoveSelection(range) ){
					this.removeSelection(range);
					this.onRemoved(range);
				}
			} else {
				this.active = true;
				this.startPoint = pointer;
				this.startCellIndex = this.cells.indexOf(element);
			}
			Event.stop(event);
		}
	},
	updateDrag: function(event){
		if( this.active ){
			var pointer  = [Event.pointerX(event), Event.pointerY(event)];
			this.preselected.each(function(cell){ Element.removeClassName(cell, 'pre-selected') })
			this.preselected = this.getSelectedCellsForPoint(pointer).map(function(i){
				Element.addClassName(this.cells[i], 'pre-selected');
				return this.cells[i];
			}.bind(this));
			Event.stop(event);
		}
	},
	endDrag: function(event){
		if( this.active ){
			this.active = false;
			var pointer  = [Event.pointerX(event), Event.pointerY(event)];
			this.preselected.each(function(cell){ Element.removeClassName(cell, 'pre-selected') })
			var range = this.getSelectedCellsForPoint(pointer);
			this.addSelection(range, true );
			this.onAdded(range);
			Event.stop(event);
		}
	},
	getSelectedCellsForPoint: function(endPoint){
		//determine leftTop and rightBottom points of selection
		var dx = endPoint[0] - this.startPoint[0];
		var dy = endPoint[1] - this.startPoint[1];
		if( dx >= 0 || dy >= 0 ){
			var leftP = this.startPoint;
			var rightP = endPoint;
		} else {
			var leftP = endPoint;
			var rightP = this.startPoint;
		}
		//lets think that all cells have same sizes (for perfomance, but I not check it, and possibe that it's not need, and offcetWidth can be used)
		var w = this.cells[0].offsetWidth;
		var h = this.cells[0].offsetHeight;
		//find first cell that greater then TopLeft point of selection
		var start = this.startCellIndex;
		for(var i=0; i <= this.cells.length; i++){
			var cp = Position.cumulativeOffset(this.cells[i]);
			if( (cp[0]+w) >= leftP[0] && (cp[1]+h) >= leftP[1] ) {
				start = i;
				break;
			}
		}
		//find first cell from END that less then BottomRight point of selection
		var end = start;
		for(var i = this.cells.length-1; i >= 0; i--){
			var cp = Position.cumulativeOffset(this.cells[i]);
			if( cp[0] <= rightP[0] && cp[1] <= rightP[1] ){
				end = i;
				break;
			}
		}
		var range = $R(Math.min(start, end), Math.max(start, end));//standart 'sort()' compare integers line strings, so I use min/max to sort. SORT should be no need, but algorithm is bad and it's is need, since user can drag from right-to-left
		//no allow selection overlaping, stop selection when next reached
		if( range.start == this.startCellIndex ){//from left to right
			for(var i = range.start; i <= range.end; i++){
				if( this.cells[i].selectionObj != null ){
					range.end = i-1;
					break;
				}
			}
		}else{//from right to left
			for(var i = range.end; i >= range.start; i--){
				if( this.cells[i].selectionObj != null ){
					range.start = i+1;
					break;
				}
			}
		}
		return range;
	},
	/* @return true if selection should be removed, else - false  */
	onBeforeRemoveSelection: function(range){
		var message = range.start == range.end ?
					  "Are you you sure you want to delete this holiday?" :
					  "Are you sure you want to delete this range?";
		return confirm(message);
	},
	/* this handler save should set to 'range' param real ID */
	onAdded: function(selection){  },
	/* selection removed from grid, remove it from datastore */
	onRemoved: function(selection){  }
}

/* Class that manage all schedule calendar controls */
var ScheduleCalendar = {
	disabled: false,
	initialize: function(data, holidays, options){
		this.tables = $A($('timeRange').getElementsByTagName('TABLE')).map(function(table){ return new MonthExceptionsTable(table) });

		this.year = data.year;
		this.semester = data.semester;
		this.data = data;
		Object.extend(this, options || {});

		//setup selection
		new TableSelectionManager('timeRange',
				this.tables.map(function(t){ return $A(t.cells) }),
				holidays,
				{ onAdded: this.saveAddedHoliday.bind(this), 
					onRemoved: this.saveRemovedHoliday.bind(this),
					disabled: this.disabled
				}
		);
		//disable build-in selection mechanism
		if( navigator.appVersion.match(/\bMSIE\b/) ){
			Event.observe('timeRange', 'selectstart', function(event){ Event.stop(event) });
		}

		//setup holidays editors
		if( !this.disabled )
			this.initHolidaysEditors(holidays);

		//setup top slider
		var firstRange = $R(1, this.tables.first().cells.length);
		$('timeRangeFirstSliderDayN').innerHTML = data.start_day;
		this.firstSlider = new MyUniversityRangeSlider('timeRangeFirstSliderHandle','timeRangeFirstSlider',
			{axis:'horizontal',
				range: firstRange,
				values: firstRange.toArray(),
				sliderValue: data.start_day,
				endSpan: 'timeRangeFirstSliderSpan',
				onSlide: function(value){
					$('timeRangeFirstSliderDayN').innerHTML = value;
				},
				onChange: this.save.bind(this),
				disabled: this.disabled
			});
		//setup bottom slider
		var lastRange = $R(1, this.tables.last().cells.length);
		$('timeRangeLastSliderDayN').innerHTML =data.end_day;
		this.lastSlider = new MyUniversityRangeSlider('timeRangeLastSliderHandle','timeRangeLastSlider',
			{axis:'horizontal',
				range: lastRange,
				values: lastRange.toArray(),
				sliderValue: data.end_day,
				startSpan: 'timeRangeLastSliderSpan',
				onSlide: function(value){
					$('timeRangeLastSliderDayN').innerHTML = value;
				},
				onChange: this.save.bind(this),
				disabled: this.disabled
			});
	},
	initHolidaysEditors: function(holidays){
		holidays.each(function(holiday){
			//set editor
			new MyInPlaceEditor('holidaySubject'+holiday.id, URL.saveHolidayName, {
					onComplete: Prototype.emptyFunction,
					highlightendcolor: '',
					savingClassName: 'saving',
					callback: function(form){ return Form.serialize(form)+'&id='+holiday.id + '&year=' + this.year + '&semester=' + this.semester }.bind(this)
				});
		}.bind(this));
	},
	showIndicator: function(){ Element.addClassName('universityCalendar', 'updating') },
	hideIndicator: function(){ Element.removeClassName('universityCalendar', 'updating') },
	semesterChanged: function(spinnerUp, spinnerValue){
		this.showIndicator();		
		new Ajax.Updater(
			{success: 'universityCalendarBody'},
			URL.changePeriod,
			{ parameters: spinnerValue+'&next_semester='+spinnerUp, 
				evalScripts: true }
		);
	},
	save: function(){
		this.showIndicator();
		new Ajax.Request(URL.saveRange, {parameters: 'year='+this.year
				+'&semester='+this.semester
				+'&start_day='+this.firstSlider.value
				+'&end_day='+this.lastSlider.value,
				onSuccess: this.hideIndicator
		});
	},
	saveAddedHoliday: function(sel){
		this.showIndicator();
		var params = 'year=' + this.year + '&semester=' + this.semester
				+ '&start_row=' + sel.start_row + '&start_col=' + sel.start_col
				+ '&end_row=' + sel.end_row + '&end_col=' + sel.end_col;
		new Ajax.Request(URL.addHoliday, {
			parameters: params,
			onSuccess: function(transport) {
				sel.id = parseInt(transport.responseText) || null;
				//update holidays area
				new Ajax.Updater(
					{success: 'holidays'},	URL.reloadHolidays,
					{ parameters: 'year=' + this.year + '&semester=' + this.semester+'&new_holiday_id='+sel.id, evalScripts: true }
				);
			}.bind(this)
		});
	},
	saveRemovedHoliday: function(sel) {
		this.showIndicator();
		//remove editor
		Element.remove('holidayItem'+sel.id);
		//remove from DB
		new Ajax.Request(URL.removeHoliday, {
			parameters: 'id=' + sel.id+'&year=' + this.year + '&semester=' + this.semester,
			onSuccess: this.hideIndicator
		});
	}
}
if( navigator.appVersion.match(/\bMSIE\b/) ){//this fix only for IE, since it not support CSS :hover for non A element
	Behaviour.register({
		"#timeRange .range-slider TD": function(el){
			el.onmouseover = function(){ Element.addClassName(this, 'hover') }
			el.onmouseout = function(){ Element.removeClassName(this, 'hover') }
		}
	})
}
