//
//  ContentElement.js
//  Written by Stefan Habel
//  Version 2.0
//  Last modified: 09.04.2008
//


//
//
//
window.ContentElement = function ( elementId, attributes )
{

    this.elementId        = elementId;                // ID of associated HTML element that is the actual content element
    this.outerContainerId = null;                     // ID of associated HTML element containing the inner container element (is created automatically)
    this.innerContainerId = null;                     // ID of associated HTML element containing the actual element (is created automatically)
    this.outerContainer   = null;                     // pointer to associated HTML element containing the inner container element in the DOM tree
    this.innerContainer   = null;                     // pointer to associated HTML element containing the actual element in the DOM tree
    this.fullWidth        = 0;                        // width of associated HTML element
    this.fullHeight       = 0;                        // height of associated HTML element
    this.fullOpacity      = 1.0;                      // opacity of associated HTML element
    this.automaticHeight  = false;                    // flag that states whether the height of the HTML element should be automatically set by the browser
    this.animationStyle   = Animation.Style.VERTICAL; // style of the element's animation
    this.animationState   = Animation.State.IDLE;     // state of the element's animation
    this.remoteAddress    = "";
    this.contentLoaded    = true;
    this.xmlHttp          = null;                     // for Ajax

    // store pointer to the object itself for use in functions that are called
    // by window.setTimeout or window.setInterval (in these functions 'this'
    // points to the browser's window object and not to the actual object)
    var object = this;


    //
    // Performs a step in the animation of the content element.
    //
    // \param phase         the current animation phase
    // \param t             the unfiltered animation parameter
    // \param filteredT     the filtered animation parameter (e.g. ease-in-filtered)
    //
    this.Animate = function ( phase, t, filteredT )
    {
        if (object.animationStyle == Animation.Style.NONE) {
            console.warn("Content element \"" + object.elementId + "\" does not need to be animated.");
            return;
        }
//        console.info(object.elementId + ".Animate(" + phase + ", " + t + ", " + filteredT + ") has been called.");

        // determine which element should be animated
        var containerToAnimate;
        if (object.freeSpaceMode == ContentElement.FreeSpaceMode.MAINTAIN)
            containerToAnimate = object.innerContainer;
        else
            containerToAnimate = object.outerContainer;

        // determine which properties of the element should be animated
        var animateWidth = false;
        var animateHeight = false;
        var animateOpacity = false;

        switch (object.animationStyle) {
            case Animation.Style.VERTICAL:
                animateWidth = phase == Animation.Phase.ADJUST_SPACE;
                animateHeight = phase != Animation.Phase.ADJUST_SPACE;
                animateOpacity = phase != Animation.Phase.ADJUST_SPACE;
                break;
            case Animation.Style.HORIZONTAL:
                animateWidth = phase != Animation.Phase.ADJUST_SPACE;
                animateHeight = phase == Animation.Phase.ADJUST_SPACE;
                animateOpacity = phase != Animation.Phase.ADJUST_SPACE;
                break;
            case Animation.Style.DIAGONAL:
                // there is only one phase for diagonally animated and cross-faded elements
                animateWidth = true;
                animateHeight = true;
                animateOpacity = true;
                break;
            case Animation.Style.OPACITY:
                animateWidth = phase == Animation.Phase.ADJUST_SPACE;
                animateHeight = phase == Animation.Phase.ADJUST_SPACE;
                animateOpacity = phase != Animation.Phase.ADJUST_SPACE;
                break;
        }

        if (animateWidth) {
            // calculate element's width
            var width;
            if (object.animationState == Animation.State.FADING_IN)
                width = filteredT * object.fullWidth;
            else
                width = object.fullWidth - filteredT * object.fullWidth;

            // due to strange behavior in Safari the width may not be 0
            if (width == 0)
                width = 1;

            // set element's width
            containerToAnimate.style.width = width + "px";
        }

        if (animateHeight) {
            // calculate element's height
            var height;
            if (object.animationState == Animation.State.FADING_IN)
                height = filteredT * object.fullHeight;
            else
                height = object.fullHeight - filteredT * object.fullHeight;

            // set element's height
            containerToAnimate.style.height = height + "px";
        }

        if (animateOpacity && (object.animateOpacity || object.animationStyle == Animation.Style.OPACITY)) {
            // calculate element's opacity
            var opacity;
            if (object.animationState == Animation.State.FADING_IN)
                opacity = filteredT * object.fullOpacity;
            else
                opacity = object.fullOpacity - filteredT * object.fullOpacity;

            // set element's opacity
            containerToAnimate.style.opacity = opacity;
            if (document.all)
                containerToAnimate.style.filter = "Alpha(opacity=" + opacity * 100 + ")";
        }
    }


    //
    //
    //
    this.EndAnimation = function ( reversed )
    {
//        console.info(object.elementId + ".EndAnimation(" + reversed + ") has been called.");

        // check if the animation has been reversed
        if (reversed) {
            // toggle animation state to get the right object state
            if (object.animationState == Animation.State.FADING_IN)
                object.animationState = Animation.State.FADING_OUT;
            else
                object.animationState = Animation.State.FADING_IN;
        }

        // determine the element's new state according to its animation state
        if (object.animationState == Animation.State.FADING_IN)
            object.state = ContentElement.State.OPEN;
        else
            object.state = ContentElement.State.CLOSED;

        if (object.animationStyle == Animation.Style.NONE
                || object.animationStyle == Animation.Style.VERTICAL
                || object.animationStyle == Animation.Style.HORIZONTAL
                || object.animationStyle == Animation.Style.DIAGONAL
                || (object.animationStyle == Animation.Style.OPACITY && object.freeSpaceMode != ContentElement.FreeSpaceMode.MAINTAIN)) {
            // calculate and set the element's height
            var height;
            if (object.state == ContentElement.State.OPEN) {
                if (object.automaticHeight)
                    height = "";
                else
                    height = object.fullHeight + "px";
            } else
                height = "0px";

            if (object.freeSpaceMode == ContentElement.FreeSpaceMode.MAINTAIN)
                object.innerContainer.style.height = height;
            else
                object.outerContainer.style.height = height;
        }

        if (object.animationStyle == Animation.Style.NONE
                || (object.animationStyle == Animation.Style.VERTICAL && object.freeSpaceMode != ContentElement.FreeSpaceMode.MAINTAIN)
                || object.animationStyle == Animation.Style.HORIZONTAL
                || object.animationStyle == Animation.Style.DIAGONAL
                || (object.animationStyle == Animation.Style.OPACITY && object.freeSpaceMode != ContentElement.FreeSpaceMode.MAINTAIN)) {
            // calculate and set the element's width
            var width;
            if (object.state == ContentElement.State.OPEN)
                width = object.fullWidth + "px";
            else
                width = window.safariBrowser ? "1px" : "0px";   // due to strange behavior in Safari the width may not be "0px"

            if (object.freeSpaceMode == ContentElement.FreeSpaceMode.MAINTAIN)
                object.innerContainer.style.width = width;
            else
                object.outerContainer.style.width = width;
        }

        object.animationState = Animation.State.IDLE;
    }


    this.SetAttributes(attributes, true);
    this.Init();

}


