// File: /include/js/callListView.js
// Desc: Call list div
// $Revision: 157$
// $Date: 5/23/2007 12:16:40 PM$
// $Author: Donnie Tognazzini$
// $NoKeywords$

/******************************************************************************
  Constants
 ******************************************************************************/

var VM_RESOURCE_PHONE_ICON_HOME       = "images/y_icon_home.gif";
var VM_RESOURCE_PHONE_ICON_WORK       = "images/y_icon_work.gif";
var VM_RESOURCE_PHONE_ICON_CELL       = "images/y_icon_cell.gif";
var VM_RESOURCE_PHONE_ICON_OTHER      = "images/y_icon_other.gif";

var VM_RESOURCE_PHONE_ICON_HOME_W     = "images/y_icon_home_w.gif";
var VM_RESOURCE_PHONE_ICON_WORK_W     = "images/y_icon_work_w.gif";
var VM_RESOURCE_PHONE_ICON_CELL_W     = "images/y_icon_cell_w.gif";

var VM_RESOURCE_PLAY_IMAGE            = "images/y_play.gif";
var VM_RESOURCE_EDIT_IMAGE            = "images/y_edit.gif";
var VM_RESOURCE_DELETE_IMAGE          = "images/y_delete.gif";
var VM_RESOURCE_STOP_IMAGE            = "images/y_stop.gif";

var VM_RESOURCE_OK_BUTTON_IMAGE       = "images/y_ok_button.gif";

var VM_CARRIER_ID_UNDEFINED           = -1;
var VM_CARRIER_ID_OTHER               = 0;

var VM_MESSAGE_FETCH_TIMEOUT_MS       = 30000;

/******************************************************************************
  VM_CallListView class
 ******************************************************************************/

/******************************************************************************
  VM_CallListView C'tor

    Exceptions:
        None

 ******************************************************************************/
function VM_CallListView( widgetSource, widgetVersion, widgetOSVersion )
{
    try
    {
        UTILS.inheritFromBase( this, "VM_CallListView", "#callListViewFrame" );

        this.jCallList                  = $( "#callListFrame" );
        this.sync_ms                    = this.DEFAULT_SYNC_INTERVAL_MS;
        this.m_syncTimer                = null;
        this.minimalSyncMultiplier      = 10;
        this.isApplyingUpdates          = false;
        this.flashDetectionState        = this.FLASH_NOT_CHECKED;
        this.m_callWidthManager         = null;
        this.m_gettingCallsStatusMsg    = false;
        this.m_callViewSerialNumber     = 0;
        this.m_selectedCallId = "";
        this.m_editingCallId  = "";

        this.addProperty( "height", true, 0 );

        this.m_callViewList = new this.CallViewList( this );

        this.m_callList = new CL_CallList( );

        this.m_callListHandler = new CLH_CallListHandler( widgetSource, widgetVersion, widgetOSVersion );

        this.bindToEvent( "inBackgroundChange", this.startSync );
        this.bindToEvent( "viewStateChange" );
        this.bindToEvent( PREF_KEY_AUTH_INFO + "Change" );
        this.bindToEvent( "GetAccountInfoSuccess" );
        this.bindToEvent( "CallsUpdated" );
        this.bindToEvent( "GetCallListFailed" );

        // Bind to all the flash events
        var self = this;

        function BindToFlashEvent( eventName )
        {
            self.bindToEvent( eventName,
                function( e, sCallId, message )
                {
                    try
                    {
                        var callView = self.m_callViewList.GetCallViewByCallId( sCallId );

                        if ( callView )
                        {
                            callView[ "on" + eventName ]( );
                        }
                    }
                    catch ( e )
                    {
                        EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView ~ invoking Flash callback for event='" + eventName + "'" );
                    }
                }
            );
        }

        BindToFlashEvent( "FlashError" );
        BindToFlashEvent( "FlashComplete" );
        BindToFlashEvent( "FlashSeekStart" );
        BindToFlashEvent( "FlashSeekComplete" );
    }
    catch ( e )
    {
        EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::VM_CallListView( )" );
    }
}

