var $dom = {
    _cssToJSCache: {
        "float": "cssFloat"
    },
    _cssToJS: function(cssName) {
        if ($dom._cssToJSCache[cssName])
            return $dom._cssToJSCache[cssName];
        else {
            return $dom._cssToJSCache[cssName] =
                   cssName.split(/-/).map(
                           function(i, part) {
                               if (i != 0)
                                   return part.charAt(0).toUpperCase() + part.slice(1);
                               else
                                   return part;
                           }).accumulate("",
                           function(sum, x) {
                               return sum + x;
                           });
        }
    },
    replaceChildren: function(node, subnodes) {
        if (typeof node == 'string')
            node = document.getElementById(node);
        var child_nodes = $dom.childNodes(node);
        var res = $dom.add.apply($dom, Array.fromObject(arguments));
        child_nodes.foreach(function(v) {
            node.removeChild(v);
        });
        return res;
    },
    add: function(node) {
        if (typeof node == 'string')
            node = document.getElementById(node);
        for (var i = 1; i < arguments.length; i++) {
            var child = arguments[i];
            if (!child)
                continue;
            if (child.nodeName)
                node.appendChild(child);
            else if (child.constructor == Array && !child.childType)
                child.foreach(function(c) {
                    $dom.add(node, c);
                });
            else switch (child.childType) {
                case 1: //attribute
                    switch (child[0]) {
                        case "class":
                            if (node.className)
                                node.className += " " + child[1];
                            else
                                node.className = child[1];
                            break;
                        default:
                            node.setAttribute(child[0], child[1]);
                    }
                    break;
                case 2: //style
                    node.style[$dom._cssToJS(child[0])] = child[1];
                    break;
                case 3: //event
                    if (child[0] == 'language') {
                        if (node.onlanguage)
                            node.onlanguage.add(child[1])
                        else {
                            var listeners = [child[1]];
                            node.onlanguage = function() {
                                listeners.foreach(function(v) {
                                    v.call(node);
                                });
                            }
                            node.onlanguage.add = function(v) {
                                listeners.push(v);
                            }
                        }
                    } else if (node.addEventListener) {
                        node.addEventListener(child[0],
                                function(e) {
                                    event = e;
                                    return child[1].call(node);
                                },
                                window.opera == undefined);
                    } else
                        node.attachEvent("on" + child[0],
                                function() {
                                    (child[1]).call(node);
                                });
                    break;
                case 4: //text node
                    node.appendChild(document.createTextNode(child[0]));
                    break;
                default:
                    throw new Error("Invalid argument.");
            }
        }
        return node;
    },
    element: function(name) {
        var res = document.createElement(name);
        for (var i = 1; i < arguments.length; i++)
            $dom.add(res, arguments[i]);
        return res;
    },
    attr: function(name, value) {
        var res = [name, value];
        res.childType = 1;
        return res;
    },
    style: function(name, value) {
        var res = [name, value];
        res.childType = 2;
        return res;
    },
    event: function(type, handler) {
        if (type instanceof Array)
            return type.map(function(v) {
                return $dom.event(v, handler);
            });
        if (type instanceof Array) {
            return type.map(function(v) {
                return $dom.event(v, handler);
            });
        } else {
            var res = [type, handler];
            res.childType = 3;
            return res;
        }
    },
    text: function(txt) {
        var res = [txt];
        res.childType = 4;
        return res;
    },
    removeClass: function(el, cl) {
        if (typeof el == 'string')
            el = document.getElementById(el);
        if (!el.className)
            return el;
        el.className = el.className.split(/\s+/g).filter(function(s) {
            return s != cl;
        }).accumulate(false, function(res, v) {
            return res ? (res + " " + v) : v;
        });
        return el;
    },
    haveClass: function(el, cl) {
        if (!el.className)
            return false;
        return el.className.split(/\s+/g).accumulate(false, function(res, v) {
            return res || v == cl;
        });
    },
    childNodes: function(el) {
        if (typeof el == 'string')
            el = document.getElementById(el);
        if (!el || !el.childNodes)
            return [];
        return Array.fromObject(el.childNodes).filter(function(v) {
            return v != null;
        });
    }
}

var $event = function() {
    return {
        keys: {
            "alt": event.altKey,
            "ctrl": event.ctrlKey,
            "shift": event.shiftKey
        },
        keyCode: event.keyCode ? event.keyCode : event.which,
        cancel: function() {
            event.returnValue = false;
        }
    }
}