//
// Sets the attribute values of the content element object to the given values.
// Missing attribute values will be set to defaults when the respective flag is
// set to true.
//
// \param attributes    the collection of attributes to set for the element
// \param setDefaults   flag that controls whether to set default values for missing attributes
//
window.ContentElement.prototype.SetAttributes = function ( attributes, setDefaults )
{
    // state
    if (attributes.state != undefined)
        this.state = attributes.state;
    else
        if (setDefaults)
            this.state = ContentElement.Default.state;

    // animation style
    if (attributes.animationStyle != undefined)
        this.animationStyle = attributes.animationStyle;
    else
        if (setDefaults)
            this.animationStyle = ContentElement.Default.animationStyle;

    // group name
    if (attributes.groupName != undefined)
        this.groupName = attributes.groupName;
    else
        if (setDefaults)
            this.groupName = ContentElement.Default.groupName;

    // animate opacity
    if (attributes.animateOpacity != undefined)
        this.animateOpacity = attributes.animateOpacity;
    else
        if (setDefaults)
            this.animateOpacity = ContentElement.Default.animateOpacity;

    // free space mode
    if (attributes.freeSpaceMode != undefined) {
        this.freeSpaceMode = attributes.freeSpaceMode;

        if (this.innerContainer != undefined) {
            // update containers immediately
            if (this.freeSpaceMode == ContentElement.FreeSpaceMode.MAINTAIN) {
                if (this.state == ContentElement.State.CLOSED)
                    this.UpdateInnerContainer(0, 0, 0);
                this.UpdateOuterContainer(this.fullWidth, this.fullHeight, 1);
            } else
                if (this.state == ContentElement.State.CLOSED) {
                    this.UpdateOuterContainer(0, 0, 0);
                    this.UpdateInnerContainer(this.fullWidth, this.fullHeight, 1);
                }
        }
    } else
        if (setDefaults)
            this.freeSpaceMode = ContentElement.Default.freeSpaceMode;

    // remote address
    if (attributes.remoteAddress != undefined) {
        if (attributes.remoteAddress != this.remoteAddress) {
            this.remoteAddress = attributes.remoteAddress;
            this.contentLoaded = false;
        }
    } else
        if (setDefaults)
            if (attributes.remoteAddress != this.remoteAddress) {
                this.remoteAddress = ContentElement.Default.remoteAddress;
                this.contentLoaded = false;
            }

    if (!this.contentLoaded && this.remoteAddress != "" && this.state == ContentElement.State.OPEN)
        this.LoadContent();
}


