/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:  CHtiCommAdapter implementation
*
*/


#include "HtiCommAdapter.h"
#include "HtiDispatcher.h"
#include "HTICommPluginInterface.h"
#include "HtiMessage.h"
#include "HtiNotifier.h"

#include "HTILogging.h"

//default value for max message size for incoming messages
//used if value in constructor is not valid (<0)
const static TInt KMaxMessageSize = 10 * 1024; // 10 KB

const static TInt KHtiCommPriority = 2;

_LIT( KCommPanic, "HtiCommAdapter" );

CHtiCommAdapter* CHtiCommAdapter::NewLC( CHTICommPluginInterface* aCommPlugin,
    CHtiDispatcher* aDispatcher, TInt aMaxMsgSize )
    {
    CHtiCommAdapter* obj = new (ELeave) CHtiCommAdapter( aDispatcher,
                                        aCommPlugin, aMaxMsgSize );
    CleanupStack::PushL( obj );
    obj->ConstructL();
    return obj;
    }

CHtiCommAdapter* CHtiCommAdapter::NewL( CHTICommPluginInterface* aCommPlugin,
    CHtiDispatcher* aDispatcher, TInt aMaxMsgSize )
    {
    CHtiCommAdapter* obj = NewLC( aCommPlugin, aDispatcher, aMaxMsgSize );
    CleanupStack::Pop();
    return obj;
    }

CHtiCommAdapter::CHtiCommAdapter( CHtiDispatcher* aDispatcher,
                           CHTICommPluginInterface* aCommPlugin,
                           TInt aMaxMsgSize )
    :CActive( KHtiCommPriority ),
    iState( EIdle ),
    iDispatcher( aDispatcher ),
    iCommPlugin( aCommPlugin ),
    iBufferPtr( NULL, 0 ),
    iMsgToReceive( NULL ),
    iMsgToSend( NULL ),
    iMsgToSendPtr( NULL, 0 ),
    iMsgToSendOffset( 0 ),
    iSkipLength( 0 ),
    iMaxMessageSize( aMaxMsgSize > 0 ? aMaxMsgSize : KMaxMessageSize )
    {
    HTI_LOG_FORMAT( "MaxMsgSize %d", iMaxMessageSize );
    };

CHtiCommAdapter::~CHtiCommAdapter()
    {
    HTI_LOG_FUNC_IN( "CHtiCommAdapter::~CHtiCommAdapter" );
    Cancel();

    delete iBuffer;
    delete iLeftovers;

    delete iMsgToReceive;
    delete iMsgToSend;

    HTI_LOG_FUNC_OUT( "CHtiCommAdapter::~CHtiCommAdapter" );
    }

void CHtiCommAdapter::ConstructL()
    {
    HTI_LOG_FUNC_IN( "CHtiCommAdapter::ConstructL" );

    //allocate recommended buffer plus some extra space
    //needed to handle msg parsing
    iBuffer = HBufC8::NewL( iCommPlugin->GetReceiveBufferSize() +
                CHtiMessage::MinHeaderSize() );

    iBuffer->Des().SetLength( iCommPlugin->GetReceiveBufferSize() );
    iBufferPtr.Set( iBuffer->Des().LeftTPtr(
            iCommPlugin->GetReceiveBufferSize() ) );

    iLeftovers = HBufC8::NewL( CHtiMessage::MinHeaderSize() );

    CActiveScheduler::Add( this );
    HTI_LOG_FUNC_OUT( "CHtiCommAdapter::ConstructL" );
    }

void CHtiCommAdapter::ReceiveMessage()
    {
    ReceiveMessage( EFalse );
    }

void CHtiCommAdapter::ReceiveMessage( TBool aContinue )
    {
    HTI_LOG_FUNC_IN( "CHtiCommAdapter::ReceiveMessage" );

    iState = aContinue?EReceivingCont:EReceiving;
    iCommPlugin->Receive( iBufferPtr, iStatus );
    SetActive();

    HTI_LOG_FUNC_OUT( "CHtiCommAdapter::ReceiveMessage" );
    }

