/*
* Copyright (c) 1997-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 part of UsbMan USB Class Framework.
*
*/

/**
 @file
*/

#include "CUsbDummyClassController.h"
#include <usb_std.h>
#include "inifile.h"
#include <usb/usblogger.h>

#ifdef __FLOG_ACTIVE
_LIT8(KLogComponent, "USBSVR");
#endif

_LIT(KDummyControllerPanic, "UsbDummyCC"); // must be <=16 chars
// Panic codes
enum
	{
	// Bad value for the iSynchronicity member.
	EDummyPanicBadSynchronicity = 0,
	
	// Used for environment errors we do not handle.
	EDummyPanicUnhandledError = 1,
	
	// Used for bad iStatus and other state errors.
	EDummyPanicBadState = 2,
	
	// Error reading ini file.
	EDummyPanicBadIniFile = 3,
	
	// We already have our async request active.
	EDummyPanicAlreadyActive = 4,
	
	// We already have a request outstanding from the device class. 
	EDummyPanicOutstandingRequestFromDevice = 5,
	
	// Attempt to call Start() when in illegal state
	EDummyPanicBadApiCallStart = 6,
	
	// Attempt to call Stop() when in illegal state
	EDummyPanicBadApiCallStop = 7,
	};

const TInt KDummyClassPriority = 1;

// Keys for reading the ini file.
_LIT(KStartup, "Startup");
_LIT(KShutdown, "Shutdown");
_LIT(KType, "Type");
_LIT(KTime, "Time");
_LIT(KError, "Error");
_LIT(KSync, "sync");
_LIT(KAsync, "async");
_LIT(KNever, "never");

CUsbDummyClassController* CUsbDummyClassController::NewL(
		MUsbClassControllerNotify& aOwner,
		TUint aIndex)
/**
 * Constructs a CUsbDummyClassController object.
 *
 * @param aOwner USB Device that owns and manages the class
 * @param aIndex The index number of the instance
 * @return Ownership of a new CUsbDummyClassController object
 */
	{
	LOG_STATIC_FUNC_ENTRY

	CUsbDummyClassController* self = new(ELeave) CUsbDummyClassController(aOwner, aIndex);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
	}


CUsbDummyClassController* CUsbDummyClassController::NewL(
	MUsbClassControllerNotify& aOwner, 
	TUint aIndex, TInt aPriority)
/**
 * Constructs a CUsbDummyClassController object.
 *
 * @param aOwner USB Device that owns and manages the class
 * @param aIndex The index number of the instance
 * @param aPriority The startup priority of the instance
 * @return Ownership of a new CUsbDummyClassController object
 */
 	{
	LOG_STATIC_FUNC_ENTRY

 	CUsbDummyClassController* self = new(ELeave) CUsbDummyClassController(aOwner, aIndex, aPriority);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
 	}
 
CUsbDummyClassController::CUsbDummyClassController(
		MUsbClassControllerNotify& aOwner,
		TUint aIndex)
/**
 * Constructor.
 *
 * @param aOwner USB Device that owns and manages the class
 * @param aIndex The index number of the instance
 */
 :	CUsbClassControllerBase(aOwner, KDummyClassPriority),
	iIndex(aIndex)
	{
	iState = EUsbServiceIdle; // needs explicit initialisation as non-zero
	}

CUsbDummyClassController::CUsbDummyClassController(
		MUsbClassControllerNotify& aOwner,
		TUint aIndex, TInt aPriority)
		
/**
 * Constructor.
 *
 * @param aOwner USB Device that owns and manages the class
 * @param aIndex The index number of the instance
 * @param aPriority a startup priority for the class controller
 */		
 : 	CUsbClassControllerBase(aOwner, aPriority),
 	iIndex(aIndex)
 	{
 	iState = EUsbServiceIdle; // needs explicit initialisation as non-zero
 	}
		
void CUsbDummyClassController::ConstructL()
/**
 * Method to perform second phase construction.
 */
	{
	LEAVEIFERRORL(iTimer.CreateLocal());
	}

CUsbDummyClassController::~CUsbDummyClassController()
/**
 * Destructor.
 */
	{
	Cancel();

	iTimer.Close();
	}
	
void CUsbDummyClassController::GetBehaviour(CIniFile& aIniFile, 
											const TDesC& aSection, 
											TBehaviour& aBehaviour)
