/*
 * Creates html FORM input elements client-side
 *
 * Requires: /js/dom.js
 * Requires: /js/date.js
 * Requires: /js/generictoolkits.js
 */

/*
 * Contains:
 * - IntegerBox
 * - TimeBox
 * - CreateCheckBox
 * - more
 */
var COLON = ":";

/*****************************************************************************/
/*
 * IntegerBox
 */
function IntegerBox(htmlContainer, value, className, onChangeHandler, onKeyUpHandler)
{
	if(htmlContainer.tagName.toLowerCase() != "input")
	{
		this.htmlInput = document.createElement('input');
		htmlContainer.appendChild(this.htmlInput);
	}
	else
	{
		this.htmlInput = htmlContainer;
	}
	// this.htmlInput will now leak in IE

	this.htmlInput.type = "text";
	if(value != null) this.htmlInput.value = value;
	this.htmlInput.setAttribute("autocomplete","off");
	Event.observe(this.htmlInput, 'keypress', this.OnKeyPress.bindAsEventListener(this));
	Event.observe(this.htmlInput, 'paste', this.OnPaste.bindAsEventListener(this));
	this.AllowDecimals = false;
	
	if(onKeyUpHandler) Event.observe(this.htmlInput, 'keyup', onKeyUpHandler);
	if(onChangeHandler) Event.observe(this.htmlInput, 'change', onChangeHandler);
	if(className) this.htmlInput.className = className;
}


/// Destructor. Needed because IE 6.x's GC hemorages memory. Possible not needed anymore
IntegerBox.prototype.Destroy = function()
{
	this.htmlInput = null;
}

/// Convenience method. Find all inputs with classname containing IntegerBox
/// and convert them into IntegerBoxes
function ActivateIntegerboxes()
{
	var inputs = document.getElementsByTagName('input');
	for(var i=0; i<inputs.length; i++)
	{
		var input = $(inputs[i]);
		if(input.hasClassName('IntegerBox'))
		{
			new IntegerBox(input, null, null, null, null);
		}
		if(input.hasClassName('DecimalBox'))
		{
			var ib = new IntegerBox(input, null, null, null, null);
			ib.AllowDecimals = true;
		}
	}
}

IntegerBox.prototype.ContainsDot = function()
{
	return /\./.test(this.htmlInput.value);
}
IntegerBox.prototype.GetValue = function()
{
	return this.htmlInput.value;
}
IntegerBox.prototype.SetValue = function(val)
{
	this.htmlInput.value = val;
}
IntegerBox.prototype.GetHtmlInput = function()
{
	return this.htmlInput;
}

IntegerBox.prototype.OnKeyPress = function(e)
{
	if(document.selection != null)
	{
		if(document.selection.createRange().text != '') document.selection.clear();
	}
	var key = GetKeyCode(e);
	if(IsComma(key)) SetKeyCode(e, ".".charCodeAt(0));
	var key = GetKeyCode(e);

	var keychar = String.fromCharCode(key);

	if(IsControlKey(key, keychar) || IsDigit(keychar)) return;
	if(this.AllowDecimals && IsDot(keychar) && !this.ContainsDot()) return;
	
	Event.stop(e);
}

IntegerBox.prototype.OnPaste = function(e)
{
	//
	// Note that FF will not call this function, since FF doesn't support the onpaste event.
	//
	var pasted = window.clipboardData.getData("Text");
	var source = GetSource(e);

	var pasted = (source.maxLength) ? pasted.substring(0, source.maxLength) : pasted;

	if(/^\d*$/.test(pasted))
	{
		source.value = pasted;
		source.onchange(e);
	}
	
	Event.stop(e);
}

/*****************************************************************************/
/*
 * class TimeBox
 *
 * @param defaultDate: the date against which to compare to determine whether
 *                     our time is more than a day off
 * @param startDateTime: the datetime value against which our inputbox will be initialized
 */
