// CourseCast Viewer
// Copyright 2007 Panopto Inc
// All rights reserved.  Reuse and redistribution strictly prohibited.

// globals
var g_urlDeliveryInfo = "DeliveryInfo.xml";

var g_bIsLiveNotes = false;

var g_dMinViewerHeight = 500;
var g_dMinViewerWidth = 930;
var g_dScrollbarHeight = 17;

var g_fLeftPaneWidthPerc = .37;
var g_fRightPaneWidthPerc = .63;

var g_dMinEventViewerHeight = 170;
var g_dMinEventViewerWidth = 340;

var g_fOffsetThreshold = 5.0;

/* modified [20081111 by wmc], changed thumbnail size to 160 x 120 */
var g_dThumbnailHeight = 145;
var g_dThumbnailWidth = 190;

/* 
var g_dThumbnailHeight = 218;
var g_dThumbnailWidth = 250;
*/
var g_dContainerSpacing = 10;

function PanoptoViewer(el)
{
    var m_el = el;  // our root element
    
    // member controls
    var m_pEventTabViewer = null; // table of contents control
    var m_pVideo = null;          // presenter video stream
    var m_pObjectVideo = null;    // object video player
    var m_pThumbnails = null;     // thumbnail view    
    var m_pTabViewer = null;      // tab control to switch between streams

    // our delivery info object - parses xml and holds our data
    var m_pDelivery = null;

    // true if there are object streams to show
    var m_bHasObjectRegion = true;
    var m_bObjectRegionMaximized = false;

    var m_bHasTimestamps = true;
    var m_bThumbnailsShowing = true;
    

    // lastsetvideoposition keeps track of the last place we manually positioned the video
    // we need this to get around a bug where the video player sets its position slightly before
    // the specified time which results in the display of incorrect events
    var m_fLastSetVideoPosition = 0;
    
    // timer used to update event streams 
    var m_syncTimer;
    
    // the delivery id
    this.deliveryID = null;
    
    // initialize the viewer root and hosted controls
    // this will leak if called multiple times!
    this.Initialize = function Initialize()
    {
        // create the events tab viewer
        m_pEventTabViewer = new EventTabViewer(document.getElementById("eventViewerDiv"), this);
        
        // create our archival video player
        if( g_bUsingSilverlight )
        {
            m_pVideo = new StartPlayer_0(document.getElementById("videoPlayerDiv"));
        }
        else
        {
            m_pVideo = new VideoPlayer(document.getElementById("videoPlayerDiv"), "archivalPlayer");
        }

        // create our thumbnails display
        m_pThumbnails = new Thumbnails(document.getElementById("thumbnails"), this);
        
        // create our tab viewer (boolean specifies large tabs)
        m_pTabViewer = new TabViewer(document.getElementById("tabViewer"), this, true);
        
        // set event handler for window resize and clicking the thumbnail toggle button
        window.onresize = OnResize;
        
        var thumbButton = document.getElementById("thumbnailsToggle");
        thumbButton.onclick = Clicker(thumbButton, ToggleObjectRegionMaximized);
        thumbButton.onselectstart = function () { return false; };
    }
    
    // get the data for the supplied delivery ID, then reconfigure the viewer
    // to display it.
    this.OpenDelivery = function OpenDelivery(iDeliveryID)
    {
        function deliveryCallback( pDocument, bSuccess )
        {
            if( !pDocument || !bSuccess )
            {
                alert( "Error connecting to server. Please try refreshing the page later." );
            }
            else
            {
                var pDelivery = new DeliveryInfo( pDocument );
                SetDelivery( pDelivery );
            }
        }
        
        this.deliveryID = iDeliveryID;
        CreateRequest(g_urlDeliveryInfo, "deliveryid=" + iDeliveryID, deliveryCallback);
    }
    
    
    // load information from the specified delivery into our viewers
    function SetDelivery(pDelivery)
    {
        m_pDelivery = pDelivery;

				// modified [20081112 by wmc], added SessionGroupLongName to player
				if (m_pDelivery.courseLongName)
					document.getElementById("courseTag").innerHTML = m_pDelivery.courseLongName + "<br>";
				if (m_pDelivery.courseShortName)
					document.getElementById("courseTag").innerHTML += m_pDelivery.courseShortName + ":";
        document.getElementById("sessionName").innerHTML = m_pDelivery.sessionName;
        /*
        document.getElementById("courseTag").innerHTML = m_pDelivery.courseShortName + ":";
        document.getElementById("sessionName").innerHTML = m_pDelivery.sessionName;
				*/
				
        // resize elements before setting contents
        OnResize();

        // render our event tab viewer and load the first event
        if( m_pEventTabViewer )
        {
            m_pEventTabViewer.RenderContents( m_pDelivery.arrTimestamps );
        }
        
        // render the thumbnails
        if( m_pThumbnails )
        {
            // if we have thumbnails to show, render them - otherwise hide the thumbnail pane
            var thumbEl = document.getElementById("thumbnails");
            m_bHasTimestamps = (m_pDelivery.arrTimestamps.length > 0);
            if( m_bHasTimestamps )
            {
                m_pThumbnails.RenderContents( m_pDelivery.arrTimestamps, m_pDelivery.sessionPID );
                SetVisible( thumbEl, true );
            }
            else
            {
                SetVisible( thumbEl, false );
                m_bThumbnailsShowing = false;
            }
        }
        
        // render video player
        if( m_pVideo )
        {
            m_pVideo.Initialize( m_pDelivery.archivalStream, OnVideoPositionChanged, OnPlayStateChanged );
        }
           
        // render the object material region and load the first event
        if( m_pTabViewer )
        {
            // Video + eventViewer + tabViewer
            if( m_pDelivery.arrStreams.length > 0 )
            {
                m_pTabViewer.SetVisible( true );
                m_pTabViewer.SetViews( m_pDelivery.arrStreams );
            }
            // Video + eventViewer only
            else
            {
                m_pTabViewer.SetVisible( false );
                
                // change the left pane to take up the full width and the right to take up none
                document.getElementById("leftPane").style.width = "100%";
                document.getElementById("rightPane").style.display = "none";
                var eventViewerDiv = document.getElementById("eventViewerDiv");
                eventViewerDiv.style.position = "absolute";
                eventViewerDiv.style.top = "0px";
                eventViewerDiv.style.right = "0px";
                eventViewerDiv.style.paddingTop = "0px";
                m_bHasObjectRegion = false;
                
                g_dMinViewerWidth = 700;
                document.getElementById("viewer").style.minWidth = g_dMinViewerWidth + "px";
            }   
        }
        
        OnResize();
        
        // start our synchronization timer
        m_syncTimer = setInterval( Synchronize, 500 );
    }
    
    // enlarge / reduce object material tabviewer
    function ToggleObjectRegionMaximized()
    {
        // if we're currently enlarged, show thumbnails and reduce object material region
        if( m_bObjectRegionMaximized )
        {
            m_bObjectRegionMaximized = false;
            // If there are thumbnails, show them
            m_bThumbnailsShowing = m_bHasTimestamps;
            document.getElementById("thumbnailsToggle").innerHTML = "Enlarge";
        }
        // if we're currently reduced, hide thumbnails and expand object material region
        else
        {
            m_bObjectRegionMaximized = true;
            m_bThumbnailsShowing = false;
            document.getElementById("thumbnailsToggle").innerHTML = "Reduce";
        }
        m_pThumbnails.SetVisible( m_bThumbnailsShowing );
        OnResize();
    }
    
    function OnVideoPositionChanged( fVidPos, bUserInitiated )
    {        
        // if this was triggered by a user action then keep track of the last set position
        if( bUserInitiated )
        {
            m_fLastSetVideoPosition = fVidPos;
        }
        
        // determine the current item
        var iItem = GetCurrentItem( m_pDelivery.arrTimestamps, fVidPos );
        m_pThumbnails.SelectThumbByIndex( iItem );
        
        // send the current state to the tabcontrol
        var pStatus = { Time: fVidPos, 
                        PlayState: m_pVideo.GetPlayState() };
        
        m_pTabViewer.UpdateStatus( pStatus, bUserInitiated );
        m_pEventTabViewer.UpdateStatus( pStatus );
    }
    
    var m_isBuffering = false;
    
    function StartBuffering()
    {
        // when buffering, pause our object video and keep track of our state
        if( !m_isBuffering )
        {
            m_pObjectVideo.SetPlayState( "Paused" );
            m_isBuffering = true;
        }
    }
    
    function EndBuffering()
    {
        // when done start playing the object video again
        if( m_isBuffering )
        {
            m_isBuffering = false;
            m_pObjectVideo.SetPlayState( "Playing" );
        }
    }
    
    // called when our archival video changes play state
    function OnPlayStateChanged( playState )
    {
        if( playState == "Buffering" )
        {
            // if we are buffering, pause the object video
            StartBuffering();
        }
        else if( m_isBuffering )
        {          
            // we are not now buffering but were buffering, so start playing the object video again  
            EndBuffering();
        }
        else
        {
            // we were not buffering and are now not buffering, so synchronize the object video's play state
            m_pObjectVideo.SetPlayState( playState );
        }
    }
    
    this.OnObjectVideoPlayStateChanged = function( playState )
    {
        // this handles the case where object video gets initialized after archival video is playing (clicking a tab)
        // it simply synchronizes play states when the object video is not buffering
        if( playState != "Buffering" && playState != m_pVideo.GetPlayState() )
        {
            m_pObjectVideo.SetPlayState( m_pVideo.GetPlayState() );
        }
    }

    // needed to allow tabviewer to pass in our object video instance
    this.SetObjectVideoPlayer = function( player )
    {
        m_pObjectVideo = player;
    }
    
    this.SetVideoPosition = function( fVidPos )
    {  
        m_pVideo.SetVideoPosition( fVidPos );
        OnVideoPositionChanged( fVidPos, true );
    }
    
    this.ViewStream = function( streamID )
    {
        m_pTabViewer.SelectStream( streamID );
    }
    
    // callback to synchronize streams
    function Synchronize()
    {
        // take the later of the actual and lastsetvideo positions
        var fVidPos = m_pVideo.GetVideoPosition();
        var playState = m_pVideo.GetPlayState();
        
        // case where actual time is before the last set time
        if( fVidPos < m_fLastSetVideoPosition )
        { 
            fVidPos = m_fLastSetVideoPosition;
        }
        
        OnVideoPositionChanged( fVidPos, false );
    }

    
    // event handler for window resize
    function OnResize()
    {
        // determine our viewer height and set our outermost div - subtract 10 for the margin
        var viewerHeight = GetWindowHeight() - m_el.offsetTop - g_dContainerSpacing;
        viewerHeight = Math.max(g_dMinViewerHeight, viewerHeight);
        
        m_el.style.height = viewerHeight + "px";
        
        var viewerWidth = GetWindowWidth();
        viewerWidth = Math.max(g_dMinViewerWidth, viewerWidth);
        
        // set the height of our panes
        var leftPane = document.getElementById("leftPane");
        leftPane.style.height = viewerHeight + "px";
        
        var rightPane = document.getElementById("rightPane");
        rightPane.style.height = viewerHeight + "px";
        
        // call SetHeight for our events viewer and tab viewer
        // tab viewer is adjusted based on thumbnail display
        if( m_bHasObjectRegion )
        {
            var leftPaneWidth = (m_bObjectRegionMaximized ? g_dMinEventViewerWidth : (viewerWidth * g_fLeftPaneWidthPerc));

            var videoWidthAvailable = leftPaneWidth - (g_dContainerSpacing * 2);
            var videoHeightAvailable = viewerHeight - g_dMinEventViewerHeight;
            m_pVideo.OnResize(videoHeightAvailable, videoWidthAvailable);

            // Video may have shrunk to accommodate min event viewer height
            leftPaneWidth = m_pVideo.GetWidth() + (g_dContainerSpacing * 2);

            leftPane.style.width = leftPaneWidth + "px";
            rightPane.style.left = leftPaneWidth + "px";
            rightPane.style.width = viewerWidth - leftPaneWidth + "px";
            
            var eventTabViewerHeight = viewerHeight - m_pVideo.GetHeight() - 7;
            eventTabViewerHeight = Math.max(g_dMinEventViewerHeight, eventTabViewerHeight);
            
            m_pEventTabViewer.SetHeight(eventTabViewerHeight);
        }
        else
        {
            m_pVideo.OnResize(viewerHeight, viewerWidth - g_dMinEventViewerWidth - g_dContainerSpacing * 3);
            m_pEventTabViewer.SetWidth(viewerWidth - m_pVideo.Width - g_dContainerSpacing * 3);
            m_pEventTabViewer.SetHeight(m_pVideo.Height);
        }
        
        var tabViewerHeight = viewerHeight;
        if( m_bHasObjectRegion && m_bThumbnailsShowing )
        {
            tabViewerHeight -= g_dThumbnailHeight;
            
            // for some reason ie6 doesn't size widths properly in absolutely 
            // positioned elements so we need to do it manually
            if( g_bIsIE6 )
            {
                document.getElementById("thumbnails").style.width = rightPane.offsetWidth - g_dContainerSpacing + "px";
            }
        }
            
        var tabViewerWidth = rightPane.offsetWidth - g_dContainerSpacing;
        m_pTabViewer.OnResize(tabViewerHeight, tabViewerWidth);
    }
    this.Resize = OnResize;
}

