//
//  qb-ware.js
//  Version 2.1
//  Written by Stefan Habel
//  Last modified: 20.07.2008
//


// the document's base window title
window.baseTitle = "";
// a pointer to the page's main navigation Tab (by default this is the first Tab set up for the page)
window.mainNavigationTab = "";
// list of Tab objects
window.tabs = [];
// list of ContentElement objects
window.contentElements = [];
// list of content element changes to process
window.contentElementChanges = [];
// list of elements that form the path of a specific section of the page that
// is displayed when the page is loaded
window.initialSection = [];
// list of elements that form the path of a specific section of the page that
// should be displayed
window.requestedSection = [];
// list of elements that form the path of the current section of the page
window.currentSection = [];
// flag that states whether the client uses the Safari web browser
window.safariBrowser = navigator.userAgent.indexOf("AppleWebKit") != -1;

/* content popup DIV */

// the ID of the content DIV that is shown in the popup DIV
window.popupContentId = "";
// the state in the animation of the content popup DIV
window.popupAnimationState = IDLE;
// the opacity of the popup DIV
window.popupAnimationOpacity = 0;

/* simple content opacity animation */

// the ID of the content DIV that is currently animated
window.animatedContentId = "";
// the state in the animation of the content DIV
window.contentAnimationState = IDLE;
// the opacity of the content DIV
window.contentAnimationOpacity = 0;


//
// Returns the content element with the given ID.
// If the content element with the given ID could not be found an error message
// is printed in the console.
//
// \param id    the ID of the content element to return
// \return The content element with the given ID, or Null if it doesn't exist.
//
window.GetContentElement = function ( id )
{
    if (window.contentElements[id])
        return window.contentElements[id];
    else {
        console.error("Content element with ID \"" + id + "\" could not be found.");
        return null;
    }
}


//
// Ajax helper class.
//
window.Ajax = {

    xmlHttp: null,
    ASYNCHRONOUSLY: true,   // the XML HTTP request object will NOT wait for the server's response
    SYNCHRONOUSLY: false,   // the XML HTTP request object will block the execution of scripts and wait for the server's response
    DONT_WAIT_FOR_RESPONSE: true,
    WAIT_FOR_RESPONSE: false

}


//
// Creates an XMLHTTP object to be used in requests.
//
// \return A new XMLHTTP object.
//
window.Ajax.CreateXmlHttpObject = function ()
{
    var xmlHttp = null;
    if (window.XMLHttpRequest) {
        xmlHttp = new XMLHttpRequest();
    } else if (window.ActiveXObject) {
        try {
            xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
        }
        catch (ex) {
            try {
                xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
            }
            catch (ex) {
                xmlHttp = null;
            }
        }
    }
    return xmlHttp;
}


//
// Requests the given path and returns the servers response text.
//
// \param url    the relative path of the file to request
// \return The response text given by the server.
//
window.Ajax.Get = function ( url )
{
    if (window.Ajax.xmlHttp == null)
        window.Ajax.xmlHttp = window.Ajax.CreateXmlHttpObject();

    if (window.Ajax.xmlHttp != null) {
        window.Ajax.xmlHttp.open("GET", url, false);
        window.Ajax.xmlHttp.send(null);
        return window.Ajax.xmlHttp.responseText;
    } else
        return "";
}


//
// Requests the script from the given address and evaluates it.
//
// \param path  the relative path of the script to load
//
window.LoadScript = function ( path )
{
    var fileContent = Ajax.Get(path);
    if (fileContent != "")
        eval(fileContent);
}


//
// Loads additional JavaScript files to be included in the page.
//
window.LoadIncludes = function ()
{
    var consoleGroupName = "Loading include files";
    console.group(consoleGroupName)
    console.time(consoleGroupName)

    var includes = [
        "/clean/script/Animation.js",
        "/clean/script/EventHandlers.js",
        "/clean/script/ContentElement.js",
        "/clean/script/TriggerElement.js",
        "/clean/script/Tab.js",
        "/clean/script/nodes.js",
        "/clean/script/constants.js"     /* must be the last file to include */
    ];

    for (var i in includes)
        LoadScript(includes[i]);

    console.timeEnd(consoleGroupName)
    console.groupEnd()
}