/**
 * Reads information from the ini file, from a given section, to the given 
 * behaviour structure.
 *
 * @param aIniFile The ini file to read from.
 * @param aSection The section to reads from.
 * @param aBehaviour The behaviour struct to read to.
 */
	{
	LOG_FUNC
#ifdef __FLOG_ACTIVE
	TBuf8<KMaxName> buf;
	buf.Copy(aSection);
	LOGTEXT2(_L8("\taSection = %S"), &buf);
#endif // __FLOG_ACTIVE

	TPtrC temp;
	if ( !aIniFile.FindVar(aSection, KType(), temp) )
		{
		LOGTEXT2(_L8("\tPANICKING: can't find Type item in section %S"), &aSection);
		_USB_PANIC(KDummyControllerPanic, EDummyPanicBadIniFile);
		}
	if ( temp == KSync )
		{
		aBehaviour.iSynchronicity = ESynchronous;
		}
	else if ( temp == KAsync )
		{
		aBehaviour.iSynchronicity = EAsynchronous;
		}
	else if ( temp == KNever )
		{
		aBehaviour.iSynchronicity = ENever;
		}
	else
		{
		LOGTEXT3(_L8("\tPANICKING: bad Type value (%S) in section %S"), &temp, &aSection);
		_USB_PANIC(KDummyControllerPanic, EDummyPanicBadIniFile);
		}
	TInt delay;
	if ( !aIniFile.FindVar(aSection, KTime(), delay) )
		{
		LOGTEXT2(_L8("\tPANICKING: can't find Time item in section %S"), &aSection);
		_USB_PANIC(KDummyControllerPanic, EDummyPanicBadIniFile);
		}
	aBehaviour.iDelay = delay;
	if ( !aIniFile.FindVar(aSection, KError(), aBehaviour.iErrorCode) )
		{
		LOGTEXT2(_L8("\tPANICKING: can't find Error item in section %S"), &aSection);
		_USB_PANIC(KDummyControllerPanic, EDummyPanicBadIniFile);
		}
	}

void CUsbDummyClassController::DoGetConfigL()
/**
 * Reads the config from the ini file.
 */
	{
	LOG_FUNC

	CIniFile* iniFile = CIniFile::NewL(_L("dummy.ini"));
	CleanupStack::PushL(iniFile);
	TName section;

	// Read startup behaviour
	section.Format(_L("%S.%d"), &KStartup(), iIndex);
	GetBehaviour(*iniFile, section, iStartupBehaviour);
	// Read shutdown behaviour
	section.Format(_L("%S.%d"), &KShutdown(), iIndex);
	GetBehaviour(*iniFile, section, iShutdownBehaviour);

	CleanupStack::PopAndDestroy(iniFile);
	}

void CUsbDummyClassController::GetConfig()
/**
 * Reads the config from the ini file.
 * Note that this is assumed to succeed. Any failure will break the test and 
 * it's much cleaner to panic out of the test entirely rather than leave it to 
 * the user to figure out what's gone wrong.
 */
	{
	LOG_FUNC

	// Always use dummy.ini. The entity setting up the test is responsible for 
	// copying the correct file to c:\\dummy.ini. The first found 
	// by FindByDir will be used. TODO: enforce c:\\.
	TRAPD(err, DoGetConfigL());
	if ( err != KErrNone )
		{
		_USB_PANIC(KDummyControllerPanic, EDummyPanicUnhandledError);
		}
	
	LOGTEXT2(_L8("\tLogging dummy class controller behaviour for instance %d"), iIndex);
	LOGTEXT2(_L8("\tiStartupBehaviour.iSynchronicity = %d"), iStartupBehaviour.iSynchronicity);
	LOGTEXT2(_L8("\tiStartupBehaviour.iDelay = %d"), iStartupBehaviour.iDelay.Int());
	LOGTEXT2(_L8("\tiStartupBehaviour.iErrorCode = %d"), iStartupBehaviour.iErrorCode);
	LOGTEXT2(_L8("\tiShutdownBehaviour.iSynchronicity = %d"), iShutdownBehaviour.iSynchronicity);
	LOGTEXT2(_L8("\tiShutdownBehaviour.iDelay = %d"), iShutdownBehaviour.iDelay.Int());
	LOGTEXT2(_L8("\tiShutdownBehaviour.iErrorCode = %d"), iShutdownBehaviour.iErrorCode);
	}