// data object to store all info for a delivery
function DeliveryInfo( pXml )
{
    var m_pContentsXML = pXml;
    
    // public data fields
    this.arrTimestamps = new Array();
    this.arrStreams = new Array();
    this.arrPPTEvents = new Array();
    this.archivalStream;
    this.sessionPID;
    this.sessionName;
    this.courseShortName;
    this.courseLongName;

    // parse the xml and extract our data
    var pTimestamps = SelectSingleNode( pXml, "Timestamps" );
    var pArrTimestamps = SelectNodes( pTimestamps, "SimpleTimestamp" );
    
    // extract timestamp information and add objects to our array
    for (var i = 0; i < pArrTimestamps.length; i++)
    {
        var pEvent = ParseEvent( pArrTimestamps[i] );
        
        // skip this if we dont have time, eventtargetid, or sequencenumber
        if( (pEvent.Time != null) && (pEvent.EventTargetID != null) && (pEvent.SequenceNumber != null) )
        {
            this.arrTimestamps.push( pEvent );
            if( pEvent.EventTargetType == "PowerPoint" )
            {
                this.arrPPTEvents.push( pEvent );
            }
        }
    }
    
    // adjust the first event to start at 0
    if( this.arrTimestamps.length )
        this.arrTimestamps[0].Time = 0;
    
    // get our session and course info
    this.sessionPID = SelectSingleNodeValue( pXml, "SessionPublicID" );
    this.sessionName = SelectSingleNodeValue( pXml, "SessionName" );
    this.courseShortName = SelectSingleNodeValue( pXml, "SessionGroupShortName" );
    this.courseLongName = SelectSingleNodeValue( pXml, "SessionGroupLongName" );
    
    // add our synthetic powerpoint stream if we have powerpoint
    if( this.arrPPTEvents.length > 0 )
    {
        this.arrStreams.push( { Title:                  "PowerPoint", 
                                Type:                   "Image", 
                                EventTargetID:          this.arrPPTEvents[0].EventTargetID,
                                Timestamps:             this.arrPPTEvents, 
                                SessionPID:             this.sessionPID,
                                StreamID:               this.arrPPTEvents[0].StreamID } );
    }

    // extract stream information from xml
    var pStreams = SelectSingleNode( pXml, "Streams" );        
    var pArrStreams = SelectNodes( pStreams, "SimpleStream" );
    
    // get the segment from the archival stream
    var pXMLSegments = null;
    for (var i = 0; i < pArrStreams.length; i++)
    {
        var pStreamType = SelectSingleNodeValue( pArrStreams[i], "StreamType" );
        if( pStreamType == "Archival" )
        {
            var pSegments = SelectSingleNode( pArrStreams[i], "Segments" );
            if( pSegments )
            {
                pXMLSegments = SelectNodes( pSegments, "Segment" );
            }
        }
    }
    
    for (var i = 0; i < pArrStreams.length; i++)
    {
        var pStreamType = SelectSingleNodeValue( pArrStreams[i], "StreamType" );
        var pStreamUrl = SelectSingleNodeValue( pArrStreams[i], "StreamUrl" );
        var iStreamID = SelectSingleNodeValue( pArrStreams[i], "PublicID" );
        var fRelativeStart = parseFloat(SelectSingleNodeValue( pArrStreams[i], "RelativeStart", "float" ));
        var fRelativeEnd = parseFloat(SelectSingleNodeValue( pArrStreams[i], "RelativeEnd", "float" ));
        var strStreamTag = SelectSingleNodeValue( pArrStreams[i], "Tag");
        
        if( pStreamType == "Archival" )
        {
            this.archivalStream = { Url: pStreamUrl,
                                    Length: fRelativeEnd,
                                    Tag: strStreamTag };
        }
        else if( pStreamType == "Streaming" )
        {
            var pArrSegments = null;
            if( pSegments )
            {
                var segRelativeStart = 0;
                pArrSegments = new Array();
                for( var s = 0; s < pXMLSegments.length; s++ )
                {
                    var segStart = SelectSingleNodeValue( pXMLSegments[s], "Start", "float" );
                    var segEnd = SelectSingleNodeValue( pXMLSegments[s], "End", "float" );
                    var segLength = segEnd - segStart;
                    pArrSegments.push( { RelativeStart: segRelativeStart,
                                         Offset:        segStart - fRelativeStart } );
                    segRelativeStart += segLength;
                }
            }
            
		/* modified [20081112 by wmc], changed tab title to 'display graphics' */
            var tagLookup = { SCREEN:   "Screen Capture",
                              OBJECT:   "Screen Display" };
						/*
						var tagLookup = { SCREEN:   "Screen Capture",
                              OBJECT:   "Object Video" };
						*/
                              
            var strStreamDisplayName = "Other";
            if( tagLookup[strStreamTag] )
            {
                strStreamDisplayName = tagLookup[strStreamTag];
            }

            this.arrStreams.push( { Url:           pStreamUrl, 
                                    RelativeStart: fRelativeStart, 
                                    RelativeEnd:   fRelativeEnd,
                                    Title:         strStreamDisplayName,
                                    Type:          "Object",
                                    Segments:      pArrSegments,
                                    StreamID:      iStreamID } );
        }
    }
    
    // prune object streams that fall outside of the dv video
    var i = 0;
    while( i < this.arrStreams.length )
    {
        var stream = this.arrStreams[i];
        if( stream.RelativeEnd <= 0 || stream.RelativeStart >= this.archivalStream.Length )
        {
            this.arrStreams.splice( i, 1 ); 
        }
        else
        {
            i++;
        }
    }
}


