// Copyright (c) 2003-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:
// Implements the eSCO SAP class
// 
//

#include <bluetooth/logger.h>
#include <bt_sock.h>
#include <bluetooth/hci/hciframe.h>

#include "SCOSAP.h"
#include "physicallinksmanager.h"
#include "physicallinks.h"
#include "hcifacade.h"
#include "linkutil.h"
#include "linkmuxer.h"

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

// ----------------------------------------------------------------------------
// eSCO link
// ----------------------------------------------------------------------------
CeSCOLink* CeSCOLink::NewLC(CPhysicalLinksManager& aConnectionMan, CPhysicalLink* aConnection)
	{
	CeSCOLink* s = new(ELeave) CeSCOLink(aConnectionMan, aConnection);
	CleanupStack::PushL(s);
	s->ConstructL();
	return s;
	}


CeSCOLink* CeSCOLink::NewL(CPhysicalLinksManager& aLinksMan, CPhysicalLink* aPhysicalLink)
	{
	CeSCOLink* s = NewLC(aLinksMan, aPhysicalLink);
	CleanupStack::Pop();
	return s;
	}

CeSCOLink::CeSCOLink(CPhysicalLinksManager& aLinksMan, CPhysicalLink* aPhysicalLink)
: CBTSynchronousLink(aLinksMan, aPhysicalLink, EeSCOLink)
	{
	LOG_FUNC
	iUserPacketTypes = TBTSyncPackets::ESyncAnyESCOPacket;
	}

void CeSCOLink::ConstructL()
	{
	CBTSynchronousLink::ConstructL();
	}

CeSCOLink::~CeSCOLink()
	{
	LOG_FUNC
	}

TInt CeSCOLink::GetOption(TUint aLevel, TUint aName, TDes8& aOption) const
	{
	if (aLevel != KSolBtESCO)
		{
		return KErrNotSupported;
		}
	
	switch (aName)
		{
		case EeSCOExtOptions:
			{
			TBTeSCOLinkParams* options = reinterpret_cast<TBTeSCOLinkParams*>(const_cast<TUint8*>(aOption.Ptr()));
			options->iBandwidth.iTransmit = iTransmitBandwidth;
			options->iBandwidth.iReceive = iReceiveBandwidth;
			options->iLatency = iMaxLatency;
			options->iRetransmissionEffort = iRetransmissionEffort;
			options->iCoding = iVoiceSettings;
			return KErrNone;
			}
		
		default:
			return KErrNotSupported;
		}
	}

TInt CeSCOLink::SAPSetOption(TUint aLevel, TUint aName, const TDesC8& aOption)
/**
	Case ESyncSetUserPacketTypes:
	Store the user specified pakcet types if at least one of the eSCO packet types is set
	The packet types bitmask will be passed as a parameter of CPhysicalLink::Connect(const TBTSyncPackets aUserPacketTypes)
	in DoActiveOpenL()
*/
	{
	switch (aLevel)
		{
		case KSolBtSCO:
			switch (aName)
				{
				case ESyncUserPacketTypes:
					{
					TBTSyncPacketTypes optPacketTypes = *reinterpret_cast<const TBTSyncPacketTypes*>(aOption.Ptr());
					if (TBTSyncPackets::ESyncAnyESCOPacket & optPacketTypes) //make sure at least one packet type is supported
						{
						iUserPacketTypes = static_cast<TBTSyncPacketTypes>(optPacketTypes & (TBTSyncPackets::ESyncAnyESCOPacket));//Just eSCO packet types are stored
						}
					else
						{
						return KErrNotSupported;
						}
					
					break;
					}
				
				default:
					return KErrNotSupported;
				}
			break;
			
		case KSolBtESCO:
			switch (aName)
				{
				case EeSCOExtOptions:
					{
					TBTeSCOLinkParams options = *reinterpret_cast<const TBTeSCOLinkParams*>(aOption.Ptr());
					
					iTransmitBandwidth	= Max(options.iBandwidth.iTransmit, KMinESCOBandwidth);
					iReceiveBandwidth	= Max(options.iBandwidth.iReceive, KMinESCOBandwidth);
					iMaxLatency 		= Max(options.iLatency, KMinESCOLatency);
					iRetransmissionEffort = options.iRetransmissionEffort;
					iVoiceSettings		= options.iCoding;
					
					break;
					}

				default:
					return KErrNotSupported;
				}
			break;
			
		default:
			return KErrNotSupported;
		}
	
	return KErrNone;
	}


