// File: /include/js/Shared/Http/HTTP.js
// Desc: HttpClient
// $Revision: 24$
// $Date: 5/14/2007 4:51:08 PM$
// $Author: Donnie Tognazzini$
// $NoKeywords$

/**************************************************************

   HttpClient class.

    This class provides support for GET and POST requests with
    failover and redirects..

**************************************************************/

var HTTP_VERB_GET = 'GET';
var HTTP_VERB_POST = 'POST';

//-- HTTP states ---
var HTTP_STATE_NONE     = 0;
var HTTP_STATE_PRIMARY  = 1;
var HTTP_STATE_FAILOVER = 2;

var HTTP_DEFAULT_REQUEST_TIMEOUT_SECONDS = 30;

function HttpClient( )
{
}

HttpClient.prototype =
{
    m_verb              : '',
    m_url               : '',
    m_failoverUrl       : '',
    m_contentType       : '',
    m_xmlhttp           : null,
    m_requestData       : '',
    m_state             : HTTP_STATE_NONE,
    m_timeoutSecs       : HTTP_DEFAULT_REQUEST_TIMEOUT_SECONDS,
    m_requestTimer      : null,
    m_yahooRequestTimer : null,

    // call backs
    onSuccess           : null,
    onError             : null,
    onRedirect          : null,
    onFailover          : null,

    /********************************************************
    PUBLIC METHODS
    ********************************************************/

    IsRequestInProgress: function( )
    {
        return ( this.m_state != HTTP_STATE_NONE );
    },

    sendRequest:function( verb, url, failoverUrl, contentType, requestData, timeoutSecs )
    {
        // precondition
        if ( this.m_state != HTTP_STATE_NONE )
        {
            throw new Error( "HttpClient - request in progress!" );
        }

        try
        {
            this.m_state = HTTP_STATE_PRIMARY;

            this.m_verb        = verb;
            this.m_url         = url;
            this.m_failoverUrl = failoverUrl;
            this.m_contentType = contentType;
            this.m_requestData = requestData;
            this.m_timeoutSecs = HTTP_DEFAULT_REQUEST_TIMEOUT_SECONDS;

            if ( ( typeof timeoutSecs != 'undefined' ) && timeoutSecs != 0 )
            {
                this.m_timeoutSecs = timeoutSecs;
            }

            this.PrepareRequest( );

            this.Send( );
        }
        catch( e )
        {
            this.reset( );

            throw new HTTP_EX_SendRequestFailed( e );
        }
    },

    reset:function( )
    {
        try
        {
            this.CancelRequest( );

            if ( this.m_xmlhttp != null )
            {
                delete this.m_xmlhttp["onreadystatechange"];

                // Need to get rid of the object in IE so that
                // failures handled by ProcessResponse( ) reset
                // the members. In the case of aborted requests
                // from timeouts, this is not needed: go figure...
                this.m_xmlhttp = null;
            }

            this.m_state = HTTP_STATE_NONE;
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "reset( )" );
        }
    },

    /********************************************************
    PRIVATE METHODS
    ********************************************************/

    PrepareRequest:function( )
    {
        if ( this.m_xmlhttp != null )
        {
            this.m_xmlhttp.abort( );
            return;
        }

        try
        {
            // Mozilla / Safari / IE7
            this.m_xmlhttp = new XMLHttpRequest( );
        }
        catch( e )
        {
            var XMLHTTP_IDS = new Array('MSXML2.XMLHTTP.5.0',
                                        'MSXML2.XMLHTTP.4.0',
                                        'MSXML2.XMLHTTP.3.0',
                                        'MSXML2.XMLHTTP',
                                        'Microsoft.XMLHTTP' );

            for( var i = 0; i < XMLHTTP_IDS.length; i++ )
            {
                try
                {
                     this.m_xmlhttp = new ActiveXObject( XMLHTTP_IDS[i] );

                     // if we get here then the creation didn't fail
                     // so return, since we're done.
                     return;
                }
                catch( e )
                {
                }
            }

            // if we get here we failed creating the object
            throw new Error('Unable to create XMLHttpRequest.');
         }
    },

    Send: function( )
    {
        try
        {
            // precondition
            if ( this.m_state == HTTP_STATE_NONE )
            {
                throw new Error( "HttpClient::Send( ) - Invalid state" );
            }

            try
            {
                var requestURL = ( this.m_state == HTTP_STATE_PRIMARY ? this.m_url : this.m_failoverUrl );

                if ( this.m_verb == HTTP_VERB_GET && this.m_requestData != '' )
                {
                    requestURL = requestURL + '?' + this.m_requestData;
                }

                // set onreadystatechange here since it will be reset after a
                //completed call in Mozilla
                delete this.m_xmlhttp[ "onreadystatechange" ];
                var self = this;
                this.m_xmlhttp.onreadystatechange = function( )
                {
                    self.ProcessResponse( );
                };

                // send async request
                this.m_xmlhttp.open( this.m_verb, requestURL, true );
                if ( this.m_verb == HTTP_VERB_GET )
                {
                    this.m_xmlhttp.send( null );
                }
                else
                {
                    this.m_xmlhttp.send( this.m_requestData );
                }

                this.StartRequestTimeout( );
            }
            catch ( e )
            {
                if ( this.m_state != HTTP_STATE_PRIMARY )
                {
                    throw e;
                }
                this.PerformFailover( );
            }
        }
        catch( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "HttpClient::Send( )" );
        }
    },

    ProcessResponse:function( )
    {
        try
        {
            if ( this.m_xmlhttp.readyState != 4 ) // only if req shows 'loaded'
            {
                return;
            }

            this.CancelRequestTimeout( );

            if ( this.m_xmlhttp.status == 200 )
            {
                if ( this.m_xmlhttp.responseXML == null )
                {
                    this.HandleAsyncError( 'XML response is empty. Response = ' + this.m_xmlhttp.responseText );
                    return;
                }

                this.HandleSuccess( this.m_xmlhttp.responseXML );

                return;
            }

            // handle redirects
            if( this.m_xmlhttp.status == 301 || this.m_xmlhttp.status == 302 || this.m_xmlhttp.status == 305 )
            {
                this.PerformRedirect( );
                return;
            }

            if ( this.m_state == HTTP_STATE_PRIMARY )
            {
                if ( this.m_xmlhttp.status == 400 )
                {
                    VM_URL.set_areRedirectsAllowed( false );
                }
                this.PerformFailover( );
                return;
            }

            this.HandleAsyncError( );
        }
        catch( e )
        {
            this.HandleAsyncError( "HttpClient::ProcessResponse( ) failed.", e );
        }
    },

    CancelRequest:function( )
    {
        try
        {
            this.CancelRequestTimeout( );

            if ( this.m_xmlhttp != null )
            {
                this.m_xmlhttp.abort( );
            }
        }
        catch( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "HttpClient::CancelRequest( )" );
        }
    },

    HandleRequestTimeout:function( )
    {
        try
        {
            this.CancelRequest( );

            switch ( this.m_state )
            {
            case HTTP_STATE_NONE:
                    return;

            case HTTP_STATE_PRIMARY:
                {
                    this.PerformFailover( );
                }
                return;

            case HTTP_STATE_FAILOVER:
                {
                    this.HandleAsyncError( null, null, 'Request timed out' );
                }
                return;
            }
        }
        catch( e )
        {
            EX_Log( "HttpClient::HandleRequestTimeout( ) failed " + e.message );
        }
    },

    PerformFailover:function( )
    {
        try
        {
            this.CancelRequest( );

            this.m_state = HTTP_STATE_FAILOVER;

            try
            {
                if ( this.onFailover != null )
                {
                    this.onFailover( this.m_failoverUrl );
                }
            }
            catch ( e )
            {
                EX_Log( "HttpClient::PerformFailover( ) caught exception from onFailover call back\n" + e.message );
            }

            this.Send( );
        }
        catch ( e )
        {
            this.HandleAsyncError( "HttpClient::PerformFailover( ) failed", new HTTP_EX_FailoverFailed( e ) );
        }
    },

    PerformRedirect:function( )
    {
        try
        {
            var redirectURL = this.m_xmlhttp.getResponseHeader( 'Location' );

            if ( this.m_state == HTTP_STATE_PRIMARY )
            {
                this.m_url = redirectURL;
            }
            else
            {
                this.m_failoverUrl = redirectURL;
            }

            this.CancelRequest( );

            try
            {
                if ( this.onRedirect != null )
                {
                    this.onRedirect( redirectURL );
                }
            }
            catch ( e )
            {
                EX_Log( "HttpClient::PerformRedirect( ) caught exception from onRedirect call back\n" + e.message );
            }

            this.Send( );
        }
        catch( e )
        {
            this.HandleAsyncError( "HttpClient::PerformRedirect( ) failed", new HTTP_EX_RedirectFailed( e ) );
        }
    },

    HandleAsyncError: function( logMessage, exception, userMessage )
    {
        try
        {
            if ( logMessage || exception )
            {
                var message = ( logMessage ? logMessage : '' );

                if ( exception )
                {
                    message += ( logMessage ? '\n' : '' ) + exception.message;
                }

                EX_Log( message );
            }

            this.reset( );

            if ( this.onError != null )
            {
                this.onError( userMessage ? userMessage : 'Unable to process HTTP request. Please try again later.' );
            }
        }
        catch ( e )
        {
            EX_Log( "HttpClient::HandleAsyncError( ) caught exception\n" + e.message );
        }
    },

    HandleSuccess: function( responseXML )
    {
        try
        {
            try
            {
                if ( this.onSuccess != null )
                {
                    this.onSuccess( responseXML );
                }
            }
            catch ( e )
            {
                EX_Log( "HttpClient::HandleSuccess( ) caught exception from onSuccess call back\n" + e.message );
            }

            this.reset( );
        }
        catch ( e )
        {
            EX_Log( "HttpClient::HandleSuccess( ) caught exception\n" + e.message );
        }
    },

    StartRequestTimeout:function( )
    {
        try
        {
            if ( this.m_timeoutSecs == 0 )
            {
                return;
            }

            var self = this;
            if ( typeof VM_WIDGET_SOURCE != 'undefined' &&
                 ( VM_WIDGET_SOURCE == "Y" || VM_WIDGET_SOURCE == "SY" || VM_WIDGET_SOURCE == "VY" ) )
            {
                this.m_yahooRequestTimer = new Timer( );
                this.m_yahooRequestTimer.interval     = this.m_timeoutSecs;
                this.m_yahooRequestTimer.onTimerFired = function( )
                {
                    self.HandleRequestTimeout( );
                }
                this.m_yahooRequestTimer.ticking      = true;
            }
            else
            {
                this.m_requestTimer = setTimeout( function( ) { self.HandleRequestTimeout(); }, this.m_timeoutSecs * 1000 );
            }
        }
        catch( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "HttpClient::StartRequestTimeout( )" );
        }
    },

    CancelRequestTimeout:function( )
    {
        try
        {
            if ( this.m_yahooRequestTimer != null )
            {
                this.m_yahooRequestTimer.ticking      = false;
                this.m_yahooRequestTimer.onTimerFired = "";
                this.m_yahooRequestTimer              = null;
            }
            if ( this.m_requestTimer != null )
            {
                clearTimeout( this.m_requestTimer );
                this.m_requestTimer = null;
            }
        }
        catch( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "HttpClient::CancelRequestTimeout( )" );
        }
    }
};

