/**
 * ajaxWM - the free web based window manager for remote terminals
 * Copyright (C) 2007 Dennis Felsing, Andreas Waidler
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $Id: AjaxWM_Manager.js 64 2007-11-30 19:10:59Z pwnd $
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * AjaxWM_Manager
 *
 * The window manager class that holds the windows, configuration, handles
 * events, communicates with the server and manages the virtual desktops.
 * Performs requests to the server only when its by one of the other classes
 * or a key is pressed. Mouse interaction and dynamic synchronization timeouts
 * are handled by the windows or other elements themselfes.
 */

//-----------------------------------------------------------------------------

// Object.clone()//{{{

Object.prototype.clone = function ()
{
    var clone = new Object();
    for (var property in this) {
        if ('object' == typeof(this[property]))
            clone[property] = this[property].clone();
        else
            clone[property] = this[property];
    }
    return clone;
}

//}}}
// Array.clone()//{{{

Array.prototype.clone = function ()
{
    var clone = new Array();
    for (var property in this) {
        if ('object' == typeof (this[property]))
            clone[property] = this[property].clone();
        else
            clone[property] = this[property];
    }
    return clone;
}

//}}}
// function traceNode(path, node)//{{{

    /**
     * Follows a path and returns the node the path points to.
     * Path has to be relative to node.
     */

    function traceNode(path, node)
    {
// if (node.nodeType != Node.ELEMENT_NODE)
//            traceNode(path[0], node.nextSibling);

        var trace = path.clone();
        trace.shift(); // this position

        if (trace.length == 0)
            return node;

        var next = trace[0];
        return traceNode(trace, node.childNodes[next]);
    }

//}}}
////function _replaceTrace(path, node)//{{{
//
//
//    /**
//     * Replaces the passed position with a node.
//     */
//
//    function _replaceTrace(path, node)
//    {
//    }
//
////}}}