//
// Creates a new tab object containing the trigger and content elements with
// the given IDs and bearing the given attributes.
//
// \param name        the tab's unique name
// \param triggerIds  a list of trigger element IDs
// \param contentIds  a list of content element IDs
// \param attributes  an array containing pairs of attribute names and values
//
window.CreateTab = function ( name, triggerIds, contentIds, attributes )
{
    console.info("CreateTab() has been called. Name: " + name);
    // check if a tab by that name already exists
    if (window.tabs[name] != undefined) {
        console.error("Error when creating Tab \"" + name + "\": A tab by that name already exists.");
        return;
    }

    if (!IsArray(triggerIds))
        triggerIds = [triggerIds];
    if (!IsArray(contentIds))
        contentIds = [contentIds];
    if (attributes == undefined)
        attributes = {};
    if (attributes.contentStates == undefined) {
        // build list of content states with the first state being OPEN, the
        // other states being CLOSED
        attributes.contentStates = [[ContentElement.State.OPEN]];
        for (var i = 0; i < contentIds.length - 1; ++i)
            attributes.contentStates.push([ContentElement.State.CLOSED]);
    }
    if (attributes.style == undefined)
        attributes.style = Tab.Style.VERTICAL;

    // make sure the number of content IDs and the number of trigger IDs match
    if (triggerIds.length > 1 && contentIds.length != triggerIds.length) {
        console.error("Error when creating Tab \"" + name + "\": Number of content IDs does not match number of trigger IDs.");
        return;
    }

    // make sure the number of content states and the number of content IDs match
    if (attributes.contentStates.length != contentIds.length) {
        console.error("Error when creating Tab \"" + name + "\": Number of content states does not match number of content IDs.");
        return;
    }

    // navigate to a specific section of the page if requested
    if (window.requestedSection.length > 0) {
        for (var i = 0; i < window.requestedSection.length; ++i) {
            var elementToOpen = window.requestedSection[i];
            if (IsIn(elementToOpen, triggerIds)) {
                window.currentSection.push(elementToOpen);
                var index = IndexOf(elementToOpen, triggerIds);
                // build new list of content states with the state of the
                // requested section being OPEN, the other states being CLOSED
                attributes.contentStates = [];
                for (var n = 0; n < contentIds.length; ++n)
                    if (n == index)
                        attributes.contentStates.push([ContentElement.State.OPEN]);
                    else
                        attributes.contentStates.push([ContentElement.State.CLOSED]);
            }
        }
    }

    // create a new Tab object with the given data
    window.tabs[name] = new Tab(name, triggerIds, contentIds, attributes);

    // save the first created Tab as the page's main navigation Tab
    if (window.mainNavigationTab == "")
        window.mainNavigationTab = window.tabs[name];
}


//
// Updates the Tab with the given name with the given attributes.
//
// \param name          the name of the Tab to update
// \param attributes    the new attributes for the Tab
//
window.SetTab = function ( name, attributes )
{
    var tab = window.tabs[name];
    if (!tab) {
        console.error("SetTab(): Tab \"" + name + "\" not found.");
        return;
    }
    tab.SetAttributes(attributes);
}


//
// Returns the Tab that contains the trigger element with the given ID.
//
// \param triggerID     the ID of the trigger element to return the Tab for
//
window.FindTabForTriggerId = function ( triggerId )
{
    var tab = null;
    for (var name in window.tabs)
        if (tab == null && window.tabs[name].GetIndexByTriggerId(triggerId) != -1)
            tab = window.tabs[name];

    if (!tab)
        console.warn("FindTabForTriggerId(): No Tab containing the trigger element \"" + triggerId + "\" found.");
    return tab;
}