void CHtiCommAdapter::SendMessage( CHtiMessage* aMessage )
    {
    HTI_LOG_FUNC_IN( "CCommAdapter::SendMessage" );
    iState = ESending;
    if ( aMessage != NULL )
        {
        HTI_LOG_TEXT( "send first packet" );
        iMsgToSend = aMessage;
        iMsgToSendOffset = 0;

        //first send header
        iMsgToSendPtr.Set( iMsgToSend->Header() );
        }
    else if ( iMsgToSend != NULL )
        {
        //send next chunk
        if ( iMsgToSend->BodySize() <=
            ( iMsgToSendOffset + iCommPlugin->GetSendBufferSize() ) )
            {
            HTI_LOG_TEXT( "send remainder" );
            iMsgToSendPtr.Set( iMsgToSend->Body().Mid( iMsgToSendOffset ) );
            iMsgToSendOffset = iMsgToSend->BodySize(); //last sending
            }
        else
            {
            HTI_LOG_TEXT( "send packet" );
            iMsgToSendPtr.Set( iMsgToSend->Body().Mid( iMsgToSendOffset,
                                    iCommPlugin->GetSendBufferSize() ) );
            iMsgToSendOffset += iCommPlugin->GetSendBufferSize();
            }
        }
    else
        {
        User::Panic( KCommPanic, KErrGeneral );
        }

    iCommPlugin->Send( iMsgToSendPtr, iStatus );
    SetActive();

    HTI_LOG_FUNC_OUT( "CCommAdapter::SendMessage" );
    }

void CHtiCommAdapter::Reset()
    {
    HTI_LOG_FUNC_IN( "CHtiCommAdapter::Reset" );
    Cancel();
    //receiving msg
    delete iMsgToReceive;
    iMsgToReceive = NULL;

    //send msg
    delete iMsgToSend;
    iMsgToSend = NULL;
    iMsgToSendOffset = 0;

    //empty buffers
    iLeftovers->Des().Zero();
    iBuffer->Des().Zero();
    iSkipLength = 0;

    //set idle
    iState = EIdle;

    HTI_LOG_FUNC_OUT( "CHtiCommAdapter::Reset" );
    }

void CHtiCommAdapter::RunL()
    {
    HTI_LOG_FUNC_IN( "CHtiCommAdapter::RunL" );
    HTI_LOG_FORMAT( "iStatus %d", iStatus.Int() );

    //handle reset code common for all states
    if ( iStatus == KErrComModuleReset )
        {
        HTI_LOG_TEXT( "Reset received from comm module" );
        //reset dispatcher
        iDispatcher->Reset();
        //dispatcher will call Reset() for comm adapters and reissue requests
        return;
        }

    //check for critical (unrecoverable) communication errors
    else if ( iStatus == KErrServerTerminated )
        {
        if ( iDispatcher->GetShowErrorDialogs() )
            {
            User::Panic( KCommPanic, KErrServerTerminated );
            }
        else
            {
            User::Exit( KErrServerTerminated );
            }
        }
    // USB errors from d32usbc.h
    else if ( -6700 > iStatus.Int() && iStatus.Int() > -6712 )
        {
        if ( iDispatcher->GetShowErrorDialogs() )
            {
            TBuf<48> errorText;
            errorText.Append( _L( "USB connection lost (" ) );
            errorText.AppendNum( iStatus.Int() );
            errorText.Append( _L( "). HTI stopped." ) );
            CHtiNotifier::ShowErrorL( errorText );
            }
        User::Exit( KErrDisconnected );
        }
    else if ( iStatus == KErrDisconnected )
        {
        // This happens if Bluetooth connection is lost.
        if ( iDispatcher->GetShowErrorDialogs() )
            {
            CHtiNotifier::ShowErrorL(
                    _L( "Connection lost. HTI stopped." ), KErrDisconnected );
            }
        User::Exit( KErrDisconnected );
        }

    switch ( iState )
        {
        case EReceiving:
            //process
            HTI_LOG_TEXT( "EReceiving" );
            if ( iStatus == KErrNone )
                {
                iBuffer->Des().SetLength( iBufferPtr.Length() );
                TRAPD( err, HandleReceiveL() );
                if ( err != KErrNone )
                    {
                    HTI_LOG_FORMAT( "Error in HandleReceiveL %d", err );

                    delete iMsgToReceive;
                    iMsgToReceive = NULL;

                    iDispatcher->Notify( err );

                    ReceiveMessage();
                    }
                }
            else
                {
                HTI_LOG_FORMAT( "Error %d, reissue request", iStatus.Int() );
                iDispatcher->Notify( iStatus.Int() );
                ReceiveMessage();
                }
            break;
        case EReceivingCont:
            //process
            HTI_LOG_TEXT( "EReceivingCont" );
            if ( iStatus == KErrNone )
                {
                iBuffer->Des().SetLength( iBufferPtr.Length() );
                TRAPD( err, HandleReceiveContL() );
                if ( err != KErrNone )
                    {
                    HTI_LOG_FORMAT( "Error in HandleReceiveContL %d", err );

                    delete iMsgToReceive;
                    iMsgToReceive = NULL;

                    iDispatcher->Notify( err );

                    ReceiveMessage();
                    }
                }
            else
                {
                HTI_LOG_FORMAT( "Error %d, reissue request", iStatus.Int() );
                HTI_LOG_TEXT( "and dismiss received HTI msg beginning" );
                delete iMsgToReceive;
                iMsgToReceive = NULL;

                iDispatcher->Notify( iStatus.Int() );

                ReceiveMessage();
                }
            break;
        case ESending:
            HTI_LOG_TEXT( "ESending" );
            if ( iStatus == KErrNone )
                {
                HandleSend();
                }
            else
                {
                HTI_LOG_FORMAT( "Error %d, stop sending, go to EIdle", iStatus.Int() );

                delete iMsgToSend;
                iMsgToSend = NULL;
                iMsgToSendOffset = 0;
                iState = EIdle;

                iDispatcher->Notify( iStatus.Int() );
                }
            break;
        case EIdle:
            //ERROR
            HTI_LOG_TEXT( "EIdle" );
            User::Panic( KCommPanic, KErrGeneral );
            break;
        default:
            //ERROR
            HTI_LOG_TEXT( "default" );
            User::Panic( KCommPanic, KErrGeneral );
        };

    HTI_LOG_FUNC_OUT( "CHtiCommAdapter::RunL" );
    }