TUint CeSCOLink::DoWrite(const TDesC8& aData, TUint /*aOptions*/, TSockAddr* /*aAddr*/)
/**
	Actually do the write - has been checked for length
	In future could write data on a specific SCO "subchannel" if we do that sort of thing
	between devices - possibly then use aAddr or aOptions
**/
	{
	TUint retVal = 0;
	TInt err = KErrNone;
	
#ifdef STACK_SCO_DATA
	// Trim length of data if necessary
	TPtrC8 data = aData.Left(CHctlSynchronousDataFrame::KHCTLMaxSynchronousDataSize);
	if (iLinksMan.LinkManagerProtocol().LinkMuxer().CanWriteSCOData())
		{
		// for now just try to write - if it fails then it doesnt matter - its
		// a lot more *s*ynchronous than sticking it in a queue ;-)
		LOG(_L("CSCOLink: Can write SCO data..."));
		delete iOutboundSCOFrame;
		iOutboundSCOFrame = NULL;
		const CHCIFacade& facade = iLinksMan.HCIFacade(); 
		TRAP(err, iOutboundSCOFrame = facade.NewSynchronousDataFrameL(static_cast<TUint8>(data.Length())));
		if(iOutboundSCOFrame && err == KErrNone)
			{
			// got the frame - so go and use it
			facade.FormatSCOData(*iOutboundSCOFrame, iHandle, data);
			if (facade.WriteSCOData(*iOutboundSCOFrame) == KErrNone)
				{
				retVal = data.Length(); // we sent this many bytes
				}
			}
		}
	else
		{
		LOG(_L("CSCOLink: ERROR Cannot write SCO data."));
		Socket()->Error(((err == KErrNone) ? KErrInUse : err), MSocketNotify::EErrorSend);
		}
#else
	LOG(_L("CSCOLink: ERROR SCO data via stack not supported.")))
	Socket()->Error(KErrNotSupported, MSocketNotify::EErrorSend);
#endif
	return retVal;
	}

void CeSCOLink::PacketTypeChange(THCIErrorCode aErr, THCIConnHandle aConnH, TUint16 aNewPacket)
	{
	iState->PacketTypeChange(*this, aErr, aConnH, aNewPacket);
	}

TBool CeSCOLink::IsLinkProbablyPossible()
	{
	TUint minBandwidth = MinBandwidth();
	if ((iTransmitBandwidth < minBandwidth) || (iReceiveBandwidth < minBandwidth))
		{
		return EFalse;
		}
	
	TUint maxBandwidth = MaxBandwidth();
	if ((iTransmitBandwidth != KESCOBandwidthDontCare) && (iTransmitBandwidth > maxBandwidth))
		{
		return EFalse;
		}
	
	if ((iReceiveBandwidth != KESCOBandwidthDontCare) && (iReceiveBandwidth > maxBandwidth))
		{
		return EFalse;
		}
	
	if (iMaxLatency < KMinESCOLatency)
		{
		return EFalse;
		}
	
	TBool airCodingOK = EFalse;
	TBTFeatures airCodings = iLinksMan.LinkManagerProtocol().PacketsSupportedLocally();
	switch (iVoiceSettings & KAirCodingFormatBits)
		{
		case ECVSD:
			airCodingOK = airCodings[ESupportedCVSDBit];
			break;
			
		case EuLawLog:
			airCodingOK = airCodings[ESupportedu_lawLogBit];
			break;
		
		case EALawLog:
			airCodingOK = airCodings[ESupportedA_lawLogBit];
			break;
		
		case ETransparentData:
			airCodingOK = airCodings[ESupportedTransparentSCODataBit];
			break;
		
		default:
			break;
		}

	return airCodingOK;
	}


