/**
 * Copyright 2007 Debyte Oy /Teemu Lehtinen, VKALENTERI.NET
 *
 * Generates html table for calendar rows.
 */

var WORD_LIMIT = 10;

/**
 * Constructor.
 */
function CalendarRows(rowPeriod, rowTransfer, reserveSkip, offset, minTime, maxTime, curTime, ds) {

	// Properties.
	this.rowPeriod = rowPeriod;
	this.rowTransfer = rowTransfer;
	this.reserveSkip = reserveSkip;
	this.offset = offset;
	this.minTime = minTime;
	this.maxTime = maxTime;
	this.begTime = maxTime;
	this.endTime = minTime;
	this.curTime = curTime;
	this.ds = ds;
	this.ocells = new Object();
	this.ocells["0"] = new Object();
	this.rcells = new Object();
	this.rcells["0"] = new Object();
	this.lines = new Object();
	this.lines["0"] = new Object();

	this.entryHtml = '<td{_lin} style="background: #{color};"><a href="{url}?id={id}&ext={ext}" title="{desc}">{name}</a>{more}</td>';
	this.signHtml = ' <form name="sign{id}" method="post"><input type="hidden" name="signid" value="{id}" /><input type="submit" value="{signlabel}" class="sign" /></form>';
	this.attendHtml = ' <a href="{aurl}?id={id}&ext={ext}">{attend}</a>';
	this.continuedHtml = '<td{_lin} style="background: #{color};">"</td>';
	this.freeHtml = '<td{_lin}><form name="add{target}{time}" method="get" action="{url}"><input type="hidden" name="id" value="new" /><input type="hidden" name="target" value="{target}" /><input type="hidden" name="time" value="{time}" /><input type="hidden" name="dt" value="{dt}" /><input type="hidden" name="ext" value="{ext}" /><input type="submit" value="+" class="calrows" title="{price}/h" /></form></td>';
	this.closedHtml = '<td class="closed"><form name="add{target}{time}" method="get" action="{url}"><input type="hidden" name="id" value="new" /><input type="hidden" name="target" value="{target}" /><input type="hidden" name="time" value="{time}" /><input type="hidden" name="ext" value="{ext}" /><input type="submit" value="+" class="calrows" /></form></td>';
	this.reserveHtml = null;

	// Methods.
	this.setOpen = cr_setOpen;
	this.setClosed = cr_setClosed;
	this.isOpen = cr_isOpen;
	this.isReservable = cr_isReservable;
	this.isLine = cr_isLine;
	this.setTargets = cr_setTargets;
	this.setEntries = cr_setEntries;
	this.addCell = cr_addCell;
	this.getPeriodTimes = cr_getPeriodTimes;
	this.getTimeString = cr_getTimeString;
	this.formatHtml = cr_formatHtml;
	this.getTableHtml = cr_getTableHtml;
	this.getEmptyHtml = cr_getEmptyHtml;
	this.getEntryHtml = cr_getEntryHtml;
}

/**
 * Sets open time periods.
 */
function cr_setOpen(open) {
	for(var i = 0; i < open.length; i++) {
		var tt = open[i]["target"];
		var beg = parseInt(open[i]["begtime"]);
		var end = parseInt(open[i]["endtime"]);

		// Crop to limits.
		if(beg < this.minTime) {
			if(end < this.minTime) {
				continue;
			}
			beg = this.minTime;
		}
		if(end > this.maxTime) {
			if(beg > this.maxTime) {
				continue;
			}
			end = this.maxTime;
		}

		// Update calendar beg and end times.
		if(beg < this.begTime) {
			this.begTime = beg;
		}
		if(end > this.endTime) {
			this.endTime = end;
		}

		// Create open maps and lines per target types.
		if(this.ocells[tt] == undefined) {
			this.ocells[tt] = new Object();
			this.rcells[tt] = new Object();
			this.lines[tt] = new Object();
		}

		// Get reservable and skip settings.
		var res = open[i]["userentryflag"];
		var trans = open[i]["userentrytransfer"];
		var skip = this.reserveSkip;
		if(open[i]["userentryskip"] >= 0) {
			skip = parseInt(open[i]["userentryskip"]);
		}

		// Open cells in period.
		var times = this.getPeriodTimes(beg, end);
		this.lines[tt][times[0].toString()] = true;
		for(var j = 0; j < times.length; j++) {
			var tj = times[j].toString();
			this.ocells[tt][tj] = open[i];
			this.rcells[tt][tj] = res;
			if(res && skip > 0) {
				var rownum = (times[j] - this.rowTransfer) / this.rowPeriod;
				if((rownum - trans) % (skip + 1) != 0) {
					this.rcells[tt][tj] = 0;
				}
			}
		}
	}
}

