/******************************************************************************

    Defines the core implementation of the mobile phone number control.

    Classes:
        SMS_MobileNumberControlPersistentState
        SMS_MobileNumberControlBase

    $Revision: 7$
    $Date: 8/29/2007 4:59:37 PM$
    $NoKeywords$

******************************************************************************/


/******************************************************************************
  SMS_MobileNumberControlPersistentState c'tor

    Encapsulates persistent state of the mobile phone number control. Provides
    serialization methods allowing the state to be persisted.

    Parameters:
        [in]    countryCode
        [in]    countryName
        [in]    numberFieldText

******************************************************************************/
function SMS_MobileNumberControlPersistentState( countryCode, countryName, numberFieldText )
{
    this.m_countryCode      = countryCode;
    this.m_countryName      = countryName;
    this.m_numberFieldData  = numberFieldText;
}
SMS_MobileNumberControlPersistentState.prototype =
{

    /******************************************************************************
    PUBLIC METHODS
    ******************************************************************************/

    /******************************************************************************
      IsEmpty

        Returns:
            True  - object is empty.
            False - object is NOT empty.

    ******************************************************************************/
    IsEmpty : function( )
    {
        return this.m_countryCode === CMD_COUNTRY_CODE_CONTROL_ID &&
               this.m_countryName === '' &&
               this.m_numberFieldData === '';
    },

    /******************************************************************************
      AsString

        Returns:
            String representation of the serialized object.

    ******************************************************************************/
    AsString : function( )
    {
        return "{  m_countryCode : '" + this.m_countryCode +
               "', m_countryName : '" + this.m_countryName +
               "', m_numberFieldData : '" + this.m_numberFieldData + "' }";
    },

    /******************************************************************************
      GetNumberString

        Returns:
            String representation of the number stored in the object.

    ******************************************************************************/
    GetNumberString : function( )
    {
        try
        {
            if ( this.m_countryCode == CMD_COUNTRY_CODE_CONTROL_ID )
            {
                return this.m_numberFieldData;
            }

            var mobileNumber = new PHONE_PhoneNumber( this.m_numberFieldData );

            if ( ! mobileNumber.HasCCDelimiter( ) )
            {
                mobileNumber.OverrideCountryCodeInferrence( this.m_countryCode );
            }

            return mobileNumber.GetFullyQualifiedString( );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "SMS_MobileNumberControlBase::GetNumberString( )" );
        }
    },

    /******************************************************************************
      CreateFromString {STATIC}

        Factory method that creates SMS_MobileNumberControlPersistentState objects
        from strings containing serialized objects.

        Parameters:
            [in] data - string containing seralized object.

        Returns:
            Unserialized SMS_MobileNumberControlPersistentState object.

    ******************************************************************************/
    CreateFromString : function( data )
    {
        try
        {
            var obj = new SMS_MobileNumberControlPersistentState( CMD_COUNTRY_CODE_CONTROL_ID, '', '' );

            if ( data && data.length != 0 )
            {
                var unserializedObj = eval( '( ' + data + ' ) ' );

                EXT_extend( obj, unserializedObj );
            }

            return obj;
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "SMS_MobileNumberControlPersistentState::CreateFromString( )" );
        }
    }
};