//
// Sets the element that is to be used as a home button.
//
// \param homeId      the ID of the element to be used as the home button
// \param triggerId   the ID of the element that will be triggered when the home element is clicked
//
window.SetHome = function ( homeId, triggerId )
{
    // TODO
}


//
// Simulates clicks on trigger elements so that the browser displays the given
// section of the page.
//
// \param sectionPath   the path of the section to select (a comma-separated list of trigger IDs)
//
window.SelectSection = function ( sectionPath )
{
    var sectionPathList = sectionPath.split(",");
    // iterate over the list of requested section names
    for (var i in sectionPathList) {
        var section = sectionPathList[i];
        var tab = FindTabForTriggerId(section);
        if (tab && tab.GetSelectedTriggerId() != section)
            tab.OnClick(section);
    }
}


//
// Checks if there are anchor names in the current location and triggers the
// respective elements to display the requested page.
//
// \param initializing  flag that states whether the page is currently being initialized
//
window.EvaluateLocation = function ( initializing )
{
    if (initializing && location.hash.length <= 1) {
        // build requested section string that represents the initially selected
        // trigger elements
        var firstTab = true;
        for (name in window.tabs)
            if (firstTab) {
                var selectedTriggerId = window.tabs[name].GetSelectedTriggerId();
                window.initialSection = [selectedTriggerId];
                window.requestedSection = [selectedTriggerId];
                window.currentSection = [selectedTriggerId];
                // TODO: implement this for any number of hierarchy levels
                var contentIds = window.tabs[name].contentIds[window.tabs[name].GetIndexByTriggerId(selectedTriggerId)];
                for (i in contentIds) {
                    var contentId = contentIds[i];
                    if (window.tabs[contentId] != undefined) {
                        var innerSelectedTriggerId = window.tabs[contentId].GetSelectedTriggerId();
                        window.initialSection.push(innerSelectedTriggerId);
                        window.requestedSection.push(innerSelectedTriggerId);
                        window.currentSection.push(innerSelectedTriggerId);
                    }
                }
                firstTab = false;
            }
        return;
    }

    var requestedSection;
    if (location.hash.length > 1) {
        var anchorNames = location.hash.substr(1);
        if (anchorNames[anchorNames.length - 1] == ",")
            anchorNames = anchorNames.substr(0, anchorNames.length - 1);
        requestedSection = anchorNames.split(",");
    } else {
        // go back to initial section (no hash given)
        requestedSection = window.initialSection.join(",").split(",");
    }

    if (initializing || requestedSection.join(",") != window.requestedSection.join(",")) {
        window.requestedSection = requestedSection;
        if (initializing) {
            console.info("Initially requested section is: " + window.requestedSection);
            // update the document's title using the caption of the trigger element
            // that loads the requested page
            var sectionIndex = requestedSection.length - 1;
            var titleFound = false;
            while (sectionIndex >= 0 && !titleFound) {
                var lastTriggerId = requestedSection[sectionIndex];
                for (name in window.tabs) {
                    var lastTriggerIndex = window.tabs[name].GetIndexByTriggerId(lastTriggerId)
                    if (lastTriggerIndex != -1) {
                        var lastTrigger = window.tabs[name].triggers[lastTriggerIndex];
                        var spans = lastTrigger.GetElement().getElementsByTagName("SPAN");
                        if (spans.length > 1) {
                            document.title = window.baseTitle + " - " + spans[1].firstChild.nodeValue;
                            titleFound = true;
                        }
                    }
                }
                if (!titleFound)
                    --sectionIndex;
            }
        } else {
            console.info("Location has changed. Requested section is now: " + window.requestedSection);
            SelectSection(window.requestedSection.join(","));
        }
    }
}


//
// Updates all trigger and content elements to respect changes in the DOM tree,
// and registers all created event handlers with their respective elements.
//
window.UpdateElements = function ()
{
    var consoleGroupName = "Updating elements";
    console.group(consoleGroupName)
    console.time(consoleGroupName)

    for (var tabName in tabs)
        tabs[tabName].UpdateElementPointers();

    eventHandlers.RegisterAll();

    console.timeEnd(consoleGroupName)
    console.groupEnd()
}