/**
 * Sets closed time periods.
 */
function cr_setClosed(closed) {
	for(var i = 0; i < closed.length; i++) {
		var tt = closed[i]["target"];
		var beg = parseInt(closed[i]["begtime"]);
		var end = parseInt(closed[i]["endtime"]);

		// Crop to limits.
		var fb = false;
		var fe = false;
		if(beg < this.minTime) {
			if(end < this.minTime) {
				continue;
			}
			beg = this.minTime;
			fb = true;
		}
		if(end > this.maxTime) {
			if(beg > this.maxTime) {
				continue;
			}
			end = this.maxTime;
			fe = true;
		}

		// Close whole day.
		if(fb && fe) {
			this.ocells[tt] = new Object();
			this.rcells[tt] = new Object();
			continue;
		}

		// If open map missing copy default.
		if(this.ocells[tt] == undefined) {
			this.ocells[tt] = new Object();
			this.rcells[tt] = new Object();
			for(var ti in this.ocells["0"]) {
				this.ocells[tt] = this.ocells["0"][ti];
				this.rcells[tt] = this.rcells["0"][ti];
			}
		}

		// Close required cells.
		var times = this.getPeriodTimes(beg, end);
		for(var j = 0; j < times.length; j++) {
			this.ocells[tt][times[j].toString()] = null;
			this.rcells[tt][times[j].toString()] = null;
		}
	}
}

/**
 * Checks if cell is open.
 */
function cr_isOpen(targetType, time) {
	if(this.ocells[targetType] == undefined) {
		targetType = "0";
	}
	if(this.ocells[targetType][time] == undefined) {
		return null;
	}
	return this.ocells[targetType][time];
}

/**
 * Checks if cell is reservable.
 */
function cr_isReservable(targetType, time) {
	if(time - 1800 < this.curTime) {
		return false;
	}
	if(this.rcells[targetType] == undefined) {
		targetType = "0";
	}
	if(this.rcells[targetType][time] == undefined || this.rcells[targetType][time] != 1) {
		return false;
	}
	return true;
}

/**
 * Checks if cell has line.
 */
function cr_isLine(targetType, time) {
	if(this.lines[targetType] == undefined) {
		targetType = "0";
	}
	if(this.lines[targetType][time] == undefined) {
		return false;
	}
	return true;
}

/**
 * Sets calendar targets (columns).
 */
function cr_setTargets(targets) {
	this.targets = targets;
	this.cells = new Object();
	for(var i = 0; i < targets.length; i++) {
		this.cells[targets[i]["id"]] = new Object();
	}
}

/**
 * Sets calendar entries (cells).
 */
function cr_setEntries(entries) {

	// If calendar rows defined add collapsing rows.
	var autosize = true;
	if(this.begTime <= this.endTime) {
		autosize = false;
		this.begTime -= 1;
		this.endTime += 1;
	}

	// Add all entries.
	for(var i = 0; i < entries.length; i++) {
		if(this.cells[entries[i]["target"]] == undefined) {
			continue;
		}
		var beg = parseInt(entries[i]["begtime"]);
		var end = parseInt(entries[i]["endtime"]);

		// Crop to limits.
		if(beg < this.minTime) {
			if(end < this.minTime) {
				continue;
			}
			beg = this.minTime;
		}
		if(end > this.maxTime) {
			if(beg > this.maxTime) {
				continue;
			}
			end = this.maxTime;
		}

		// Automaticly add calendar rows.
		if(autosize) {
			if(beg < this.begTime) {
				this.begTime = beg - 1;
			}
			if(end > this.endTime) {
				this.endTime = end + 1;
			}
		}

		// Or limit inside rows.
		else {
			if(beg < this.begTime) {
				beg = this.begTime;
				if(end < this.begTime) {
					end = this.begTime;
				}
			}
			if(end > this.endTime) {
				end = this.endTime;
				if(beg > this.endTime) {
					beg = this.endTime;
				}
			}
		}

		// Add cells.
		var target = entries[i]["target"];
		var half = entries[i]["half"] == "1";
		var times = this.getPeriodTimes(beg, end);
		this.addCell(target, times[0].toString(), half, entries[i]);
		var con = { "con": true, "color": entries[i]["color"] };
		for(var j = 1; j < times.length; j++) {
			this.addCell(target, times[j].toString(), half, con);
		}
	}

	// Make minimum calendar rows visible.
	if(this.begTime > this.endTime) {
		this.begTime = this.offset + 43200 - this.rowPeriod;
		this.endTime = this.offset + 43201 + this.rowPeriod;
	}
}