function ThumbnailImage(pParent, url, time, targetStream)
{
    this.m_time = time;
    this.m_targetStream = targetStream;
    
    var m_imgThumb = CreateChildElement( pParent, "img", "thumbImage" );
    m_imgThumb.src = url;
    m_imgThumb.alt = FormatSeconds( time );
    
    var m_bSelected = false;
    var m_bHovering = false;
    m_imgThumb.onmouseover = function(e)
    {
        e = GetEvent( e );
        m_bHovering = true;
        SetStyle();
        return false;
    }
    
    m_imgThumb.onmouseout = function(e)
    {
        e = GetEvent( e );
        m_bHovering = false;
        SetStyle();
        return false;
    }
            
    function SetStyle()
    {        
        if( m_bSelected )
        {
            m_imgThumb.className = "thumbImageSelected";
        }
        else if( m_bHovering )
        {
            m_imgThumb.className = "thumbImageHover";
        }
        else
        {
            m_imgThumb.className = "thumbImage";
        }
    }
    
    this.SetSelected = function( bSelected )
    {
        m_bSelected = bSelected;
        SetStyle();
    }
    
    this.GetImage = function()
    {
        return m_imgThumb;
    } 
}


// the thumbnail strip control
function Thumbnails(el, pViewer)
{
    var m_el = el;
    var m_pViewer = pViewer;
    var m_pCanvas = CreateChildElement(m_el, "div", "thumbnailContainer");

    var m_pSelectedItem = null;
    var m_arrItems = new Array();
    
    this.SetVisible = function(bVisible)
    {
        SetVisible(m_el, bVisible);
    }
    
    this.RenderContents = function(arrTimestamps, strSessionPID)
    {
        m_pCanvas.style.width = arrTimestamps.length * g_dThumbnailWidth + "px";
        for (var i = 0; i < arrTimestamps.length; i++)
        {
            var pEvent = arrTimestamps[i];

						// modified [20081117 by wmc], force slide name to lowercase
		        var url = pEvent.EventTargetPublicID + "_ET/thumbs/slide" + pEvent.SequenceNumber + ".jpg";        
		        /*
            var url = pEvent.EventTargetPublicID + "_ET/thumbs/Slide" + pEvent.SequenceNumber + ".JPG";
						*/
            var imgThumb = new ThumbnailImage(m_pCanvas, url, pEvent.Time, pEvent.StreamID);
        
            function thumbnailClick( pItem )
            {
                SelectThumb( pItem );
                m_pViewer.SetVideoPosition( pItem.m_time );    
                m_pViewer.ViewStream( pItem.m_targetStream );           
            }
            
            var image = imgThumb.GetImage();
            image.onclick = Clicker(image, thumbnailClick, imgThumb); 
            m_arrItems.push(imgThumb);
        }
    }
    
    this.SelectThumbByIndex = function( iThumb )
    {
        if( iThumb < m_arrItems.length )
        {
            var thumb = m_arrItems[iThumb];
            SelectThumb( thumb );
        }
    }
    
    function SelectThumb(pItem)
    {
        if( m_pSelectedItem == pItem )
        {
            return;
        }
        if( m_pSelectedItem )
        {
            m_pSelectedItem.SetSelected( false );
        }
        m_pSelectedItem = pItem;
        m_pSelectedItem.SetSelected( true );
        ScrollIntoView( pItem ); 
    }
    
    function ScrollIntoView( pThumb )
    {
        var imageWidth = pThumb.GetImage().offsetWidth;
        var imageLeft = pThumb.GetImage().offsetLeft;
        var scrollWidth = m_el.offsetWidth;

        // center the image in the thumbnail client region        
        var scroll = Math.max(0, (scrollWidth - imageWidth) / 2);
        m_el.scrollLeft = imageLeft - scroll;
    }
    
    this.SetWidth = function( width )
    {
        m_el.style.width = width + "px";
    }
}


