/*
* Copyright (c) 2006-2010 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:  Bluetooth Audio Manager ECom plug-in class definition.
*
*/


#include <ecom/ecom.h>
#include <bttypes.h>
#include "btaudiomanplugin.h"
#include "btaccTypes.h"
#include "debug.h"

enum TRequestId
    {
    ERequestConnect = 1,
    ERequestDisconnect = 2,
    ENotifyProfileStatusChange = 3,
    ERequestDisconnectAll,
    };

CBtAudioManPlugin* CBtAudioManPlugin::NewL()
    {
    CBtAudioManPlugin* self = new (ELeave) CBtAudioManPlugin();
    CleanupStack::PushL(self);
    self->ConstructL();
    CleanupStack::Pop(self);
    return self;
    }

CBtAudioManPlugin::~CBtAudioManPlugin()
    {
    delete iActive4ClientReq;
    delete iActive4ProfileStatus;
    iClient.Close();
    TRACE_FUNC
    }

void CBtAudioManPlugin::SetObserver( MBTEngPluginObserver* aObserver )
    {
    iObserver = aObserver;
    }

void CBtAudioManPlugin::GetSupportedProfiles( RProfileArray& aProfiles )
    {
    aProfiles.Reset();
    aProfiles.Append(EBTProfileHSP);
    aProfiles.Append(EBTProfileHFP);
    aProfiles.Append(EBTProfileA2DP);
    }

TBool CBtAudioManPlugin::IsProfileSupported(const TBTProfile aProfile ) const
    {
    return (aProfile == EBTProfileHSP ||
        aProfile == EBTProfileHFP  ||
        aProfile == EBTProfileA2DP);
    }

TInt CBtAudioManPlugin::Connect( const TBTDevAddr& aAddr )
    {
    TRACE_FUNC
    TInt err = PrepareAsyncRequest(aAddr, ERequestConnect);
    if(!err)
        {
        iDiagnostic.Zero();
        iClient.ConnectToAccessory(iActive4ClientReq->iStatus, iBTDevAddrPckgBuf, iDiagnostic);
        iActive4ClientReq->GoActive();
        }
    return err;
    }

void CBtAudioManPlugin::CancelConnect( const TBTDevAddr& aAddr )
    {
    if (iBTDevAddrPckgBuf() == aAddr &&
        iActive4ClientReq->IsActive() &&
        iActive4ClientReq->RequestId() == ERequestConnect )
        {
        TRACE_INFO(_L("CBtAudioManPlugin::CancelConnect KErrCancel"))
        iActive4ClientReq->Cancel();
        if (iObserver)
            {
            iObserver->ConnectComplete(aAddr, EBTProfileHFP, KErrCancel);
            }
        }
    else
        {
        TRACE_INFO(_L("CBtAudioManPlugin::CancelConnect KErrNotFound"))
        if (iObserver)
            {
            iObserver->ConnectComplete(aAddr, EBTProfileHFP, KErrNotFound);
            }
        }

    }

TInt CBtAudioManPlugin::Disconnect( const TBTDevAddr& aAddr, TBTDisconnectType /*aDiscType*/ )
    {
    TInt req = ERequestDisconnect;
    if (aAddr == TBTDevAddr())
        {
        req = ERequestDisconnectAll;
        }
    TInt err = PrepareAsyncRequest(aAddr, req);
    if (!err)
        {
        if (req == ERequestDisconnect)
            {
            iClient.DisconnectAccessory(iActive4ClientReq->iStatus, iBTDevAddrPckgBuf, iDiagnostic);
            }
        else // if (req == ERequestDisconnectAll)
            {
            iClient.DisconnectAllGracefully(iActive4ClientReq->iStatus);
            }
        iActive4ClientReq->GoActive();
        }
    return err;
    }