function TimeBox(htmlContainer, defaultDate, startDateTime, modelUpdateFunction)
{
	this.IntegerBox(htmlContainer, null, "TimeBox", null, null);
	this.defaultDate = new Date(defaultDate);

	this.htmlInput.maxLength = 5;
	this.htmlInput.onblur = this.OnBlur.bindAsEventListener2(this);
	this.htmlInput.onfocus = this.OnFocus.bindAsEventListener2(this);
	this.htmlInput.onchange = this.OnChange.bindAsEventListener2(this, modelUpdateFunction);
	
	this.daySign = new TimeDaySign(defaultDate, htmlContainer, 'TimeBox_DayIndicator');
	this.Update(startDateTime);
}
copyPrototype(TimeBox, IntegerBox);

TimeBox.prototype.Update = function(dateValue)
{
	this.SetValueAsDate(dateValue);
	this.daySign.Update(dateValue);
}


TimeBox.prototype.OnChange = function(e, modelAccessorFunction)
{
	this.ExpandValue();
	this.AddColon();

	if(modelAccessorFunction) modelAccessorFunction(this.GetValueAsDate());
}
TimeBox.prototype.OnBlur = function(e)
{
	//
	// OnBlur will be called even if the value has not changed (in which case onblur
	// duplicates onchange)
	//
	this.ExpandValue();
	this.AddColon();
}

TimeBox.prototype.OnFocus = function(e)
{
	this.RemoveColon();
}

TimeBox.prototype.ExpandValue = function()
{
	switch (this.GetLength())
	{
		case 0 : this.SetValue("0000"); break;
		case 1 : this.SetValue("0" + this.GetValue() + "00"); break;
		case 2 : this.SetValue(this.GetValue() + "00"); break;
		case 3 : this.SetValue("0" + this.GetValue()); break;
		case 4 : break;
	}
}

TimeBox.prototype.AddColon = function()
{
	if(this.htmlInput.value.length < 3) return;
	this.htmlInput.maxLength = 5;
	this.htmlInput.value = this.htmlInput.value.replace(COLON, "");
	this.htmlInput.value = this.htmlInput.value.replace(/(\d{1,2})(\d{2})$/, "$1" + COLON + "$2");
}

TimeBox.prototype.RemoveColon = function()
{
	this.htmlInput.value = this.htmlInput.value.replace(COLON, "");

	this.htmlInput.maxLength = 4;
	this.htmlInput.select();
}

TimeBox.prototype.GetValue = function()      { return this.htmlInput.value; }
TimeBox.prototype.SetValue = function(value) { this.htmlInput.value = value; }
TimeBox.prototype.GetLength = function()     { return this.GetValue().length; }

TimeBox.prototype.GetValueAsDate = function()
{
	var num = parseInt(this.GetValue().replace(COLON, ""), 10); // parse in base-10
	var hours = Math.floor(num / 100);
	var minutes = num % 100;
	
	var newDate = new Date(this.startDateTime);
	newDate.setHours(hours);
	newDate.setMinutes(minutes);

	return newDate;
}

TimeBox.prototype.SetValueAsDate = function(date)
{
	this.startDateTime = new Date(date);

	this.SetValue(TimeStringFromDate(date));
}

function TimeStringFromDate(date)
{
	if(!IsDate(date)) return "00:00";
	return	PadZeros(date.getHours(), 2) +
			COLON + 
			PadZeros(date.getMinutes(), 2);

}




/*****************************************************************************/
/*
 * Labels
 */

/*
 * TimeLabel
 */
function TimeLabel(htmlContainer, defaultDate, dateValue)
{
	TextLabel.call(this, htmlContainer, null, "TimeLabelControl", null);
	this.daySign = new TimeDaySign(defaultDate, htmlContainer, 'TimeLabel_DayIndicator');
	this.Update(dateValue);
}
TimeLabel.prototype = new TextLabel();

