// File: /include/js/Shared/ClientHandlers/CallListHandler/CallListHandler.js
// Desc: CallList handler
// $Revision: 58$
// $Date: 5/18/2007 12:27:36 AM$
// $Author: Donnie Tognazzini$
// $NoKeywords$

/******************************************************************************
  Dependencies:

    Shared/Extensions/Object.js
    Shared/XML/XML.js
    Shared/XML/Exceptions.js
    Shared/Calls/Calls.js
    Shared/Http/HTTP.js
    Shared/Http/URL.js
    Shared/Messages/Exceptions.js
    Shared/Messages/RequestMessages.js
    Shared/Messages/ResponseMessages.js

 ******************************************************************************/

var CLH_BLANK_STATUS_TEXT          = "";
var CLH_NO_CALLS_STATUS_TEXT       = "No calls.";
var CLH_NEW_ACTIVATION_STATUS_TEXT = "Congratulations. Your service is activated.";

var CLH_MIN_SECONDS_TO_SYNC_AFTER_DELETE = 10;

/******************************************************************************
  CLH_CallListHandler C'tor

    Parameters:
        [in] source
        [in] version
        [in] os
        [in] getAccountNumberCallBack
        [in] getUStringCallBack
        [in] getMaxCallsCallBack

 ******************************************************************************/
