// Copyright (c) 2004-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:
//

/**
 @file
 @internalComponent
*/

#include <bluetooth/logger.h>
#include <e32base.h>
#include "remconserver.h"
#include <remconaddress.h>
#include "remconclient.h"

#ifdef __FLOG_ACTIVE
_LIT8(KLogComponent, LOG_COMPONENT_REMCON_INNER);
#endif

/** 
Starts the server process.
*/
static TInt StartServer()
	{
	LOG_STATIC_FUNC
	const TUidType serverUid(KNullUid, KNullUid, KRemConSrvUid);

	//
	// EPOC and EKA2 is easy, we just create a new server process. Simultaneous
	// launching of two such processes should be detected when the second one
	// attempts to create the server object, failing with KErrAlreadyExists.
	//
	RProcess server;
	TInt err = server.Create(KRemConServerImg, KNullDesC, serverUid);
	
	if ( err != KErrNone )
		{
		return err;
		}

	TRequestStatus stat;
	server.Rendezvous(stat);
	
	if ( stat != KRequestPending )
		{
		server.Kill(0); 	// abort startup
		}
	else
		{
		server.Resume();	// logon OK - start the server
		}

	User::WaitForRequest(stat); 	// wait for start or death

	// we can't use the 'exit reason' if the server panicked as this
	// is the panic 'reason' and may be '0' which cannot be distinguished
	// from KErrNone
	err = (server.ExitType() == EExitPanic) ? KErrServerTerminated : stat.Int();

	server.Close();

	return err;
	}

RRemCon::RRemCon(TRemConClientType aClientType) 
:	iClientType(aClientType),
	iNumRemotesPckg(0),
	iReceivePckg(TRemConClientReceivePackage())
	{
	LOG_FUNC
	}

EXPORT_C TVersion RRemCon::Version() const
	{
	LOG_FUNC
	return(TVersion(	KRemConSrvMajorVersionNumber,
						KRemConSrvMinorVersionNumber,
						KRemConSrvBuildNumber
					)
			);
	}

EXPORT_C TInt RRemCon::Connect()
	{
	LOG_FUNC

	TInt err = DoConnect();
	if ( err == KErrNone )
		{
		err = SetClientType();
		if ( err != KErrNone )
			{
			// For this API to be clean, we must clean up the session handle 
			// already successfully created.
			Close();
			}
		}
	return err;
	}

EXPORT_C TInt RRemCon::Connect(const TPlayerType& aClientType, const TPlayerSubType& aClientSubType, const TDesC8& aName)
	{
	LOG_FUNC

	TInt err = DoConnect();
	if ( err == KErrNone )	
		{
		err = SetClientType(aClientType,aClientSubType,aName);
		if ( err != KErrNone )
			{
			// For this API to be clean, we must clean up the session handle 
			// already successfully created.
			Close();
			}
		}
	return err;
	}
/**
Connects the session, starting the server if necessary. This involves two IPC 
calls, the first to connect the session to the server, the second to set the 
server-side session's type correctly (controller or target).
@return Error.
*/
TInt RRemCon::DoConnect()
	{
	LOG_FUNC
	TInt retry = 2;
	
	FOREVER
		{
		// 4 is the number of asynchronous APIs which may be outstanding 
		// simultaneously.
		TInt err = CreateSession(KRemConServerName, Version(), 4);

		if ((err != KErrNotFound) && (err != KErrServerTerminated))
			{
			return err;
			}

		if (--retry == 0)
			{
			return err;
			}

		err = StartServer();

		if ((err != KErrNone) && (err != KErrAlreadyExists))
			{
			return err;
			}
		}
	}

/**
Does IPC with the server to set the type of the session from our member (set 
at construction time).
@return Error.
*/
TInt RRemCon::SetClientType()
	{
	LOG_FUNC
	return SendReceive(ERemConSetClientType, TIpcArgs(iClientType));
	}

TInt RRemCon::SetClientType(const TPlayerType& aClientType, const TPlayerSubType& aClientSubType, const TDesC8& aName)
	{
	LOG_FUNC
	TIpcArgs args;
	args.Set(0,iClientType);
	iPlayerTypePckg().iPlayerType = aClientType;
	iPlayerTypePckg().iPlayerSubType = aClientSubType;
	args.Set(1,&iPlayerTypePckg);
	args.Set(2,aName.Length());
	args.Set(3, &aName);
	return SendReceive(ERemConSetClientType, args);
	}