TimeLabel.prototype.Update = function(value)
{
	this.htmlSpan.childNodes[0].nodeValue = TimeStringFromDate(value);
	this.daySign.Update(value);
}

/*
 * TextLabel
 */
function TextLabel(htmlContainer, value, className, onclick)
{
	if(arguments.length == 0) return;
	
	this.htmlSpan = document.createElement('span');
	if(className) this.htmlSpan.className = className;
	if(onclick) this.htmlSpan.onclick = onclick;
	
	this.htmlSpan.appendChild(document.createTextNode(value));
	
	htmlContainer.appendChild(this.htmlSpan);
}

/// Destructor
TextLabel.prototype.Destroy = function()
{
	this.htmlSpan.onclick = null;
	this.htmlSpan = null;
}

TextLabel.prototype.Update = function(text)
{
	this.htmlSpan.childNodes[0].nodeValue = text;
}

/*
 * Very simple Label
 */
function CreateLabel(htmlContainer, text, noWrap)
{
	if(noWrap == true)
	{
		var div = CreateContainer(htmlContainer);
		div.innerHTML = new String(text).replace(/\s/g, "&nbsp;");
	}
	else
	{
		htmlContainer.appendChild(document.createTextNode(text));
	}
}
function CreateDivLabel(htmlContainer, text, className)
{
	var div = CreateContainer(htmlContainer, className);
	CreateLabel(div, text);
	return div;
}

function CreateSpan(htmlContainer, text, className)
{
	var span = document.createElement('span');
	if(className) span.className = className;
	span.appendChild(document.createTextNode(text));
	htmlContainer.appendChild(span);
	return span;
}

function SetLabel(htmlContainer, text, noWrap)
{
	if(noWrap == true)
	{
		htmlContainer.innerHTML = new String(text).replace(/\s/g, "&nbsp;");
	}
	else
	{
		ClearChildren(htmlContainer);
		CreateLabel(htmlContainer, text, false);
	}
}

/*
 * an empty div container
 */
function CreateContainer(htmlContainer, className)
{
	var container = document.createElement('div');
	if(className != null) container.className = className;
	htmlContainer.appendChild(container);
	return container;
}

/*
 * Image
 */
function CreateImage(htmlContainer, src, className, title)
{
    return Append(htmlContainer, CreateImage2(src, className, title));
}

function CreateImage2(src, className, title)
{
	var img = new Image();
	if(className != null) img.className = className;
	img.src = src;
	if(title != null)
	{
		img.alt = title;
		img.title = title;
	}

	return img;
}

function Append(container, item)
{
    container.appendChild(item);
    return item;
}
function Prepend(container, item)
{
    container.insertAdjacentElement('afterBegin', item);
    return item;
}
/*****************************************************************************/
/*
 * a class to display a date difference sign next to a
 * TimeBox or a TimeLabel
 */
function TimeDaySign(defaultDate, htmlContainer, className)
{
	this.defaultDate = defaultDate;
	this.htmlDaySign = document.createElement('span');
	this.htmlDaySign.className = className;
	this.htmlDaySign.appendChild(document.createTextNode(''));
	htmlContainer.appendChild(this.htmlDaySign);
}

/// Destructor
TimeDaySign.prototype.Destroy = function()
{
	this.htmlDaySign = null;
}
TimeDaySign.prototype.Update = function(date)
{
	this._SetText("");
	
	if(this.defaultDate.equalsDate(date)) return;

	this._SetText(this._DifferenceInDays(date));
	if(this._DifferenceInDays(date) > 0)
	{
		this.htmlDaySign.title = "+1: Dit tijdstip valt op de volgende dag";
	}
	else
	{
		this.htmlDaySign.title = "-1: Dit tijdstip valt op de voorgaande dag";
	}
}
TimeDaySign.prototype._SetText = function(txt)
{
	this.htmlDaySign.childNodes[0].nodeValue = txt;
}
TimeDaySign.prototype._DifferenceInDays = function(date)
{
	var diffDays = this.defaultDate.differenceInDays(date);

	return (diffDays > 0)
		? '+' + diffDays
		: diffDays.toString();
}