void CUsbDummyClassController::Start(TRequestStatus& aStatus)
/**
 * Called by UsbMan to start this class.
 *
 * @param aStatus Will be completed with success or failure.
 */
	{
	LOG_FUNC
	
	//Start() should only be called if the CC is idle or started		
	__ASSERT_DEBUG((iState == EUsbServiceIdle || iState == EUsbServiceStarted), 
							_USB_PANIC(KDummyControllerPanic, EDummyPanicBadApiCallStart) );

	// Get config from ini file. Note that can't be done once in ConstructL 
	// because then, in the case of a CC which doesn't Stop, we'd never be 
	// able to shut down USBMAN!
	GetConfig();

	// NB We enforce that the device doesn't re-post requests on us.
	__ASSERT_DEBUG(!iReportStatus, 
		_USB_PANIC(KDummyControllerPanic, EDummyPanicOutstandingRequestFromDevice));
	aStatus = KRequestPending;
	iReportStatus = &aStatus;
	
	iState = EUsbServiceStarting;

	switch ( iStartupBehaviour.iSynchronicity )
		{
	case ESynchronous:
		User::After(iStartupBehaviour.iDelay);
		iState = EUsbServiceStarted;
		User::RequestComplete(iReportStatus, iStartupBehaviour.iErrorCode);
		iReportStatus = NULL;
		break;

	case EAsynchronous:
		iTimer.After(iStatus, iStartupBehaviour.iDelay);
		__ASSERT_DEBUG(!IsActive(), _USB_PANIC(KDummyControllerPanic, EDummyPanicAlreadyActive));		
		SetActive();
		break;

	case ENever:
		// Don't do anything and never complete
		break;

	default:
		_USB_PANIC(KDummyControllerPanic, EDummyPanicBadSynchronicity);
		break;
		}
	}

void CUsbDummyClassController::Stop(TRequestStatus& aStatus)
/**
 * Called by UsbMan to stop this class.
 *
 * @param aStatus Will be completed with success or failure.
 */
	{
	LOG_FUNC
		
	//Stop() should only be called if the CC is Started or Idle
	__ASSERT_DEBUG((iState == EUsbServiceStarted || iState == EUsbServiceIdle), 
				_USB_PANIC(KDummyControllerPanic, EDummyPanicBadApiCallStop));
	
	// Get config from ini file. Note that can't be done once in ConstructL 
	// because then, in the case of a CC which doesn't Stop, we'd never be 
	// able to shutdown USBMAN!
	GetConfig();

	// NB We enforce that the device doesn't re-post requests on us.
	__ASSERT_DEBUG(!iReportStatus, 
		_USB_PANIC(KDummyControllerPanic, EDummyPanicOutstandingRequestFromDevice));
	aStatus = KRequestPending;
	iReportStatus = &aStatus;

	iState = EUsbServiceStopping;

	switch ( iShutdownBehaviour.iSynchronicity )
		{
	case ESynchronous:
		User::After(iShutdownBehaviour.iDelay);
		iState = EUsbServiceIdle;
		User::RequestComplete(iReportStatus, iShutdownBehaviour.iErrorCode);
		iReportStatus = NULL;
		break;

	case EAsynchronous:
		iTimer.After(iStatus, iShutdownBehaviour.iDelay);
    	__ASSERT_DEBUG(!IsActive(), _USB_PANIC(KDummyControllerPanic, EDummyPanicAlreadyActive));		
		SetActive();
		break;

	case ENever:
		// Don't do anything and never complete
		break;

	default:
		_USB_PANIC(KDummyControllerPanic, EDummyPanicBadSynchronicity);
		break;
		}
	}

void CUsbDummyClassController::GetDescriptorInfo(TUsbDescriptor& aDescriptorInfo) const
/**
 * Returns information about the interfaces supported by this class.
 *
 * @param aDescriptorInfo Will be filled in with interface information.
 */
	{
	aDescriptorInfo.iNumInterfaces = 0;
	aDescriptorInfo.iLength = 0;
	}