void CHtiCommAdapter::HandleReceiveL()
    {
    TBool toContinue = EFalse; //for reissuing state

    if ( iLeftovers->Length() > 0 )
        {
        HTI_LOG_TEXT( "handle leftovers" );
        //if something left from previous time
        //insert it to the buffer beggining
        //check that there is enough space (should be)
        if ( iBuffer->Length() + iLeftovers->Length() <=
             iBuffer->Des().MaxLength() )
            {
            iBuffer->Des().Insert( 0, *iLeftovers );
            }
        else
            {
            //error
            HTI_LOG_TEXT( "iMsgToReceive contain too much leftovers, drop them" );
            }

        iLeftovers->Des().Zero();
        }

    //use loop cause can be several HTI messages in iBuffer
    while ( iBuffer->Length() >= CHtiMessage::MinHeaderSize() )
        {
        HTI_LOG_FORMAT( "iBuffer.Length() %d", iBuffer->Length() );
        HTI_LOG_TEXT( "check header" );

        if ( CHtiMessage::CheckValidHtiHeader( *iBuffer ) )
            {
            HTI_LOG_TEXT( "valid header" );
            TInt msgSize = CHtiMessage::Size( *iBuffer );
            HTI_LOG_FORMAT( "msgSize %d", msgSize );
            HTI_LOG_HEX( iBuffer->Des().Ptr(), 14 );

            __ASSERT_ALWAYS( iMsgToReceive == NULL,
                            User::Panic( KCommPanic, KErrGeneral ) );

            //check msgSize is acceptable
            if ( msgSize > iMaxMessageSize )
                {
                //send err message
                iDispatcher->DispatchOutgoingErrorMessage( EHtiErrBigMessage );
                iSkipLength = msgSize;
                }
            else if ( msgSize > iDispatcher->GetFreeMemory() )
                {
                //send err message
                iDispatcher->DispatchOutgoingErrorMessage( EHtiErrNoMemory );
                iSkipLength = msgSize;
                }
            else
                {
                iMsgToReceive = CHtiMessage::NewL( *iBuffer );
                }

            if ( iMsgToReceive == NULL )
                {
                HTI_LOG_TEXT( "skip message" );
                if ( iSkipLength > iBuffer->Length() )
                    {
                    iSkipLength -= iBuffer->Length();
                    iBuffer->Des().Zero();
                    // and continue to receive HTI message
                    //switch state to EReceiveCont
                    toContinue = ETrue;
                    }
                else
                    {
                    //should not be here, but just in case
                    iBuffer->Des().Delete( 0, iSkipLength );
                    iSkipLength = 0;
                    }
                }
            else if ( iMsgToReceive->IsBodyComplete() )
                {
                HTI_LOG_TEXT( "handle small message" );
                //iMsgToReceive is already contain whole HTI message
                iDispatcher->DispatchIncomingMessage( iMsgToReceive );

                //delete sent msg from buffer (and procces the rest)
                iBuffer->Des().Delete( 0, iMsgToReceive->Size() );
                iMsgToReceive = NULL;
                }
            else
                {
                HTI_LOG_TEXT( "start receive big message" );
                //clear it to leave loop
                iBuffer->Des().Zero();
                // and continue to receive HTI message
                //switch state to EReceiveCont
                toContinue = ETrue;
                }
            }
        else
            {
            HTI_LOG_TEXT( "invalid header, dismiss buffer and reissue request" );
            HTI_LOG_HEX( iBuffer->Des().Ptr(), 14 );
            // clear all buffer cause don't know where is valid header starts
            // and wait for the next packet
            iBuffer->Des().Zero(); //to leave loop
            }
        }

    if ( iBuffer->Length() > 0 )
        {
        HTI_LOG_FORMAT( "copy leftovers %d", iBuffer->Length() );
        //header beggining left in the buffer
        //copy it to iLeftovers
        ( *iLeftovers ) = ( *iBuffer );
        }
    //reissue request
    ReceiveMessage( toContinue );
    }

