var A =
{
    ToArray: function(iterable)
    {
        if (!iterable) return [];
        if (iterable.toArray) return iterable.toArray();
        var length = iterable.length, results = new Array(length);
        while (length--) results[length] = iterable[length];
        return results;
    },

    Debugger:
    {
        instance: null,
        
        writeLine: function(message)
        {
            if (this.instance)
                this.instance.writeLine(message);
        }
    }
}

A.Element =
{
    getPosition: function(el, overflown)
    {
		overflown = overflown || [];
		
		var left = 0, top = 0;
		
		do {
			left += el.offsetLeft || 0;
			top += el.offsetTop || 0;
			el = el.offsetParent;
		} while (el);
		
		overflown.each(function(element)
		{
			left -= element.scrollLeft || 0;
			top -= element.scrollTop || 0;
		});
		
		return {'x': left, 'y': top};
	}
}

if (!Function.prototype.bind)
{
    Function.prototype.bind = function()
    {
        if (arguments.length < 2 && arguments[0] === undefined)
            return this;

        var __method = this;
        var args = A.ToArray(arguments);
        var object = args.shift();
        
        return function()
        {
            return __method.apply(object, args.concat(A.ToArray(arguments)));
        }
    }
}

Array.prototype.each = function(iterator, context)
{
    var index = 0;
    iterator = iterator.bind(context);
    
    try
    {
        this._each(function(value)
        {
            iterator(value, index++);
        });
    }
    catch (e)
    {
        if (e != A.Break)
            throw e;
    }
    
    return this;
}

Array.prototype._each = function(iterator)
{
    for (var i = 0, length = this.length; i < length; i++)
        iterator(this[i], i);
}

Array.prototype.some = function(iterator)
{
    for (var i = 0, length = this.length; i < length; i++)
        if (iterator(this[i], i))
            return true;
            
    return false;
}

Array.prototype.remove = function(item)
{
    var i = 0;
    var len = this.length;
    
    while (i < len)
    {
        if (this[i] === item)
        {
            this.splice(i, 1);
            len--;
        }
        else
        {
            i++;
        }
    }
    
    return this;
}

// Use native browser JS 1.6 implementation if available.
if (Array.prototype.forEach)
    Array.prototype._each = Array.prototype.forEach;
    
if (!Array.prototype.indexOf)
{
    Array.prototype.indexOf = function(item, i)
    {
        i || (i = 0);
        var length = this.length;
        
        if (i < 0)
            i = length + i;

        for (; i < length; i++)
            if (this[i] === item)
                return i;

        return -1;
    }
};

if (!String.prototype.trim)
{
    String.prototype.trim = function()
    {
        return this.replace(/^\s+|\s+$/g, "");
    }
}

if (!String.prototype.format)
{
    String.prototype.format = function()
    {
        var value = this;

        var parameters = A.ToArray(arguments);

        if (parameters.length == 0)
            value;
        
        parameters.each(function(parameter, index)
        {
            var indexString = "{" + index + "}";

            if (value.indexOf(indexString) == -1)
                throw "String.format(): Index not found (" + index + ")";

            value = value.replace(indexString, parameter);
        });

        return value;
    }
}

if (!Date.prototype.diff)
{
    Date.prototype.diff = function(start)
    {
        var secondsStart = start.getSeconds();
        var milisecondsStart = start.getMilliseconds();
        
        var secondsEnd = this.getSeconds();
        var milisecondsEnd = this.getMilliseconds();
        
        var diffMiliseconds = (secondsStart == secondsEnd ? milisecondsEnd - milisecondsStart : (1000 - milisecondsStart) + milisecondsEnd);
        var startCeiled = Math.ceil(new Number(secondsStart + "." + milisecondsStart));
        var endFloored = Math.floor(new Number(secondsEnd + "." + milisecondsEnd));
        
        var diffSeconds = (endFloored > startCeiled ? endFloored - startCeiled : secondsStart == secondsEnd || startCeiled == endFloored ? 0 : startCeiled == 0 ? endFloored : ((60 - endFloored) + startCeiled));

        /*console.log("secondsStart = " + secondsStart);
        console.log("milisecondsStart = " + milisecondsStart);
        console.log("secondsEnd = " + secondsEnd);
        console.log("milisecondsEnd = " + milisecondsEnd);
        console.log("diffSeconds = " + diffSeconds);
        console.log("diffMiliseconds = " + diffMiliseconds);*/

        output = ((diffSeconds * 1000) + diffMiliseconds);

        if (output > 60000)
        {
            alert("secondsStart = " + secondsStart + "\n" +
            "milisecondsStart = " + milisecondsStart + "\n" +
            "secondsEnd = " + secondsEnd + "\n" +
            "milisecondsEnd = " + milisecondsEnd + "\n" +
            "startCeiled = " + startCeiled + "\n" +
            "endFloored = " + endFloored + "\n" +
            "diffSeconds = " + diffSeconds + "\n" +
            "diffMiliseconds = " + diffMiliseconds);
        }

        return output;
    }
}