function ParseEvent( pXML )
{
    var pEvent = { Time:                    SelectSingleNodeValue( pXML, "Time", "float" ),
                   EventTargetID:           SelectSingleNodeValue( pXML, "ObjectIdentifier" ),
                   EventTargetPublicID:     SelectSingleNodeValue( pXML, "ObjectPublicIdentifier" ),
                   EventTargetType:         SelectSingleNodeValue( pXML, "EventTargetType" ),
                   SequenceNumber:          SelectSingleNodeValue( pXML, "ObjectSequenceNumber" ),
                   StreamID:                SelectSingleNodeValue( pXML, "ObjectStreamID" ),
                   Caption:                 SelectSingleNodeValue( pXML, "Caption" ),
                   EventID:                 SelectSingleNodeValue( pXML, "ID" ),
                   Data:                    SelectSingleNodeValue( pXML, "Data" ) };
                   
    if( !pEvent.Caption && pEvent.Data )
    {
        pEvent.Caption = pEvent.Data;
    }
    
    return pEvent;
}  


function GetCurrentItem( arrItems, time )
{
    var iItem = 0;
    while( iItem < arrItems.length && arrItems[iItem].Time <= time )
        iItem++;
    if( iItem > 0 )
        iItem--;  
        
    return iItem;
}