void CBtAudioManPlugin::GetConnections( RBTDevAddrArray& aAddrArray, TBTProfile aConnectedProfile )
    {
    TRACE_FUNC
    aAddrArray.Reset();
    TProfiles profile = EUnknownProfile;
    
    if (aConnectedProfile == EBTProfileHSP || aConnectedProfile == EBTProfileHFP)
        {
        profile = EAnyMonoAudioProfiles;
        }
    else if (aConnectedProfile == EBTProfileA2DP)
        {
        profile = EStereo;
        }
    else
        {
        return;
        }
    
    //guess there are 2 addresses as a 'best guess'
    TInt numAddresses = 2;
    TInt count = numAddresses;
    RBuf8 addrbuf;
    
    do
        {
        //if this is > 1st time round, the buffer must be deleted and the
        //count might have changed
        addrbuf.Close();
        numAddresses = count;
        
        //create a buffer using the 'best guess' size
        TInt err = addrbuf.Create(KBTDevAddrSize * numAddresses);
        
        if (err != KErrNone)
            {
            //we can't do anything if the buffer fails to create, just return
            //and ignore the error as there is no error path (maybe this will
            //change in the future)
            return;
            }
        
        //get the number of connections and the addresses. count can be either
        //the number of connections or a system-wide error code
        count = iClient.GetConnections(addrbuf, profile);
        
        if (count < KErrNone)
            {
            //error occurred so we're finished with the buffer
            addrbuf.Close();
            
            //ignore the error as there is no error path (maybe this will
            //change in the future)
            return;
            }
        }
    //iterate if the number of connections is greater than our 'best guess' or
    //maybe another connection was established while this was taking place
    while (count > numAddresses);
    
    TPtrC8 ptr(addrbuf);
    
    //iterate through the addresses buffer
    while (ptr.Length() >= KBTDevAddrSize)
        {
        //append each address to the device address array
        TInt err = aAddrArray.Append(TBTDevAddr(ptr.Left(KBTDevAddrSize)));
        
        if (err == KErrNone)
            {
            //shift the pointer along to the next address  
            ptr.Set(ptr.Mid(KBTDevAddrSize));
            }
        else
            {
            //error occurred so we're finished with the buffer
            addrbuf.Close();
            
            //ignore the error as there is no error path (maybe this will
            //change in the future)
            return;
            }
        }
    
    //now finished with the address buffer
    addrbuf.Close();
    }

TBTEngConnectionStatus CBtAudioManPlugin::IsConnected( const TBTDevAddr& aAddr )
    {
    TBTEngConnectionStatus stat = (TBTEngConnectionStatus) iClient.IsConnected(aAddr);
    TRACE_INFO((_L("IsConnected %d"), stat))
    return stat;
    }

void CBtAudioManPlugin::RequestCompletedL(CBasrvActive& aActive)
    {
    TRACE_FUNC
    TInt result = aActive.iStatus.Int();
    switch (aActive.RequestId())
        {
        case ENotifyProfileStatusChange:
            {
            // Notify any observer if one is present, and we got valid data (i.e. no error)
            if (result == KErrNone && iObserver)
                {
                ReportProfileConnectionEvents(iProfileStatus.iAddr, iProfileStatus.iProfiles, iProfileStatus.iConnected);
                }
            // Handle resubscribing for future notifications
            static const TInt KMaxFailedNotifyConnectionStatusAttempts = 3;
            if (result == KErrNone)
                {
                iNotifyConnectionStatusFailure = 0; // reset failure count
                NotifyConnectionStatus();
                }
            else if (result != KErrServerTerminated && iNotifyConnectionStatusFailure < KMaxFailedNotifyConnectionStatusAttempts)
                {
                TRACE_ERROR((_L8("Connection Status Notification failed (transiently): %d"), result))
                ++iNotifyConnectionStatusFailure;
                NotifyConnectionStatus();
                }
            else
                {
                // The server has died unexpectedly, or we've failed a number of times in succession so we cannot re-subscribe. 
                // The lack of state here makes it diffcult to know how to report (handle?) this.  However, we are in
                // no worse situation than before, plus the issue is now logged.
                TRACE_ERROR((_L8("Connection Status Notification failed (terminally): %d"), result))
                iClient.Close(); // clean the handle, this might kill outstanding disconnect/connect requests
                                 // but it looks like the server is hosed anyway...
                // In keeping with the existing logic we don't attempt to re-start the server.  That will only happen when an
                // active request is made.
                }
            break;
            }
        case ERequestConnect:
            {
            if (result != KErrNone) // might have conflicts, decode iDiagnostic
                {
                if (iDiagnostic.Length() >= KBTDevAddrSize)
                    {
                    RBTDevAddrArray array;
                    CleanupClosePushL(array);
                    TPtrC8 ptr(iDiagnostic);
                    while (ptr.Length() >= KBTDevAddrSize)
                        {
                        array.AppendL(TBTDevAddr(ptr.Left(KBTDevAddrSize)));
                        #ifdef _DEBUG
                        const TPtrC8 myPtr(array[array.Count() - 1].Des());
                        #endif
                        TRACE_INFO((_L8("conflict <%S>"), &myPtr))
                        ptr.Set(ptr.Mid(KBTDevAddrSize));
                        }
                    iObserver->ConnectComplete(iBTDevAddrPckgBuf(), EBTProfileHFP, result, &array);
                    CleanupStack::PopAndDestroy(&array);
                    }
                else
                    {
                    iObserver->ConnectComplete(iBTDevAddrPckgBuf(), EBTProfileHFP, result);
                    }
                }
            else
                {
                TInt profile = 0;
                if (iDiagnostic.Length() >= sizeof(TInt))
                    {
                    TPckg<TInt> pckg(profile);
                    pckg.Copy(iDiagnostic.Mid(0, sizeof(TInt))); 
                    }
                ReportProfileConnectionEvents(iBTDevAddrPckgBuf(), profile, ETrue);
                }
            break;
            }
        case ERequestDisconnect:
            {
            if (result != KErrNone)
                {
                iObserver->DisconnectComplete(iBTDevAddrPckgBuf(), EBTProfileHFP, result);
                }
            else
                {
                TInt profile = 0;
                if (iDiagnostic.Length() >= sizeof(TInt))
                    {
                    TPckg<TInt> pckg(profile);
                    pckg.Copy(iDiagnostic.Mid(0, sizeof(TInt))); 
                    }
                ReportProfileConnectionEvents(iBTDevAddrPckgBuf(), profile, EFalse);
                }
            break;
            }
        case ERequestDisconnectAll:
            {
            iObserver->DisconnectComplete(iBTDevAddrPckgBuf(), EBTProfileHFP, result);
            break;
            }
        }
    }
    