EXPORT_C TInt RRemCon::SendUnreliable(TUid aInterfaceUid, 
		TUint aOperationId, 
		TRemConMessageSubType aSubType,
		const TDesC8& aData)
	{
	LOG_FUNC
	TIpcArgs args;
	iOpInfoPckg().iOperationId = aOperationId;
	iOpInfoPckg().iMessageSubType = aSubType;
	
	args.Set(0, aInterfaceUid.iUid);
	args.Set(1, &iOpInfoPckg);
	args.Set(2, &aData);
	TInt err = SendReceive(ERemConSendUnreliable, args); 
	return err;
	}


EXPORT_C void RRemCon::Send(TRequestStatus& aStatus, 
		TUid aInterfaceUid, 
		TUint aOperationId, 
		TUint& aNumRemotes,
		TRemConMessageSubType aSubType,
		const TDesC8& aData)
	{
	LOG_FUNC

	TIpcArgs args;
	iOpInfoPckg().iOperationId = aOperationId;
	iOpInfoPckg().iMessageSubType = aSubType;
	args.Set(0, aInterfaceUid.iUid);
	args.Set(1, &iOpInfoPckg);
	iNumRemotesPckg.Set((TUint8*)&aNumRemotes, sizeof(TUint), sizeof(TUint));
	args.Set(2, &iNumRemotesPckg);
	args.Set(3, &aData);

	SendReceive(ERemConSend,
		args,
		aStatus
		);
	}

/**
Sends a notify command to the remote device.

@see RRemCon::Send()
*/
EXPORT_C void RRemCon::SendNotify(TRequestStatus& aStatus, 
		TUid aInterfaceUid, 
		TUint aOperationId, 
		TRemConMessageSubType aSubType,
		const TDesC8& aData)
	{
	LOG_FUNC

	TIpcArgs args;
	iOpInfoPckg().iOperationId = aOperationId;
	iOpInfoPckg().iMessageSubType = aSubType;
	args.Set(0, aInterfaceUid.iUid);
	args.Set(1, &iOpInfoPckg);
	args.Set(2, &aData);
		
	SendReceive(ERemConSendNotify,
		args,
		aStatus
		);
	}
	
EXPORT_C TInt RRemCon::SendCancel()
	{
	LOG_FUNC
	
	//Ignore Return code because
	// a) It'll mostly be other than KErrNone because the server has terminated, in which
	//    case the original async request will have completed with the error anyway!
	// b) It's meaningless to the client whatever the return code is.
	(void)SendReceive(ERemConSendCancel);
	
	return KErrNone; 
	}

EXPORT_C void RRemCon::Receive(TRequestStatus& aStatus, 
							   TRemConClientReceivePackage& aReceivePackage,
							   TDes8& aData)
	{
	LOG_FUNC

	TIpcArgs args;
	iReceivePckg.Set(reinterpret_cast<TUint8*>(&aReceivePackage), sizeof(TRemConClientReceivePackage), sizeof(TRemConClientReceivePackage));
	args.Set(0, &iReceivePckg);
	args.Set(1, &aData);

	SendReceive(ERemConReceive, 
		args,
		aStatus);
	}

EXPORT_C TInt RRemCon::ReceiveCancel()
	{
	LOG_FUNC

	// See RRemCon::SendCancel() for comment
	(void)SendReceive(ERemConReceiveCancel);
	
	return KErrNone;
	}

EXPORT_C TInt RRemCon::GetConnections(TSglQue<TRemConAddress>& aConnections)
	{
	LOG_FUNC

	// Plan: 
	// 1/ Empty the given queue (in case of any subsequent error).
	// 2/ First IPC call: get the current number of connections from the 
	// server and allocate a buffer to read their statuses into. 
	// 3/ Second IPC call: read the connections into that buffer, and translate 
	// them into the client's queue.
	// What if the set of connections changes between these 2 IPC calls? The 
	// server-side session needs to remember the set of connection statuses at 
	// the point in time of the 1st IPC call, to correctly answer the 2nd IPC 
	// call. This means the server-side session has to queue connection state 
	// changes that occur in the meantime, which can fail due to memory 
	// allocation. This is OK because (a) they can fail a new connection 
	// indication back to the bearer, and (b) they can make sure they can 
	// handle a disconnection indication by pre-allocating (at connection 
	// establishment time) enough memory to be able to remember its future 
	// disconnection.

	// 1/
	aConnections.Reset();

	// 2/
	TUint connCount = 0;
	TPckg<TUint> countBuf(connCount);
	TInt err = SendReceive(ERemConGetConnectionCount, TIpcArgs(&countBuf));
	// Only bother to get the statuses if there are some to get- otherwise 
	// just give the client back their empty queue and KErrNone.
	if ( err == KErrNone && connCount != 0 )
		{
		RBuf8 buf;
		err = buf.Create(connCount * sizeof(TRemConAddress));
		if ( err == KErrNone )
			{
			// 3/
			err = SendReceive(ERemConGetConnections, TIpcArgs(&buf));
			if ( err == KErrNone )
				{
				// Read the statuses into the client's array.
				for ( TUint ii = 0 ; ii < connCount ; ii++ )
					{
					// Divide the data in to TRemConAddress portions
					TRemConAddress* tempAddr = new TRemConAddress();
					if (tempAddr)
						{
						Mem::Copy((TAny*)(tempAddr),(TAny*)(buf.Ptr()+(ii*sizeof(TRemConAddress))) , sizeof(TRemConAddress));	
						aConnections.AddLast(*tempAddr);			
						}
					else
						{
						// Perform the cleanup in case of error
						TSglQueIter<TRemConAddress> iter(aConnections);
						iter.SetToFirst();
						TRemConAddress* addr;
						while (( addr = iter++ )  != NULL )
							{
							aConnections.Remove(*addr);
							delete addr;	
							};
							
						err = KErrNoMemory;
						break;				
						}
					}
				}
			// The data that was in buf is passed into small appBuf's 
			// and it is now owned by the client (via their TSglQue).
			}
		// Cleanup the allocated buffer for statuses info
		buf.Close();
		}
	return err;
	}