Date.prototype.differenceInDays = function(otherDate)
{
	if(!IsDate(otherDate)) return null;

	var d1 = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0, 0);
	var d2 = new Date(otherDate.getFullYear(), otherDate.getMonth(), otherDate.getDate(), 0, 0, 0, 0);
	
    // Calculate the difference in milliseconds
    var difference_ms = d2.getTime() - d1.getTime();
    
	// The number of milliseconds in one day
    var ONE_DAY = 1000 * 60 * 60 * 24;

    return Math.floor(difference_ms / ONE_DAY);
}


/*****************************************************************************/
/*
 * Dropdown options
 */
function Dropdown(htmlContainer, arrayOfIndexElements, selectedValue, selectClassName)
{
	var select = document.createElement("SELECT");
	if(selectClassName) select.className = selectClassName;

	SetArrayOfIndexElementsToDropdown(select, arrayOfIndexElements, selectedValue);

	htmlContainer.appendChild(select);
	return select;
}

function SetArrayOfIndexElementsToDropdown(htmlSelect, arrayOfIndexElements, 
	selectedValue)
{
	//
	// BTW there is no point in setting a classname for the options.
	// the only thing you can influence in IE is the color & backgroundcolor :-(
	//
	ClearChildren(htmlSelect);
	for(var i=0; i<arrayOfIndexElements.length; i++)
	{
		var option = document.createElement("OPTION");
		htmlSelect.appendChild(option);
		option.innerHTML = arrayOfIndexElements[i].label;
		option.value = arrayOfIndexElements[i].ID;
		option.selected = (option.value == selectedValue);
	}
}

/// returns the selected value after an onchange event occured for a DropDown
function GetSelectedValueForDropdownOnchange(e)
{
	var dd = GetSource(e);
	return dd.options[dd.selectedIndex].value;
}

/*****************************************************************************/
/*
 * Creates a DIV
 */
function AppendDiv(htmlContainer, className)
{
	var div = document.createElement("DIV");
	div.className = className;
	htmlContainer.appendChild(div);
	return div;
}

function PrependDiv(htmlContainer, className)
{
	var div = document.createElement("DIV");
	div.className = className;
    htmlContainer.insertAdjacentElement('afterBegin', div);
    return div;
}

/*****************************************************************************/
/*
 * Convenience stuff
 */
function SetDisplayForId(divId, displayValue)
{
	SetDisplay(document.getElementById(divId), displayValue);
}
function SetDisplay(div, displayValue)
{
	if(div != null)	div.style.display = displayValue;
}
function GetDisplay(div)
{
	return (div != null)
		? div.style.display
		: "";
}
function ToggleDisplay(div)
{
	if(GetDisplay(div) == "none")
	{
		SetDisplay(div, "");
	}
	else
	{
		SetDisplay(div, "none");
	}
}
function SetVisibility(div, visibilityValue)
{
	if(div != null)	div.style.visibility = visibilityValue;
}


/*****************************************************************************/
/*
 * CheckBox
 */
function CreateCheckBox(htmlContainer, checked, onClickHandler)
{
	var cb = document.createElement('input');
	cb.type = "checkbox";
	cb.defaultChecked = checked;
	cb.checked = checked;
	cb.onclick = onClickHandler;
	
	htmlContainer.appendChild(cb);
	return cb;
}

function CreateTextBox(htmlContainer, text, className)
{
    var input = document.createElement('input');
    input.className = className;
    input.value = text;
    if(htmlContainer != null) htmlContainer.appendChild(input);
    return input;
}


/*****************************************************************************/
/*
 * A simple helper to create tables
 */
function TableCreator()
{
	this.tagTable = document.createElement('table');
	this.tagTableBody = document.createElement('tbody');
	this.tagTable.appendChild(this.tagTableBody);
	
	this.tagCurrentRow = null;
}

