var WebApp = (function() {
    var L2R = +1;
    var R2L = -1;

    var HEAD = 0;
    var HOME = 1;
    var BACK = 2;
    var LEFT = 3;
    var RIGHT = 4;
    var TITLE = 5;

    var _def, _headView, _head;
    var _webapp, _group, _bdo, _bdy;
    var _maxw, _maxh;

    var _prev        = -1;    // for beginslide/endslide event (SlideInfo)
    var _historyPos    = -1;    // warning: must order properly var names for reduction script
    var _location    = [];
    var _history    = [];
    var _scroll        = [];
    var _loader        = [];
    var _fading        = [];
    var _header        = [];
    var _ajax        = [];
    var _initialNav    = history.length;
    var _sliding    = 0;
    var _hold        = false;
    var _baseTitle    = "";
    var _baseBack    = "";
    var _lockCnt    = 0;
    var _width        = 0;
    var _height        = 0;
    var _lastScroll    = 1;
    var _dialog        = null;
    var _v2            = !(!document.getElementsByClassName) && Contains("WebKit", navigator.userAgent);
    var _fullscreen    = false;
    var _proxy        = "";

    var _handler    = {};
    _handler.load                = [];
    _handler.beginslide            = [];
    _handler.endslide            = [];
    _handler.error                = [];
    _handler.success            = [];
    _handler.orientationchange    = [];
    _handler.tabchange            = [];
//    _handler.contentchange        = [];

/* Public */
    __p = {
        Proxy: function(url) { _proxy = url; },

        HideBar: function() {
            window.scrollTo(0, 1);
//            setTimeout(window.scrollTo, 0, 0, 0); // TODO: with border-top = 1px
            return false;
        },

        Header: function(show, what) {
            Buttons(show);
            Display(_headView, 0);
            _headView = $(what);
            Display(_headView, !show);
            return false;
        },

        Tab: function(id, active) {
            var o = $(id);
            ShowTab(o, $$("li", o)[active]);
        },

        AddEventListener: function(evt, handler) {
            if (typeof _handler[evt] != "undefined")
                if (_handler[evt].indexOf(handler) == -1)
                    _handler[evt].push(handler);
        },
/*
        RemoveEventListener: function(evt, handler) {
            if (typeof _handler[evt] != "undefined")
                _handler[evt].splice(_handle.lastIndexOf(handler), 1);
        },
*/
        Toggle: function() {
            if (_history.length > 1) {
                if (_historyPos == _history.length - 1) {    // TODO: check this, _history.length should be greater than _historyPos
                    AdjustView();
                    history.back();
                } else {
                    AdjustView();
                    history.forward();
                }
            }
            return false;
        },

        Back: function() {
            if (_hold)
                return (_hold = false);

            if (history.length - _initialNav == _historyPos) {
                AdjustView();
                history.back();
            } else {
                AdjustView();
                location = _location[_historyPos - 1] || "#";
            }
            return false;
        },

        Home: function() {
            if (history.length - _initialNav == _historyPos) {
                AdjustView();
                history.go(-_historyPos);
            } else {
                AdjustView();
                location = "#";
            }
            return (_hold = false);
        },

        Form: function(frm) {
            var s, a, b, c, o, k, f;

            a = $(frm);
            b = $(_history[_historyPos]);
            s = (a.style.display != "block");

            f = GetName(a) == "form" ? a : GetParent(a, "form");

            // WARNING: this variable must not be changed, else onsumbit will not point to the function anymore!
            k = f.onsubmit;
            if (!s) {
                f.onsubmit = f.onsubmit(null, true);

            } else {
                a.style.top = _group.offsetTop - 2 + "px";

                f.onsubmit = function(e, b) {
                    if (b) return k;
                    if (k) k(e);

                    e.preventDefault();
                    __p.Submit(this);
                };
            }

            AdjustView();
            Shadow(s, _group.offsetTop);
            Display(a, s);
            
            o = $$("legend", a)[0];
            SetTitle(s && o ? o.innerHTML : null);

            _dialog = (s) ? a : null;
            if (s) { c = a; a = b; b = c }

            DelLayerButtons(a);
            AddLayerButtons(b, s);

            if (s)    __p.Header(s);
            else    Buttons(!s);

            return false;
        },

        Submit: function(frm) {
            var a = arguments[1];    // submiter object to help focus fixer, should not be used by client code!
            var e = arguments[2];

            var f = $(frm);
            if(f && GetName(f) != "form") f = GetParent(f, "form");
            if (f) {
                var _ = function(i, f) {
                    var q = "";
                    for (var n = 0; n < i.length; n++)
                        if (i[n].name && !i[n].disabled && (f ? f(i[n]) : 1))
                            q += "&" + i[n].name + "=" + encodeURIComponent(i[n].value);
                    return q;
                }

                var q  = _($$("input", f),
                    function(i) {
                        with(i) return ((Contains(type, ["text", "password", "hidden", "search"]) ||
                                        (Contains(type, ["radio", "checkbox"]) && checked)))
                    });
                    q += _($$("select", f));
                    q += _($$("textarea", f));

                e  = e || event;
                a  = !a ? GetLink(e.target) : a;
                if (!a) FocusFixer();
                q += "&" + (a && a.id ? a.id : "__submit") + "=1";
                q  = q.substr(1);

                BasicAsync(f.action || self.location.href, null, q);
                if (_dialog) __p.Form(_dialog);
            }
            return false;
        },

        Postable: function(keys, values) {
            var q = "";
            for (var i = 1; i < values.length && i <= keys.length; i++)
                q += "&" + keys[i - 1] + "=" + encodeURIComponent(values[i]);
            return q.replace(/&=/g, "&").substr(1);
        },

        Request: function(url, prms, cb, async, loader) {
            cb = cb == -1 ? DefaultCallback() : cb;

            var o = new XMLHttpRequest();
            var c = function() { __callback(o, cb, loader) };
            var m = prms ? "post" : "get";

            async = !!async;
            if (loader) __p.Loader(loader, true);
            _ajax[_ajax.length] = [o, url, prms, arguments[5]];

            if ( url.indexOf('?') > 0  ) {
                m = "post";
                if ( url.indexOf('#') > 0 )
                    url = url.substr(0, url.indexOf('#'));

                if ( prms && prms!=null ) prms += '&' + url.substr( url.indexOf('?')+1 ) ;
                else prms = url.substr( url.indexOf('?')+1 ) ;

                url = url.substr(0, url.indexOf('?'));
            } 

            o.open(m, url, async);
            if (prms) o.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            o.onreadystatechange = (async) ? c : null;
            o.send(prms);

            if (!async) c();
        },

        Loader: function(obj, show) {
            var o = obj;
            var h = HasClass(o, "__lod");
            if (h == show)
                return h;

            if (show) {
                AddClass(o, "__lod");
                _loader.push(o);
            } else DelClass(o, "__lod");

            ApplyMore(o);
            return h;
        },

        Player: function(src) {
            src = src || GetLink(event.target).href;

            if (!IsMobile()) {
                window.open(src);
            } else {
                // prevent the back request sent by the internal player
                if (_v2) location = "#" + Math.random();

                var w = $("__wa_media");
                var o = NewItem("iframe");
                o.id = "__wa_media";
                o.src = src;            // must be before appendChild to prevent Safari weird behavior

                _webapp.appendChild(o);
                if (w) _webapp.removeChild(w);
            }
            return false;
        }
    }

    function CalcEaseOut(s, w, dir, step, mn) {
        s += Math.max((w - s) / step, mn || 4);
        return [s, (w + w * dir) / 2 - Math.min(s, w) * dir];
    }

    function ApplyMore(o) {
        if (HasClass(o, "iMore")) {
            var a = $$("a", o)[0];
            if (a && a.title) {
                o = a.innerHTML;
                a.innerHTML = a.title;
                a.title = o;
            }
        }
    }

    function FocusFixer() {
        var i = NewItem("input");
        _group.appendChild(i);
        i.type = "text";
        i.focus();
        Display(i, 0);
        setTimeout(_group.removeChild, 5, i);
    }

    function Buttons(s) {
        if (_head) {
            for (var i = 1; i < _header.length; i++)
                Display(_header[i], s);
    
            Display(_header[BACK], s && !_header[LEFT] && _historyPos);
            Display(_header[HOME], s && !_header[RIGHT] && !_hold && _historyPos > 1);
        }
    }

    function AddLayerButtons(lay, ignore) {
        if (_head) {
            var a = $$("a", lay);
            var p = RIGHT;
    
            for (var i = 0; i < a.length && p >= LEFT; i++) {
                if (_header[p] && !ignore) { i--; p--; continue; }
    
                if (HasToken(a[i].rel, "action") ||
                    HasToken(a[i].rel, "back")) {
    
                    AddClass(a[i], p == RIGHT ? "iRightButton" : "iLeftButton");
                    Display(a[i], 1);
                    _header[p--] = a[i];
                    _head.appendChild(a[i--]);
                }
            }
        }
    }

    function DelLayerButtons(lay) {
        if (_head) {
            for (var i = LEFT; i <= RIGHT; i++) {
                var a = _header[i];
                if (a && (    HasToken(a.rel, "action") ||
                            HasToken(a.rel, "back")) ) {
    
                    Display(a, 0);
                    DelClass(a, i == RIGHT ? "iRightButton" : "iLeftButton");
                    lay.insertBefore(a, lay.firstChild);
                }
            }
            _header[RIGHT] = $("waRightButton");
            _header[LEFT] = $("waLeftButton");
        }
    }

/* Private */
    function NoTag(s) { return s.replace(/<.+?>/g, "").replace(/^\s+|\s+$/g, "").replace(/\s{2,}/, " "); }
    function Contains(o, a) { return a.indexOf(o) != -1 }
    function IsAsync(o) { return HasToken(o.rev, "async") || HasToken(o.rev, "async:np"); }

    function $(i) { return typeof i == "string" ? document.getElementById(i) : i; }
    function $$(t, o) { return (o || document).getElementsByTagName(t); }
    function NewItem(t, c) {
        var o = document.createElement(t);
        if (c) o.innerHTML = c;
        return o;
    }
    function GetLink(o) { return GetName(o) == "a" ? o : GetParent(o, "a") }
    function GetName(o) { return o.localName.toLowerCase() }
    function HasToken(o, t) { return o && Contains(t, o.toLowerCase().split(" ")); }

    function HasClass(o, c) { return o && Contains(c, GetClass(o)); }
    function GetClass(o) { return o.className.split(" "); }
    function AddClass(o, c) {
        var h = HasClass(o, c);
        if (!h) o.className += " " + c;
        return h;
    }
    function DelClass(o) {
        var c = GetClass(o);
        var a = arguments;
        for (var i = 1; i < a.length; i++) {
            var p = c.indexOf(a[i]);
            if (p != -1) c.splice(p, 1);
        }
        o.className = c.join(" ");
    }
    function GetParent(o, t) {
        while ((o = o.parentNode) && (o.nodeType != 1 || GetName(o) != t));
        return o;
    }    
    function AnyOf(o, c) {
        while ((o = o.parentNode) && (o.nodeType != 1 || !HasClass(o, c)));
        return o;
    }    

    function GetText(o) {
        var o = o.childNodes;
        for (var i = 0; i < o.length; i++)
            if (o[i].nodeType == 3)
                return o[i].nodeValue.replace(/^\s+|\s+$/g, "");
        return null;
    }

    function InitBlocks() {
        if (!_webapp || !_group) {
            _webapp    = $("WebApp");
            _group    = $("iGroup");
        }
    }

    function InitVars() {
        InitBlocks();
        _header[HEAD] = $("iHeader");
        _header[BACK] = $("waBackButton");
        _header[HOME] = $("waHomeButton");
        _header[RIGHT] = $("waRightButton");
        _header[LEFT] = $("waLeftButton");
        _header[TITLE] = $("waHeadTitle");

        _bdy = document.body;
        _bdo = (_bdy.dir == "rtl") ? -1 : +1;
    }

    function Display(o, s) { if (o) o.style.display = s ? "block" : "none" }

    function ShowLayer(o) {
        Display(o, 1);
        o.style.width = "100%";
    }

    // TODO: any way to do this with CSS??
    function AdjustLayer(o) {
        o = o || $(GetActive());

        if (o) {
            var z = $$("div", o);
            if (z[0] && HasClass(z[0], "iList")) {
                AddClass(o, "__lay");
                o.style.minHeight = parseInt(_webapp.style.minHeight) - _group.offsetTop + "px";
            } else DelClass(o, "__lay");
        }
    }

    function Shadow(s, p) {
        var o = $("__wa_shadow");
        o.style.top = p + "px";
        Display(o, s);
    }

    function Lock() {
        if (!_lockCnt++)
            Display($("__wa_noclick"), 1);
    }
    function Unlock() {
        if (!--_lockCnt)
            Display($("__wa_noclick"), 0);
    }

    function Historize(o, l) {
        if (o) {
            // TODO: push an array with [o, l ? location.hash : null, _lastScroll]
            // then use _history[x][0]...[x][2]
            _history.splice(++_historyPos, _history.length);
            _history.push(o);

            _location.splice(_historyPos, _location.length);
            _location.push(l ? location.hash : null);

            _scroll.splice(_historyPos, _scroll.length);
            _scroll.push(_lastScroll);
        }
    }

    // We need to remove scripts from the clone to prevent execution
    function PrepareClone(o) {
        var s = $$("script", o);
        while(s.length)
            s[0].parentNode.removeChild(s[0]);
        return o;
    }

    function Cleanup() {
        var s, i, c;

        while (s = _loader.pop())
            __p.Loader(s, 0);

        s = $$("li");
        for (i = 0; i < s.length; i++)
            if (HasClass(s[i], "__sel"))
                DelClass(s[i], "__sel");
    }

    function ParseParams(s, np) {
        var ed = s.indexOf("#_");
        if (ed == -1)
            return null;

        var rs = "";
        var bs = SplitURL(s);
        if (!np) for (var i = 0; i < bs[1].length; i++)
                    rs += "/" + bs[1][i].split("=").pop();

        return bs[2] + rs;
    }

    function FadeWait(o, cb) {
        setTimeout(function() {
            if (_fading.indexOf(o) != -1)
                FadeWait(o, cb);
            else cb();
        }, 5);
    }

    function FadeItem(o, show, cb, sp, nx) {
        // if we're already fading the same oject, exits
        if (!nx) {
            if (!o) {
                if (cb) cb();
                return;

            } else if (_fading.indexOf(o) != -1)
                return;

            else
                _fading.push(o);
        }

        // set default step if not defined
        if (!sp) sp = 0.5;

        // process fading effect
        with (o.style) {
            if ((!show && opacity > 0) || (show && opacity < 1)) {
                if (show) display = "block";
                opacity = parseFloat(opacity) + (show ? +sp : -sp);
                setTimeout(FadeItem, 0, o, show, cb, sp, 1);
            } else {
                display = (opacity == 0) ? "none" : "block";
                _fading.splice(_fading.indexOf(o), 1);
                if (cb) cb();
            }
        }
    }

    function IsMobile() {
        with (navigator.userAgent)
            return (indexOf("iPhone") + indexOf("iPod") + indexOf("Aspen") > -3);
    }

    function Resizer() {
        InitBlocks();
        if (_sliding || !_webapp || !_group)
            return;

        var w = (window.innerWidth >= _maxh) ? _maxh : _maxw;
        if (w != _width) {
            _width = w;
            _webapp.className = (w == _maxw) ? "portrait" : "landscape";
            AdjustLayer();
            CallListeners("orientationchange");
        }

        var h = window.innerHeight;
        var m = ((_width == _maxw) ? 416 : 268);    // TODO
            h = (h < m) ? m : h;
        if (h != _height) {
            _height = h;
            _webapp.style.minHeight = (1 + h) + "px";
        }
    }

    function Locator() {
        if (_sliding || _hold == location.href)
            return;
        _hold = false;

        var act = GetActive();
        if (act == null)
            if (location.hash.length > 0)    // there is a simple anchor, jump to it
                return;
            else                            // No? should slide back to home
                act = _history[0];

        var pos = _history.indexOf(act);
        var cur = _history[_historyPos];

        if (act == cur)
            return;

        if (pos != -1 && pos < _historyPos) {
            _historyPos = pos + 1;
            InitSlide(cur, act, L2R);
        } else {
            SlideTo(act);
        }
    }

    function CallListeners(evt, ctx, obj) {
        // Do not waste time and memory if no handler have been defined for the given event
        var l = _handler[evt].length;
        if (l == 0)
            return true;

        var e = {};
        e.type = evt;
        e.target = obj || null;
        e.context = ctx || Explode(_location[_historyPos]);
        e.windowWidth = _width;
        e.windowHeight = _webapp.offsetHeight;

        var k = true;
        for (var i = 0; i < l; i++)
            k = k && (_handler[evt][i](e) == false ? false : true);
        return k;
    }

    function Init() {
        InitVars();
        InitCheck();
        InitRadio();
        InitTab();
        InitHeader();
        InitObj("div", "__wa_noclick");
        InitObj("div", "__wa_shadow");

        // Display loader if any
        var l = $("iLoader");
        if (l) Display(l, 0);

        // get screen size
        _maxw = screen.width;
        _maxh = screen.height;
        if (_maxw > _maxh) { l = _maxh; _maxh = _maxw; _maxw = l; }

        // Get the default layer
        _def = GetLayers()[0].id;
        Historize(_def);

        var a = GetActive();
        if (a != _def) Historize(a, true);
        if (!a) a = _def;

        ShowLayer($(a));
        AddLayerButtons($(a));

        Display(_header[BACK], (!_header[LEFT] && _historyPos));
        Display(_header[HOME], (!_header[RIGHT] && _historyPos > 1 && a != _def));

        if (_header[BACK])
            _baseBack = _header[BACK].innerHTML;
        if (_header[TITLE]) {
            _baseTitle = _header[TITLE].innerHTML;
            _header[TITLE].innerHTML = GetTitle($(a));
        }

        setInterval(Locator, 100);
        setTimeout(CallListeners, 100, "load");
        setTimeout(AdjustView, 500);
    }

/* Event */
    function ShowTab(ul, li, h, ev) {
        var c, s, al = $$("li", ul);
        for (var i = 0; i < al.length; i++) {
            c = (al[i] == li);
            if (c) s = i;

            Display($(ul.id + i), (!h && c));
            DelClass(al[i], "__act");
        }
        AddClass(li, "__act");
        if (ev) CallListeners("tabchange", [s], ul);
    }

    function ListenClick(e, b) {
        if (_sliding) {
            e.preventDefault();
            return;
        }

        /* Checkbox */
        var o = e.target;
        var n = GetName(o);
        if (n == "label") {
            var f = $(o.getAttribute("for"));
            if (HasClass(f, "iToggle"))
                setTimeout(FlipCheck, 1, f.previousSibling.childNodes[1], true);
            return;
        }

        /* Radio parent */
        var li = GetParent(o, "li");        
        if (li && HasClass(li, "iRadio")) {
            AddClass(li, "__sel");
            ShowRadio(li);
            return;
        }

        /* Tab */
        var ul = GetParent(o, "ul");
        var pr = !ul ? null : ul.parentNode;
        var a  = GetLink(o);
        var ax = a && IsAsync(a);
        if (ul && HasClass(pr, "iTab")) {
            var h = $(ul.id + "-loader");
            Display(h, 0);

            if (ax) {
                Display(h, 1);
                BasicAsync(a, function(o) {
                    Display(h, 0);
                    Display($(ShowAsync(o)[0]), 1);
                    ShowTab(ul, li, null, 1);
                });

            } else { h = null }
            ShowTab(ul, li, h, !ax);
            //e.preventDefault();
            return;
        }

        /* Common button */
        if (a && !a.onclick &&
            Contains(a.id, ["waBackButton", "waHomeButton"])) {

            if (a.id == "waBackButton")    __p.Back();
            else                        __p.Home();

            e.preventDefault();
            return;            
        }

        /* Radio list */
        if (ul && HasClass(ul, "iCheck")) {
            var al = $$("li", ul);
            for (var i = 0; i < al.length; i++)
                DelClass(al[i], "__act", "__sel");
            AddClass(li, "__act __sel");
            setTimeout(DelClass, 1000, li, "__sel");    // TODO: autoback option with radio _hold = false
            e.preventDefault();
            return;
        }

        /* Menu and list */
        if (ul && !HasClass(li, "iMore") &&
            ((HasClass(ul, "iMenu") || HasClass(pr, "iMenu")) ||
             (HasClass(ul, "iList") || HasClass(pr, "iList"))) ) {

            if (a && !HasClass(a, "iButton")) {
                var c = AddClass(li, "__sel");
                if (ax) {
                    if (!c) BasicAsync(a);
                    e.preventDefault();
                    return;
                }
            }
        }

        /* More */
        var dv = AnyOf(o, "iMore");
        if (dv) {
            if (!__p.Loader(dv, 1) && IsAsync(a))
                BasicAsync(a);
            e.preventDefault();
            return;
        }

        /* Top form button */
        if (a && _dialog && !a.onclick) {
            if (HasToken(a.rel, "back"))
                __p.Form(_dialog, a);
            if (HasToken(a.rel, "action"))
                __p.Submit(_dialog, a);
            e.preventDefault();
            return;
        }

        /* Media player */
        if (a && HasToken(a.rev, "media")) {
            __p.Player(a.href);
            Unselect(li);
            e.preventDefault();
            return;
        }

        /* Basic async link */
        if (ax) {
            BasicAsync(a);
            e.preventDefault();

        } else if (a) {
            /* Basic go layer */
            var l = Contains("#_", a.href);
            if (li || l) {
                AdjustView();
                setTimeout(function() { location = a.href } , !l ? 1000 : 0);
                if (!l)    Unselect(li);
                e.preventDefault();
/*
            } else if (li) {
                Unselect(li);
                e.preventDefault();*/
            }
        }
    }

    function Unselect(li) {
        if (li) setTimeout(DelClass, 500, li, "__sel");
    }

/* Animate */

    function SlideTo(to) {
        if (_history[_historyPos] != to)
            InitSlide(_history[_historyPos], to);
        return false;
    }

    function InitSlide(src, dst, dir) {
        if (_sliding)
            return;

        Lock();

        if (dst == _history[0])
            _initialNav = history.length;

        dir = dir || R2L;
        src = $(src);
        dst = $(dst);
        AdjustLayer(dst);

        Display($("iFooter"), 0);
        _sliding = 1;
        HideHeader(0, function() {
            DoSlide(src, dst, dir);
        });
    }

    function SlideInfo(d) {
        return [Explode(_location[_prev]), Explode(location.hash), d];
    }

    function DoSlide(src, dst, dir) {
        CallListeners("beginslide", SlideInfo(dir));
        _prev = _historyPos;

        DelLayerButtons(src);
        AddLayerButtons(dst);
        InitCheck(dst);
        InitRadio(dst);

        var w = src.offsetWidth;
        var c, b = _bdy;

        ShowLayer(src);
        AddClass(b, "__wa_slideV1");

        if (dir * _bdo == L2R) {
            setTimeout(window.scrollTo, 5, w, window.pageYOffset);
            c = PrepareClone(src.cloneNode(true));
            _group.appendChild(c);

        } else if (_bdo == R2L) { ShowLayer(dst) }

        setTimeout(function() {
            ShowLayer(dst);
            if (c) { _group.removeChild(c) }

            if (dir == R2L) {
                _group.insertBefore(src, dst);
                Historize(dst.id, true);
            } else {
                _group.insertBefore(dst, src);
                while (_historyPos && _history[--_historyPos] != dst.id) {}
            }

            // Sliding effect for v1.x Safari Mobile
            setTimeout(function() {
                var s = 0;
                var i = setInterval(function() {
                    if (s <= w) {
                        var z = CalcEaseOut(s, w, dir * _bdo, 6, 2);
                        s = z[0]; window.scrollTo(z[1], 1);
                        return;
                    }
                    clearInterval(i);

                    c = PrepareClone(dst.cloneNode(true));
                    _group.insertBefore(c, dst.nextSibling);
                    Display(src, 0);

                    setTimeout(function() {
                        _group.removeChild(c);
                        EndSlide(src, dst, dir);
                    }, 5);
                }, 5);
            }, 5);
        }, 5);
    }

    function EndSlide(src, dst, dir) {
        Cleanup();

        if (_header[BACK]) {
            var txt;
            if (dir == R2L)
                txt = (_hold ? "" : NoTag(src.title)) || _baseBack;
            else if (_historyPos)
                txt = NoTag($(_history[_historyPos - 1]).title) || _baseBack;
            if (txt) _header[BACK].innerHTML = txt;
        }

        Display($("iFooter"), 1);
        SetTitle(_hold ? dst.title : null);
        DelClass(_bdy, "__wa_slideV1");

        ShowLayer(dst);
        ShowHeader(null, function() {
            CleanSlide(dir);
            CallListeners("endslide", SlideInfo(dir));
            _prev = -1;
        });
    }

    function CleanSlide(dir) {
        Unlock();
        setTimeout(AdjustView, 0, (dir == L2R) ? _scroll[_historyPos + 1] : null);
        _sliding = 0;
    }

    function SetTitle(title/*, hide*/) {
        var o;
        if (o = _header[TITLE]) {
            o.innerHTML = title || GetTitle($(GetActive())) || _baseTitle;
            //Display(o, !hide);
        }
    }

    function HideHeader(s, cb) {
        FadeItem(_head, 0, function() {
            if (cb) cb();
            if (_dialog) __p.Form(_dialog);
            Display(_headView, 0);
        }, s ? 1 : null);
    }

    function ShowHeader(s, cb) {
        FadeWait(_head, function() {
            Display(_header[BACK], !_header[LEFT] && _historyPos);
            Display(_header[HOME], !_header[RIGHT] && _historyPos > 1);
            Display(_header[LEFT], 1);
            Display(_header[RIGHT], 1);
            Buttons(1);
            FadeItem(_head, 1, cb, s ? 1 : null);
        });
    }

    function FlipCheck(o, dontChange) {
        var i = $(o.parentNode.title);
        var txt = i.title.split("|");
        if (!dontChange)
            i.click();

        with (o.nextSibling) {
            innerHTML = txt[i.checked ? 0 : 1];
            if (i.checked) {
                o.style.left = "";
                o.style.right = "-1px";
                o.parentNode.className = "iToggleOn";
                style.left = 0;
                style.right = "";
            } else {
                o.style.left = "-1px";
                o.style.right = "";
                o.parentNode.className = "iToggle";
                style.left = "";
                style.right = 0;
            }
        }
    }

    function AdjustView(to) {
        _lastScroll = window.pageYOffset;

        var h = to ? to : Math.min(50, _lastScroll);
        var s = to ? Math.max(1, to - 50) : 1;
        var d = to ? -1 : +1;

        while (s <= h) {
            var z = CalcEaseOut(s, h, d, 6, 2);
            s = z[0]; window.scrollTo(0, z[1]);
        }
        if (!to) __p.HideBar();
    }

    function Explode(loc) {
        // WARNING: with classic anchors the returned value of this function will be wrong
        if (loc) {
            var pos = loc.indexOf("#_");
            var vis = [];

            if (pos != -1) {
                loc = loc.substring(pos + 2).split("/");
                vis = GetLayers().filter(
                        function(l) { return l.id == "wa" + loc[0] });
            }
            if (vis.length) {
                loc[0] = vis[0].id;
                return loc;
            }
        }
        return [];
    }

    function GetLayers() {
        var lay = [];
        var src = _group.childNodes;
        for (var i = 0; i < src.length; i++)
            if (src[i].nodeType == 1 && HasClass(src[i], "iLayer"))
                lay.push(src[i]);
        return lay;
    }

    function GetTitle(o) {
        return (!_historyPos && _baseTitle) ? _baseTitle : o.title;
    }

    function GetActive() {
        // FIXME: should always return the active layer even if the hash is incorrect or use a classic anchor?
        var h = location.hash;
        return !h ? _def : Explode(h)[0];
    }

    function SetURL(url) {
        var d = url.match(/[a-z]+:\/\/(.+:.*@)?([a-z0-9-\.]+)((:\d+)?\/.*)?/i);
        return (!_proxy || !d || d[2] == location.hostname)
            ? url : AddParam(_proxy, "__url=", url);
    }

    function SplitURL(u) {
        var s, q, d;

        s = u.replace(/&amp;/g, "&");
        d = s.indexOf("#");
        d = s.substr(d != -1 ? d : s.length);
        s = s.substr(0, s.length - d.length);
        q = s.indexOf("?");
        q = s.substr(q != -1 ? q : s.length);
        s = s.substr(0, s.length - q.length);
        q = !q ? [] : q.substr(1).split("&");

        return [s, q, d];
    }

    function AddParam(u, k, v) {
        u = SplitURL(u);
        var q = u[1].filter(
                function(o) { return o && o.indexOf(k + "=") != 0 });    // != 0 => any parameter not starting with (k + "=")
        q.push(k + "=" + encodeURIComponent(v));
        return u[0] + "?" + q.join("&") + u[2];
    }

    function BasicAsync(item, cb, q) {
        var h, o, u, i;

        i = (typeof item == "object");
        u = (i ? item.href : item);
        o = GetParent(item, "li");    // get loader

        if (!cb) cb = DefaultCallback(u, HasToken(item.rev, "async:np"));
        __p.Request(u, q, cb, true, o, (i ? item : null));
    }

    function DefaultCallback(i, np) {
        return function(o) {
            var u = i ? ParseParams(i, np) : null;
            var g = ShowAsync(o);

            if (g && (g[1] || u)) {
                AdjustView();
                location = g[1] || u;
            } else {
                setTimeout(Cleanup, 250);
            }

            return null;
        };
    }
    
    function ReadTextNodes(o) {
        var nds = o.childNodes;
        var txt = "";
        for (var y = 0; y < nds.length; y++) 
            txt += nds[y].nodeValue;
        return txt;
    }

    function ShowAsync(o) {
        if (o.responseXML) {
            o = o.responseXML.documentElement;

            var s, t, k, a = GetActive();

            /* force jump to a given layer */
            var g = $$("go", o);
            g = (g.length != 1) ? null : g[0].getAttribute("to");

            /* get all parts to update */
            var f, p = $$("part", o);
            if (p.length == 0) p = [o];

            for (var z = 0; z < p.length; z++) {
                var dst = $$("destination", p[z])[0];
                if (!dst) break;

                var mod = dst.getAttribute("mode");
                var txt = ReadTextNodes($$("data", p[z])[0]);

                var i = dst.getAttribute("zone");
                if (dst.getAttribute("create") == "true" &&
                    i.substr(0, 2) == "wa" && !$(i)) {

                    var n = NewItem("div");
                    n.className = "iLayer";
                    n.id = i;
                    _group.appendChild(n);
                }

                f = f || i;
                g = g || dst.getAttribute("go");        // For compatibility with older version
                i = $(i || dst.firstChild.nodeValue);    // For compatibility with older version

//                if (!CallListeners("contentchange", [mod, txt], i))
//                    continue;

                /* if we target the active layer, remove buttons */
                if (!k && a == i.id) {
                    HideHeader(1);
                    DelLayerButtons(i);
                    k = i;
                }

                /* update content */
                SetContent(i, txt, mod);
            }

            /* Custom title for the given layer */
            if (t = $$("title", o)[0]) {
                var s = t.getAttribute("set");
                $(s).title = ReadTextNodes(t);
                if (a == s) SetTitle(null, 1);
            }

            /* active layer is targeted? show new header */
            if (k) {
                AddLayerButtons(k);
                ShowHeader(1);
            }

            /* script to execute */
            var e = $$("script", o)[0];
            if (e) eval(ReadTextNodes(e));

            return [f, g ? "#_" + g.substr(2) : null];
        }

        throw "Invalid asynchronous response received.";
    }

    function SetContent(o, c, m) {
        // Store content in a temp <DIV> to prepare script execution
        c = NewItem("div", c);
        // Clone the <DIV> so that Safari properly recognize <SCRIPT> tags
        c = c.cloneNode(true);

        // Append content to webapp
        if (m == "replace" || m == "append") {
            if (m != "append")
                while (o.hasChildNodes())
                    o.removeChild(o.firstChild);

            while (c.hasChildNodes())
                o.appendChild(c.firstChild);
        } else {
            var p = o.parentNode;
            var w = (m == "before") ? o : o.nextSibling;

            if (m == "self")
                p.removeChild(o);
            while (c.hasChildNodes())
                p.insertBefore(c.firstChild, w);
        }
    }

    function __callback(o, cb, lr) {
        if (o.readyState != 4)
            return;

        var er, ld, ob;
        er = (o.status != 200 && o.status != 0);

        if (!er) {
            try { if (cb) ld = cb(o, lr); } catch (ex) { er = ex; console.error(er); }
        }

        if (lr) {
            __p.Loader(lr, 0);
            if (er) DelClass(lr, "__sel");
        }

        if (ob = _ajax.filter(function(a) { return o == a[0] })[0]) {
            CallListeners(er ? "error" : "success", ob, ob.pop());
            _ajax.splice(_ajax.indexOf(ob), 1);
        }
    }

/* Render */
    function InitHeader() {
        var hd = _header[HEAD];
        if (hd) {
            var dv = NewItem("div");
            dv.style.opacity = 1;
            while (hd.hasChildNodes())
                dv.appendChild(hd.firstChild);
            hd.appendChild(dv);
            _head = dv;
        }
    }

    function InitTab() {    // TODO: use showtab?
        var o = $$("ul");
        for(var i = 0; i < o.length; i++) {
            var p = o[i].parentNode;
            if (p && HasClass(p, "iTab")) {
                AddClass($$("li", o[i])[0], "__act");
                if (p = $(o[i].id + "0"))
                    Display(p, 1);
            }
        }
    }

    function InitRadio(p) {
        var s = "wa__radio";
        var o = $$("li", p);

        for (var i = 0; i < o.length; i++) {
            if (HasClass(o[i], "iRadio") && !HasClass(o[i], "__done")) {
                var lnk = NewItem("a");
                var sel = NewItem("span");
                var cpy = o[i].childNodes;
                var inp = $$("input", o[i]);
                for (var j = 0; j < inp.length; j++) {
                    with (inp[j]) if (type == "radio" && checked) {
                        sel.innerHTML = GetText(parentNode);
                        break;
                    }
                }
                lnk.appendChild(sel);
                while (o[i].hasChildNodes())
                    lnk.appendChild(o[i].firstChild);
                o[i].appendChild(lnk);
                lnk.href = "#";
                lnk.onclick = function() { _hold = location.href; return SlideTo(s) };
                AddClass(o[i], "__done");
            }
        }

        if (!$(s)) {
            var d = NewItem("div");
            d.className = "iLayer";
            d.id = s;
            _group.appendChild(d);
        }
    }

    function ClickRadio(a, p, u) {
        var x = $$("input", p);
        var y = $$("a", u);
        var b;
        for (var i = 0; i < y.length; i++)
            if (y[i] == a) {
                x[i].checked = true;
                $$("span", p)[0].innerHTML = GetText(x[i].parentNode);
                b = p.getAttribute("value");
                if (b && b.toLowerCase() == "autoback")
                    setTimeout(__p.Back, 0);
                break;
            }
    }

    function ShowRadio(p) {
        var o = $$("input", p);
        var dv = NewItem("div");
        var ul = NewItem("ul");
        ul.className = "iCheck";

        for (var i = 0; i < o.length; i++) {
            if (o[i].type == "radio") {
                var li = NewItem("li");
                var a = NewItem("a", o[i].nextSibling.nodeValue); // TODO: is that correct???
                a.href = "#";
                a.onclick = function() { ClickRadio(this, p, ul) };
                li.appendChild(a);
                ul.appendChild(li);

                if (o[i].checked) li.className = "__act";
            }
        }

        dv.className = "iMenu";
        dv.appendChild(ul);

        o = $("wa__radio");
        if (o.firstChild)
            o.removeChild(o.firstChild);
        o.title = GetText(p.firstChild);
        o.appendChild(dv);
    }

    function InitObj(t, i) {
        var o = NewItem(t);
        o.id = i;
        _webapp.appendChild(o);
        return o;
    }

    function InitCheck(p) {
        var o = $$("input", p);

        for (var i = 0; i < o.length; i++) {
            if (o[i].type == "checkbox" && HasClass(o[i], "iToggle") && !HasClass(o[i], "__done")) {
                if (!o[i].id)
                    o[i].id = "__" + Math.random();
                if (!o[i].title)
                    o[i].title = "ON|OFF";

                var txt = o[i].title.split("|");

                var b1 = NewItem("b", "&nbsp;");
                var b2 = NewItem("b");
                var i1 = NewItem("i", txt[1]);

                b1.className = "iToggle";
                b1.title = o[i].id;
                b1.appendChild(b2);
                b1.appendChild(i1);
                o[i].parentNode.insertBefore(b1, o[i]);
                b2.onclick = function() { FlipCheck(this); };
                FlipCheck(b2, true);
                AddClass(o[i], "__done");
            }
        }
    }

/* PreLoad */
    setInterval(Resizer, 100);
    addEventListener("load", Init, true);
    addEventListener("click", ListenClick, true);

/* Static */
    return __p;
})();

var WA = WebApp;