//
//
//
window.ContentElement.prototype.LoadContent = function ()
{
    var element = document.getElementById(this.elementId);
    if (!element) {
        console.error(this.elementId + ".LoadContent(): Associated HTML element \"" + this.elementId + "\" could not be found.");
        return;
    }

    if (!this.xmlHttp) {
        this.xmlHttp = Ajax.CreateXmlHttpObject();
        if (this.xmlHttp) {
            var thisContentElement = this;
            this.xmlHttp.onreadystatechange = function ()
            {
                ContentElement_OnReadyStateChange(thisContentElement);
            }
        } else {
            console.error(this.elementId + ".LoadContent(): Couldn't create the required XML HTTP object.");
            return;
        }
    }

    var content = '<img src="/vers3/loading_small.gif" alt="Loading..." title="Loading..." width="24" height="24" style="padding-bottom: 100px;" />';
    RemoveAllChildren(element);
    CreateHtmlNodes(element, content);

    this.xmlHttp.open("GET", this.remoteAddress, Ajax.DONT_WAIT_FOR_RESPONSE);
    this.xmlHttp.send("");
}


//
// Callback function for the XML HTTP object of content elements.
//
window.ContentElement_OnReadyStateChange = function ( contentElement )
{
    if (!contentElement || !contentElement.xmlHttp) {
        console.error("ContentElement_OnReadyStateChange(): Content element or XML HTTP object not found.");
        return;
    }

    if (contentElement.xmlHttp.readyState == 4) {
        var element = document.getElementById(contentElement.elementId);
        if (!element) {
            console.error("ContentElement_OnReadyStateChange(): Associated HTML element \"" + contentElement.elementId + "\" could not be found.");
            return;
        }

        var contentLoaded = contentElement.xmlHttp.status == 200;
        var content;
        if (contentLoaded)
            content = contentElement.xmlHttp.responseText;
        else
            content = "Content could not be loaded.";

        RemoveAllChildren(element);
        CreateHtmlNodes(element, content);

        contentElement.contentLoaded = contentLoaded;
    }
}


//
//
//
window.ContentElement.prototype.CreateContainerDiv = function ( id, containedElement )
{
    var containerDiv = document.createElement("div");
    containerDiv.id = id;

    if (containedElement) {
        // copy styles from inner element
        containerDiv.classname          = containedElement.classname;
        containerDiv.style.margin       = containedElement.style.margin;
        containerDiv.style.marginLeft   = containedElement.style.marginLeft;
        containerDiv.style.marginRight  = containedElement.style.marginRight;
        containerDiv.style.marginTop    = containedElement.style.marginTop;
        containerDiv.style.marginBottom = containedElement.style.marginBottom;
        
        // remove styles from inner element
        containedElement.style.margin       = "";
        containedElement.style.marginLeft   = "";
        containedElement.style.marginRight  = "";
        containedElement.style.marginTop    = "";
        containedElement.style.marginBottom = "";
    }

    // set container-specific styles
    containerDiv.style.overflow = "hidden";

    return containerDiv;
}