// event handler helpers
// creates object with callback and payload used for handling events
function Clicker(pEl, pFunc, pPayload)
{
    pEl.clicker = { fn: pFunc, data: pPayload };
    
    function callback( e )
    {
        e = GetEvent(e);
        var pSrc = GetSrcElement( e );
        while( pSrc && !pSrc.clicker )
        {
            pSrc = GetParentElement( pSrc );
        }
        
        if( pSrc )
        {
            pSrc.clicker.fn( pSrc.clicker.data );
        }
        
        return false;
    }

    return callback;
}

function GetEvent(e)
{
    if( g_bIsIE )
    {
        event.cancelBubble = true;
        return event;
    }
    else
    {
        return e;
    }
}

function GetKey(e)
{
    if( e.keyCode )
    {
        return e.keyCode;
    }
    else
    {
        return e.charCode;
    }     
}


// dom element helpers
function CreateElement( type, className )
{
    var el = document.createElement( type );
    el.className = className;
    return el;
}

function CreateChildElement( parent, type, className, id )
{
    var el = CreateElement( type, className );
    parent.appendChild( el );
    if( id )
    {   
        el.id = id;
    }
    return el;
}

function GetWindowHeight() 
{
    if (g_bIsMozilla == false)
        return document.documentElement.clientHeight;
    else
        return window.innerHeight;
}