//
// Event handler for the login text input field.
// Is called when a key is pressed.
//
// \param element   the login text input field
// \param event     the key down event object
//
window.loginKeyDownHandler = function ( element, event )
{
    if (!event)
        event = window.event;

    if (event.keyCode == 13) {
        LoginButton_OnClick();
        // old solution
        //location.href = location.protocol + "//" + location.hostname + "/" + window.page + "/" + element.value;
        return false;
    }
    return true;
}


//
// Displays or hides the content DIV with the given ID.
//
// \param id        the ID of the content DIV to toggle
// \param popup     flag that controls whether to open the content in a popup window
// \param height    the height of the content DIV to toggle
//
window.toggleContentDiv = function ( id, popup, height )
{
    var contentDiv = document.getElementById(id);
    if (!contentDiv) {
        console.error("Content with ID \"" + id + "\" not found.");
        return;
    }

    if (popup) {
        var popupDiv = document.getElementById("popupDiv");

        if (id != window.popupContentId)
            popupDiv.style.visibility = "hidden";

        // set dimensions of popup window
        var width = contentDiv.offsetWidth;
        popupDiv.style.width = width + "px";
        popupDiv.style.height = height + "px";
        // center popup DIV in the window
        popupDiv.style.left = (getInnerWidth() - width) / 2 + getScrollXOffset() + "px";
        popupDiv.style.top = (getInnerHeight() - height) / 2 + getScrollYOffset() + "px";

        popupDiv.innerHTML = contentDiv.innerHTML;
        if (popupDiv.style.visibility == "hidden" && popupAnimationState == IDLE) {
            popupDiv.style.opacity = 0;
            popupDiv.style.visibility = "visible";
            popupAnimationState = FADING_IN;
            popupAnimationOpacity = 0;
            window.setTimeout("popupFadeIn()", 25);
        }
        window.popupContentId = id;
    } else {
        if (window.contentAnimationState == IDLE) {
            window.animatedContentId = id;
            if (contentDiv.style.visibility == "hidden") {
                contentAnimationState = FADING_IN;
                contentAnimationOpacity = 0;
                contentDiv.style.opacity = 0;
                contentDiv.style.height = "auto";
                contentDiv.style.visibility = "visible";
                window.setTimeout("contentFadeIn()", 25);
            } else {
                contentAnimationState = FADING_OUT;
                contentAnimationOpacity = 100;
                window.setTimeout("contentFadeOut()", 25);
            }
        }
    }
}


//
// Increases the opacity of the content popup DIV.
//
window.popupFadeIn = function ()
{
    popupAnimationOpacity += 10;

    var popupDiv = document.getElementById("popupDiv");
    popupDiv.style.opacity = popupAnimationOpacity / 100;

    // check if the content popup has been fully faded in
    if (popupAnimationOpacity < 100)
        window.setTimeout("popupFadeIn()", 25);
    else {
        popupAnimationState = IDLE;
        window.onmousedown = window.popupMouseDownHandler;
    }
}


//
// Increases the opacity of the content DIV.
//
window.contentFadeIn = function ()
{
    contentAnimationOpacity += 10;

    var contentDiv = document.getElementById(window.animatedContentId);
    contentDiv.style.opacity = contentAnimationOpacity / 100;

    // check if the content popup has been fully faded in
    if (contentAnimationOpacity < 100)
        window.setTimeout("contentFadeIn()", 25);
    else {
        contentAnimationState = IDLE;
    }
}


//
// Decreases the opacity of the content DIV.
//
window.contentFadeOut = function ()
{
    contentAnimationOpacity -= 10;

    var contentDiv = document.getElementById(window.animatedContentId);
    contentDiv.style.opacity = contentAnimationOpacity / 100;

    // check if the content popup has been fully faded in
    if (contentAnimationOpacity > 0)
        window.setTimeout("contentFadeOut()", 25);
    else {
        contentAnimationState = IDLE;
        contentDiv.style.height = "0px";
        contentDiv.style.visibility = "hidden";
    }
}