//
//
//
window.ContentElement.prototype.Init = function ()
{
    var element = document.getElementById(this.elementId);
    if (!element) {
        console.error("Error initializing content element: Associated HTML element \"" + this.elementId + "\" could not be found.");
        return;
    }

    // get dimensions of associated HTML element
    this.fullWidth  = element.offsetWidth;
    this.fullHeight = element.offsetHeight;
    if (element.style.height == "")
        this.automaticHeight = true;
    if (this.fullWidth == 0)
        console.warn("Element " + this.elementId + " has a width of 0.");

    // embed the element in container DIVs to prevent contents from breaking
    // when animating the width and to allow for maintaining the contents space
    this.outerContainerId = this.elementId + "_OuterContainer";
    this.innerContainerId = this.elementId + "_InnerContainer";
    var outerContainerDiv = this.CreateContainerDiv(this.outerContainerId, element);
    var innerContainerDiv = this.CreateContainerDiv(this.innerContainerId);
    //console.info(this.elementId + " w = " + this.fullWidth + " h = " + this.fullHeight + " | w = " + element.offsetWidth + " h = " + element.offsetHeight);
    element.parentNode.replaceChild(outerContainerDiv, element);
    outerContainerDiv.appendChild(innerContainerDiv);
    innerContainerDiv.appendChild(element);
    this.UpdateElementPointers();

    // set width & height of closed elements to 0 pixels
    if (this.state == ContentElement.State.CLOSED) {
        var width = window.safariBrowser ? 1 : 0;
        this.UpdateOuterContainer(width, 0, 0);
    }
}


//
// Returns whether the given animation phase is needed when animating the
// element.
//
// \param phase     the animation phase to check
//
window.ContentElement.prototype.IsPhaseNeeded = function ( phase )
{
    switch (phase) {
        case Animation.Phase.FADE_OUT:
            return this.state == ContentElement.State.OPEN;
        case Animation.Phase.ADJUST_SPACE:
            return this.animationStyle != Animation.Style.DIAGONAL && this.freeSpaceMode == ContentElement.FreeSpaceMode.ANIMATED;
        case Animation.Phase.FADE_IN:
            return this.state == ContentElement.State.CLOSED;
        default:
            return false;
    }
}


//
//
//
window.ContentElement.prototype.IsOpen = function ()
{
    return this.state == ContentElement.State.OPEN;
}


//
//
//
window.ContentElement.prototype.IsClosed = function ()
{
    return this.state == ContentElement.State.CLOSED;
}


//
//
//
window.ContentElement.prototype.IsSelected = function ()
{
    return this.state == ContentElement.State.SELECTED;
}


//
//
//
window.ContentElement.prototype.IsAnimated = function ()
{
    return this.animationStyle != Animation.Style.NONE;
}


//
//
//
window.ContentElement.prototype.IsAnimating = function ()
{
    return this.animationState != Animation.State.IDLE;
}


//
//
//
window.ContentElement.prototype.IsPartOfAGroup = function ()
{
    return this.groupName != "";
}


//
//
//
window.ContentElement.prototype.GetGroupName = function ()
{
    return this.groupName;
}


//
//
//
window.ContentElement.prototype.GetElement = function ()
{
    return this.outerContainer;
}


//
// Updates element pointers to respect changes in the DOM tree.
//
window.ContentElement.prototype.UpdateElementPointers = function ()
{
    this.outerContainer = document.getElementById(this.outerContainerId);
    this.innerContainer = document.getElementById(this.innerContainerId);
}


//
// Updates the width of outer containers of open elements, which is necessary
// for Opera to correctly initialize the element's width.
//
window.ContentElement.prototype.UpdateElementLayout = function ()
{
    if (this.state == ContentElement.State.OPEN) {
        var element = document.getElementById(this.elementId);
        if (element)
            this.fullWidth = element.offsetWidth;
        if (this.outerContainer)
            this.outerContainer.style.width = this.fullWidth + "px";
    }
}


//
//
//
window.ContentElement.prototype.UpdateInnerContainer = function ( width, height, opacity )
{
    if (opacity == 1)
        // set opacity to an empty string to remove the style attribute
        opacity = "";

    this.innerContainer.style.width   = width  + "px";
    this.innerContainer.style.height  = height + "px";
    this.innerContainer.style.opacity = opacity;
    if (document.all)
        this.innerContainer.style.filter = "Alpha(opacity=" + opacity * 100 + ")";
}