function GetWindowWidth() 
{
    if (g_bIsMozilla == false)
        return document.documentElement.clientWidth;
    else
        return window.innerWidth;
}

function SetVisible(el, bVisible)
{
    if (el)
    {
        // actually changes layout rather than visibility
        if (bVisible)
        {
            el.style.display = "block";
        }
        else
        {
            el.style.display = "none";
        }
    }
}

function GetSrcElement(e)
{
    if( g_bIsMozilla )
    {
        return e.target;
    }
    else
    {
        return e.srcElement;
    }
}


function GetParentElement(pEl)
{
    if( g_bIsMozilla )
    {
        return pEl.parentNode;
    }
    else
    {
        return pEl.parentElement;
    }
}


// browser/silverlight detect helpers
var g_bIsIE = false;
var g_bIsIE6 = false;
var g_bIsMozilla = false;
var g_bIsSafari = false;
var g_bUsingSilverlight = false;
var g_sSilverlightVersion = "2.0";

var sAgent = navigator.userAgent.toLowerCase();
if( sAgent.indexOf( "msie" ) != -1 ) 
{
    g_bIsIE = true;
    if( sAgent.indexOf( "msie 6" ) != -1 ) g_bIsIE6 = true;
}
else if( sAgent.indexOf( "firefox" ) != -1 ) g_bIsMozilla = true;
else if( sAgent.indexOf( "mozilla" ) != -1 ) g_bIsMozilla = true;
else if( sAgent.indexOf( "safari" ) != -1 ) g_bIsSafari = true;