TableCreator.prototype.GetTableTag = function()
{
	return this.tagTable;
}
TableCreator.prototype.CreateRow = function()
{
	this.tagCurrentRow = document.createElement('tr');
	this.tagTableBody.appendChild(this.tagCurrentRow);
}
TableCreator.prototype.CreateCell = function()
{
	if(this.tagCurrentRow == null) this.CreateRow();
	var tagCell = document.createElement('td');
	this.tagCurrentRow.appendChild(tagCell);
	return tagCell;
}



/*****************************************************************************/
/*
 * An ApplicationEvent contains a set of listeners which can be notified
 * through a callback method
 */
function ApplicationEvent()
{
	this.listeners = new Array();
	this.activeNotificationQueue = null;
}

ApplicationEvent.prototype.AddListener = function(listener)
{
	this.listeners.push(listener);
}

ApplicationEvent.prototype.RemoveListener = function(listener)
{
	for(var i=this.listeners.length -1; i>=0; i--)
	{
		if(this.listeners[i] != listener) continue;
		
		this.listeners.splice(i, 1);
	}
}

ApplicationEvent.prototype.NotifyListeners = function() // any parameters will passthrough to the delegates
{
	if(this.activeNotificationQueue != null)
	{
		//
		// this.NotifyListeners is called recursively if the activeNotifaticationQueue != null
		// make sure the events are still chronological; append to the queue and exit
		//
		for(var i=0; i<this.listeners.length; i++)
		{
			this.activeNotificationQueue.push({ func : this.listeners[i], args : arguments });
		}
		return;
	}
	
	
	this.activeNotificationQueue = new Array();
	for(var i=0; i<this.listeners.length; i++)
	{
		this.activeNotificationQueue.push({ func : this.listeners[i], args : arguments });
	}
	for(var i=0; i<this.activeNotificationQueue.length; i++)
	{
		//
		// if you were to do this.listeners[i](arguments) then
		// your delegate would receive not a variable amount of arguments
		// but a single argument containing an array. Using .apply()
		// works around this. The first parameter to apply is the object
		// instance that the delegate should attach to. We don't use that here.
		//
		//this.activeNotificationQueue[i].apply(null, arguments);
		var funcObject = this.activeNotificationQueue[i];
		funcObject.func.apply(null, funcObject.args);
	}
	this.activeNotificationQueue = null;
	
}


/*****************************************************************************/
/*
 * IndexElement
 */
function IndexElement(ID, label, value2, value3)
{
	//
	// not very convenient, you might as well use object notation {}
	//
	this.ID = ID;
	this.label = label;
	this.value2 = value2;
	this.value3 = value3;
}




/*****************************************************************************/
/*
 * IdItem
 */

//
// add key-value pairs at will after the label, like so
// new IdItem(id, label, key1, value1, key2, value2, key3, value3, etc);
//
function IdItem(ID, label)
{
	this.ID = ID;
	this.label = label;
	for(var i=2; i<arguments.length; i++)
	{
		if(i + 1 >= arguments.length) break;
		this[arguments[i]] = arguments[i +1];
	}
	this.children = new Array();
}
IdItem.prototype.AddChild = function(child)
{
	this.children.push(child);
}
/// Recursively looks for a child by ID
IdItem.prototype.FindChild = function(childID)
{
	for(var i=0; i<this.children.length; i++)
	{
		if(this.children[i].ID == childID) return this.children[i];
		var child = this.children[i].FindChild(childID);
		if(child != null) return child;
	}
	return null;
}



/*****************************************************************************/
/*
 * Array enhancements
 */
// TODO move this to generictoolkits.js where it belongs
Array.prototype.findByID = function(ID)
{
	for(var i=0; i<this.length; i++)
	{
		if(this[i].ID == ID) return this[i];
	}
	return null;
}