//
//
//
window.ContentElement.prototype.UpdateOuterContainer = function ( width, height, opacity )
{
    if (opacity == 1)
        // set opacity to an empty string to remove the style attribute
        opacity = "";

    this.outerContainer.style.width   = width  + "px";
    this.outerContainer.style.height  = height + "px";
    this.outerContainer.style.opacity = opacity;
    if (document.all)
        this.outerContainer.style.filter = "Alpha(opacity=" + opacity * 100 + ")";
}


//
// Toggle function for non-animated content elements like tabs.
//
window.ContentElement.prototype.Toggle = function ()
{
    // load content on demand
    if (this.state == ContentElement.State.CLOSED && !this.contentLoaded && this.remoteAddress != "")
        this.LoadContent();

    // set inner width, height and opacity
    var innerWidth   = 0;
    var innerHeight  = 0;
    var innerOpacity = 0;
    if (this.state == ContentElement.State.CLOSED) {
        innerWidth   = this.fullWidth;
        innerHeight  = this.fullHeight;
        innerOpacity = 1;
    }

    // update containers
    if (this.freeSpaceMode == ContentElement.FreeSpaceMode.MAINTAIN)
        this.UpdateInnerContainer(innerWidth, innerHeight, innerOpacity);
    else
        this.UpdateOuterContainer(innerWidth, innerHeight, innerOpacity);

    // toggle state
    if (this.state == ContentElement.State.OPEN)
        this.state = ContentElement.State.CLOSED;
    else
        this.state = ContentElement.State.OPEN;
}


//
//
//
window.ContentElement.prototype.BeginAnimation = function ()
{
    if (this.state == ContentElement.State.OPEN) {
        if (this.automaticHeight)
            this.fullHeight = this.outerContainer.offsetHeight;
        this.animationState = Animation.State.FADING_OUT;
    } else {
        // load content on demand
        if (this.state == ContentElement.State.CLOSED && this.remoteAddress != "" && !this.contentLoaded) {
            this.LoadContent();

            if (this.automaticHeight) {
                this.fullHeight = this.innerContainer.offsetHeight;
//                console.info("The inner container of this element has a height of " + this.fullHeight);
            }
        }

        this.animationState = Animation.State.FADING_IN;

        if (this.animationStyle == Animation.Style.VERTICAL || this.animationStyle == Animation.Style.OPACITY)
            if (this.freeSpaceMode == ContentElement.FreeSpaceMode.MAINTAIN)
                this.innerContainer.style.width = this.fullWidth + "px";
            else if (this.freeSpaceMode == ContentElement.FreeSpaceMode.DIRECTLY)
                this.outerContainer.style.width = this.fullWidth + "px";

        if (this.animationStyle == Animation.Style.HORIZONTAL || this.animationStyle == Animation.Style.OPACITY)
            if (this.freeSpaceMode == ContentElement.FreeSpaceMode.MAINTAIN)
                this.innerContainer.style.height = this.fullHeight + "px";
            else if (this.freeSpaceMode == ContentElement.FreeSpaceMode.DIRECTLY)
                this.outerContainer.style.height = this.fullHeight + "px";
    }

    if (this.animationStyle != Animation.Style.OPACITY && !this.animateOpacity) {
        this.outerContainer.style.opacity = "";
        this.innerContainer.style.opacity = "";
        if (document.all) {
           this.outerContainer.style.filter = "";
           this.innerContainer.style.filter = "";
        }
    }
}



// state enumeration
window.ContentElement.State = {
    CLOSED     : false,
    OPEN       : true,
    DESELECTED : false,
    SELECTED   : true
};

// free space mode enumeration
window.ContentElement.FreeSpaceMode = {
    MAINTAIN : 0,    // maintain space using the outer container DIV
    DIRECTLY : 1,    // collapse the outer container DIV when the inner container DIV's animation has ended
    ANIMATED : 2     // animate collapsing the outer container DIV when the inner container DIV's animation has ended
};


// default attributes
window.ContentElement.Default = {
    state          : ContentElement.State.OPEN,
    animationStyle : Animation.Style.VERTICAL,
    groupName      : "",
    animateOpacity : true,
    freeSpaceMode  : ContentElement.FreeSpaceMode.ANIMATED,
    remoteAddress  : "",
    contentLoaded  : true
}