var VM_CallListView_prototype =
{
    DEFAULT_SYNC_INTERVAL_MS : 30 * 1000,
    FLASH_DETECTION_DELAY_MS : 3000,

    /******************************************************************************
      VM_CallListView::CallViewList

        Manages a collection of call views.

    ******************************************************************************/
    CallViewList : function( callListView )
    {
        var self = this;
        this.m_callListView = callListView;

        this.m_visibleViews = new Array( );
        this.m_callIdLookupHash = { };

        /******************************************************************************
          DeleteCallView {PRIVATE}

            Deletes a call view from the list and lookup-hash.

        ******************************************************************************/
        function DeleteCallView( index )
        {
            try
            {
                var callView = ( index < self.m_visibleViews.length ? self.m_visibleViews[ index ] : null );

                if ( ! callView )
                {
                    return;
                }

                self.m_visibleViews[ index ] = null;
                delete self.m_callIdLookupHash[ callView.GetCallIdString( ) ];

                callView.destructor( );
            }
            catch ( e )
            {
                EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::CallViewList::DeleteCallView( )" );
            }
        }

        /******************************************************************************
          RemoveViewsForNonExistentCalls {PRIVATE}

            Iterates through the call views and performs the following actions:
                1. For any call view with a call not present in the new call list:
                    -> Delete associated DOM node from DOM tree. (accomplished via VM_CallView::destructor( ))
                    -> Delete VM_CallView and remove from CallListView collection.
                2. For any call view with a call present in the server list:
                    -> Update VM_CallView with attributes from call in server list.
                3. For any call view with a call present in both lists but in different order:
                    -> Delete VM_CallView at int-index from CallListView collection.

        ******************************************************************************/
        function RemoveViewsForNonExistentCalls( callList, maxCallsToShow )
        {
            try
            {
                for ( var i = 0; i < self.m_visibleViews.length; i++ )
                {
                    var callView = self.m_visibleViews[ i ];

                    // Lookup the call corresponding to the call view in the list from the server.
                    var callInNewCallListAtStringIndex = callList.at( callView.GetCallIdString( ) );

                    // The call associated with the call view does not exist in the list from the server.
                    if ( callInNewCallListAtStringIndex == null )
                    {
                        // delete call view from call list view collection
                        DeleteCallView( i );
                        continue;
                    }

                    // If we've reached the max calls to show skip the updates below.
                    if ( i >= maxCallsToShow )
                    {
                        // delete call view from call list view collection
                        DeleteCallView( i );
                        continue;
                    }

                    // Check to see if the call order in the call list matches the order
                    // in the call list view.
                    var callInNewCallListAtIntIndex = ( i < callList.length( ) ? callList.at( i ) : null );

                    if ( callInNewCallListAtIntIndex == null ||
                         ! callInNewCallListAtIntIndex.m_cid.equals( callInNewCallListAtStringIndex.m_cid ) )
                    {
                        // The order in the new call list differs from that in
                        // the call list view.
                        DeleteCallView( i );
                        continue;
                    }

                    // Update call in call list view
                    callView.updateFrom( callInNewCallListAtStringIndex );
                }
            }
            catch ( e )
            {
                EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::CallViewList::RemoveViewsForNonExistentCalls( )" );
            }
        }

        function RemoveTrailingNullEntries( )
        {
            try
            {
                // remove all the null entries off the back of the array.
                for ( ; self.m_visibleViews[ self.m_visibleViews.length - 1 ] === null; self.m_visibleViews.pop( ) )
                {
                }
            }
            catch ( e )
            {
                EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::CallViewList::RemoveTrailingNullEntries( )" );
            }
        }


        /******************************************************************************
          GetCallViewByCallId

            Query by call-id

        ******************************************************************************/
        this.GetCallViewByCallId = function( callId )
        {
            return this.m_callIdLookupHash[ callId ];
        };

        /******************************************************************************
          Clear

            Clears the collection

        ******************************************************************************/
        this.Clear = function( )
        {
            try
            {
                // Clean up call views
                var viewCount = this.m_visibleViews.length;

                while ( --viewCount >= 0 )
                {
                    DeleteCallView( viewCount );
                }

                RemoveTrailingNullEntries( );
            }
            catch ( e )
            {
                EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::CallViewList::Clear( )" );
            }
        };

        /******************************************************************************
          StripeRows

            Stripes the rows

        ******************************************************************************/
        this.StripeRows = function( )
        {
            try
            {
                if ( this.m_visibleViews.length == 0 )
                {
                    return;
                }

                this.m_visibleViews[ 0 ].jObject.removeClass( "stripe" );

                var stripeRow = this.m_visibleViews[ 0 ].m_call.getReviewStatus( ) != CL_REVIEW_STATE_DELETED;

                for ( var i = 1; i < this.m_visibleViews.length; i++ )
                {
                    this.m_visibleViews[ i ].jObject.removeClass( "stripe" );

                    if ( stripeRow )
                    {
                        this.m_visibleViews[ i ].jObject.addClass( "stripe" );
                    }

                    if ( this.m_visibleViews[ i ].m_call.getReviewStatus( ) != CL_REVIEW_STATE_DELETED )
                    {
                        stripeRow = ! stripeRow;
                    }
                }
            }
            catch ( e )
            {
                EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::CallViewList::StripeRows( )" );
            }
        };

        /******************************************************************************
          Refresh

            Refreshes call view list from call list.

        ******************************************************************************/
        this.Refresh = function( callList )
        {
            try
            {
                var maxCallsToShow = PREF_GetPreferences( ).CallListLength.get( );

                RemoveViewsForNonExistentCalls( callList, maxCallsToShow );

                var elemAfter = document.getElementById( "divBaseAlign" );

                // Create/re-use call views for displayed calls.
                for ( var i = 0; i < callList.length( ) && i < maxCallsToShow; ++i )
                {
                    var callView = null;

                    // If there is no entry for this call...
                    if ( i >= this.m_visibleViews.length || this.m_visibleViews[ i ] === null )
                    {
                        // Create a call view for the call
                        callView = new VM_CallView( callList.at( i ), this.m_callListView, elemAfter );

                        // Add to hash call list view.  Create keys for both name and index of visible calls.
                        this.m_callIdLookupHash[ callView.GetCallIdString( ) ] = callView;
                        this.m_visibleViews[ i ] = callView;
                    }
                    else
                    {
                        // assertion!
                        if ( ! this.m_visibleViews[ i ].m_call.m_cid.equals( callList.at( i ).m_cid ) )
                        {
                            throw new Error( "Visible view inconsistent with call from server.\n" +
                                             "Index: " + i + "\n" +
                                             "Visible view callId: "     + this.m_visibleViews[ i ].m_call.m_cid + "\n" +
                                             "Call from server callId: " + callList.at( i ).m_cid );
                        }

                        callView = this.m_visibleViews[ i ];
                    }

                    // Set the current DOM view element and increment the visibleCalls counter.
                    elemAfter = callView.jObject[ 0 ];
                }

                RemoveTrailingNullEntries( );

                UTILS.adjustHeight( );

                this.StripeRows( );
            }
            catch ( e )
            {
                EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::CallViewList::Refresh( )" );
            }
        };
    },

    GetCallViewSerialNumber : function( )
    {
        var nextSerialNumber = this.m_callViewSerialNumber + 1;

        if ( nextSerialNumber == Number.MAX_VALUE ||
             nextSerialNumber == Number.MIN_VALUE ||
             nextSerialNumber == Number.NEGATIVE_INFINITY ||
             nextSerialNumber == Number.POSITIVE_INFINITY ||
             isNaN( nextSerialNumber ) )
        {
            nextSerialNumber = 0;
        }

        this.m_callViewSerialNumber = nextSerialNumber;

        return nextSerialNumber;
    },

    handleLogout : function( )
    {
        try
        {
            // CLear the call list objects
            this.m_callList = new CL_CallList( );
            this.m_callListHandler.clearCallList( );

            this.sync_ms = this.DEFAULT_SYNC_INTERVAL_MS;

            // Clear the sync timer
            if ( this.m_syncTimer )
            {
                clearTimeout( this.m_syncTimer );
                this.m_syncTimer = null;
            }

            // Clean up call views
            this.m_callViewList.Clear( );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::handleLogout( )" );
        }
    },

    onGetAccountInfoSuccess: function( e, responseMessage )
    {
        try
        {
            if ( responseMessage.m_secondsBetweenSync && responseMessage.m_secondsBetweenSync * 1000 != this.sync_ms )
            {
                this.sync_ms = responseMessage.m_secondsBetweenSync * 1000;

                this.startSync( );
            }
            CW_GetMessageArea( ).display( 'Getting calls...', CW_GetMessageArea( ).MESSAGE_TYPE_STATUS );
            this.m_gettingCallsStatusMsg = true;
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::onGetAccountInfoSuccess( )" );
        }
    },

    onAuthInfoChange: function( e, oldValue, newValue )
    {
        try
        {
            this.ResetSelectedCallId( );
            this.ResetEditingCallId( );

            this.m_callListHandler.reset( );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::onAuthInfoChange( )" );
        }
    },

    OnCallSelected : function( selectedCallId )
    {
        try
        {
            if ( this.m_selectedCallId == selectedCallId )
            {
                return;
            }

            if ( this.m_selectedCallId != "" )
            {
                var callView = this.m_callViewList.GetCallViewByCallId( this.m_selectedCallId );

                if ( callView != null )
                {
                    callView.UpdateSelectedState( false );
                }
            }

            this.m_selectedCallId = selectedCallId;

            UTILS.adjustHeight( );

            this.ResetEditingCallId( );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::OnCallSelected( )" );
        }
    },

    OnCallUnselected : function( callId )
    {
        try
        {
            if ( this.m_selectedCallId == callId )
            {
                this.m_selectedCallId = "";
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::OnCallUnselected( )" );
        }
    },

    ResetSelectedCallId : function( )
    {
        try
        {
            if ( this.m_selectedCallId == "" )
            {
                return;
            }

            var callView = this.m_callViewList.GetCallViewByCallId( this.m_selectedCallId );

            if ( callView == null )
            {
                return;
            }

            callView.UpdateSelectedState( false );

            UTILS.adjustHeight( );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::ResetSelectedCallId( )" );
        }
    },

    OnCallEditingBegin : function( newEditingCallId )
    {
        try
        {
            if ( this.m_editingCallId == newEditingCallId )
            {
                return;
            }

            if ( this.m_editingCallId != "" )
            {
                var callView = this.m_callViewList.GetCallViewByCallId( this.m_editingCallId );

                if ( callView != null )
                {
                    callView.UpdateEditingState( false );
                }
            }

            this.m_editingCallId = newEditingCallId;
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::OnCallEditingBegin( )" );
        }
    },

    OnCallEditingEnd : function( callId )
    {
        try
        {
            if ( this.m_editingCallId == callId )
            {
                this.m_editingCallId = "";
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::OnCallEditingEnd( )" );
        }
    },

    ResetEditingCallId : function( )
    {
        try
        {
            if ( this.m_editingCallId == "" )
            {
                return;
            }

            var callView = this.m_callViewList.GetCallViewByCallId( this.m_editingCallId );

            if ( callView == null )
            {
                return;
            }

            callView.UpdateEditingState( false );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::ResetEditingCallId( )" );
        }
    },

    onheightChange: function( e, oldHeight, newHeight )
    {
        try
        {
            this.jObject.css("height", newHeight + "px" );
            UTILS.adjustHeight( );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::onheightChange( )" );
        }
    },

    isBusy: function( hasUpdatedCallList )
    {
        try
        {
            if ( this.isApplyingUpdates )
            {
                return true;
            }

            if ( ! hasUpdatedCallList )
            {
                return false;
            }

            var newCallList     = this.m_callListHandler.getCallList( ).m_callList;

            // Since we update in place, the only reasons we'll refuse the new call list are

            //  a) if the the editing or playing call has been deleted
            if ( this.m_selectedCallId != "" && this.m_callViewList.GetCallViewByCallId( this.m_selectedCallId ).IsPlayerInUse( ) &&
                 ( ! newCallList.hasOwnProperty( this.m_selectedCallId ) ||
                   newCallList[ this.m_selectedCallId ].m_revStatus == CL_REVIEW_STATE_DELETED ) )
            {
                return true;
            }

            //  b) if the callerName or phoneType is updated for the editing call
            return ( this.m_editingCallId != ""
                     &&
                     ( ! newCallList.hasOwnProperty( this.m_editingCallId )                              ||
                       newCallList[ this.m_editingCallId ].m_revStatus == CL_REVIEW_STATE_DELETED        ||
                       newCallList[ this.m_editingCallId ].m_name != this.m_callViewList.GetCallViewByCallId( this.m_editingCallId ).m_call.m_name ||
                       newCallList[ this.m_editingCallId ].m_phoneType != this.m_callViewList.GetCallViewByCallId( this.m_editingCallId ).m_call.m_phoneType ) );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::isBusy( )" );
        }
    },

    onCallsUpdated: function( e, statusMessage )
    {
        try
        {
            if ( this.m_gettingCallsStatusMsg == true )
            {
                CW_GetMessageArea( ).Clear( );
                this.m_gettingCallsStatusMsg = false;
            }

            // Don't accept new updates if this view is already processing updates.
            if ( this.isBusy( true ) )
            {
                this.m_callListHandler.refuseUpdates( );
                return;
            }

            // Set flag indicating we're applying updates.
            this.isApplyingUpdates = true;

            // Reset the internal call list from that which is retrieved by the handler from the server.
            this.m_callList = this.m_callListHandler.getCallList( );

            // Refresh call view list
            this.m_callViewList.Refresh( this.m_callList );

            this.isApplyingUpdates = false;

            if ( statusMessage.length > 0 )
            {
                CW_GetMessageArea( ).display( statusMessage, CW_GetMessageArea( ).MESSAGE_TYPE_STATUS );
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::onCallsUpdated( )" );
        }
    },

    onviewStateChange: function( e, oldState, newState )
    {
        try
        {
            if ( oldState == controller.VIEW_STATE_SHOW_CALL_LIST )
            {
                if ( this.m_syncTimer )
                {
                    clearInterval( this.m_syncTimer );
                    this.m_syncTimer = null;
                }
            }
            else if ( newState == controller.VIEW_STATE_SHOW_CALL_LIST )
            {
                if ( this.m_callWidthManager == null )
                {
                    this.m_callWidthManager = new VM_callWidthManager( this );
                }

                this.startSync( );
                UTILS.adjustHeight( );
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::onviewStateChange( )" );
        }
    },

    startSync: function( )
    {
        try
        {
            if ( controller.viewState.get( ) != controller.VIEW_STATE_SHOW_CALL_LIST )
                return;

            if ( this.m_syncTimer )
                clearInterval( this.m_syncTimer );

            if ( !this.isBusy( false ) && !controller.inBackground.get( ) )
                this.m_callListHandler.synchronize( );

            var sync_ms = controller.inBackground.get( ) ? this.sync_ms * this.minimalSyncMultiplier : this.sync_ms;

            var self = this;
            this.m_syncTimer = setInterval( function( )
            {
                try
                {
                    if ( ! self.isBusy( false ) && ! self.m_callListHandler.isRequestInProgress( ) )
                    {
                        self.m_callListHandler.synchronize( );
                    }
                }
                catch ( e )
                {
                    EX_Log( "VM_CallListView::startSync( ) - Synchronization timeout." );
                }
            }, sync_ms );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::startSync( )" );
        }
    },

    onGetCallListFailed: function( e, responseMessage )
    {
        if ( responseMessage.m_status == MSG_ResponseStatus.UNEXPECTED_ERROR )
            EX_Log( "VM_CallListView::OnSyncFailed : " + responseMessage.m_statusMsg );
    },

    updateCallerName: function( sCallId, newCallerName )
    {
        try
        {
            var updatedCallIds = this.m_callListHandler.updateCallerName( sCallId, newCallerName );
            for ( var i = 0; i < updatedCallIds.length; ++i )
            {

                var callView = this.m_callViewList.GetCallViewByCallId( updatedCallIds[ i ].toString( ) );

                if ( callView )
                {
                    // Only update the call if it exists in the view
                    callView.updateCallerName( newCallerName );
                }
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListview::updateCallerName" );
        }
    },

    updatePhoneType: function( sCallId, newPhoneType )
    {
        try
        {
            var updatedCallIds = this.m_callListHandler.updatePhoneType( sCallId, newPhoneType );

            for ( var i = 0; i < updatedCallIds.length; ++i )
            {
                var callView = this.m_callViewList.GetCallViewByCallId( updatedCallIds[ i ].toString( ) );

                if ( callView )
                {
                    // Only update the call if it exists in the view
                    callView.updatePhoneType( newPhoneType );
                }
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::updatePhoneType" );
        }
    },

    OnUndeleteClicked: function( callId )
    {
        try
        {
            var callView = this.m_callViewList.GetCallViewByCallId( callId );

            if ( callView )
            {
                callView.UpdateReviewStatus( CL_REVIEW_STATE_NEW );
            }
            else
            {
                CW_GetMessageArea( ).Clear( );
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::OnUndeleteClicked" );
        }
    },

    OnClearMessage: function( callId )
    {
        try
        {
            var callView = this.m_callViewList.GetCallViewByCallId( callId );

            if ( callView )
            {
                callView.onClearMessage( );
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::OnClearMessage" );
        }
    },

    UpdateCallReviewStatus: function( callView, oldReviewStatus, newReviewStatus )
    {
        try
        {
            // Don't accept new updates if this view is already processing updates.
            if ( this.isBusy( false ) )
            {
                return;
            }

            // Set flag indicating we're applying updates.
            this.isApplyingUpdates = true;

            this.m_callListHandler.updateCallReviewStatus( callView.GetCallIdString( ), newReviewStatus );

            callView.ApplyNewReviewStatus( oldReviewStatus, newReviewStatus );

            this.m_callViewList.StripeRows( );

            UTILS.adjustHeight( );

            this.isApplyingUpdates = false;
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::UpdateCallReviewStatus" );
        }
    },

    FLASH_OK                    : 0,
    FLASH_OK_BUT_NOT_REFRESHED  : 1,
    FLASH_OUT_OF_DATE           : 2,
    FLASH_NOT_INSTALLED         : 3,
    FLASH_NOT_CHECKED           : 4,

    CompleteSetup : function( )
    {
        try
        {
            var flashMsg = $( "#flashMessageArea" );

            this.flashDetectionState = this.detectFlash( );

            switch ( this.flashDetectionState )
            {
            case this.FLASH_OK:
                flashMsg.html = "";
                break;

            case this.FLASH_OK_BUT_NOT_REFRESHED:
                if ( controller.source.substr( 1 ) == "G" )
                {
                    flashMsg.html( "Now that the Flash&reg; Player is installed, you need to refresh the " +
                                   "page to be able to listen to all your calls." );
                }
                else
                {
                    flashMsg.html( "Now that Flash&reg; Player is installed, you need to restart the " +
                                   "dashboard to be able to listen to all your calls." );
                }

                break;

            case this.FLASH_OUT_OF_DATE:
                flashMsg.html( "To listen to your calls, you need a newer Flash&reg; Player.<br/>" +
                               "<a href=\"http://www.adobe.com/go/getflashplayer\" target=\"_blank\">" +
                               "Click here</a> to get the latest version." );
                break;

            case this.FLASH_NOT_INSTALLED:
                flashMsg.html( "To listen to your calls, you need to install the Adobe&reg; Flash&reg; " +
                               "Player.<br/><a href=\"http://www.adobe.com/go/getflashplayer\" target=\"_blank\">" +
                               "Click here</a> to get the latest version." );
                break;

            case this.FLASH_NOT_CHECKED:
                flashMsg.show( );

                break;
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::CompleteSetup( )" );
        }
    },

    detectFlash: function( )
    {
        try
        {
            if ( typeof ActiveXObject != "undefined" )
            {
                var axo;
                try
                {
                    axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");
                    axo.AllowScriptAccess = "always";
                    return ( this.flashDetectionState == this.FLASH_NOT_CHECKED ) ?
                             this.FLASH_OK : this.FLASH_OK_BUT_NOT_REFRESHED;
                }
                catch( e )
                {
                }
                try
                {
                    axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
                    return this.FLASH_OUT_OF_DATE;
                }
                catch( e )
                {
                }
                return this.FLASH_NOT_INSTALLED;
            }

            if ( navigator.plugins )
            {
                if ( !navigator.plugins.length )
                    return this.FLASH_NOT_INSTALLED;

                var flashPlugin = navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"];
                if (flashPlugin)
                {
                    var descArray = flashPlugin.description.split(" ")
                      , tempArrayMajor = descArray[2].split(".")
                      , versionMajor = tempArrayMajor[0]
                      , versionMinor = tempArrayMajor[1]
                      , tempArrayMinor = descArray[3] ? descArray[3].split("r") : descArray[4].split("r")
                      , versionRevision = tempArrayMinor[1] > 0 ? tempArrayMinor[1] : 0;

                    if ( versionMajor > 6 || ( versionMajor == 6 && ( versionMinor > 0 || versionRevision >= 47 ) ) )
                        return ( this.flashDetectionState == this.FLASH_NOT_CHECKED ) ?
                                 this.FLASH_OK : this.FLASH_OK_BUT_NOT_REFRESHED;
                    else
                        return this.FLASH_OUT_OF_DATE;
                }
                return this.FLASH_NOT_INSTALLED;
            }

            // undetectable!
            return this.FLASH_NOT_INSTALLED;
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "VM_CallListView::detectFlash( )" );
        }
    }
};
