/**
 * 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_Template.js 62 2007-11-30 19:10:35Z pwnd $
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * AjaxWM_Template
 *
 * Handles the behaviour of the GUI according to the template parsed.
 * Contains all the XML nodes of a theme, parses them to HTML, traces
 * nodes that have special events like onmousedown.
 * Classes get their very own nodes by calling AjaxWM_Template::cloneNodes().
 * The only thing they have to do is additionally calling their own
 * methods for registering the events (i.e. tracing positions of nodes
 * in their main node).
 *
 * The node property contains the parsed HTML nodes and traces as follows:
 * 'window'  => associative array
 *   'main'    => main node
 *   'title'   => node that contains only the window title
 *   'content' => node that contains only the terminal
 *   'chmove'  => array of nodes that change their class while moving
 *   'chresize'=> array of nodes that change their class while resizing
 *   'pos'     => assoc. array of non-auto positioned nodes
 *     'fixed'   => array of nodes that move by the same amount as the window
 *     'right'   => array of n. placed directly right to the window content
 *     'bottom'  => array of n. placed directly under the window content
 *   'size'    => assoc. array of non-auto sized nodes
 *     'set'     => array o. nodes resized by the same amount as the window
 *   'events'  => array of event types
 *     <type>    => associative array of events
 *       'path'    => array. each value is part of path to this element
 *       'action'  => keyword of what should be done when that event occurs
 */
//-----------------------------------------------------------------------------