void CUsbDummyClassController::RunL()
/**
 * Standard active object RunL. 
 */
	{
	LOGTEXT3(_L8(">>CUsbDummyClassController::RunL [iStatus=%d,iState=%d]"),
			iStatus.Int(), iState);

	if ( iStatus != KErrNone )
		{
		// Panic runtime errors from the timer. We can't ignore them, and 
		// there's no point trying to code round them. This is part of the 
		// test framework and if it's failing we want to alert the user 
		// without faffing around. (It invalidates the test.)
		_USB_PANIC(KDummyControllerPanic, EDummyPanicUnhandledError);
		}								  

	__ASSERT_DEBUG(iReportStatus, 
		_USB_PANIC(KDummyControllerPanic, EDummyPanicBadState));

	switch ( iState )
		{
	case EUsbServiceStarting:
		// Completion of asynchronous startup...
		__ASSERT_DEBUG(iStartupBehaviour.iSynchronicity == EAsynchronous, 
			_USB_PANIC(KDummyControllerPanic, EDummyPanicBadSynchronicity));
		iState = EUsbServiceStarted;
		User::RequestComplete(iReportStatus, iStartupBehaviour.iErrorCode);
		iReportStatus = NULL;
		break;

	case EUsbServiceStopping:
		// Completion of asynchronous shutdown...
		__ASSERT_DEBUG(iShutdownBehaviour.iSynchronicity == EAsynchronous, 
			_USB_PANIC(KDummyControllerPanic, EDummyPanicBadSynchronicity));
		iState = EUsbServiceIdle;
		User::RequestComplete(iReportStatus, iShutdownBehaviour.iErrorCode);
		iReportStatus = NULL;
		break;

	case EUsbServiceIdle:
	case EUsbServiceStarted:
	default:
		_USB_PANIC(KDummyControllerPanic, EDummyPanicBadState);
		break;
		}

	LOGTEXT(_L8("<<CUsbDummyClassController::RunL"));
	}

void CUsbDummyClassController::DoCancel()
/**
 * Standard active object cancellation function. 
 */
	{
	LOG_FUNC

	// Note that CActive::Cancel does not call DoCancel unless we are active. 
	// Therefore we are at this point active. Therefore, we should have 
	// iReportStatus pointing to something. This is true whether we are in 
	// our destructor or being cancelled by the device. It is also true that 
	// (for us to be active at all) our synchronicity should be set to async 
	// and the timer must have been CreateLocal'd successfully.

	// Cancel may be called in one of the following situations: 
	// 1/ In our destructor. 
	// 2/ The device wants to cancel us in the middle of a Start before 
	// issuing a Stop (or vice versa). Note that the device may cancel us in 
	// the middle of a Start, then immediately issue another Start.
	
	// Cancel our own asynchronous operation.
	__ASSERT_DEBUG(iTimer.Handle(), 
		_USB_PANIC(KDummyControllerPanic, EDummyPanicBadState));
	iTimer.Cancel();

	// Update our iState. If we're starting, then roll back to idle. If we're 
	// stopping, role back to started. Nothing else is legal.
	switch ( iState )
		{
	case EUsbServiceStarting:
		__ASSERT_DEBUG(iStartupBehaviour.iSynchronicity == EAsynchronous, 
			_USB_PANIC(KDummyControllerPanic, EDummyPanicBadSynchronicity));
		iState = EUsbServiceIdle;
		break;

	case EUsbServiceStopping:
		__ASSERT_DEBUG(iShutdownBehaviour.iSynchronicity == EAsynchronous, 
			_USB_PANIC(KDummyControllerPanic, EDummyPanicBadSynchronicity));
		iState = EUsbServiceStarted;
		break;

	case EUsbServiceIdle:
	case EUsbServiceStarted:
	default:
		_USB_PANIC(KDummyControllerPanic, EDummyPanicBadState);
		break;
		}

	// Complete the client's request.	
	__ASSERT_DEBUG(iReportStatus, 
		_USB_PANIC(KDummyControllerPanic, EDummyPanicBadState));
	User::RequestComplete(iReportStatus, KErrCancel); 
	iReportStatus = NULL;
	}

TInt CUsbDummyClassController::RunError(TInt /*aError*/)
/**
 * Standard active object error-handling function. 
 *
 * Should return KErrNone to avoid an active scheduler panic. This function
 * should never be called as there is another mechanism for catching errors.
 */
	{
	__ASSERT_DEBUG(EFalse, 
		_USB_PANIC(KDummyControllerPanic, EDummyPanicBadState));
	return KErrNone;
	}

//
// End of file