function FormatSeconds(dSeconds)
{
    // hooray, javascript
    function pad(int)
    {
        return int = (int < 10) ? '0' + int : int;
    }

    // surely there is some built-in way to do this
    var iHours = Math.floor(dSeconds / 3600);
    var iMinutes = Math.floor((dSeconds / 60) % 60);
    var iSeconds = Math.floor(dSeconds % 60);
    
    if( iHours > 0 )
    {
        return iHours + ":" + pad(iMinutes) + ":" + pad(iSeconds);
    }
    else
    {
        return iMinutes + ":" + pad(iSeconds);
    }
}

function FormatFileTime(lSeconds)
{
    // convert time from windows file time epoch to unix timestamp epoch.  convert from seconds to millis.
    var javascriptTime = (lSeconds - 11644473600) * 1000;

    var pTime = new Date();
    pTime.setTime(javascriptTime);
    
    var h = pTime.getHours();
    var m = pTime.getMinutes();
    var s = pTime.getSeconds();
    var period = (h < 12 ? "am" : "pm");
    
    if( h > 12 )
    {
        h -= 12;
    }
    
    function padTime(i, c)
    {
        if (i < 10)
        {
            i = c + i;
        }
        return i;
    }

    h = padTime(h, "<span style='visibility:hidden'>0</span>");
    m = padTime(m, "0");
    s = padTime(s, "0");

    return h + ":" + m + ":" + s + " " + period;
}