AjaxWM_Template = function(name)
{
// properties//{{{

    var _this  = this;
    var _class = 'AjaxWM_Template';

    var _name;
    var _path;
    var _path_css;
    var _path_xml;
    var _xml;
    var _info; // name (from xml), descriptiom, author, email
    var _node; // window, panel, menu, startmenu
    var _attrDefault; // element that has default attributes and values

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

    /**
     * Loads the XML file belonging to this theme, stores its
     * contents as property and directly starts with parsing it.
     */

    this.parse = function()
    {
        ajaxWM.debugMessage(_class, 'parse', 'Begin parsing.');

        var result = false;
        try {
            var request = new XMLHttpRequest();
            request.onreadystatechange = function()
            {
                if (request.readyState != 4)
                    return;

                if (request.status != 200)
                    throw 'Bad status code' + request.status;

                _xml = request.responseXML;
                _parse();
            }
            request.open('GET', _path_xml, true);
            request.send(null);
            result = true;
        } catch (e) {
            ajaxWM.debugMessage(_class, 'parse', e);
            alert(e);
        } finally {
            return result;
        }
    }

//}}}
// function _init(name)//{{{

    function _init(name)
    {
        _node = new Object();
        _info = new Object();
        _name = name;
        _path = 'themes/' + _name + '/';
        _path_css = _path + _name + '.css';
        _path_xml = _path + _name + '.xml';

        _attrDefault = document.createElement('xml');
        _attrDefault.setAttribute('move',     'fixed');
        _attrDefault.setAttribute('resize',   'fixed');
        _attrDefault.setAttribute('position', 'fixed');
        _attrDefault.setAttribute('size',     'set');
    }

//}}}
//function _parse()//{{{

    /**
     * Parses the template.
     */

    function _parse()
    {
        ajaxWM.debugMessage(_class, '_parse', 'begin');
        _node['window'] = new Object();
        _node['window']['chmove']   = new Array();
        _node['window']['chresize'] = new Array();
        _node['window']['pos']      = new Object();
        _node['window']['size']     = new Object();
        _node['window']['pos']['fixed']  = new Array();
        _node['window']['pos']['right']  = new Array();
        _node['window']['pos']['bottom'] = new Array();
        _node['window']['size']['set']   = new Array();
        _node['window']['events']        = new Array();
        var win = _xml.documentElement.getElementsByTagName('window')[0];
        _node['window']['main'] = _parseNodeTree(win);
    }

//}}}
// function _parseNodeTree(xml, pos)//{{{

    /**
     * Recursively parse a XML tree into HTML starting at node passed
     * as first parameter. Will only parse specified node and its childs!
     *
     * On first call you should never pass the parameter pos. It will
     * contain the path of the currently processed node in the tree,
     * which can be accessed via childNodes of the root node and its
     * other parents. Pos is needed to write an entry into the respective
     * array if a node contains some special attributes. The element on
     * the end of pos is the index of the current node.
     *
     * @param xml Node  Root of XML node tree
     * @param pos Array Position of currently processed node
     * @param return Node  HTML node parsed from passed XML one
     * @param return false On all nodes/childs in root being invalid
     */

    function _parseNodeTree(xml, pos)
    {
        try {
            // parameter checking//{{{

            var html;
            if (pos == undefined) {
                var pos = new Array();
                pos.push(0);
                var isRoot = true;
            } else if (!pos instanceof Array) {
                throw 'Wrong parameter for pos: ' + pos;
            }

            if (!xml)
                throw 'InvalidCall';

            //}}}
            // parse this node//{{{

            if (xml.nodeType != Node.ELEMENT_NODE)
                throw 'InvalidElement';

            html = _xml2html(xml, pos);

            var debug = 'Node ' + xml.nodeName + '(';
            debug += pos + ') parsed to ' + html.nodeName + '. ';

            //}}}
            // parse childs and their trees, if existing//{{{

            if (!xml.hasChildNodes())
                throw 'IsLeaf';

            var debug2 = debug;
            debug2 += 'Continueing with child 1 of ';
            debug2 += xml.childNodes.length + '. ';
            debug2 += 'First child is ' + xml.firstChild.nodeName;
            ajaxWM.debugMessage(_class, '_parseNodeTree', debug2);
            delete(debug2);

            var posClone = pos.clone();
            var length = xml.childNodes.length;
            var childNode;
            var childPos = 0;
            for (var i = 0; i < length; ++i) {
                posClone.push(childPos);
                childNode = _parseNodeTree(xml.childNodes.item(i), posClone);
                if (childNode) {
                    html.appendChild(childNode);
                    ++childPos;
//                } else {
//                    --childPos;
                }
                posClone.pop();
            }
            delete(i);

            var debug = 'Childs of ' + xml.nodeName + '(';
            debug += pos + ') parsed. ';

            //}}}
        } catch (e) {
            switch (e) {
            // InvalidElement//{{{

            case 'InvalidElement':
                var debug = 'Node skipped: ' + xml.nodeName + ". ";
                break;

            //}}}
            // IsLeaf//{{{

            case 'IsLeaf':
//                    debug += 'Node was a Leaf: ' + xml.nodeName + '. ';
                debug += 'Node was a Leaf. ';
                break;

            //}}}
            // let unexpected exceptions bubble up//{{{

            default:
                ajaxWM.debugMessage(_class, '_parseNodeTree',
                    'Letting exception bubble up: ' + e);
                throw e;

            //}}}
            }
        } finally {
            // return Node or false//{{{

            if (!html) {
                // TODO: throw exception?
                var error = 'WARNING! ';
                error += 'Invalid node <' + xml.nodeName + '>, ';
                error += 'returning false!';
                ajaxWM.debugMessage(_class, '_parseNodeTree', error);
                return false;
            }

            debug += 'End of root, returning ' + html.nodeName;
            debug += ' with ' + html.childNodes.length + ' childs';
            ajaxWM.debugMessage(_class, '_parseNodeTree', debug);

            return html;

            //}}}
        }
    }

//}}}
// function _xml2html(xml, pos)//{{{

    /**
     * TODO update doc
     *
     * Parses the passed XML node into a HTML one and returns it.
     *
     * When the XML element contains some special attributes they will
     * be written to the respective array in _node. For this reason
     * the parameter indizes is needed. It will be added to the array
     * to let the nodes in these arrays be reconstructed.
     *
     * @return Node
     */

    function _xml2html(xml, pos)
    {
        // create HTML node

        switch (xml.nodeName) {
        case 'window':
        case 'panel':
            var name = 'div';
            xml.setAttribute('class', xml.nodeName);
            break;

        case 'theme':
            throw '<theme> node cannot be parsed to html!';

        default:
            var name = xml.nodeName;
        }

//alert(pos);

        var html = document.createElement(name);

        // set attributes
        _setAutoAttr(xml, html, pos);

        return html;
    }

//}}}
// function _setAutoAttr(xml, pos)//{{{

    /**
     * Automatically returns a NamedNodeMap of attributes by the node
     * that was passed in consideration of all parent nodes and possibly
     * inherited attributes.
     * The parameter pos indicates the position of the node relative to
     * the window node and thus what could be inherited.
     */

    function _setAutoAttr(xml, html, pos)
    {
        // get own attributes and the ones from the parent
        if(xml.parentNode.nodeName != 'theme'
        && xml.parentNode.nodeName != '#document') {
            var parent = xml.parentNode;
        } else {
            var parent = _attrDefault;
        }

    // handle own XML attributes//{{{

        var length = xml.attributes.length;
        for (var i = 0; i < length; ++i) {
            var attr  = xml.attributes.item(i);
            var name  = attr.name;
            var value = attr.value;

            // try to parse the attribute, if it is special
            if (_parseSpecialAttr(name, value, pos))
                continue;
            // if not, write attribute to HTML node
            html.setAttribute(name, value);
        }

    //}}}
    // inherit attributes from parent//{{{

        var length = parent.attributes.length;
        for (var i = 0; i < length; ++i) {
            var attr  = parent.attributes.item(i);
            var name  = attr.name;
            var value = attr.value;

            // attributes not to be inherited
            if ('content' == name || 'class' == name)
                continue;
            // do not inherit if already inherited
            if (xml.hasAttribute(name) || html.hasAttribute(name))
                continue;

            // try to parse the attribute, if it is special
            if (_parseSpecialAttr(name, value, pos))
                continue;
            // if not, write attribute to HTML node
            html.setAttribute(name, value);
        }

    //}}}
        return true;
    }

//}}}
//function _isSpecialAttr(name)//{{{

    /**
     * Checks whether a given attribute is a special one which has
     * nothing to do in a HTML document.
     *
     * @return true On name being something special
     * @return false On name being some default HTML attribute
     */

    function _isSpecialAttr(name)
    {
        switch (name) {
        case 'move':
        case 'resize':
        case 'position':
        case 'size':
        case 'onclick':
        case 'onmousedown':
        case 'content':
        case 'name':
        case 'description':
        case 'author':
        case 'email':
            return true;

        default:
            return false;
        }
    }

//}}}
//function _parseSpecialAttr(name, value, pos)//{{{

    /**
     * Parses any special XML attribute that has nothing to do with
     * HTML. Returns true when such an attribute was passed, false
     * otherwise.
     *
     * Maps events to node positions which can be restored by calling
     * traceNode() later.
     *
     * @return true On name being something special
     * @return false On name being some default HTML attribute
     */

    function _parseSpecialAttr(name, value, pos)
    {
        switch (name) {
        case 'move':
            var key = 'chmove';
        case 'resize':
            if (!key)
                var key = 'chresize';
            if (value != 'change')
                return true;
            _node['window'][key].push(pos.clone());
            return true;

        case 'position':
            var attrKey  = 'pos';
            var valueKey = value;
            // position="auto" does not need to be handled
            if ('auto' == value)
                return true;
            else if ('fixed' != value && 'right' != value && 'bottom' != value)
                throw 'Invalid value for position: ' + value;
        case 'size':
            if (undefined == attrKey) {
                var attrKey  = 'size';
                var valueKey = value;
                // size="auto" does not need to be handled
                if ('auto' == value)
                    return true;
                else if ('set' != value)
                    throw 'Invalid value: "' + value + '"';
            }

//alert('attribute attrKey=' + attrKey + ', valueKey=' + valueKey);
            _node['window'][attrKey][valueKey].push(pos.clone());
//alert(
//_node['window'][attrKey][valueKey][
// _node['window'][attrKey][valueKey].length
//]
//);
//alert(_node['window'][attrKey] + ' ' + _node['window'][attrKey][valueKey]);
            return true;

        case 'onclick':
        case 'onmousedown':
            var e = new Object();
            e[name] = new Object();
            e[name]['path'] = pos.clone();
            e[name]['action'] = value;
            _node['window']['events'].push(e);
            return true;

        case 'content':
            if (value == 'windowtitle') {
                _node['window']['title'] = pos.clone();
            } else if (value == 'windowcontent') {
                _node['window']['content'] = pos.clone();
            } else {
                throw 'Wrong content: ' + value;
            }
            return true;

        // TODO
        case 'name':
        case 'description':
        case 'author':
        case 'email':
            return true;

        default:
            return false;
        }
    }

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

    /**
     * Loads the CSS file which belongs to this theme.
     *
     * @return void
     */

    this.loadCss = function()
    {
        var css  = document.createElement('link');
        css.rel  = 'stylesheet';
        css.type = 'text/css';
        css.href = _path_css;
        document.getElementsByTagName('head')[0].appendChild(css);
    }

//}}}
//this.cloneNodes = function(root)//{{{

    /**
     * TODO: doc
     */

    this.cloneNodes = function(root)
    {
        switch (root) {
        case 'window':
            return _cloneWindow();

        default:
            throw "CloneError";
        }
    }

//}}}
//function _cloneWindow()//{{{

    /**
     * Clones window node, reconstructs references out of traces
     * and returns that clone.
     * Note that events are not reconstructed - the traces are simply
     * cloned. You have to call registerEvents() first.
     */

    function _cloneWindow()
    {
        var clone = new Object();
        clone['main'] = _node['window']['main'].cloneNode(true);
        for (key in _node['window']) {
            if (key == 'title' || key == 'content') {
                var trace = _node['window'][key];
                clone[key] = traceNode(trace, clone['main']);
            } else if (key.substr(0,2) == 'ch') {
                clone[key] = new Array();
                var length = _node['window'][key].length;
                for (var i = 0; i < length; ++i) {
                    var trace = _node['window'][key][i];
                    clone[key][i] = traceNode(trace, clone['main']);
                }
            } else if ('pos' == key || 'size' == key) {
                clone[key] = new Object();
                for (attrValue in _node['window'][key]) {
                    if (attrValue == 'clone')
                        continue;
                    clone[key][attrValue] = new Array();
                    var length = _node['window'][key][attrValue].length;
                    for (var i = 0; i < length; ++i) {
                        var trace = _node['window'][key][attrValue][i];
                        clone[key][attrValue].push(traceNode(trace,
                                                             clone['main']));
                    }
                }
            } else if (key == 'events') {
                clone['events'] = _node['window']['events'].clone();
            }
        }
        return clone;
    }

//}}}

    _init(name);
}

//-----------------------------------------------------------------------------
// 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