//
// Event handler that is activated when the content popup DIV is shown.
// Closes the popup DIV that contains content from a content DIV.
//
// \param event     the object containing details about the mouse event
//
window.popupMouseDownHandler = function ( event )
{
    if (event.button != 0)
        return true;

    var targetElement = event.target;
    if (event.srcElement)
        targetElement = event.srcElement;

    // check if the target element is a child of the popup DIV
    var popupDiv = document.getElementById("popupDiv");
    var node = targetElement;
    while (node && node != popupDiv && node.nodeName != "BODY")
        node = node.parentNode;

    if (node == popupDiv)
        return true;

    popupDiv.style.visibility = "hidden";
    window.onmousedown = null;
    return true;
}


//
// Flags the content element with the given ID as to be loaded when it is first
// opened.
//
// \param id              the ID of the content element for which to load its contents on demand
// \param remoteAddress   the URL to load when the content element is first openend
//
window.LoadContentOnDemand = function ( id, remoteAddress )
{
    var contentElement = window.contentElements[id];
    if (contentElement)
        contentElement.SetAttributes({remoteAddress: remoteAddress});
    else
        contentElementChanges.push({elementId: id, attributes: {remoteAddress: remoteAddress}});
}


//
// Event handler for the browser window.
// Is called when the page has been fully loaded.
//
window.onload = function ()
{
    window.debugMode = true;
    window.baseTitle = document.title;

    // make the page invisible
    var loadingElement = document.getElementById("loading");
    if (loadingElement)
        loadingElement.style.visibility = "visible";
    else
        document.body.style.visibility = "hidden";

    // load the FirebugLite script (if Firebug could not be found)
    if (typeof console == "undefined") {
        LoadScript("/clean/script/firebuglite/firebug.js");
        if (typeof console == "undefined") {
            alert("Console is not defined! Something went wrong with the FirebugLite include.");
            return;
        }
    }

    // check if JavaScript debugging mode is enabled
    if (!window.debugMode) {
        // disable all console functions
        window.console = {};
        var functionNames = [
            "log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
            "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"
        ];
        for (var i = 0; i < functionNames.length; ++i)
            window.console[functionNames[i]] = function() {}
    }

    // initialize page
    console.group("Initializing page");
    console.time("Initializing page");

    if (window.Setup) {
        // initialize the requested section
        EvaluateLocation(true);
        // dynamically load all qb-ware JavaScript framework include files
        //LoadIncludes();
        // call the page's own setup function
        Setup();
        // update all elements and register all event handlers
        UpdateElements();
        // initialize the initial section
        EvaluateLocation(true);
    }

    // apply content element changes
    for (var i = 0; i < contentElementChanges.length; ++i) {
        var elementId = contentElementChanges[i].elementId;
        var attributes = contentElementChanges[i].attributes;
        var contentElement = GetContentElement(elementId);
        if (contentElement)
            contentElement.SetAttributes(attributes);
    }

    var popupDiv = document.getElementById("popupDiv");
    if (popupDiv) {
        popupDiv.style.visibility = "hidden";
        if (window.getComputedStyle) {
            // set the font size of the popup DIV according to the 'global' div
            var globalDiv = document.getElementById("global");
            var globalStyle = getComputedStyle(globalDiv, "");
            var globalFontSize = globalStyle.fontSize;
            popupDiv.style.fontSize = globalFontSize;
        }
    }

    // make the page visible
    if (loadingElement)
        loadingElement.parentNode.removeChild(loadingElement);
    else
        document.body.style.visibility = "visible";

    console.timeEnd("Initializing page");
    console.groupEnd();

    // set up the timer to check if the page location has changed
    window.locationCheckerTimer = window.setInterval("EvaluateLocation(false)", 200);
}