void CBtAudioManPlugin::CancelRequest(CBasrvActive& aActive)
    {
    if (aActive.RequestId() == ENotifyProfileStatusChange)
        {
        iClient.CancelNotifyConnectionStatus();
        }
    else if (aActive.RequestId() == ERequestConnect)
        {
        iClient.CancelConnectToAccessory();
        }
    }

CBtAudioManPlugin::CBtAudioManPlugin() : iProfileStatusPckg(iProfileStatus)
    {
    TRACE_FUNC
    }

void CBtAudioManPlugin::ConstructL()
    {
    LEAVE_IF_ERROR(iClient.Connect());
    // Create the handler for profile notifications
    iActive4ProfileStatus = CBasrvActive::NewL(*this, CActive::EPriorityStandard, ENotifyProfileStatusChange);
    NotifyConnectionStatus();
    // Create the handler for active requests (connect, disconnect, etc.)
    iActive4ClientReq = CBasrvActive::New(*this, CActive::EPriorityStandard, ERequestConnect);
    }

void CBtAudioManPlugin::NotifyConnectionStatus()
    {
    iClient.NotifyConnectionStatus(iProfileStatusPckg, iActive4ProfileStatus->iStatus);
    iActive4ProfileStatus->GoActive();
    }

TInt CBtAudioManPlugin::ReconnectIfNeccessary()
    {
    TInt err = KErrNone;
    if (iClient.Handle() == KNullHandle)
        {
        TRACE_INFO((_L8("Handle to Audio Man Server is not valid, connecting again...")))
        err = iClient.Connect();
        TRACE_INFO((_L8("... reconnection result = %d"), err))
        if(!err)
            {
            // Now reconnected, we should start status notifications again...
            NotifyConnectionStatus();
            }
        }
    return err;
    }

TInt CBtAudioManPlugin::PrepareAsyncRequest(const TBTDevAddr& aAddr, TInt aRequestId)
    {
    TInt err = KErrNone;
    err = ReconnectIfNeccessary();
    if (!err && iActive4ClientReq->IsActive())
        {
        // I would normally expect KErrInUse, so as to distinguish this failure from running out of message slots
        err = KErrServerBusy;
        }
    if (!err)
        {
        iBTDevAddrPckgBuf() = aAddr;
        iActive4ClientReq->SetRequestId(aRequestId);
        }
    return err;    
    }

void CBtAudioManPlugin::ReportProfileConnectionEvents(const TBTDevAddr& aAddr, const TInt aProfiles, TBool aConnected)
    {
    TRACE_FUNC
    TRACE_INFO((_L("status %d profiles 0x%04X"), aConnected, aProfiles))
    TBTEngConnectionStatus status = IsConnected(aAddr);
    if (iObserver)
        {
        if (aConnected)
            {
            if (aProfiles & EHFP)
                {
                iObserver->ConnectComplete(aAddr, EBTProfileHFP, KErrNone);
                }
            if (aProfiles & EHSP)
                {
                iObserver->ConnectComplete(aAddr, EBTProfileHSP, KErrNone);
                }
            if (aProfiles & EStereo)
                {
                iObserver->ConnectComplete(aAddr, EBTProfileA2DP, KErrNone);
                }
            }
        else
            {
            if (status != EBTEngConnected)
                {
                if (aProfiles & EHFP)
                    {
                    iObserver->DisconnectComplete(aAddr, EBTProfileHFP, KErrNone);
                    }
                if (aProfiles & EHSP)
                    {
                    iObserver->DisconnectComplete(aAddr, EBTProfileHSP, KErrNone);
                    }
                if (aProfiles & EStereo)
                    {
                    iObserver->DisconnectComplete(aAddr, EBTProfileA2DP, KErrNone);
                    }
                }
            }
        }
    }

//