function CreateRequest(url, strVars, fnCallback)
{
    if(g_bIsIE)
    {
		var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
		xmlDoc.onreadystatechange = function () {
			if (xmlDoc.readyState == 4){
			    fnCallback(xmlDoc.documentElement, true);
			}
		};
	    xmlDoc.load(url);
    }
    else
    {
        var request = new XMLHttpRequest();

        request.open("GET", url, false);
        request.send(null);

        if (request.responseXML)
        {
            fnCallback(request.responseXML.documentElement, true);
        }
        else
        {
            fnCallback(null, false);
        }
    }
}


// xml parsing helper functions
function SelectNodes( pParent, sTag )
{
    if( g_bIsMozilla )
    {
        var pEvaluator = new XPathEvaluator();
        var pResult = pEvaluator.evaluate( sTag, pParent, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null );    
    	  
        var arrNodes = new Array();
        
        if (pResult != null) 
        {
            var pElement = pResult.iterateNext();
            while(pElement) 
            {
                arrNodes.push(pElement);
                pElement = pResult.iterateNext();
            }
        }
        
        return arrNodes;
    }
    else
    {
        return pParent.selectNodes( sTag );
    }
}

function SelectSingleNode( pParent, sTag )  
{
    if( g_bIsMozilla )
    {
        var pEvaluator = new XPathEvaluator();
        var pResult = pEvaluator.evaluate( sTag, pParent, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null );  

        if (pResult != null) 
        {
            return pResult.singleNodeValue;
        } 
        else 
        {
            return null;
        }        
    }
    else
    {
        if(pParent)
        {
            return pParent.selectSingleNode( sTag );
        }
    }      
}

function SelectSingleNodeValue( pParent, sTag, type )
{
    var pNode = SelectSingleNode( pParent, sTag );
    if( !pNode )
    {
        return null;
    }
    
    var value;
    if( g_bIsIE )
    {
        value = pNode.text;
    }
    else if( g_bIsMozilla )
    {
        value = pNode.textContent;
    }
    
    if( type == "float" )
    {
        return parseFloat( value );
    }
    else if (type == "int")
    {
        return parseInt( value );
    }
    else
    {
        return value;
    }
}

function getCookie(c_name)
{
    if (document.cookie.length>0)
    {
        c_start=document.cookie.indexOf(c_name + "=");
        if (c_start!=-1)
        { 
            c_start=c_start + c_name.length+1; 
            c_end=document.cookie.indexOf(";",c_start);
            if (c_end==-1) 
            {
                c_end=document.cookie.length;
            }
            return unescape(document.cookie.substring(c_start,c_end));
        } 
    }
    return "";
}

function setCookie(c_name,value,expiredays)
{
    var exdate=new Date();
    exdate.setDate(exdate.getDate()+expiredays);
    document.cookie=c_name+ "=" +escape(value)+((expiredays==null) ? "" : ";expires="+exdate.toGMTString());
}

function deleteCookie(c_name)
{
    document.cookie = c_name + '=; expires=Thu, 01-Jan-70 00:00:01 GMT;';
}
