function __ajax_getObject()
{
    var obj;
    try
    {
        obj = new ActiveXObject('Msxml2.XMLHTTP');
    }
    catch(e)
    {
        try
        {
            obj = new ActiveXObject('Microsoft.XMLHTTP');
        }
        catch(e2)
        {
            obj = null;
        }
    }
    
    if(!obj && typeof XMLHttpRequest != 'undefined')
        obj = new XMLHttpRequest();

    if(!obj)
        window.status = 'Could not initialize XMLHttp object - ajax will not work';

    return obj;
}

function __ajax_call(funcID, funcArgs, requestURI, requestType)
{
   var obj = __ajax_getObject(); // get XMLHTTP object
   if(!obj) // ajax not supported in this browser
       return false;
   
   if(requestType == 'GET')
   {
       requestURI += requestURI.indexOf('?') == -1 ? '?__ajax_call='+funcID : '&__ajax_call='+funcID;
       requestURI += '&__ajax_unique='+(new Date().getTime()); // prevent caching
       for(var i = 0; i < funcArgs.length; i++)
           requestURI += '&__ajax_args[]='+escape(funcArgs[i]);
   }
   else if(requestType == 'POST')
   {
       var postData = '__ajax_call='+funcID;
       postData += '&__ajax_unique='+(new Date().getTime()); // prevent caching
       for(var i = 0; i < funcArgs.length; i++)
           postData += '&__ajax_args[]='+escape(funcArgs[i]);
   }
   else
   {
       alert('Invalid request type: '+requestType);
       return false;
   }
   
   obj.open(requestType, requestURI, true);
   if(requestType == 'POST')
   {
       try
       {
           obj.setRequestHeader('Method', 'POST '+requestURI+' HTTP/1.1');
           obj.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
       }
       catch(e)
       {
           alert('AJAX POST not supported by your browser!');
           return false;
       }
   }
   
   obj.onreadystatechange = function()
   {
       if(obj.readyState != 4 || obj.status != 200)
           return;

       if(obj.responseXML)
           __ajax_process_xml(obj.responseXML.documentElement, obj.responseText);
   }
   obj.send(requestType == 'POST' ? postData : null);
   return true;
}