function CLH_CallListHandler( source,
                              version,
                              os,
                              getAccountNumberCallBack,
                              getUStringCallBack,
                              getMaxCallsCallBack )
{
    this.m_src              = source;
    this.m_ver              = version;
    this.m_os               = os;

    this.m_calls            = new CL_CallList( );
    this.m_tz               = "";
    this.m_syncLevel        = 0;
    this.m_numNewCalls      = 0;
    this.m_timeOfLastDelete = 0;
    this.m_updateDiscarded  = false;

    this.m_getAccountNumberCallBack = getAccountNumberCallBack;
    this.m_getUStringCallBack = getUStringCallBack;
    this.m_getMaxCallsCallBack = getMaxCallsCallBack;

    var self = this;
    this.m_httpClient = new HttpClient( );
    this.m_httpClient.onError        = function( errorMessage )
    {
        self.handleError( errorMessage );
    };
    this.m_httpClient.onSuccess      = function( xmlResponse )
    {
        self.handleSuccessResponse( xmlResponse );
    };
}
CLH_CallListHandler.prototype =
{
    /**************************************************************************
       Callback functions
     **************************************************************************/

    m_onCallListChanged                 : null,
    m_onCallListChangedAndActivated     : null,
    m_onNewCallsAvailable               : null,
    m_onLoginRequired                   : null,
    m_onSyncFailed                      : null,
    m_onCallListSyncCompletedNoChange   : null,
    m_onSynchronize                     : null,
    m_onProcessResponseComplete         : null,

    /**************************************************************************
       Invoke callback functions
     **************************************************************************/

    invokeOnCallListChangedCallback: function( statusMessage )
    {
        this.m_onCallListChanged && this.m_onCallListChanged( statusMessage );
    },

    invokeOnCallListChangedAndActivatedCallback: function( statusMessage )
    {
        this.m_onCallListChangedAndActivated && this.m_onCallListChangedAndActivated( statusMessage );
    },

    invokeOnNewCallsAvailableCallback: function( )
    {
        this.m_onNewCallsAvailable && this.m_onNewCallsAvailable( );
    },

    invokeOnLoginRequiredCallback: function( errorMessage )
    {
        this.m_onLoginRequired && this.m_onLoginRequired( errorMessage );
    },

    invokeOnSyncFailedCallback: function( errorMessage )
    {
        this.m_onSyncFailed && this.m_onSyncFailed( errorMessage );
    },

    invokeOnCallListSyncCompletedNoChange: function( statusMessage )
    {
        this.m_onCallListSyncCompletedNoChange && this.m_onCallListSyncCompletedNoChange( statusMessage );
    },

    invokeOnSynchronize: function( )
    {
        try
        {
            if ( this.m_onSynchronize )
            {
                this.m_onSynchronize( );
            }
        }
        catch ( e )
        {
            EX_Log( "CLH_CallListHandler::invokeOnSynchronize( )\n" + e.message );
        }
    },

    invokeOnProcessResponseComplete: function( )
    {
        try
        {
            if ( this.m_onProcessResponseComplete )
            {
                this.m_onProcessResponseComplete( );
            }
        }
        catch ( e )
        {
            EX_Log( "CLH_CallListHandler::invokeOnProcessResponseComplete( )\n" + e.message );
        }
    },


    /**************************************************************************
       CallList accessor and modifier methods
     **************************************************************************/

    getCallList: function( )
    {
        return this.m_calls;
    },

    reset: function( )
    {
        this.m_httpClient.reset( );
        this.clearCallList( );
    },

    clearCallList: function( )
    {
        this.m_syncLevel = 0;
        if ( this.m_calls.length( ) )
        {
            // only update call list if it needs it
            this.m_calls = new CL_CallList( );
            this.invokeOnCallListChangedCallback( CLH_BLANK_STATUS_TEXT );
        }
    },

    setTimeOfLastDelete: function( )
    {
        this.m_timeOfLastDelete = new Date( ).getTime( );
    },

    updateCallerName: function( callId, newCallerName )
    {
        try
        {
            var call = this.m_calls.at( callId );
            var callsUpdated = []; // keep track of calls that get updated in this function call.
            if ( call && call.getCallerName() != newCallerName )
            {
                // Update this call
                call.setCallerName(newCallerName);
                callsUpdated.push( call.getCallId() );

                // Update all calls with the same caller ID (if caller ID has 3 numeric characters)
                var callerIdOfUpdatedCall = call.getFormattedCallerId( );
                var numberCount = 0;
                for ( var i = 0; i < callerIdOfUpdatedCall.length; ++i )
                {
                    if ( ! isNaN( callerIdOfUpdatedCall.charAt( i ) ) )
                    {
                        ++numberCount;
                    }
                }

                if ( numberCount >= 3 )
                {
                    for ( var callIndex = 0; callIndex < this.m_calls.length( ); ++callIndex )
                    {
                        if ( call !== this.m_calls.at( callIndex ) &&
                             this.m_calls.at( callIndex ).getFormattedCallerId( ) == callerIdOfUpdatedCall )
                        {
                            callsUpdated.push( this.m_calls.at( callIndex ).getCallId() );
                            this.m_calls.at( callIndex ).setCallerName(newCallerName);
                        }
                    }
                }
            }

            return callsUpdated;
        }
        catch( e )
        {
            return EX_ASSERT_NO_EXCEPTIONS( e, 'CLH_CallListHandler::updateCallerName( )' );
        }
    },

    updatePhoneType: function( callId, newPhoneType )
    {
        try
        {
            var call = this.m_calls.at( callId );
            var callsUpdated = []; // keep track of calls that get updated in this function call.
            if ( call && call.getPhoneType() != newPhoneType )
            {
                // Update this call
                call.setPhoneType(newPhoneType);
                callsUpdated.push( call.getCallId() );
                // Update all calls with the same caller ID (if caller ID has 3 numeric characters)
                var callerIdOfUpdatedCall = call.getFormattedCallerId( );
                var numberCount = 0;
                for ( var i = 0; i < callerIdOfUpdatedCall.length; ++i )
                {
                    if ( ! isNaN( callerIdOfUpdatedCall.charAt( i ) ) )
                    {
                        ++numberCount;
                    }
                }

                if ( numberCount >= 3 )
                {
                    for ( var callIndex = 0; callIndex < this.m_calls.length( ); ++callIndex )
                    {
                        if ( call !== this.m_calls.at( callIndex ) &&
                             this.m_calls.at( callIndex ).getFormattedCallerId( ) == callerIdOfUpdatedCall )
                        {
                            callsUpdated.push( this.m_calls.at( callIndex ).getCallId() );
                            this.m_calls.at( callIndex ).setPhoneType(newPhoneType);
                        }
                    }
                }
            }
            return callsUpdated;
        }
        catch( e )
        {
            return EX_ASSERT_NO_EXCEPTIONS( e, 'CLH_CallListHandler::updatePhoneType( )' );
        }
    },

    updateCallReviewStatus: function( callId, newStatus )
    {
        var call = this.m_calls.at( callId );
        if ( call && call.getReviewStatus() != newStatus )
        {
            call.setReviewStatus( newStatus );
        }
    },

    handleCallSaved: function( callId )
    {
        try
        {
            this.updateCallReviewStatus( callId, CL_REVIEW_STATE_SAVED );
        }
        catch( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, 'CLH_CallListHandler::handleCallSaved( )' );
        }
    },

    handleCallDeleted: function( callId )
    {
        try
        {
            this.updateCallReviewStatus( callId, CL_REVIEW_STATE_DELETED );
        }
        catch( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, 'CLH_CallListHandler::handleCallDeleted( )' );
        }
    },

    handleCallUndeleted: function( callId )
    {
        try
        {
            this.updateCallReviewStatus( callId, CL_REVIEW_STATE_NEW );
        }
        catch( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, 'CLH_CallListHandler::handleCallUndeleted( )' );
        }
    },

    /**************************************************************************
       Synchronization methods
     **************************************************************************/
    isRequestInProgress: function( )
    {
        return this.m_httpClient.IsRequestInProgress( );
    },

    synchronize: function( )
    {
        try
        {
            var currentTime = new Date( ).getTime( );

            // Only synchronize if user has NOT deleted a call within the last CLH_MIN_SECONDS_TO_SYNC_AFTER_DELETE seconds
            if ( ( currentTime - this.m_timeOfLastDelete ) < CLH_MIN_SECONDS_TO_SYNC_AFTER_DELETE * 1000 )
            {
                return;
            }

            this.invokeOnSynchronize( );

            this.m_timeOfLastDelete = 0;

            var callUpdates = new CL_CallUpdateList( );

            for ( var index = 0; index < this.m_calls.length( ); ++index )
            {
                var call = this.m_calls.at( index );
                if ( call.isMarkedForUpdate( ) )
                {
                    callUpdates.addCallUpdate( new CL_CallUpdate(call) );
                }
            }

            var requestMessage = new MSG_GetCallsRequest( this.m_src,
                                                          this.m_ver,
                                                          this.m_os,
                                                          this.m_getAccountNumberCallBack( ),
                                                          this.m_getUStringCallBack( ),
                                                          this.m_syncLevel,
                                                          callUpdates,
                                                          this.m_getMaxCallsCallBack( ) );


            this.m_httpClient.sendRequest( HTTP_VERB_POST,
                                           VM_URL.WX1,
                                           VM_URL.WX2,
                                           "application/xml",
                                           requestMessage.asXML( ) );
        }
        catch ( e )
        {
            this.invokeOnSyncFailedCallback( "CLH_CallListHandler::synchronize( ) : " + e );
        }
    },

    handleSuccessResponse: function( xmlResponse )
    {
        try
        {
            var responseMessage = new MSG_GetCallsResponse( xmlResponse );

            if ( responseMessage.m_status == "0" )
            {
                // Success - update the call list and related attributes
                this.m_tz          = responseMessage.m_tz;
                this.m_numNewCalls = responseMessage.m_numNewCalls;

                //alert( "responseMessage.m_successCase: " + responseMessage.m_successCase );
                //alert( "this.m_calls: " + this.m_calls + "; " + (this.m_calls && this.m_calls.length ? this.m_calls.length() : '-' ) )
                if ( responseMessage.m_successCase == "0" )
                {
                    //alert( this.m_syncLevel + " -> " + responseMessage.m_newSyncLevel );
                    // accept new Sync Level
                    this.m_syncLevel   = responseMessage.m_newSyncLevel;

                    // Success -- no server call list changes
                    // updates were accepted as is and nothing else changed so let all calls know that they have been saved.
                    for (var i=0; i < this.m_calls.length(); i++) {
                        this.m_calls.at(i).updatesSaved();
                    }

                    this.invokeOnCallListSyncCompletedNoChange( ( this.m_calls.length( ) == 0 ) ? CLH_NO_CALLS_STATUS_TEXT : CLH_BLANK_STATUS_TEXT );
                    this.invokeOnProcessResponseComplete( );

                    return;
                }

                // Success and call list changed
                var oldCallList = this.m_calls;

                this.m_calls = responseMessage.m_calls;

                // Notify of call list change event
                if ( responseMessage.m_successCase == 1 )
                {
                    // Update call list and status message
                    if ( this.m_calls == null || this.m_calls.length( ) == 0 )
                    {
                        this.invokeOnCallListChangedCallback( CLH_NO_CALLS_STATUS_TEXT );
                    }
                    else
                    {
                        // Updated, but no additional calls to display
                        this.invokeOnCallListChangedCallback( CLH_BLANK_STATUS_TEXT );
                    }
                }
                else if ( responseMessage.m_successCase == 2 )
                {
                    this.invokeOnCallListChangedAndActivatedCallback( CLH_NEW_ACTIVATION_STATUS_TEXT );
                }

                // Notify of new calls
                if ( responseMessage.m_numNewCalls != 0 )
                {
                    this.invokeOnNewCallsAvailableCallback( );
                }

                // if message is playing, client may refuse updates
                if ( this.m_updateDiscarded )
                {
                    this.m_updateDiscarded = false;

                    // revert call list
                    this.m_calls = oldCallList;
                }
                else
                {
                    this.m_syncLevel = responseMessage.m_newSyncLevel;
                }
            }
            else if ( responseMessage.m_status == 11 )
            {
                // Login failure
                this.invokeOnLoginRequiredCallback( responseMessage.m_statusMsg );
            }
            else
            {
                // Failed for some other reason.
                this.invokeOnSyncFailedCallback( responseMessage.m_statusMsg );
            }
        }
        catch ( e )
        {
            // Failed to parse/process the response
            this.invokeOnSyncFailedCallback( "CLH_CallListHandler::handleSuccessResponse( ) : Failed to process response :\n" + e.message ? e.message : UTILS.dump(e));
        }

        this.invokeOnProcessResponseComplete( );
    },

    refuseUpdates: function( )
    {
        this.m_updateDiscarded = true;
    },

    handleError: function( errorMessage )
    {
        // Failed to synchronize
        this.invokeOnSyncFailedCallback( "CLH_CallListHandler::handleError( ) : Failed to submit sync request : " + errorMessage );

        this.invokeOnProcessResponseComplete( );
    }
};