TUint CeSCOLink::MinBandwidth()
	{
	TReal bandwidth = ((KMinESCOPacketSize *  KMilliSecondsFactor) / iMaxLatency);
	return static_cast<TUint>(bandwidth);
	}


TUint CeSCOLink::MaxBandwidth()
	{
	TInt maxPacketSize = 0;
	TInt interval = 1;
	
	if (iUserPacketTypes & TBTSyncPackets::ESyncPacketsEV5)
		{
		maxPacketSize = KMaxEV5Size;
		interval = KEV5Slots + KEV3Slots;
		}
	else if (iUserPacketTypes & TBTSyncPackets::ESyncPacketsEV4)
		{
		maxPacketSize = KMaxEV4Size;
		interval = KEV4Slots + KEV3Slots;
		}
	else if (iUserPacketTypes & TBTSyncPackets::ESyncPacketsEV3)
		{
		maxPacketSize = KMaxEV3Size;
		interval = KEV3Slots + KEV3Slots;
		}
	
	// Simple, easy to read version...
	//TReal bandwidth = (maxPacketSize / (KBasebandSlotTime * tesco));
	
	// Faster, still correct but harder to understand...  Add 1 to force rounding up, just in case.
	TUint bandwidth = ((KBasebandSlotsPerSecond * maxPacketSize) / interval) + 1;
	return bandwidth;
	}


TInt CeSCOLink::MakeConnection(CPhysicalLink* aPhysLink)
	{
	return aPhysLink->SynchronousConnect(iTransmitBandwidth, iReceiveBandwidth,
		iMaxLatency, iVoiceSettings,
		iRetransmissionEffort, iUserPacketTypes);
	}


TDes8& CeSCOLink::InboundFrame()
	{
	return iInboundSCOFrame;
	}

void CeSCOLink::SetExtOptions(TBTSyncConnectOpts aSyncOpts)
	{
	iVoiceSettings = aSyncOpts.iAirMode & KAirCodingFormatBits;
	
	// Using nasty magic numbers as we have a naming clash...  Responses to a command
	// use a different numbering scheme to setup commands.
	const TInt KCVSDResponse = 2;
	if (iVoiceSettings <= KCVSDResponse)
		{
		if (iVoiceSettings == KCVSDResponse)
			{
			iVoiceSettings = ECVSD;
			}
		else
			{
			iVoiceSettings++;
			}
		}
	
	TInt interval = aSyncOpts.iTransmissionInterval;
	
	TReal latency = interval * KBasebandSlotTime * KMilliSecondsFactor;
	iMaxLatency = static_cast<TInt16>(latency + 1);
	
	iTransmitBandwidth = static_cast<TUint>((KBasebandSlotsPerSecond * aSyncOpts.iTxPacketLength / interval) + 1);
	iReceiveBandwidth = static_cast<TUint>((KBasebandSlotsPerSecond * aSyncOpts.iRxPacketLength / interval) + 1);
	iRetransmissionEffort = aSyncOpts.iRetransmissionWindow;
	}

TUint16 CeSCOLink::GetPacketMask() const
	{
	return iUserPacketTypes;
	}

void CeSCOLink::GetExtOptions(TBTeSCOLinkParams& aOptions) const
	{
	aOptions.iBandwidth.iTransmit = iTransmitBandwidth;
	aOptions.iBandwidth.iReceive = iReceiveBandwidth;
	aOptions.iLatency = iMaxLatency;
	aOptions.iRetransmissionEffort = iRetransmissionEffort;
	aOptions.iCoding = iVoiceSettings;
	}