function __ajax_process_xml(xmlObj, rawText)
{
    for(var i = 0; i < xmlObj.childNodes.length; i++)
    {
        if(xmlObj.childNodes[i].nodeName != "response")
        {
            alert('unexpected XML element: '+xmlObj.childNodes[i].nodeName+"\n\ntext:\n"+rawText);
            continue;
        }

        var responseNode = xmlObj.childNodes[i];
        var responseType = responseNode.getAttribute('type');
        if(!responseType)
        {
            alert('invalid response: type attribute missing');
            continue;
        }
        else if(responseType == 'alert') // show alert message
        {
            for(var ii = 0; ii < responseNode.childNodes.length; ii++)
            {
                if(responseNode.childNodes[ii].nodeName == 'message')
                {
                    alert(responseNode.childNodes[ii].firstChild.nodeValue);
                    break;
                }
            }
        }
        else if(responseType == 'javascript') // evaluate javascript code
        {
            for(var ii = 0; ii < responseNode.childNodes.length; ii++)
            {
                if(responseNode.childNodes[ii].nodeName == 'code')
                {
                    try
                    {
                        eval(responseNode.childNodes[ii].firstChild.nodeValue);
                    }
                    catch(e)
                    {
                        alert("could not evaluate code:\n"+responseNode.childNodes[ii].firstChild.nodeValue);
                    }
                    break;
                }
            }
        }
        else if(responseType == 'update') // update element
        {
            var targetID, targetAttribute, newValue;
            for(var ii = 0; ii < responseNode.childNodes.length; ii++)
            {
                if(responseNode.childNodes[ii].nodeName == 'target')
                {
                    targetID = responseNode.childNodes[ii].firstChild.nodeValue;
                    targetAttribute = responseNode.childNodes[ii].getAttribute("attribute");
                }
                else if(responseNode.childNodes[ii].nodeName == 'value')
                {
                    newValue = responseNode.childNodes[ii].firstChild ? responseNode.childNodes[ii].firstChild.nodeValue : '';
                }
            }
            
            if(!targetID)
            {
                alert('invalid update response: target id missing');
                continue;
            }
            
            if(!targetAttribute)
            {
                alert('invalid update response: target attribute missing');
                continue;
            }
            
            if(!document.getElementById(targetID))
            {
                alert('invalid update response: object with id '+targetID+' does not exist');
                continue;
            }
            
            eval('document.getElementById("'+targetID+'").'+targetAttribute+' = newValue;');
        }
        else if(responseType == 'call') // call function
        {
            var funcName, argList = new Array();
            for(var ii = 0; ii < responseNode.childNodes.length; ii++)
            {
                if(responseNode.childNodes[ii].nodeName == 'func')
                {
                    funcName = responseNode.childNodes[ii].firstChild.nodeValue;
                }
                else if(responseNode.childNodes[ii].nodeName == 'arg')
                {
                    var argNode  = responseNode.childNodes[ii];
                    var argType  = argNode.getAttribute('type');

                    if(argType == 'array')
                        argList.push(argNode.firstChild.nodeValue);
                    else if(argType == 'number')
                        argList.push(argNode.getAttribute('value'));
                    else if(argType == 'bool')
                        argList.push(argNode.getAttribute('value') == 0 ? false : true);
                    else if(argType == 'string' && !argNode.firstChild) // empty string
                        argList.push("''");
                    else if(argType == 'string' && argNode.firstChild) // string
                        argList.push("unescape('" + escape(argNode.firstChild.nodeValue) + "')");
                    else
                        alert('invalid arg type in call: '+argType);
                }
            }
            
            if(!funcName)
            {
                alert('invalid call response: function name missing');
                continue;
            }
            
            try
            {
                eval(funcName+'('+argList.join(', ')+');');
            }
            catch(e)
            {
                alert('could not call function '+funcName);
            }
        }
        else if(responseType == 'suggest')
        {
            ajaxSuggest.receiveResponse(responseNode);
        }
        else
        {
            alert('invalid response: unknown type '+responseType);
        }
    }
}


// Autocompletion system
// Based on UserSuggest class from WCF Module com.woltlab.wcf.page.user.profile