void CHtiCommAdapter::HandleReceiveContL()
    {
    HTI_LOG_FORMAT( "In buffer: %d", iBuffer->Length() );
    TBool toContinue = ETrue;

    if ( iMsgToReceive != NULL && iSkipLength == 0 )
        {
        //add new packet to a message
        TInt copyLen = iMsgToReceive->AddToBody( *iBuffer );

        //delete what we copied to the message
        iBuffer->Des().Delete( 0, copyLen );

        if ( iMsgToReceive->IsBodyComplete() )
            {
            HTI_LOG_FORMAT( "body complete: %d", iMsgToReceive->BodySize() );

            iDispatcher->DispatchIncomingMessage( iMsgToReceive );
            iMsgToReceive = NULL;

            //message complete, go to state EReceive
            toContinue = EFalse;
            }
        }
    else if ( iSkipLength > 0 )
        {
        HTI_LOG_FORMAT( "continue skipping, remains %d", iSkipLength );

        if ( iSkipLength > iBuffer->Length() )
            {
            iSkipLength -= iBuffer->Length();
            iBuffer->Des().Zero();
            }
        else
            {
            //skip last bytes
            iBuffer->Des().Delete( 0, iSkipLength );
            iSkipLength = 0;

            toContinue = EFalse;
            }
        }

    if ( iBuffer->Length() > 0 )
        {
        //process remainder and reissue in HandleReceiveL()
        HandleReceiveL();
        }
    else
        {
        //reissue
        ReceiveMessage(toContinue);
        }
    }

void CHtiCommAdapter::HandleSend()
    {
    if ( iMsgToSendOffset < iMsgToSend->BodySize() )
        {
        //reissuse sending
        SendMessage( NULL );
        }
    else
        {
        HTI_LOG_TEXT( "finish send, go to idle" );
        iMsgToSendOffset = 0;
        delete iMsgToSend;
        iMsgToSend = NULL;
        iState = EIdle;
        iDispatcher->Notify( KErrNone );
        }
    }

TInt CHtiCommAdapter::RunError( TInt aError )
    {
    TInt err = aError;
    switch ( aError )
        {
        case KErrNoMemory:
            break;
        case KErrNotFound:
            break;
        }
    return err;
    }

void CHtiCommAdapter::DoCancel()
    {
    switch ( iState )
        {
        case EReceiving:
        case EReceivingCont:
            iCommPlugin->CancelReceive();
            break;
        case ESending:
            iCommPlugin->CancelSend();
            break;
        default:
            break;
        }
    }