ajaxWM = new function AjaxWM_Manager()
{
// {{{ properties

    // version of the whole project
    var _version = "0.1.1";
    var _class = "AjaxWM_Manager";
    // session id that will be generated by the server on login
    var _sid;
    var _sessionTimeout;
    var _maxIdleTime = 600000; // ms = 10min // TODO value in minutes
    // current scree, values can be "login", "desktop", undefined
    var _screenType;
    var _theme     = 'lim';
    var _templates = new Object();
    // html nodes of virtual desktops. Amount can be changed
    var _desktops     = new Array(8);
    // array holding instances of the windows (AjaxWM_Window)
    var _windows      = new Object();
    // order of the windows. Key is position, value is id
    var _windowOrder  = new Array();
    // ids of the currently focused/active window or desktop
    var _activeDeskId;
    var _focusedWinId;
    // queue of event objects (associative arrays (Object))
    var _queuedEvents = new Array();
    // flag that indicates if currently performing a request
    var _isSending = false;
    // timeout object for calling the method _processQueue
    var _processQueueTimeout;
    // timeout object for displaying that a request failed
    var _connectionTimeout;
    // indicates if the javascript part should print debug messages
    var _enableDebugging = true;
    // div node where debug messages should be displayed
    var _debugNode;
    // maximum characters in the debug message area
    var _debugMaxLength = 500;
    // how long should the debug area be displayed (seconds)
    // -1 disables auto-hiding
    var _debugDisplayDuration = -1;
    // before the debug message area is hidden again
    var _debugHideTimeout;

// }}}
// {{{ this.init = function()

    /**
     * Initializes the window manager by setting up the GUI and
     * creating the login screen.
     *
     * @see AjaxWM_Manager::_initGui();
     * @see AjaxWM_Login_Screen
     */

    this.init = function()
    {
        _initGui();
        
        // start displaying a login screen
        ajaxWM_LoginScreen.create();

        _screenType = 'login';
    };

// }}}
//{{{this.login = function(name, password)

    /**
     * Tries to log in with the given login name and password.
     *
     * Does only create a login event and send it to the server _directly_
     * via _communicate(). The queue is skipped since this method should
     * only be called on when the user is not logged in and can't create
     * any other events, neither directly nor indirectly.
     * _communicate() will handle the response (session id or failure).
     */

    this.login = function(name, password)
    {
        var event_li = { 'e':'li', 'u':name, 'p': password };
        _communicate(event_li);
    }

//}}}
//{{{ this.newWindow = function(w, h, x, y) 

    /**
     * Creates a new window.
     *
     * Creates the event and enqueues it. The window ID is generated
     * by the server and will be the response to the request. Despite
     * x and y not being sent to the server they have to be put in the
     * event so that _handleResponse() can handle them. They have to
     * be the last two keys in the event object so they can be cut off
     * easily. x has to come before y!
     */

    this.newWindow = function(w, h, x, y)
    {
        if (null == w) var w = 80;
        if (null == h) var h = 25;
        if (null == x) var x = 0;
        if (null == y) var y = 0;

        var event_nw = { 'e':'nw', 'w':w, 'h':h, 'x':x, 'y':y };
        _enqueueEvent(event_nw);
    }

//}}}
//{{{ this.closeWindow = function(win)

    /**
     * Sends a close-window-event to the server
     *
     * The response is handled by _communicate(). When the server reports
     * success, the function destroy() of that window should be called
     * which finally removes it from the interface.
     *
     * @see AjaxWM_Window::close()
     * @see AjaxWM_Window::destroy()
     */

    this.closeWindow = function(win)
    {
        if (win.getStatus() != "closing") {
            ajaxWM.debugMessage(_class, 'closeWindow',
                'Window not closed. ' +
                'Call _windows[' + win.getId() + '].close() before.');
            return;
        }

        var event_cw = { 'e':'cw', 'W':win.getId() };
        _enqueueEvent(event_cw);
    }

//}}}
//{{{ this.switchToWindow = function(win)

    /**
     * Focuses the window passed as parameter.
     *
     * When the window is hidden its previous state will also be
     * restored. The order of the windows will also be updated.
     */

    this.switchToWindow = function(win)
    {
        var id = win.getId();

        // do nothing if window is already focused
        if (_focusedWinId == id)
            return;

        ajaxWM.debugMessage(_class, "switchToWindow",
            _focusedWinId + "->" + id);

        // update order

        // clear this window from list (order)
        for (var i = 0; i < _windowOrder.length; ++i) {
            if (_windowOrder[i] == id)
                _windowOrder.splice(i, 1);
        }
        // and add it to its top
        _windowOrder.push(id)
        // set flag for focused window
        _focusedWinId = id;

        // gui part: move to foreground and show it
        _desktops[_activeDeskId].appendChild(win.getNode());
        win.show();
    }

//}}}
//{{{ this.switchToDesktop = function(deskId)

    /**
     * Switch to the virtual desktop specified.
     *
     * Switches to the desktop with the passed desktop id by hiding
     * the node of the desktop currently active and displaying
     * the one from the desktop of which the id has been passed.
     */

    this.switchToDesktop = function(deskId)
    {
        // check if that desktop exists
        if (_desktops[deskId] == undefined) {
            ajaxWM.debugMessage(_class, 'switchToDesktop',
                'Desktop ' + deskId + ' does not exist.');
            return false;
        }

        // desktop exists, continue switching to it

        var debug = 'Switching ';

        if (_activeDeskId != undefined) {
            debug += 'from ' + _activeDeskId + ' ';
            _desktops[_activeDeskId].style.display = "none";
        }

        debug += 'to ' + deskId;
        ajaxWM.debugMessage(_class, 'switchToDesktop', debug);

        _desktops[deskId].style.display = "block";
        _activeDeskId = deskId;

        return true;
    }

//}}}
//{{{ this.syncWindow = function(win, isResized)

    /**
     * Enqueues a synchronization event to be sent to the server to poll
     * the current contents of the window which is passed as parameter.
     *
     * @param win object the window to be synchronized
     * @param isResized bool optional: when true resend width and height
     * @see AjaxWM_Manager::_enqueueEvent()
     * @see AjaxWM_Manager::_processQueue()
     * @see AjaxWM_Window::update()
     */

    this.syncWindow = function(win, isResized)
    {
        // create the event and add it to the queue
        var event_sy = { 'e':'sy', 'W':win.getId() };
        
        if (isResized) {
            event_sy['w'] = win.getWidth();
            event_sy['h'] = win.getHeight();
        }

        _enqueueEvent(event_sy);
    }

//}}}
//{{{this.debugMessage = function(message)

    /**
     * Prints a debugging message.
     *
     * Should always be called directly without checking if debug mode
     * is enabled before. This method will check if a message should
     * be printed itsself!
     */

    this.debugMessage = function(className, functionName, message)
    {
        // when debug mode is disabled do nothing
        if (!_enableDebugging)
            return;

        // alert when this method is called the wrong way
        if (arguments.length != 3) {
            alert("AjaxWM_Manager.debugMessage()" +
                  "called with missing parameters!");
        }

        // create div layer of new message
        var div_message = document.createElement('div');
        var p_caller    = document.createElement('p');
        var p_text      = document.createElement('p');
        p_caller.innerHTML = className + "::" + functionName + "()";
        p_text.innerHTML   = message;
        // set their classes
        div_message.className = 'message';
        p_caller.className    = 'caller';
        p_text.className      = 'text';

        var length = p_caller.innerHTML.length + p_text.innerHTML.length;

        // when the message itsself is longer than or equal to the allowed
        // maximum simply clear the other messages and cut a bit off of
        // this message in case that it is to long
        switch (true) {
        case (length > _debugMaxLength):
            if (p_caller.innerHTML.length >= _debugMaxLength) { 
                p_text.innerHTML = "";
                p_caller.innerHTML.substr(0, _debugMaxLength);
            } else {
                var charsLeft = _debugMaxLength - p_caller.innerHTML.length;
                p_text.innerHTML = p_text.innerHTML.substr(0, charsLeft);
            }

        case (length == _debugMaxLength):
            _debugNode.innerHTML = "";
            break;

        default:
            // current length of all debug messages
            var cLength = _calcDebugLength();

            // if the message plus the current length exceeds the limit
            // remove the oldest message until it fits
            while((cLength + length) > _debugMaxLength) {
                _debugNode.removeChild(_debugNode.firstChild);
                cLength = _calcDebugLength();
            }
        }

        // current debug message area and possibly the message
        // have been editted to fit into the limit.
        // Add the message to the area
        div_message.appendChild(p_caller);
        div_message.appendChild(p_text);
        _debugNode.appendChild(div_message);

        // display debugging area
        _displayDebugMessages();
    }

//}}}
    this.getVersion      = function() { return _version; }
    this.getTheme         = function() { return _theme; }
    this.getTemplate      = function() { return _templates[_theme]; }
    this.getDesktopById   = function(id) { return _desktops[id]; }
    this.getActiveDesktop = function() { return _desktops[_activeDeskId]; }
    this.getActiveDeskId  = function() { return _activeDeskId; }
//{{{function _initGui()

    /**
     * Initializes the graphical user interface.
     *
     * Takes care that everything concerning the look and usability is set up
     * correctly.
     */

    function _initGui()
    {
        document.title += "-" + _version;

        // if debug mode is enabled create the debug message node
        if (_enableDebugging) {
            _debugNode = document.createElement('div');
            _debugNode.className = 'debug';
            document.getElementsByTagName('body')[0].appendChild(_debugNode);
        }

        _templates[_theme] = new AjaxWM_Template(_theme);
        _templates[_theme].loadCss();
        _templates[_theme].parse();
    }

//}}}
//{{{function _initDesktopUsage()

    /**
     * Initializes the desktops.
     *
     * Prepares the body for desktop usage, creates the virtual desktops,
     * appends them to the body, switches to the first one of them and
     * starts the keyboard hook.
     */

    function _initDesktopUsage()
    {
        // get body and initialize it for desktop usage
        var body = document.getElementsByTagName('body')[0];
        body.className = "desktop";

        // create desktops
        for (var i = 0; i < _desktops.length; ++i) {
            _desktops[i] = document.createElement('div');
            _desktops[i].className = 'desktop';
            body.appendChild(_desktops[i]);
        }

        // switch to first desktop
        ajaxWM.switchToDesktop(0);

        // set up the keyboard hook
        document.onkeypress = _onKeyPress;
        document.onkeydown = _onKeyDown;

        _screenType = 'desktop';
    }

//}}}
//{{{function _onKeyDown(window_event)

    /**
     * Event handler for the onkeydown method.
     */

    function _onKeyDown(window_event)
    {
        if (!_SARISSA_IS_IE)
            return;

        if (!window_event)
            var window_event = window.event;

        o = { 9:1, 8:1, 27:1, 33:1, 34:1, 35:1, 36:1, 37:1, 38:1, 39:1,
              40:1, 45:1, 46:1,112:1, 113:1, 114:1, 115:1, 116:1, 117:1,
              118:1, 119:1, 120:1, 121:1, 122:1, 123:1 };

        if (o[window_event.keyCode]
        || window_event.ctrlKey || window_event.altKey) {
            window_event.which = 0;
            return _onKeyPress(window_event);
        }
    }

//}}}
//{{{function _onKeyPress(window_event)

    /**
     * Event handler for the onkeypress method.
     *
     * Checks which keys were pressed, convert their key codes back
     * into strings regarding special key combinations suchs as e.g.
     * [CTRL][<KEY>]. Hotkeys are handled by
     * AjaxWM_Manager::_handleHotkey(). Returns false so that the
     * action which would normaly be executed on a key press
     * will be blocked.
     */

    function _onKeyPress(window_event)
    {
        var keyCode;
        var keyString = "";

        // determine the key that was pressed and handle hotkeys//{{{

        if (!window_event)
            var window_event = window.event;

        if (window_event.keyCode)
            keyCode = window_event.keyCode;

        if (window_event.which)
            keyCode = window_event.which;

        // check if ALT was pressed
        if (window_event.altKey) {//{{{
            // when the ctrl key is pressed additionally to the alt key
            // then it should be a hotkey
            if (window_event.ctrlKey && _handleHotkey(window_event)) {
                // when a hotkey is found and could be executed
                // cancel this event
                return false;
            } else {
                if (keyCode >= 65 && keyCode <= 90)
                    keyCode += 32;
                if (keyCode >= 97 && keyCode <= 122) {
                    keyString = String.fromCharCode(27)
                              + String.fromCharCode(keyCode);
                }
            }//}}}
        // check if CTRL was pressed
        } else if (window_event.ctrlKey) {//{{{
            if (keyCode >= 65 && keyCode <= 90)
                keyString = String.fromCharCode(keyCode-64); // Ctrl-A..Z
            else if (keyCode >= 97 && keyCode <= 122)
                keyString = String.fromCharCode(keyCode-96); // Ctrl-A..Z
            else if (keyCode == 54)
                keyString = String.fromCharCode(30); // Ctrl-^
            else if (keyCode == 109)
                keyString = String.fromCharCode(31); // Ctrl-_
            else if (keyCode == 219)
                keyString = String.fromCharCode(27); // Ctrl-[
            else if (keyCode == 220)
                keyString = String.fromCharCode(28); // Ctrl-\
            else if (keyCode == 221)
                keyString = String.fromCharCode(29); // Ctrl-]
            else if (keyCode == 219) // TODO: duplicate, remove?
                keyString = String.fromCharCode(29); // Ctrl-]
            else if (keyCode == 219) // TODO: duplicate, remove?
                keyString = String.fromCharCode(0);  // Ctrl-@
        //}}}
        // when window.event.which is usuable but its value is 0
        // we check the window.event.keyCode here. This gives us a special key
        // like Esc, Tab...
        } else if (window_event.which == 0) {//{{{
            if (keyCode == 9)
                keyString = String.fromCharCode(9);  // Tab
            else if (keyCode == 8)
                keyString = String.fromCharCode(127);  // Backspace
            else if (keyCode == 27)
                keyString = String.fromCharCode(27); // Escape
            // pressed key has no ASCII value. Enter the string directly,
            // prefixed with escape
            else {
                if      (keyCode == 33)  keyString = "[5~";  // PgUp
                else if (keyCode == 34)  keyString = "[6~";  // PgDn
                else if (keyCode == 35)  keyString = "[4~";  // End
                else if (keyCode == 36)  keyString = "[1~";  // Home
                else if (keyCode == 37)  keyString = "[D";   // Left
                else if (keyCode == 38)  keyString = "[A";   // Up
                else if (keyCode == 39)  keyString = "[C";   // Right
                else if (keyCode == 40)  keyString = "[B";   // Down
                else if (keyCode == 45)  keyString = "[2~";  // Ins
                else if (keyCode == 46)  keyString = "[3~";  // Del
                else if (keyCode == 112) keyString = "[[A";  // F1
                else if (keyCode == 113) keyString = "[[B";  // F2
                else if (keyCode == 114) keyString = "[[C";  // F3
                else if (keyCode == 115) keyString = "[[D";  // F4
                else if (keyCode == 116) keyString = "[[E";  // F5
                else if (keyCode == 117) keyString = "[17~"; // F6
                else if (keyCode == 118) keyString = "[18~"; // F7
                else if (keyCode == 119) keyString = "[19~"; // F8
                else if (keyCode == 120) keyString = "[20~"; // F9
                else if (keyCode == 121) keyString = "[21~"; // F10
                else if (keyCode == 122) keyString = "[23~"; // F11
                else if (keyCode == 123) keyString = "[24~"; // F12
                if (keyString.length) {
                    keyString = String.fromCharCode(27)+keyString;
                }
            }
        } else {
            if (keyCode == 8)
                keyString = String.fromCharCode(127);  // Backspace
            else
                keyString = String.fromCharCode(keyCode);
        }//}}}

//}}}
        // everything that reaches this point is a key press event that
        // should be passed to a window
        try {
            if (!keyString.length || isNaN(_focusedWinId)) {
                var e = 'keyString="' + keyString + '"';
                e += ', isNaN(_focusedWinId)=' + isNaN(_focusedWinId);
                throw e;
            }
            // encode keys to UTF-8
            switch (keyString) {
            case '+':
                keyString = '%2B';
                break;
            default:
                keyString = encodeURIComponent(keyString);
            }
            _enqueueEvent({ 'e':'kp', 'W':_focusedWinId, 'k':keyString });
        } catch (e) {
            var error = "Error: " + e;
            ajaxWM.debugMessage(_class, "_onKeyPress", error);
        } finally {
            window_event.cancelBubble = true;
            if (window_event.stopPropagation) window_event.stopPropagation();
            if (window_event.preventDefault)  window_event.preventDefault();
            return false;
        }
    }

//}}}
//{{{function _handleHotkey(window_event)

    /**
     * Checks if the onkeypress event contains an hotkey. If so, its
     * corresponding action will be executed.
     *
     * Since this method is only called when _onKeyPress() finds alt
     * and ctrl pressed it will not check for them a second time.
     * Returns true if the pressed combination is a registered hotkey,
     * false if not.
     */

    function _handleHotkey(window_event)
    {
        var key = String.fromCharCode(window_event.which);
        switch (key) {
        case 'w':
            ajaxWM.newWindow(80,25,0,0);
            return true;

        case 'c':
            if (_focusedWinId == undefined)
                return true;

            if (_windows[_focusedWinId] == undefined)
                return true;

            _windows[_focusedWinId].close();
            return true;

        case 'd':
            var tmp = "";
            for (var i = 0; i < _windowOrder.length; ++i) {
                tmp += "i=" + i + " _winO[i]=" + _windowOrder[i] + "\n";
            }
            alert (tmp);
            return true;

        default:
            if (!isNaN(key)) {
                // debug for IE
                ajaxWM.debugMessage(_class, '_handleHotkey',
                    'which=' + window_event.which + ' key=' + key);
                // key is a number - change desktop
                // substract 1 because desktop ids start at 0, not at 1
                ajaxWM.switchToDesktop(key - 1);
                return true;
            } else {
                return false;
            }
        }
    }

//}}}
//{{{function _enqueueEvent(ajaxWM_event)

    /**
     * Adds an event to the queue for sending it to the server.
     *
     * The event is added to the top of the queue. When no request is
     * performed a timeout of one millisecond for the _processQueue()
     * method is set which will send everything in the queue. If that
     * method is already running it won't be called twice as it will
     * run until the whole queue is empty.
     */

    function _enqueueEvent(ajaxWM_event)
    {
        _queuedEvents.push(ajaxWM_event);
        if (_isSending)
            return;
        window.clearTimeout(_processQueueTimeout); // bugfix
        _processQueueTimeout = window.setTimeout(_processQueue, 1);
    }

//}}}
//{{{ function _isEventValid(ajaxWM_event)

    /**
     * Checks whether an event is valid or not.
     *
     * An event is invalid, when
     * * it concerns a window that is not existing
     *
     * @return  bool  true if event is valid, false if not
     */

    function _isEventValid(ajaxWM_event)
    {
        // TODO code cleaning
        var result = true;
        try {
            if (undefined != ajaxWM_event['W']) {
                if (undefined == _windows[ajaxWM_event['W']]) {
                    var e = 'Window ' + ajaxWM_event['W']
                          + ' does not exist. '
                          + 'Event: ' + ajaxWM_event['e'];
                    throw e;
                }
            }
        } catch (e) {
            ajaxWM.debugMessage(_class, '_isEventValid', e);
            result = false;
        } finally {
            return result;
        }
    }

//}}}
//{{{function _processQueue()

    /**
     * Processes the queue until it is empty.
     *
     * Calls the window managers _communicate() method which handles the
     * response according to the request that has been sent.
     *
     * FIXME: add flag that indicates if all requests have completed
     */

    function _processQueue()
    {
        // check if this method is already running.
        // Since this method will always run until the queue is empty there is
        // no need to call it twice.
        if (_isSending != 0)
            return;
        _isSending = 1;

        if (!_screenType)
            return;

        window.clearTimeout(_sessionTimeout);
        _sessionTimeout = window.setTimeout(_onSessionTimeout, _maxIdleTime);

        // start the loop which will run until all events are processed
        while (_queuedEvents.length > 0) {
            // get the next event in list. It will be on bottom of the stack
            // (index = 0, new events will be inserted on top)
            var e = _queuedEvents.shift();

            // when the event is not valid, continue with next event
            if (!_isEventValid(e))
                continue;

            // send the request for this event and handle its response
            _communicate(e);
        }

        _isSending = 0;
    }

//}}}
//{{{function _communicate(ajaxWM_event)

    /**
     * Performs a request for the passed event and handles the servers
     * response.
     *
     * Contains the method onreadystatchange of the request that was end
     * to the server. That function will check if everything went fine
     * and then call AjaxWM_Manager::_handleResponse().
     */

    function _communicate(ajaxWM_event)
    {
        var query = (_sid != undefined ? "s=" + _sid : '');
        for (key in ajaxWM_event) {
            // cut off x and y since they are only needed for internal use
            if (ajaxWM_event['e'] == 'nw' && key == 'x')
                break;
            query += "&" + key + "=" + ajaxWM_event[key];
        }

        // create request object
        var request = new XMLHttpRequest();

        // before sending the request we have to set the response handling up
    //{{{ request.onreadystatechange = function()

        /**
         * Handles the response of the XMLHttpRequest.
         *
         * Validates request state and calls
         * AjaxWM::_handleResponse() on the request passing that
         * checks.
         */
        
        request.onreadystatechange = function ()
        {
            // state 4 means that the request is completed
            if (request.readyState != 4)
                return;

            window.clearTimeout(_connectionTimeout);

            // check the HTML status code if it is 200 (OK)
            if (request.status != 200) {
                ajaxWM.debugMessage(_class, "request.onreadystatechange", 
                    "Request got bad HTML status code: " + request.status);
                return;
            }

            // every request that reaches this point is valid

            _handleResponse(request, ajaxWM_event);
        }

    //}}}
//ajaxWM.debugMessage(_class, "_communicate", query);
        request.open("POST", "event", true);
        request.setRequestHeader('Content-Type',
                                 'application/x-www-form-urlencoded');
        try {
            window.clearTimeout(_connectionTimeout); // bugfix
            _connectionTimeout = window.setTimeout(_timeout, 20000);
            request.send(query);
        } catch (exception) {
            window.clearTimeout(_connectionTimeout);
            var error = "Could not send request.";
            error += " Query: " + query;
            error += " Exception: " + exception.message;
            ajaxWM.debugMessage(_class, "_communicate", error);
        }
    }

//}}}
//{{{ function _handleResponse(r, e)

/**
 * Handles the passed response for the given ajaxWM event.
 *
 * @param XMLHttpRequest r Completed request (only response is needed)
 * @param Object e AjaxWM-event for wich the request has been launched
 */

function _handleResponse(r, e)
{
    switch (e['e']) {
    case 'li':
        // response is session id or negative error code
        var response = r.responseText;
        if (response < 0) { // error codes are negative
            ajaxWM.debugMessage(_class, '_communicate',
                                'Login failed. Response is ' + response);
            ajaxWM_LoginScreen.setResult(false);
            break;
        }
        _sid = response;
        ajaxWM.debugMessage(_class, '_communicate',
                            'Login successful, session id is ' + _sid);
        ajaxWM_LoginScreen.setResult(true);
        _initDesktopUsage();
        _sessionTimeout = window.setTimeout(_onSessionTimeout, _maxIdleTime);
        break;

    case 'nw':
        // response is id of new window or -1 on failure
        var id = r.responseText;
        if (id == -1) {
            ajaxWM.debugMessage(_class, '_communicate',
                                'Could not create new window. ' +
                                'Response was "' + id + '"');
            break;
        }
        _windows[id] = new AjaxWM_Window(
            _activeDeskId, id, e['w'], e['h'], e['x'], e['y']);
        ajaxWM.switchToWindow(_windows[id]);
        break;

    case 'cw':
        // response can be handled the same way as in e=kw (by now)

    case 'kw':
        // response is result (0 = false, 1 = true)

        var id = e['W'];
        var result = (r.responseText == 1);

        if (!result) {
            ajaxWM.debugMessage(_class, '_communicate',
                'Server returned ' + result + ' for event ' +
                e['e'] + ' of id ' + e['W']);
        } else if (_windows[id].destroy()) {
            ajaxWM.debugMessage(_class, '_communicate',
                                'Removing window ' + id);

            delete(_windows[id]);
            // remove window id from order
            for (var i = 0; i < _windowOrder.length; ++i) {
                if (_windowOrder[i] == id)
                    _windowOrder.splice(i, 1);
            }
            var previousWindow = _getRecentWindow();
            // when there is another window
            if (previousWindow != -1) {
                // switch to it
                ajaxWM.switchToWindow(previousWindow);
            // if not
            } else {
                // set the flag to indicate that no window is focused anymore
                _focusedWinId = undefined;
            }
        } else {
            ajaxWM.debugMessage(_class, '_communicate',
                                'destroy() failed.');
        }
        break;

    case 'kp':
        // response can be handled the same way as in e=sy

    case 'sy':
        // response is either new content or <idem />;
        // AjaxWM_Window::update() handles this
        var id = e['W'];
        var response = r.responseXML.documentElement;
        _windows[id].update(response);
        if (e['w'] && e['h'])
            _windows[id].calcCharSize();
        break;
    }
}

//}}}
//{{{function _timeout()

    /**
     * Called by _communicate() when the client did not receive a response
     * after a timeout of some seconds.
     */

    function _timeout()
    {
        ajaxWM.debugMessage(_class, "_communicate", "Timeout.");
    }

//}}}
//{{{ function _calcDebugLength([div_message])

    /**
     * Calculates the length of debugging messages and returns the
     * amount of characters in total.
     *
     * If the optional parameter div_message is passed, this method
     * will only calculate the total chars of that message. If not
     * the whole length of all currently existing debugging messages
     * is calculated.
     */

    function _calcDebugLength(div_message)
    {
        // the total length
        var length = 0;
        // array containing all message div-layers
        var messages = new Array();
        // temporary array that holds the two paragrafs of a message div-layer
        var p = new Array();

        if (arguments.length != 0) {
            messages[0] = div_message;
        } else {
            messages = _debugNode.getElementsByTagName('div');
        }

        for (i = 0; i < messages.length; ++i) {
            if (messages[i].className != 'message')
                continue;

            p = messages[i].getElementsByTagName('p');
            for (j = 0; j < p.length; ++j) {
                if (p[j].className != 'caller' && p[j].className != 'text')
                    continue;

                length += p[j].innerHTML.length;
            }
        }
        return length;
    }

//}}}
//{{{function _displayDebugMessages()

    /**
     * Displays the debug message area.
     *
     * Sets a timeout after which the area will be hidden again
     * and clear the existing timeout before. The message area won't
     * be hidden if -1 was specified as duration.
     */

    function _displayDebugMessages()
    {
        if (_debugNode.style.display != "block")
            _debugNode.style.display = "block";

        // clear timeout, if existing
        if (_debugHideTimeout)
            window.clearTimeout(_debugHideTimeout);

        // duration of -1 means forever
        if (_debugDisplayDuration == -1)
            return;

        // set timeout
        var time = _debugDisplayDuration * 1000;
        _debugHideTimeout = window.setTimeout(_hideDebugMessages, time);
    }

//}}}
//{{{ function _hideDebugMessages()

    /**
     * Hides the debug message area.
     * 
     * Since this method is normally called by a timeout set by
     * _displayDebugMessages() the timeout will be removed.
     */

    function _hideDebugMessages()
    {
        _debugNode.style.display = "none";
        window.clearTimeout(_debugHideTimeout);
    }

//}}}
//{{{ function _getRecentWinId()

    /**
     * Returns ID of the most recent window
     * or -1 when there has no activity been yet.
     *
     * @return  int  ID of most recent window or -1
     */

    function _getRecentWinId()
    {
        var result = -1;
        while (_windowOrder.length > 0) {
            var top = _windowOrder.pop();
            if (top != undefined) {
                _windowOrder.push(top);
                result = top;
                break;
            }
        }
        return result;
    }

//}}}
//{{{ function _getRecentWindow()

    /**
     * Returns the most recent window or -1 when there has no
     * activity been yet.
     *
     * @return  AjaxWM_Window  The most recent window, -1 on failure.
     */

    function _getRecentWindow()
    {
        var result = _getRecentWinId();
        if (result != -1)
            result = _windows[result];
        return result;
    }

//}}}
//function _onSessionTimeout()//{{{

    /**
     * Called when the user was idle for a specified timeout.
     * Kills the session and removes the GUI.
     */

    function _onSessionTimeout()
    {
        window.setTimeout(_kill, 1);
        alert("ajaxWM session timed out.");
    }

//}}}
//function _removeWindows()//{{{

    /**
     * Destroys all windows without sending any events to the server.
     * Should only be called when the session has already been closed.
     */

    function _removeWindows()
    {
        // FIXME handle case when no windows is existing
        for (key in _windows)
            _windows[key].destroy();
        _windows = new Object();
        _windowOrder = new Array();
    }

//}}}
//function _removeDesktops()//{{{

    /**
     * Removes all virtual desktops.
     *
     * Since the windows are on the desktops they will be removed from
     * display, too. But they won't be deleted, they may be added to
     * a desktop again.
     */

    function _removeDesktops()
    {
        for (var i = 0; i < _desktops; ++i) {
            document.getElementsByTagName('body')[0].removeChild(
                _desktops[i]);
            _desktops[i] = undefined;
        }
    }

//}}}
//function _kill()//{{{

    /**
     * Kills the graphical user interface.
     *
     * Removes any nodes created and resets event handlers.
     * No server request will be launched by any action taken
     * in this method.
     */

    function _kill()
    {
        _screenType = undefined;
        _sid = undefined;
        _removeWindows();
        _removeDesktops();
        if (_enableDebugging) 
            document.getElementsByTagName('body')[0].removeChild(_debugNode);
        document.getElementsByTagName('body')[0].className = '';
        document.onkeypress = null;
        document.onkeydown = null;
    }

//}}}
};

//-----------------------------------------------------------------------------
// Ensure that the code does not exceed 79 characters per line using an indent
// of 4 spaces/level. Use NO TABS at all. Leave the mode line as it is. For
// further information see file docs/CodingConvention.
// vim: et ts=4 sw=4 fdm=marker