EXPORT_C void RRemCon::NotifyConnectionsChange(TRequestStatus& aStatus)
	{
	LOG_FUNC

	SendReceive(ERemConNotifyConnectionsChange, 
		aStatus);
	}

EXPORT_C TInt RRemCon::NotifyConnectionsChangeCancel()
	{
	LOG_FUNC

	// See RRemCon::SendCancel() for comment
	(void)SendReceive(ERemConNotifyConnectionsChangeCancel);

	return KErrNone;
	}

EXPORT_C TInt RRemCon::RegisterInterestedAPIs(const TDesC8& aAPIs)
	{
	LOG_FUNC
	return SendReceive(ERemConRegisterInterestedAPIs, TIpcArgs(&aAPIs));
	}

EXPORT_C TInt RRemCon::__DbgMarkHeap()
	{
	LOG_FUNC
#ifdef _DEBUG
	return SendReceive(ERemConDbgMarkHeap);
#else
	return KErrNone;
#endif
	}

EXPORT_C TInt RRemCon::__DbgCheckHeap(TInt aCount)
	{
	LOG_FUNC
#ifdef _DEBUG
	return SendReceive(ERemConDbgCheckHeap, TIpcArgs(aCount));
#else
	(void)aCount;
	return KErrNone;
#endif
	}

EXPORT_C TInt RRemCon::__DbgMarkEnd(TInt aCount)
	{
	LOG_FUNC
#ifdef _DEBUG
	return SendReceive(ERemConDbgMarkEnd, TIpcArgs(aCount));
#else
	(void)aCount;
	return KErrNone;
#endif
	}

EXPORT_C TInt RRemCon::__DbgFailNext(TInt aCount)
	{
	LOG_FUNC
#ifdef _DEBUG
	return SendReceive(ERemConDbgFailNext, TIpcArgs(aCount));
#else
	(void)aCount;
	return KErrNone;
#endif
	}

EXPORT_C RRemConController::RRemConController()
:	RRemCon(ERemConClientTypeController)
	{
	LOG_FUNC
	}

EXPORT_C TInt RRemConController::GoConnectionOriented(const TRemConAddress& aConnection)
	{
	LOG_FUNC

	return SendReceive(ERemConGoConnectionOriented,
			TIpcArgs(aConnection.BearerUid().iUid, 
					&aConnection.Addr())
		);
	}

EXPORT_C TInt RRemConController::GoConnectionless()
	{
	LOG_FUNC

	return SendReceive(ERemConGoConnectionless);
	}

EXPORT_C void RRemConController::ConnectBearer(TRequestStatus& aStatus)
	{
	LOG_FUNC

	SendReceive(ERemConConnectBearer, 
		aStatus);
	}

EXPORT_C TInt RRemConController::ConnectBearerCancel()
	{
	LOG_FUNC

	// See RRemCon::SendCancel() for comment
	(void)SendReceive(ERemConConnectBearerCancel);
	
	return KErrNone;
	}

EXPORT_C void RRemConController::DisconnectBearer(TRequestStatus& aStatus)
	{
	LOG_FUNC

	SendReceive(ERemConDisconnectBearer, 
		aStatus);
	}

EXPORT_C TInt RRemConController::DisconnectBearerCancel()
	{
	LOG_FUNC

	// See RRemCon::SendCancel() for comment
	(void)SendReceive(ERemConDisconnectBearerCancel);

	return KErrNone;
	}

EXPORT_C RRemConTarget::RRemConTarget()
:	RRemCon(ERemConClientTypeTarget)
	{
	LOG_FUNC
	}