/******************************************************************************
  SMS_MobileNumberControlBase c'tor

    Mobile number control base class. Derived classes must call
    InitializeMobileNumberControl( ) before calling any methods on the base
    class.

    USE:
        Derive a class from SMS_MobileNumberControlBase.

        The following methods must be implemented by the derived class:
            SetMobileNumberData( mobileNumber )
            GetMobileNumberData( )

        The constructor must do the following:
            1. Set the lose-focus handler for the mobile number input field to
               OnNumberFieldLoseFocus( ).

            2. Set the fain-focus handler for the mobile number input field to
               OnNumberFieldGainFocus( ).

            3. Set the change handler for the mobile number input field to
               OnNumberFieldChange( ).

            4. Call InitializeMobileNumberControl( ).

    Signals:
        OnChange                - Raised whenever the mobile number changes.
        NumberFieldGainFocus    - Raised on number field gain-focus events.
        NumberFieldLoseFocus    - Raised on number field lose-focus events.
        NumberFieldChange       - Raised on number field change events.
        CountryPullDownChange   - Raised on CC pull-down change events.

    Parameters:
        [in/out] comboBox - Object must support the following methods:

                    GetMenuItem( index )
                    GetLength( )
                    GetSelectedMenuItem( )
                    AddMenuItem( menuItem )
                    SelectIndex( index )
                    GetOnGainFocusSignal( )
                    GetOnLoseFocusSignal( )
                    GetOnChangeSignal( )

                 Consumers can use CB_ComboBox which implements all of the
                 above methods.

        [in]     createMenuItemFactory - factory method that returns menu items
                                         containing the following methods:

                    SetEnabled( value )

                 Consumers can use the CB_ComboBoxMenuItem which implements
                 all of the above methods.

******************************************************************************/
function SMS_MobileNumberControlBase( comboBox, createMenuItemFactory )
{
    try
    {
        this.m_mobileNumberOnFocus = '';
        this.m_inferredCountryCode = '';
        this.m_lastCountryCodeSelection = CMD_COUNTRY_CODE_CONTROL_ID;

        this.m_comboBox = comboBox;

        this.m_slotManager    = new SIGSLOT_SlotManager( );
        this.m_onChangeSignal = new SIGSLOT_Signal( );

        this.m_numberFieldGainFocusSignal  = new SIGSLOT_Signal( );
        this.m_numberFieldLoseFocusSignal  = new SIGSLOT_Signal( );
        this.m_numberFieldChangeSignal     = new SIGSLOT_Signal( );
        this.m_countryPullDownChangeSignal = new SIGSLOT_Signal( );

        this.m_handlingCountryChanged = false;

        function CountryCodeMenuItem( countryData )
        {
            this.m_countryData = countryData;

            var displayName = this.m_countryData.m_name;

            if ( this.m_countryData.m_countryCode != CMD_COUNTRY_CODE_CONTROL_ID )
            {
                displayName += '   +' + this.m_countryData.m_countryCode;
            }

            EXT_extend( this, createMenuItemFactory( displayName ) );

            this.SetEnabled( this.m_countryData.m_countryCode != CMD_COUNTRY_CODE_CONTROL_ID );
        }

        // populate the popup
        for ( var i = 0; i < CMD_COUNTRY_CODES.length; ++i )
        {
            this.m_comboBox.AddMenuItem( new CountryCodeMenuItem( CMD_COUNTRY_CODES[ i ] ) );
        }
    }
    catch ( e )
    {
        EX_ASSERT_NO_EXCEPTIONS( e, "SMS_MobileNumberControlBase::SMS_MobileNumberControlBase( )" );
    }
}
SMS_MobileNumberControlBase.prototype =
{

    /******************************************************************************
    PUBLIC METHODS
    ******************************************************************************/

    GetSlotManager : function( )
    {
        return this.m_slotManager;
    },

    GetComboBox : function( )
    {
        return this.m_comboBox;
    },

    GetOnChangeSignal : function( )
    {
        return this.m_onChangeSignal;
    },

    GetNumberFieldGainFocusSignal : function( )
    {
        return this.m_numberFieldGainFocusSignal;
    },

    GetNumberFieldLoseFocusSignal : function( )
    {
        return this.m_numberFieldLoseFocusSignal;
    },

    GetNumberFieldChangeSignal : function( )
    {
        return this.m_numberFieldChangeSignal;
    },

    GetCountryPullDownChangeSignal : function( )
    {
        return this.m_countryPullDownChangeSignal;
    },

    /******************************************************************************
      SetPhoneNumber

        Sets the control to the supplied phone number.

        Parameters:
            [in] phoneNumber

    ******************************************************************************/
    SetPhoneNumber : function( phoneNumber )
    {
        try
        {
            var parsedPhoneNumber = new PHONE_PhoneNumber( phoneNumber );

            this.UpdateCountryCode( parsedPhoneNumber.GetCountryCode( ) );

            this.SetMobileNumberDataIfNotEqual( parsedPhoneNumber.GetNationalNumberWithFormatting( ) );

            this.m_onChangeSignal.Emit( this );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, 'SMS_MobileNumberControlBase::SetPhoneNumber( )' );
        }
    },

    /******************************************************************************
      SetPersistentState

        Updates the control per the supplied state.

        Parameters:
            [in] state

    ******************************************************************************/
    SetPersistentState : function( state )
    {
        try
        {
            this.m_mobileNumberOnFocus = '';
            this.m_inferredCountryCode = '';
            this.m_lastCountryCodeSelection = CMD_COUNTRY_CODE_CONTROL_ID;

            this.SetPhoneNumber( state.m_numberFieldData );

            if ( state.m_countryCode !== CMD_COUNTRY_CODE_CONTROL_ID )
            {
                var comparisonFunctor = function( item )
                {
                    return ( item.m_countryData.m_name        == state.m_countryName &&
                             item.m_countryData.m_countryCode == state.m_countryCode );
                };

                this.m_comboBox.Select( comparisonFunctor );

                this.m_countryPullDownChangeSignal.Emit( this.m_comboBox );
            }

            this.m_onChangeSignal.Emit( this );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, 'SMS_MobileNumberControlBase::SetPersistentState( )' );
        }
    },

    /******************************************************************************
      GetPersistentState

        Returns:
            A snapshot of the controls current state.

    ******************************************************************************/
    GetPersistentState : function( )
    {
        try
        {
            return new SMS_MobileNumberControlPersistentState( this.GetCountryCode( ),
                                                               this.m_comboBox.GetSelectedMenuItem( ).m_countryData.m_name,
                                                               this.GetMobileNumberData( ) );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, 'SMS_MobileNumberControlBase::GetPersistentState( )' );
        }
    },

    /******************************************************************************
      GetCountryCode

        Returns:
            The current country code selection

    ******************************************************************************/
    GetCountryCode : function( )
    {
        try
        {
            return this.m_comboBox.GetSelectedMenuItem( ).m_countryData.m_countryCode;
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, 'SMS_MobileNumberControlBase::GetCountryCode( )' );
        }
    },

    /******************************************************************************
      DumpState

        Debugging helper method that dumps the control's internal state to the
        event log.

    ******************************************************************************/
    DumpState : function( )
    {
        var dump = '' +
            'm_mobileNumberOnFocus : "' + this.m_mobileNumberOnFocus + '"\n' +
            'm_inferredCountryCode : "' + this.m_inferredCountryCode + '"\n' +
            'm_lastCountryCodeSelection : "' + this.m_lastCountryCodeSelection + '"\n' +
            'Text field contents: "' + this.GetMobileNumberData( ) + '"\n' +
            'CC selection: ' + this.m_comboBox.GetSelectedIndex( );

        EX_Log( dump );
    },

    /******************************************************************************
      OnNotify

        Handler for synchronizing state with another SMS_MobileNumberControlBase
        object.

        Parameters:
            [in] notifier - SMS_MobileNumberControlBase object to synchronize to.

    ******************************************************************************/
    OnNotify : function( notifier )
    {
        try
        {
            // update internal state
            this.m_mobileNumberOnFocus      = notifier.m_mobileNumberOnFocus;
            this.m_inferredCountryCode      = notifier.m_inferredCountryCode;
            this.m_lastCountryCodeSelection = notifier.m_lastCountryCodeSelection;

            // update view
            this.SetMobileNumberDataIfNotEqual( notifier.GetMobileNumberData( ) );
            this.m_comboBox.SelectIndex( notifier.GetComboBox( ).GetSelectedIndex( ) );

            this.m_countryPullDownChangeSignal.Emit( this.m_comboBox );
        }
        catch ( e )
        {
            EX_Log( 'SMS_MobileNumberControlBase::OnNotify( ) : ' + e.message );
        }
    },

    /******************************************************************************
      OnFormSubmit

        Handler for completing operations necessary on form submission.

    ******************************************************************************/
    OnFormSubmit : function( )
    {
        try
        {
            this.OnNumberFieldLoseFocus( );

            this.FullyQualifyNumber( );
        }
        catch ( e )
        {
            EX_Log( 'SMS_MobileNumberControlBase::OnFormSubmit( ) : ' + e.message );
        }
    },

    INVALID_COUNTRY_CODE : "Please select a country.",
    INVALID_NANP_NUMBER  : "Please enter your 10-digit cell number.",

    /******************************************************************************
      GetValidatedNumber

        Attempts to validate and return the current number.

        Returns:
            If validation succeeds the following object is returned:

            {
                PHONE_NUMBER:       , -- a PHONE_PhoneNumber holding the number
                PHONE_NUMBER_STATE: , -- a SMS_MobileNumberControlPersistentState
            };                           containing the state of the control.

            If validation fails the following object is returned:

            {
                ERROR_MESSAGE: , -- error message describing the validation error.
            };

    ******************************************************************************/
    GetValidatedNumber : function( )
    {
        try
        {
            var persistentState = this.GetPersistentState( );

            if ( persistentState.m_countryCode == CMD_COUNTRY_CODE_CONTROL_ID )
            {
                return { ERROR_MESSAGE : this.INVALID_COUNTRY_CODE };
            }

            var mobileNumber = new PHONE_PhoneNumber( persistentState.m_numberFieldData );

            if ( ! mobileNumber.HasCCDelimiter( ) )
            {
                mobileNumber.OverrideCountryCodeInferrence( persistentState.m_countryCode );
            }

            if ( mobileNumber.GetCountryCode( ) == CMD_COUNTRY_CODE_NANP &&
                 mobileNumber.GetNationalNumber( ).length < 10 )
            {
                return { ERROR_MESSAGE : this.INVALID_NANP_NUMBER };
            }

            return { PHONE_NUMBER       : mobileNumber,
                     PHONE_NUMBER_STATE : persistentState };
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, "SMS_MobileNumberControlBase::GetValidatedNumber( )" );
        }
    },


    /******************************************************************************
    PROTECTED METHODS
    ******************************************************************************/

    /******************************************************************************
      InitializeMobileNumberControl

        Completes initialization of the object that can only be completed after
        the child object is constructed. All child classes must call this method
        in their constructor.

    ******************************************************************************/
    InitializeMobileNumberControl : function( )
    {
        try
        {
            // Setup control event handlers
            this.m_comboBox.GetOnChangeSignal( ).Connect( this, "OnCountryChanged" );
            this.m_comboBox.GetOnLoseFocusSignal( ).Connect( this, "OnCountryLoseFocus" );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, 'SMS_MobileNumberControlBase::InitializeMobileNumberControl( )' );
        }
    },

    /******************************************************************************
      OnNumberFieldGainFocus

        Handler for number-field gain-focus events. Child classes should set this
        method as the gain-focus event handler.

    ******************************************************************************/
    OnNumberFieldGainFocus : function( )
    {
        try
        {
            this.m_mobileNumberOnFocus = this.GetMobileNumberData( );
            this.m_numberFieldGainFocusSignal.Emit( );
        }
        catch ( e )
        {
            EX_Log( 'SMS_MobileNumberControlBase::OnNumberFieldGainFocus( ) : ' + e.message );
        }
    },

    /******************************************************************************
      OnNumberFieldLoseFocus

        Handler for number-field lose-focus events. Child classes should set this
        method as the lose-focus event handler.

    ******************************************************************************/
    OnNumberFieldLoseFocus : function( )
    {
        try
        {
            var mobilePhoneNumber = this.GetMobileNumberData( );

            if ( this.m_mobileNumberOnFocus !== mobilePhoneNumber )
            {
                this.InferCountry( mobilePhoneNumber, false );

                this.m_onChangeSignal.Emit( this );
            }

            this.m_numberFieldLoseFocusSignal.Emit( );
        }
        catch ( e )
        {
            EX_Log( 'SMS_MobileNumberControlBase::OnNumberFieldLoseFocus( ) : ' + e.message );
        }
    },

    /******************************************************************************
      OnNumberFieldChange

        Handler for number-field change events. Child classes should set this
        method as the change event handler.

    ******************************************************************************/
    OnNumberFieldChange : function( )
    {
        try
        {
            if ( this.m_handlingCountryChanged )
            {
                return;
            }

            var mobilePhoneNumber = this.GetMobileNumberData( );

            this.InferCountry( mobilePhoneNumber, true );

            this.m_onChangeSignal.Emit( this );

            this.m_numberFieldChangeSignal.Emit( );
        }
        catch ( e )
        {
            EX_Log( 'SMS_MobileNumberControlBase::OnNumberFieldChange( ) : ' + e.message );
        }
    },

    /******************************************************************************
    PRIVATE METHODS
    ******************************************************************************/

    /******************************************************************************
      OnCountryLoseFocus

        Handler for country combo box lose-focus events.

    ******************************************************************************/
    OnCountryLoseFocus : function( )
    {
        try
        {
            this.FullyQualifyNumber( );
        }
        catch ( e )
        {
            EX_Log( 'SMS_MobileNumberControlBase::OnCountryLoseFocus( ) : ' + e.message );
        }
    },

    /******************************************************************************
      SetMobileNumberDataIfNotEqual

        Helper method for updating the mobile number if the value is different than
        the current value.

        Parameters:
            [in] value

    ******************************************************************************/
    SetMobileNumberDataIfNotEqual : function( value )
    {
        try
        {
            if ( this.GetMobileNumberData( ) !== value )
            {
                this.SetMobileNumberData( value );

                this.m_numberFieldChangeSignal.Emit( );
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, 'SMS_MobileNumberControlBase::SetMobileNumberDataIfNotEqual( )' );
        }
    },

    /******************************************************************************
      OnCountryChanged

        Handler for country combo box change events.

    ******************************************************************************/
    OnCountryChanged : function( )
    {
        try
        {
            this.m_handlingCountryChanged = true;

            var mobileNumber = new PHONE_PhoneNumber( this.GetMobileNumberData( ) );

            var selectedCountryCode = this.GetCountryCode( );

            if ( selectedCountryCode === CMD_COUNTRY_CODE_CONTROL_ID &&
                 this.m_lastCountryCodeSelection !== CMD_COUNTRY_CODE_CONTROL_ID )
            {
                // set the selection back to a valid selection
                this.UpdateCountryCode( this.m_lastCountryCodeSelection );
                return;
            }

            if ( selectedCountryCode === CMD_COUNTRY_CODE_CONTROL_ID )
            {
                this.m_comboBox.SelectIndex( 0 );
            }
            else
            {
                if ( mobileNumber.HasCCDelimiter( ) )
                {
                    mobileNumber.SetCountryCode( selectedCountryCode );
                }
                else
                {
                    mobileNumber.OverrideCountryCodeInferrence( selectedCountryCode );
                }

                this.SetMobileNumberDataIfNotEqual( mobileNumber.GetFullyQualifiedString( ) );
            }

            this.m_mobileNumberOnFocus = this.GetMobileNumberData( );

            this.m_lastCountryCodeSelection = selectedCountryCode;
            this.m_inferredCountryCode = '';

            this.m_handlingCountryChanged = false;

            this.m_countryPullDownChangeSignal.Emit( this.m_comboBox );
            this.m_onChangeSignal.Emit( this );
        }
        catch ( e )
        {
            EX_Log( 'SMS_MobileNumberControlBase::OnCountryChanged( ) : ' + e.message );
        }
    },

    /******************************************************************************
      InferCountry

        Encapsulates the country inference algorithm.

        Parameters:
            [in] mobilePhoneNumber
            [in] absoluteOnly

    ******************************************************************************/
    InferCountry : function( mobilePhoneNumber, absoluteOnly )
    {
        try
        {
            this.m_inferredCountryCode = '';

            var parsedMobileNumber = new PHONE_PhoneNumber( mobilePhoneNumber );

            if ( parsedMobileNumber.HasCCDelimiter( ) )
            {
                // update the country code always.
                this.UpdateCountryCode( parsedMobileNumber.GetCountryCode( ) );
                return;
            }

            if ( absoluteOnly )
            {
                // ignore all inferrences
                return;
            }

            if ( parsedMobileNumber.GetCountryCode( ) === CMD_COUNTRY_CODE_CONTROL_ID )
            {
                // no country code was inferred - so leave the current selection as is.
                return;
            }

            // a country code inferrence was made

            var selectedCountryCode = this.GetCountryCode( );

            if ( selectedCountryCode != CMD_COUNTRY_CODE_CONTROL_ID )
            {
                // ignore the inferrence if a country code is already selected.
                return;
            }

            this.UpdateCountryCode( parsedMobileNumber.GetCountryCode( ) );

            this.m_inferredCountryCode = parsedMobileNumber.GetCountryCode( );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, 'SMS_MobileNumberControlBase::InferCountry( )' );
        }
    },

    /******************************************************************************
      FullyQualifyNumber

        Fully qualifies the number in the input text field.

    ******************************************************************************/
    FullyQualifyNumber : function( )
    {
        try
        {
            var mobileNumber = new PHONE_PhoneNumber( this.GetMobileNumberData( ) );

            var selectedCountryCode = this.GetCountryCode( );

            if ( selectedCountryCode === CMD_COUNTRY_CODE_CONTROL_ID )
            {
                return;
            }

            if ( mobileNumber.HasCCDelimiter( ) )
            {
                return;
            }

            mobileNumber.SetCountryCode( selectedCountryCode );

            this.SetMobileNumberDataIfNotEqual( mobileNumber.GetFullyQualifiedString( ) );

            this.m_onChangeSignal.Emit( this );
        }
        catch ( e )
        {
            EX_Log( 'SMS_MobileNumberControlBase::FullyQualifyNumber( ) : ' + e.message );
        }
    },

    /******************************************************************************
      FindCountryCodeIndex

        Helper method for finding the combo box index of the supplied country code.

        Parameters:
            [in] countryCode

        Returns:
            Combo box index of country code.

    ******************************************************************************/
    FindCountryCodeIndex : function( countryCode )
    {
        try
        {
            for ( var i = 0; i < this.m_comboBox.GetLength( ); ++i )
            {
                if ( this.m_comboBox.GetMenuItem( i ).m_countryData.m_countryCode == countryCode )
                {
                    return i;
                }
            }

            throw new EX_ProgrammingError( "Invalid country code: " + countryCode );
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, 'SMS_MobileNumberControlBase::FindCountryCodeIndex( )' );
        }
    },

    /******************************************************************************
      UpdateCountryCode

        Helper method for updating the country code selection.

        Parameters:
            [in] countryCode

    ******************************************************************************/
    UpdateCountryCode : function( countryCode )
    {
        try
        {
            var countryChanged = this.m_lastCountryCodeSelection !== countryCode;

            this.m_comboBox.SelectIndex( this.FindCountryCodeIndex( countryCode ) );

            this.m_lastCountryCodeSelection = countryCode;

            if ( countryChanged )
            {
                this.m_countryPullDownChangeSignal.Emit( this.m_comboBox );
            }
        }
        catch ( e )
        {
            EX_ASSERT_NO_EXCEPTIONS( e, 'SMS_MobileNumberControlBase::UpdateCountryCode( )' );
        }
    }
};