// Hash map
function HashMap()
{
    this.keys = [];
    this.values = [];
}

HashMap.prototype.setItem = function(key, value)
{
    var index = this.keys.indexOf(key);

    if (index != -1)
    {
        this.keys[index] = value;
    }
    else
    {
        this.keys.push(key);
        this.values.push(value);
    }
}

HashMap.prototype.setItemRange = function(arrayOfKeyValuePairs)
{
    arrayOfKeyValuePairs.each(function(keyValuePair)
    {
        this.setItem(keyValuePair.key, keyValuePair.value);
    }.bind(this));
}

HashMap.prototype.getItem = function(key)
{
    var index = this.keys.indexOf(key);

    if (index != -1)
        return this.values[index];

    return null;
}

HashMap.prototype.removeItem = function(key)
{
    var index = this.keys.indexOf(key);

    if (index != -1)
    {
        var value = this.values[index];

        this.keys.remove(key);
        this.values.remove(value);
    }
}

// String builder.
function StringBuilder()
{
	this.length = 0;
	this.parts = [];
}

StringBuilder.prototype.append = function(newString)
{
	this.parts.push(newString);
}

StringBuilder.prototype.getValue = function(separator)
{
    if (separator != null)
        return this.parts.join(separator);
        
	return this.parts.join("");
}

StringBuilder.prototype.getReverted = function()
{
	return this.parts.reverse().join("");
}

StringBuilder.prototype.destroy = function()
{
	this.parts = null;
}

// Server request.
function AsyncTransport(transportType, onClientSuccess, onClientFailure)
{
    this.transportType = (transportType.toLowerCase() || "get");
    this.onClientSuccess = onClientSuccess || null;
    this.onClientFailure = onClientFailure || null;
    this.executing = false;
    
    this.transport = null;
    this.initializeTransport();

    $(window, "unload", this.destroy.bind(this));
}

AsyncTransport.prototype.initializeTransport = function()
{
    if (this.transport != null)
    {
        this.transport.onreadystatechange = null;
        this.transport = null;
    }
    
    this.transport = (window.XMLHttpRequest ? new XMLHttpRequest() : $.browser.msie ? new ActiveXObject('Microsoft.XMLHTTP') : null);
    
    if (this.transport == null)
        throw "AsyncTransport.initializeTransport(...): Unable to find transport, aborting.";
}

AsyncTransport.prototype.send = function(url, data)
{
    if (this.executing)
        return;

    // Merge url and data if it's a GET request.
    if (this.transportType == "get" && data != null)
    {
        url = url + (url.indexOf("?") != -1 ? "&" : "?") + data;
        data = null;
    }
    
    try
    {
		// Prevent cache of the output by adding a date to each request.
		url = url + (url.indexOf("?") != -1 ? "&" : "?") + "Date=" + (new Date().valueOf());
	    
		this.executing = true;
		this.transport.open(this.transportType.toUpperCase(), url, true);
		this.transport.onreadystatechange = this.onReadyStateChange.bind(this);
		this.transport.send(data);
	}
	catch (ex)
	{
		A.Debugger.writeLine("AsyncTransport.send error: " + ex);
	}
}

AsyncTransport.prototype.cancel = function()
{
    if (!this.executing)
        return;
        
    this.executing = false;
    
	this.transport.abort();
}

AsyncTransport.prototype.destroy = function()
{
    this.executing = false;
    this.transport.abort();
    this.transport.onreadystatechange = null;
    this.transport = null;
}

AsyncTransport.prototype.onSuccess = function()
{
    if (this.onClientSuccess != null)
    {
        // Return output dependent on the content type.
        var contentTypeHeader = this.transport.getResponseHeader("Content-Type").toLowerCase();
        var output = null;
        
        if (contentTypeHeader.indexOf("application/json") != -1)
        {
            try
            {
                output = eval("(" + this.transport.responseText + ")");
            }
            catch (ex)
            {
                A.Debugger.writeLine(ex);
            }
        }
        else
        {
            output = this.transport.responseText;
        }
        
        this.onClientSuccess(output);
    }
}

AsyncTransport.prototype.onFailure = function()
{
    if (this.onClientFailure != null)
        this.onClientFailure();
}

AsyncTransport.prototype.onReadyStateChange = function()
{
    if (this.transport.readyState != 4 || !this.executing)
        return;
        
    this.executing = false;
    var status = 0;
    
    // Sometimes an exception is thrown when accessing the status property.
	try
	{
	    status = this.transport.status;
	}
	catch (ex)
	{
	    A.Debugger.writeLine("AsyncTransport.onReadyStateChange status error: " + ex);
	}
	
	if (status == 200)
	    this.onSuccess();
	else
	    this.onFailure();
}