function AjaxSuggest() {
    this.inputFields = new Object();
    this.ajaxFuncs = new Object();
    this.suggestions = new Array();
    this.activeTarget = null;
    this.selectedIndex = -1;
    this.insertAutomatically = false;

    /**
     * Initialises a new suggest popup.
     */
    this.init = function(inputFieldID, ajaxFunc) {
        if (!this.inputFields[inputFieldID]) {
            this.inputFields[inputFieldID] = inputFieldID;
            this.ajaxFuncs[inputFieldID] = ajaxFunc;

            // get input selement
            var element = document.getElementById(inputFieldID);

            // set autocomplete off
            element.setAttribute('autocomplete', 'off');

            // disable submit on return
            element.form.onsubmit = function() { if (ajaxSuggest.selectedIndex != -1) return false; };

            // create suggestion list div
            var newDiv = document.createElement('div');
            newDiv.id = 'option' + inputFieldID;
            newDiv.className = 'hidden';

            // insert new div
            if (element.nextSibling) {
                element.parentNode.insertBefore(newDiv, element.nextSibling);
            }
            else {
                element.parentNode.appendChild(newDiv);
            }

            // add event listeners
            element.onkeyup = function(e) { return ajaxSuggest.handleInput(e); };
            element.onkeydown = function(e) { return ajaxSuggest.handleBeforeInput(e); };
            element.onclick = function(e) { return ajaxSuggest.handleClick(e); };
            element.onfocus = function(e) { return ajaxSuggest.handleClick(e); };
            element.onblur = function(e) { return ajaxSuggest.closeList(); }
        }
    }

    /**
     * Closes all suggestion popups.
     */
    this.closeAllLists = function(event) {
        // get event
        if (!event) event = window.event;

        // get target
        var target = this.getEventTarget(event);
        if (target.type == 'submit') return;

        for (var inputFieldID in this.inputFields) {
            if (target.id.indexOf(inputFieldID) == -1) {
                var activeTarget = document.getElementById(inputFieldID);
                this.closeList(activeTarget);
            }
        }
    }

    /**
     * Return the target of a given event.
     */
    this.getEventTarget = function(event) {
        if (event.target) return event.target;
        else if (event.srcElement) return event.srcElement;
    }

    /**
     * Handles the input event on key down.
     */
    this.handleBeforeInput = function(event) {
        // get event
        if (!event) event = window.event;

        // get key code
        var keyCode = 0;
        if (event.which) keyCode = event.which;
        else if (event.keyCode) keyCode = event.keyCode;

        // handle special keys
        // array down
        if (keyCode == 40) return this.moveList('next');
        // array up
        if (keyCode == 38) return this.moveList('previous');
        // escape
        if (keyCode == 27) return this.closeList();

        return true;
    }

    /**
     * Inserts the selected suggestion list option.
     */
    this.insertSelectedOption = function(index, target) {
        if (index == undefined) {
            index = this.selectedIndex;
        }

        if (!target) {
            target = this.activeTarget;
        }

        if (this.suggestions.length > index && this.suggestions[index]) {

            var cursorStart = this.getCursorPosition(target);
            var start = 0;
            var end = target.value.length;

            target.value = this.suggestions[index];
            var changeCB = target.getAttribute('changeCallback');
            if(changeCB)
                eval(changeCB);

            // select text
            if (typeof target.selectionStart == 'number') {
                target.selectionStart = cursorStart;
                target.selectionEnd = (start > 0 ? start + 1 : 0) + this.suggestions[index].length;
            }
            else if (typeof document.selection.createRange() == 'object') {
                var range = target.createTextRange();
                range.moveStart('character', cursorStart);
                range.moveEnd('character', (start > 0 ? start + 1 : 0) + this.suggestions[index].length);
                range.select();
            }
            else target.focus();
        }

        this.closeList(target);
        return false;
    }

    /**
     * Handles the input event on key up.
     */
    this.handleInput = function(event) {
        // get event
        if (!event) event = window.event;

        // get key code
        var keyCode = 0;
        if (event.which) keyCode = event.which;
        else if (event.keyCode) keyCode = event.keyCode;

        // get target
        var target = this.getEventTarget(event);
        this.activeTarget = target;

        // handle special keys
        // array down
        if (keyCode == 40) return false;
        // array up
        if (keyCode == 38) return false;
        // escape
        if (keyCode == 27) return false;
        // backspace
        if (keyCode == 8) return false;
        // return
        if (keyCode == 13) {
            if (this.selectedIndex != -1) {
                this.insertSelectedOption();
                return false;
            }

            return true;
        }

        // arrow left, arrow right,
        if (keyCode == 37 || keyCode == 39 || keyCode == 0) {
            this.insertAutomatically = false;
        }
        else {
            this.insertAutomatically = true;
        }

        this.getSuggestList(target);
    }

    /**
     * Sets the selected index.
     */
    this.setSelectedIndex = function(selectedIndex) {
        // remove old selection
        if (this.selectedIndex != -1) {
            var oldElement = document.getElementById('optionList'+this.activeTarget.id+'Element'+this.selectedIndex);
            if (oldElement) oldElement.className = "";
        }

        // new selection
        this.selectedIndex = selectedIndex;
        var element = document.getElementById('optionList'+this.activeTarget.id+'Element'+this.selectedIndex);
        if (element) element.className = "suggestMenuItemSelected";
    }

    /**
     * Moves to the next or previous element in the suggestion list.
     */
    this.moveList = function(direction) {
        if (direction == 'next') {
            if (this.selectedIndex + 1 < this.suggestions.length) {
                this.setSelectedIndex(this.selectedIndex + 1);
            }
        }
        else {
            if (this.selectedIndex > 0) {
                this.setSelectedIndex(this.selectedIndex - 1);
            }
        }

        return false;
    }

    /**
     * Handles the mouse click event in the input field.
     */
    this.handleClick = function(event) {
        this.closeAllLists(event);

        // get event
        if (!event) event = window.event;

        // get target
        var target = this.getEventTarget(event);
        this.activeTarget = target;

        this.insertAutomatically = false;
        this.getSuggestList(target);
    }

    /**
     * Opens a new ajax request to get a new suggestion list.
     */
    this.getSuggestList = function(target) {
        // get active string
        var string = this.getActiveString(target);
        var funcName = this.ajaxFuncs[target.id];

        // send request
        if (string != '') {
            eval(funcName + "(unescape('" + escape(string) + "'));");
        }
        else {
            this.closeList();
        }
    }

    /**
     * Receives the response of an opened ajax request.
     */
    this.receiveResponse = function(responseNode) {
        this.suggestions = new Array();
        for(var i = 0; i < responseNode.childNodes.length; i++)
        {
            if(responseNode.childNodes[i].nodeName == 'suggestion')
                this.suggestions[this.suggestions.length] = responseNode.childNodes[i].firstChild.nodeValue;
        }

        this.showList();
    }

    /**
     * Shows the suggestion list.
     */
    this.showList = function() {
        this.closeList();

        if (this.suggestions.length > 0 && this.activeTarget) {
            if (this.suggestions.length == 1) {
                if (this.insertAutomatically) {
                    this.setSelectedIndex(0);
                    this.insertSelectedOption();
                }
            }
            else {
                // get option div
                var optionDiv = document.getElementById('option'+this.activeTarget.id);
                if (optionDiv) {
                    optionDiv.className = 'suggestMenu';

                    // create option list
                    var optionList = document.createElement('ul');
                    optionList.id = 'optionList'+this.activeTarget.id
                    optionDiv.appendChild(optionList);

                    optionList = document.getElementById('optionList'+this.activeTarget.id);

                    // add list elements
                    for (var i = 0; i < this.suggestions.length; ++i) {
                        var optionListElement = document.createElement('li');
                        optionListElement.id = 'optionList'+this.activeTarget.id+'Element'+i;
                        optionList.appendChild(optionListElement);

                        var optionListLink = document.createElement('a');
                        optionListLink.innerHTML = encodeHTML(this.suggestions[i]);
                        optionListLink.name = i;
                        optionListLink.onmousedown = function() { ajaxSuggest.insertSelectedOption(this.name); };

                        document.getElementById('optionList'+this.activeTarget.id+'Element'+i).appendChild(optionListLink);
                    }
                }

                this.setSelectedIndex(0);
            }
        }
    }

    /**
     * Closes active suggestion list.
     */
    this.closeList = function(target) {
        this.selectedIndex = -1;

        if (!target) {
            target = this.activeTarget;
        }

        if (target) {
            var optionDiv = document.getElementById('option'+target.id);
            if (optionDiv) {
                // remove children
                var optionList = document.getElementById('optionList'+target.id);
                if (optionList) {
                    optionDiv.removeChild(optionList);
                }

                // change class to hidden
                optionDiv.className = 'hidden';
            }
        }
    }

    /**
     * Returns the active string.
     */
    this.getActiveString = function(target) {
        var cursorPosition = this.getCursorPosition(target);

        return target.value.substring(0, cursorPosition);
    }

    /**
     * Returns the current cursor position.
     */
    this.getCursorPosition = function(target) {
        var cursorPosition = target.value.length;

        if (typeof target.selectionStart == 'number') {
            cursorPosition = target.selectionStart;
        }
        else if (typeof document.selection.createRange() == 'object') {
            var range = document.selection.createRange();
            range.moveEnd('textedit', 1);
            var last = String(range.text);
            cursorPosition = cursorPosition - last.length;
        }

        return cursorPosition;
    }
}

var ajaxSuggest = new AjaxSuggest();