/**
 * Adds a cell to calendar view.
 */
function cr_addCell(target, time, half, entry) {
	if(this.cells[target][time] == undefined) {
		if(half) {
			this.cells[target][time] = [ entry ];
		} else {
			this.cells[target][time] = entry;
		}
	} else {
		if(this.cells[target][time] instanceof Array) {
			this.cells[target][time].push(entry);
		} else {
			var a = [ this.cells[target][time], entry ];
			this.cells[target][time] = a;
		}
	}
}

/**
 * Calculates periodical times inside begin and end.
 */
function cr_getPeriodTimes(begtime, endtime) {
	var n = begtime - (begtime % this.rowPeriod) + this.rowTransfer;
	if(n == endtime) {
		return [ n ];
	}
	var times = new Array();
	while(n < endtime) {
		times.push(n);
		n += this.rowPeriod;
	}
	return times;
}

/**
 * Generates time string for time in seconds.
 */
function cr_getTimeString(time) {
	var sec = time - this.offset + this.ds;
	var hours = Math.floor(sec / 3600);
	var mins = Math.floor((sec % 3600) / 60);
	if(hours > 24) {
		hours -= 24;
	}
	if(hours < 10) {
		hours = "0" + hours;
	}
	if(mins < 10) {
		mins = "0" + mins;
	}
	if(time <= this.curTime && time + this.rowPeriod > this.curTime) {
		return "<b>" + hours + ":" + mins + "</b>";
	}
	return hours + ":" + mins;
}

/**
 * Formats html with few parameters.
 */
function cr_formatHtml(url, aurl, ext, signLabel) {
	this.entryHtml = this.entryHtml.replace(/{url}/, url);
	this.entryHtml = this.entryHtml.replace(/{ext}/, ext);
	this.signHtml = this.signHtml.replace(/{signlabel}/, signLabel);
	this.attendHtml = this.attendHtml.replace(/{aurl}/, aurl);
	this.attendHtml = this.attendHtml.replace(/{ext}/, ext);
	this.freeHtml = this.freeHtml.replace(/{url}/, url);
	this.freeHtml = this.freeHtml.replace(/{ext}/, ext);
	this.closedHtml = this.closedHtml.replace(/{url}/, url);
	this.closedHtml = this.closedHtml.replace(/{ext}/, ext);
}

/**
 * Gets table html.
 */
function cr_getTableHtml() {

	// Get browser window width.
	var wwin = 1200;
	if(navigator.userAgent.indexOf("MSIE") >= 0) {
		wwin = document.body.offsetWidth;
	} else {
		try {
			wwin = window.innerWidth;
		} catch(ex) {
		}
	}

	// Generate target names.
	var html = "<table class=\"calrows\">\n<tr>\n<th class=\"time\" style=\"width:40px\">&nbsp;</th>\n";
	var wper = Math.round((wwin - 80) / this.targets.length);
	for(var i = 0; i < this.targets.length; i++) {
		html += "<th style=\"width:" + wper + "px\">" + this.targets[i]["name"] + "</th>\n";
	}
	html += "<th class=\"time\" style=\"width:40px\">&nbsp;</th>\n</tr>\n";

	// Travel calendar times as rows.
	var times = this.getPeriodTimes(this.begTime, this.endTime);
	var timesLast = times.length - 1;
	var evenRow = false;
	for(var j = 0; j <= timesLast; j++) {
		var time = times[j].toString();

		// Differ odd and even rows.
		if(evenRow) {
			html += "<tr class=\"even\">\n";
		} else {
			html += "<tr>\n";
		}
		evenRow = !evenRow;

		// Create time name.
		if(j == 0) {
			timeName = "&uarr;";
		} else if(j == timesLast) {
			timeName = "&darr;";
		} else {
			timeName = this.getTimeString(times[j]);
		}
		html += "<td class=\"time\">" + timeName + "</td>\n";

		// Travel calendar targets as columns.
		for(var i = 0; i < this.targets.length; i++) {
			var target = this.targets[i]["id"];
			var type = this.targets[i]["targettype"];
			var reserve = (this.targets[i]["userentryflag"] == 1);
			var tdhtml = null;

			// An empty cell.
			if(this.cells[target][time] == undefined) {
				tdhtml = this.getEmptyHtml(time, target, type, reserve, false);
			}

			// Nested cells.
			else if(this.cells[target][time] instanceof Array) {
				tdhtml = '<td>\n<table class="incalrows">\n<tr>\n';
				var arr = this.cells[target][time];
				for(var k = 0; k < arr.length; k++) {
					var tmp = this.getEntryHtml(arr[k]);
					tdhtml += tmp.replace(/{_lin}/, "");
				}
				if(arr.length == 1) {
					var tmp = this.getEmptyHtml(time, target, type, reserve, true);
					tdhtml += tmp.replace(/{_lin}/, "");
				}
				tdhtml += '</tr>\n</table>\n</td>\n';
			}

			// An entry.
			else {
				tdhtml = this.getEntryHtml(this.cells[target][time]);
			}

			// Set lines.
			var lin = "";
			if(this.isLine(type, time)) {
				lin = ' class="line"';
			}
			html += tdhtml.replace(/{_lin}/, lin);
		}

		// Time name again on right.
		html += "<td class=\"time\">" + timeName + "</td>\n</tr>\n";
	}

	// Target names again on bottom.
	html += "<tr>\n<th class=\"time\" style=\"width:40px\">&nbsp;</th>\n";
	for(var i = 0; i < this.targets.length; i++) {
		html += "<th style=\"width:" + wper + "px\">" + this.targets[i]["name"] + "</th>\n";
	}
	html += "<th class=\"time\" style=\"width:40px\">&nbsp;</th>\n</tr>\n</table>\n";
	return html;
}

/**
 * Gets empty html.
 */
function cr_getEmptyHtml(time, target, targettype, reserve, half) {
	var o = this.isOpen(targettype, time);
	if(o == null) {
		var html = this.closedHtml.replace(/{time}/g, time);
		html = html.replace(/{target}/g, target);
		return html;
	}
	var html = null;
	if(reserve && this.reserveHtml != null && this.isReservable(targettype, time)) {
		html = this.reserveHtml;
	} else {
		html = this.freeHtml;
	}
	html = html.replace(/{time}/g, time);
	if(half) {
		html = html.replace(/{target}/g, "-" + target);
	} else {
		html = html.replace(/{target}/g, target);
	}
	html = html.replace(/{dt}/, o["defentrytype"]);
	html = html.replace(/{price}/, o["price"]);
	return html;
}

/**
 * Gets entry html.
 */
function cr_getEntryHtml(entry) {
	if(entry["con"] != undefined) {
		return this.continuedHtml.replace(/{color}/, entry["color"]);
	} else {
		var more = "";
		if(entry["attend"] != "") {
			more = this.attendHtml.replace(/{id}/, entry["id"]);
			more = more.replace(/{attend}/, entry["attend"]);
		} else if(entry["sign"] == "1") {
			more = this.signHtml.replace(/{id}/g, entry["id"]);
		}
		var html = this.entryHtml.replace(/{color}/, entry["color"]);
		html = html.replace(/{id}/, entry["id"]);
		html = html.replace(/{desc}/, entry["desc"]);
		html = html.replace(/{name}/, cutLongWords(entry["name"], WORD_LIMIT));
		html = html.replace(/{more}/, more);
		return html;
	}
}

/**
 * Cuts long words with spaces.
 */
function cutLongWords(str, limit) {
	var res = "";
	var counter = 0;
	var ch = null;
	for(var n = 0; n < str.length; n++) {
		ch = str.charAt(n);
		res += ch;
		if(ch == " " || ch == "\n" || ch == "\r" || ch == "\t") {
			counter = 0;
		} else {
			counter++;
			if(counter == limit || ch == "-" || ch == "_" || ch == "/") {
				res += "<wbr />";
				counter = 0;
			}
		}
	}
	return res;
}

