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

#include "cimapsettings.h"
#include <miut_err.h>		// imap server error codes
#include <utf.h>
#include <imutcon.h>
#include "cimapsessionmanager.h"
#include "cimapsession.h"
#include "ctransportmanager.h"
#include "msocketfactory.h"
#include "cimapcapabilityinfo.h"
#include "cimaplistfolderinfo.h"
#include "cimapservergreetinginfo.h"
#include "moutputstream.h"
#include "minputstream.h"
#include "cimaplogger.h"
#include "msocketconnector.h"
#include "cimapsessionconsts.h"

#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
	#include "cimapauthhelpers.h"
#endif
/**
Static factory constructor. Part of two phased construction.

@param aImapSettings Settings for the Imap service
@param aImapMailStore Imap mail store
@return The constructed CImapSessionManager object
*/
EXPORT_C CImapSessionManager* CImapSessionManager::NewL(CImapSettings& aImapSettings, CImapMailStore& aImapMailStore)
	{
	CImapSessionManager* self = new (ELeave) CImapSessionManager(aImapSettings, aImapMailStore);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);
	return self;
	}

/**
Class constructor

@param aImapSettings Settings for IMAP service
@param aImapMailStore Imap mail store
*/
CImapSessionManager::CImapSessionManager(CImapSettings& aImapSettings, CImapMailStore& aImapMailStore)
	: CMsgActive(EPriorityStandard)
	, iProgressState(TImap4GenericProgress::EIdle)
	, iImapSettings(aImapSettings)
	, iImapMailStore(aImapMailStore)
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
	, iCurrentAuthProfile(CImapAuthMechanismHelper::EUndefined)
#endif
	{
	__LOG_TEXT(KDefaultLog, "CImapSessionManager::CImapSessionManager");
	}

/**
Class destructor
*/
CImapSessionManager::~CImapSessionManager()
	{
	__LOG_TEXT(KDefaultLog, "CImapSessionManager::~CImapSessionManager");

	Cancel();

	delete iTransportManager;
	delete iImConnect;
	iSocketServ.Close();
	delete iHostAddr8;
	iDisconnectList.Reset();
	iSeparatorFolderList.ResetAndDestroy();
	delete iImapSession;
	}

/**
Non-trival constructor. Part of two phased construction.
*/
void CImapSessionManager::ConstructL()
	{
	// We're an active object...
	CActiveScheduler::Add(this);
	}

/**
Requests construction of a connected, logged in CImapSession object

@param aStatus Signals completion of the request.
@param aSession Place to store the session once it is fully created.
*/
EXPORT_C void CImapSessionManager::GetSessionL(TRequestStatus& aStatus, CImapSession*& aSession)
	{
	__ASSERT_DEBUG(iState == EStateIdle,
	               TImapServerPanic::ImapPanic(TImapServerPanic::EUnexpectedStateForGetSession));

	__LOG_TEXT(KDefaultLog, "CImapSessionManager::GetSessionL");

	iStoreSession = &aSession;

	if (!iCreatedNetworkConnection)
		{
		CreateNetworkConnectionL();
		}
	else
		{
		CreateSocketL();
		}

	Queue(aStatus);
	}

/**
Requests logout and disconnect of a list of sessions.

@param aStatus Signals completion of the request.
@param aSessionList List of sessions to disconnect
*/
EXPORT_C void CImapSessionManager::Disconnect(TRequestStatus& aStatus, const RPointerArray<CImapSession>& aSessionList)
	{
	__ASSERT_DEBUG(iState == EStateIdle,
	               TImapServerPanic::ImapPanic(TImapServerPanic::EUnexpectedStateForDisConnect));

	__ASSERT_DEBUG(iDisconnectList.Count() == 0,
	               TImapServerPanic::ImapPanic(TImapServerPanic::EDisconnectWhenDisconnectListNotEmpty1));

	// Take a copy of the list of sessions to disconnect
	iDisconnectList.Reset();
	for (TInt session(0); session < aSessionList.Count(); ++session)
		{
		__LOG_TEXT(aSessionList[session]->LogId(), "CImapSessionManager::Disconnect (async, list)");

		iDisconnectList.Append(aSessionList[session]);
		}

	iProgressState = TImap4GenericProgress::EDisconnecting;
	DisconnectFirstSessionInList();

	Queue(aStatus);
	}

/**
Requests immediate disconnect of a list of sessions. The logout command is not
sent.

@param aSessionList List of sessions to disconnect
*/
EXPORT_C void CImapSessionManager::Disconnect(const RPointerArray<CImapSession>& aSessionList)
	{
	__ASSERT_DEBUG(iDisconnectList.Count() == 0,
	               TImapServerPanic::ImapPanic(TImapServerPanic::EDisconnectWhenDisconnectListNotEmpty2));

	// Take a copy of the list of sessions to disconnect
	iDisconnectList.Reset();
	for (TInt session(0); session < aSessionList.Count(); ++session)
		{
		__LOG_TEXT(aSessionList[session]->LogId(), "CImapSessionManager::Disconnect (sync, list)");

		iDisconnectList.Append(aSessionList[session]);
		}

	ImmediateDisconnect();
	}

/**
Requests immediate disconnect of a session. The logout command is not sent.

@param aSession Session to disconnect
*/
EXPORT_C void CImapSessionManager::Disconnect(const CImapSession& aSession)
	{
	__ASSERT_DEBUG(iDisconnectList.Count() == 0,
	               TImapServerPanic::ImapPanic(TImapServerPanic::EDisconnectWhenDisconnectListNotEmpty3));

	__LOG_TEXT(aSession.LogId(), "CImapSessionManager::Disconnect() - START - (sync, session)");

	// Create a disconnect list with just this one session
	iDisconnectList.Reset();
	iDisconnectList.Append(&aSession);
	ImmediateDisconnect();

    __LOG_TEXT(aSession.LogId(), "CImapSessionManager::Disconnect() - END - (sync, session)");
	}

/**
Gets LastSocketActivityTimeout value for the connection

@return LastSocketActivityTimeout value
*/
EXPORT_C TUint32 CImapSessionManager::LastSocketActivityTimeout()
	{
	return iLastSocketActivityTimeout;
	}

/**
Gets progress for an asynchronous operation

@param aProgress Holds progress information
*/
EXPORT_C void CImapSessionManager::Progress(TImap4GenericProgress& aProgress)
	{
	aProgress.iOperation = TImap4GenericProgress::EConnect;
	switch(iState)
		{
	case EStateCreatingNetworkConnection:
	case EStateConnectingSocket:
	case EStateGettingLastSocketActivityTimeout:
	case EStateUpgradingSSLWrappedSocket:
	case EStateGreetingWait:
	case EStateCapabilityWait:
	case EStateStartTLSWait:
	case EStateUpgradingTLSSocket:
	case EStateSecureCapabilityWait:
	case EStateLoginWait:
	case EStateSeparatorWait:
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
	case EStateAuthenticateWait:
#endif
		{
		// We are connecting a session
		aProgress.iState = TImap4GenericProgress::EConnecting;
		aProgress.iImap4SubStateProgress = TImap4GenericProgress::EIdle;

		// iMsgsDone must contain the connection stage information
		aProgress.iMsgsDone = GetConnectionStage();
		TUint32 iap = 0;
		TInt err(KErrNotFound);
		// Get the IAP value to store in iMsgsToDo
		if (iImConnect != NULL)
			{
			err = iImConnect->GetIAPValue(iap);
			}
		if (err == KErrNone)
			{
			aProgress.iMsgsToDo = iap;
			}
		else
			{
			aProgress.iMsgsToDo = err;
			}

		break;
		}

	case EStateIdle:
	default:
		{
		aProgress.iState = iProgressState;
		aProgress.iImap4SubStateProgress = TImap4GenericProgress::EIdle;
		break;
		}
		}				
	}

/**
From MSocketConnectObserver - called when a connection to the remote server
has been successfully made.

@param aInputStream Created socket's input stream API
@param aOutputStream Created socket's output stream API
*/
void CImapSessionManager::ConnectionMadeL(MInputStream& aInputStream, MOutputStream& aOutputStream)
	{
	__LOG_TEXT(KDefaultLog, "CImapSessionManager::ConnectionMadeL");

	iInputStream = &aInputStream;
	iOutputStream = &aOutputStream;

	// Temporarily bind to the streams for close notifications.
	// Once the session is created, it will take over the binding.
	iInputStream->Bind(*this, KDefaultLog);
	iOutputStream->Bind(*this, KDefaultLog);
	
	// Store the current bearer type in the imap settings class
	TUint32 bearerType;
	iImConnect->GetIAPBearer(bearerType);
	iImapSettings.SetCurrentBearerType(bearerType);

	CompleteSelf(KErrNone);
	}

/**
From MSocketConnectObserver - Handle the Connection Error

Notification that an error has occured in the connecting service. The error 
code will have a value of KErrCancel if the observer has stopped the connect 
service via the MSocketConnectObserver::StopConnect() API. If a problem
does occur the socket connector will suicide after handling the error.

@param aError The error code.
@return A value of KErrNone if the error has been handled or any other 
        value if its has not been handled.
*/
TInt CImapSessionManager::HandleConnectError(TInt aError)
	{
	__LOG_FORMAT((KDefaultLog, "CImapSessionManager::HandleConnectError - %d", aError));

	// We should only get this while trying to connect the socket.
	// At any other time we can ignore it.
	// We can also get this after stopping a socket connect due to a
	// cancel. However we will have already self completed and we don't
	// want to complete again. If we have completed, then the iSocketConnector
	// will have been set to NULL, so check this.
	if ((iState == EStateConnectingSocket) && (iSocketConnector != NULL))
		{
		CompleteSelf(aError);
		}

	return KErrNone;
	}

/**
From MOutputStreamObserver - Indicates data has been sent successfully.
*/
void CImapSessionManager::SendDataCnf()
	{
	__LOG_TEXT(KDefaultLog, "CImapSessionManager::SendDataCnf");

	// No action required
	}

/**
From MOutputStreamObserver - Indicates that output stream has closed.

@param aError Error code that caused stream close.
*/
void CImapSessionManager::OutputStreamCloseInd(TInt aError)
	{
	__LOG_FORMAT((KDefaultLog, "CImapSessionManager::OutputStreamCloseInd - Error %d, State %d", aError, iState));

	// We can only get this when bound to the stream before the stream
	// binding is taken over by the session.
	__ASSERT_DEBUG(iState == EStateUpgradingSSLWrappedSocket,
	               TImapServerPanic::ImapPanic(TImapServerPanic::EUnexpectedOutputStreamCloseInd));

	if (iState == EStateUpgradingSSLWrappedSocket)
		{
		iOutputStream = NULL;

		// The only reason the socket should have closed is if a genuine error
		// has occured. However if for some reason we don't get an error
		// returned we need to complete with KErrDisconnected because the
		// socket has closed and we need to tell the caller that we are not
		// going to be able to finish creating the requested session.
		if (aError >= KErrNone)
			{
			aError = KErrDisconnected;
			}

		CompleteSelf(aError);
		}
	}

/**
From MOutputStreamSecureObserver - Indicates that a secure socket request
has completed on the output stream.

@param aError Error code from completed request.
*/
void CImapSessionManager::SecureClientCnf(TInt aError)
	{
	__LOG_FORMAT((LogId(), "CImapSessionManager::SecureClientCnf - Error %d, State %d", aError, iState));

	// Should only get this when we are upgrading to a secure socket
	__ASSERT_DEBUG((iState == EStateUpgradingSSLWrappedSocket ||
	                iState == EStateUpgradingTLSSocket),
	                TImapServerPanic::ImapPanic(TImapServerPanic::EUnexpectedSecureClientCnf));

	if ((iState == EStateUpgradingSSLWrappedSocket) ||
	    (iState == EStateUpgradingTLSSocket))
		{
		CompleteSelf(aError);
		}
	}

/**
From MInputStreamObserver - Indicates that the stream has received some data.
*/
void CImapSessionManager::ReceivedDataIndL(const TDesC8& /*aBuffer*/)
	{
	__LOG_TEXT(KDefaultLog, "CImapSessionManager::ReceivedDataIndL");
	// No action required
	}

/**
From MInputStreamObserver - Indicates that a secure socket request
has completed on the input stream.
*/
void CImapSessionManager::SecureServerCnf()
	{
	__LOG_TEXT(KDefaultLog, "CImapSessionManager::SecureServerCnf");
 
	// No action required
	}

/**
From MInputStreamObserver - Indicates that the input stream has been closed.

@param aError Error code that caused stream close.
*/
void CImapSessionManager::InputStreamCloseInd(TInt aError)
	{
	__LOG_FORMAT((KDefaultLog, "CImapSessionManager::InputStreamCloseInd - Error %d, State %d", aError, iState));

	// We can only get this when bound to the stream before the stream
	// binding is taken over by the session.
	__ASSERT_DEBUG(iState == EStateUpgradingSSLWrappedSocket,
	               TImapServerPanic::ImapPanic(TImapServerPanic::EUnexpectedInputStreamCloseInd));

	if (iState == EStateUpgradingSSLWrappedSocket)
		{
		iInputStream = NULL;

		// The only reason the socket should have closed is if a genuine error
		// has occured. However if for some reason we don't get an error
		// returned we need to complete with KErrDisconnected because the
		// socket has closed and we need to tell the caller that we are not
		// going to be able to finish creating the requested session.
		if (aError >= KErrNone)
			{
			aError = KErrDisconnected;
			}
		
		// Complete the request if there is no error while upgrading socket to SSL/TLS.
		if(!iErrorUpgradingSSLSocket)
		   {
		   CompleteSelf(aError);	
		   }
		
		}
	}

/**
Called when asynchronous IMAP Session events occur.
Manages the various steps necessary to log in to the IMAP Service, or to
logout from it.
*/
void CImapSessionManager::DoRunL()
	{
	iCompletedSelf = EFalse;

	// If we get a positive error code such as KErrImapNO, leave now
	// which will mean that DoComplete is called with that error code.
	// This is derived from CMsgActive, so we should not get any negative
	// error codes. They will go straight to DoComplete.
	if (iStatus.Int() > KErrNone)
		{
		User::Leave(iStatus.Int());
		}

	switch(iState)
		{
		case EStateCreatingNetworkConnection:
			{
			ProcessNetworkConnectionCreatedL();
			break;
			}

		case EStateConnectingSocket:
			{
			ProcessSocketConnectedL();
			break;
			}

		case EStateUpgradingSSLWrappedSocket:
			{
			ProcessUpgradedSSLWrappedSocketL();
			break;
			}

		case EStateGreetingWait:
			{
			ProcessGreetingResponseL();
			break;
			}

		case EStateCapabilityWait:
			{
			ProcessCapabilityResponseL();
			break;
			}
	
		case EStateStartTLSWait:
			{
			ProcessStartTLSResponse();
			break;
			}

		case EStateUpgradingTLSSocket:
			{
			ProcessUpgradedTLSSocketL();
			break;
			}

		case EStateSecureCapabilityWait:
			{
			ProcessSecureCapabilityResponseL();
			break;
			}

		case EStateLoginWait:
			{
			ProcessLoginResponseL();
			break;
			}
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
		case EStateAuthenticateWait:
			{
			ProcessAuthenticateResponseL();
			break;
			}	
#endif
		case EStateSeparatorWait:
			{
			ProcessSeparatorResponseL();
			break;
			}

		case EStateLogoutWait:
			{
			ProcessLogoutResponse();
			break;
			}

		case EStateDisconnectingSocket:
			{
			ProcessSocketDisconnected();
			break;
			}

		case EStateGettingLastSocketActivityTimeout:
		default:
			{
			__ASSERT_DEBUG(ETrue, TImapServerPanic::ImapPanic(TImapServerPanic::EUnexpectedStateForDoRunL));
			break;
			}
		}
	}

/**
Cancels the current outstanding asynchronous operation.

The operation will only be cancelled here. Any cleanup is deferred to the
DoComplete method which will be called next as this is a CMsgActive.
*/
void CImapSessionManager::DoCancel()
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::DoCancel");

	switch(iState)
		{
		case EStateCreatingNetworkConnection:
			{
			iImConnect->Cancel();
			break;
			}

		case EStateConnectingSocket:
			{
			// We will call StopConnect() on the socket connector.
			// This is supposed to call back to HandleConnectError with
			// KErrCancel once it completes. However it appears that this
			// does not always happen, so it is safer to self complete here
			// with KErrCancel.
			// Just in case the HandleConnectError() routine does get called
			// we will set the iSocketConnector to NULL to indicate that the
			// request has been cancelled
			MSocketConnector* socketConnector(iSocketConnector);
			iSocketConnector = NULL;
			socketConnector->StopConnect();
			CompleteSelf(KErrCancel);
			break;
			}

		case EStateGreetingWait:
		case EStateCapabilityWait:
		case EStateStartTLSWait:
		case EStateSecureCapabilityWait:
		case EStateLoginWait:
		case EStateSeparatorWait:
		case EStateLogoutWait:
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
		case EStateAuthenticateWait:
#endif
			{
			iImapSession->Cancel();
			break;
			}

		case EStateUpgradingSSLWrappedSocket:
		case EStateUpgradingTLSSocket:
		case EStateDisconnectingSocket:
			{
			// There is no specific cancel for any of these states.
			// Just self complete with KErrCancel.
			CompleteSelf(KErrCancel);
			break;
			}

		case EStateGettingLastSocketActivityTimeout:
		default:
			{
			__ASSERT_DEBUG(ETrue, TImapServerPanic::ImapPanic(TImapServerPanic::EUnexpectedStateForDoCancel));
			CompleteSelf(KErrCancel);
			break;
			}
		}

	// Call base class version to notify parent class.
	CMsgActive::DoCancel();
	}

/**
The current outstanding asynchronous operation has completed.
Perform any necessary cleanup.

@param aStatus Status of completed operation.
*/
void CImapSessionManager::DoComplete(TInt& aStatus)
	{
	__LOG_FORMAT((LogId(), "CImapSessionManager::DoComplete - Error %d, State %d", aStatus, iState));

	iCompletedSelf = EFalse;
	
	// If we have completed with an error code, we may want to change it to a
	// more specific one based on the state we are in.
	if (aStatus != KErrNone)
		{
		ConvertError(aStatus);
		}

	// If we have completed with an error, handle any required cleanup
	if (aStatus != KErrNone)
		{
		switch (iState)
			{
			case EStateCreatingNetworkConnection:
				{
				delete iImConnect;
				iImConnect = NULL;
				break;
				}

			case EStateConnectingSocket:
				{
				// Nothing required.
				break;
				}

			case EStateGettingLastSocketActivityTimeout:
			case EStateUpgradingSSLWrappedSocket:
				{
				// Need to close the streams as we have not yet passed them
				// on to the session.
				// Closing the output stream will close the input stream too.
				if (iOutputStream != NULL)
					{
					// Since there was a error while upgrading socket to SSL/TLS.
					iErrorUpgradingSSLSocket = ETrue;
					iOutputStream->Close();
					}
				break;
				}

			case EStateUpgradingTLSSocket:
			case EStateGreetingWait:
			case EStateCapabilityWait:
			case EStateStartTLSWait:
			case EStateSecureCapabilityWait:
			case EStateLoginWait:
			case EStateSeparatorWait:
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
			case EStateAuthenticateWait:
#endif
				{
				// At this stage, the session has been created so we need
				// to close the streams and then delete the session.
				CloseSessionStreams(iImapSession);
				delete iImapSession;
				iImapSession = NULL;
				break;
				}

			case EStateLogoutWait:
			case EStateDisconnectingSocket:
				{
				// Immediately disconnect all the sessions remaining in the disconnect
				// list. This is because the protocol controller has no way of knowing
				// which ones are disconnected and which ones are not, so it is safer
				// just to disconnect them all.
				ImmediateDisconnect();
				break;
				}

			default:
				{
				__ASSERT_DEBUG(ETrue, TImapServerPanic::ImapPanic(TImapServerPanic::EUnexpectedStateForDoCompleteWithError));
				break;
				}
			}
		iProgressState = TImap4GenericProgress::EDisconnected;
		}
	else
		{
		iProgressState = TImap4GenericProgress::EIdle;
		}

	// If we have completed successfully, and we are performing a connect,
	// store the completed session information in the place requested by the
	// caller.
	// Note that iImapSession will only be non NULL for a successful connection
	// attempt.
	if (iImapSession != NULL)
		{
		*iStoreSession = iImapSession;
		iImapSession = NULL;
		}

	delete iHostAddr8;
	iHostAddr8 = NULL;

	iInputStream = NULL;
	iOutputStream = NULL;
	iSocketConnector = NULL;

	iState = EStateIdle;
	}

/**
State handler called when network connection has been created.
*/
void CImapSessionManager::ProcessNetworkConnectionCreatedL()
	{
	__LOG_TEXT(KDefaultLog, "CImapSessionManager::ProcessNetworkConnectionCreatedL");

	iCreatedNetworkConnection = ETrue;
	CreateSocketL();
	}

/**
State handler called when socket has been connected.
*/
void CImapSessionManager::ProcessSocketConnectedL()
	{
	__LOG_TEXT(KDefaultLog, "CImapSessionManager::ProcessSocketConnectedL");

	// Change state just in case the call to GetLastSocketActivityTimeout
	// returns an error and we leave. This allows the subsequent DoComplete
	// to clean up the connection
	iState = EStateGettingLastSocketActivityTimeout;
	User::LeaveIfError(iImConnect->GetLastSocketActivityTimeout(iLastSocketActivityTimeout));

	if (iImapSettings.SSLWrapper())
		{
		iState = EStateUpgradingSSLWrappedSocket;
		iErrorUpgradingSSLSocket = EFalse;
		CreateSecureSocket();
		}
	else
		{
		StartSessionL();
		}
	}

/**
State handler called when a socket has been upgraded to SSL.
*/
void CImapSessionManager::ProcessUpgradedSSLWrappedSocketL()
	{
	__LOG_TEXT(KDefaultLog, "CImapSessionManager::ProcessUpgradedSSLWrappedSocketL");

	StartSessionL();
	}

/**
State handler called when a greeting response has been received.
*/
void CImapSessionManager::ProcessGreetingResponseL()
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::ProcessGreetingResponseL");

	const CImapServerGreetingInfo& greetingInfo = iImapSession->ServerGreetingInfo();

	switch (greetingInfo.ResponseTag())
		{
		case CImapServerGreetingInfo::ETagOk:
			{
			__LOG_TEXT(LogId(), "CImapSessionManager::Greeting response OK");

			// Request capability information from server
			iImapSession->CapabilityL(iStatus);
			iState=EStateCapabilityWait;
			SetActive();
			break;
			}

		case CImapServerGreetingInfo::ETagPreAuth:
			{
			__LOG_TEXT(LogId(), "CImapSessionManager::Greeting response PreAuth");

			// Server has pre authenticated us. Just request hierarchy
			// separator and complete.
			GetHierarchySeparatorL();
			break;
			}

		case CImapServerGreetingInfo::ETagBye:
			{
			__LOG_TEXT(LogId(), "CImapSessionManager::Greeting response Bye");

			// Server is currently busy. Complete with an error
			Complete(KErrImapServerBusy);
			break;
			}
 
		default:
			{
			__ASSERT_DEBUG(ETrue, TImapServerPanic::ImapPanic(TImapServerPanic::EUnexpectedServerGreetingResponse));
			Complete(KErrCorrupt);
			break;
			}
		}
	}

/**
State handler called when a capability response has been received.
*/
void CImapSessionManager::ProcessCapabilityResponseL()
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::ProcessCapabilityResponseL");

	const CImapCapabilityInfo& capabilityInfo = iImapSession->CapabilityInfo();

	// Check that a valid IMAP revision number has been received
	if (!capabilityInfo.QueryFlag(CImapCapabilityInfo::EVersion4Rev1))
		{
		__LOG_TEXT(LogId(), "CImapSessionManager - Capability not IMAP4Rev1");

		Complete(KErrImapServerVersion);
		return;
		}

	// Check if we must use a secure socket connection
	if (iImapSettings.SecureSockets())
		{
		__LOG_TEXT(LogId(), "CImapSessionManager - Use secure socket");

		// We need a secure socket connection, so check that the server
		// supports TLS
		if (capabilityInfo.QueryFlag(CImapCapabilityInfo::EStartTls))
			{
			iImapSession->StartTlsL(iStatus);
			iState=EStateStartTLSWait;
			SetActive();
			}
		else
			{
			__LOG_TEXT(LogId(), "CImapSessionManager - Server does not support TLS");

			// Server does not support TLS, so complete with an error
			Complete(KErrImapServerNoSecurity);
			}
		}
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
	else if (iImapSettings.IMAP4Auth())
		{
		SelectAuthExtensionProfileL(capabilityInfo);
		}
#endif	
	else 
		{
		if (capabilityInfo.QueryFlag(CImapCapabilityInfo::ELoginDisabled))
			{
			__LOG_TEXT(LogId(), "CImapSessionManager - Login disabled");

			// The server does not support insecure logins, so complete with an error
			Complete(KErrImapServerLoginDisabled);
			}	
		else
			{
			// Just do plain login
			SendLoginL();	
			} 
		}
	}

/**
State handler called when the Start TLS response has been received.
*/
void CImapSessionManager::ProcessStartTLSResponse()
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::ProcessCapabilityResponseL");

	iState = EStateUpgradingTLSSocket;
	CreateSecureSocket();
	}

/**
State handler called when a socket has been upgraded to TLS.
*/
void CImapSessionManager::ProcessUpgradedTLSSocketL()
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::ProcessUpgradedTLSSocketL");

	iImapSession->CapabilityL(iStatus);
	iState=EStateSecureCapabilityWait;
	SetActive();
	}

/**
State handler called when a capability response has been received after
secure connection setup.
*/
void CImapSessionManager::ProcessSecureCapabilityResponseL()
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::ProcessSecureCapabilityResponseL");

#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)
	const CImapCapabilityInfo& capabilityInfo = iImapSession->CapabilityInfo();
	if (iImapSettings.IMAP4Auth())
		{
		SelectAuthExtensionProfileL(capabilityInfo);
		}
	else
#endif 
		{
		SendLoginL();	
		}
	}

/**
State handler called when a login response has been received.
*/
void CImapSessionManager::ProcessLoginResponseL()
	{
	__LOG_ON(LogId(), ETrue);
	__LOG_TEXT(LogId(), "CImapSessionManager::ProcessLoginResponseL");

	GetHierarchySeparatorL();
	}

/**
State handler called when a separator response has been received.
*/
void CImapSessionManager::ProcessSeparatorResponseL()
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::ProcessSeparatorResponseL");

	if ((iSeparatorFolderList.Count() > 0) &&
	    (iSeparatorFolderList[0]->iHierarchySeperator != '\0'))
		{
		iImapSettings.SetPathSeparatorL(iSeparatorFolderList[0]->iHierarchySeperator);
		}

	iSeparatorFolderList.ResetAndDestroy();

	Complete(KErrNone);
	}

/**
State handler called when a logout response has been received.
*/
void CImapSessionManager::ProcessLogoutResponse()
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::ProcessLogoutResponse");

	DisconnectSocket();
	}

/**
State handler called when a socket has been disconnected.
*/
void CImapSessionManager::ProcessSocketDisconnected()
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::ProcessSocketDisconnected");

	iDisconnectList.Remove(0);
	DisconnectFirstSessionInList();
	}

/**
Start creation of a network connection.
*/
void CImapSessionManager::CreateNetworkConnectionL()
	{
	__LOG_TEXT(KDefaultLog, "CImapSessionManager::CreateNetworkConnectionL");
	
	if (iSocketServ.Handle() == 0)
		{
		User::LeaveIfError(iSocketServ.Connect());
		}

	if (iImConnect == NULL)
		{
		iImConnect = CImConnect::NewL(iImapSettings.IAPPreferences(), iSocketServ);
		}

	iImConnect->StartL(iStatus);
	iState = EStateCreatingNetworkConnection;
	SetActive();
	}

/**
Gets the server address and password from the CImapSettings class and
establishes a socket to the server.
*/
void CImapSessionManager::CreateSocketL()
	{
	__LOG_TEXT(KDefaultLog, "CImapSessionManager::CreateSocket");

	if (iTransportManager == NULL)
		{
		iTransportManager = CTransportManager::NewL(iSocketServ.Handle(), iImConnect->GetConnection());
		}

	TPtrC hostAddress = iImapSettings.ServerAddress();
	TInt hostPort     = iImapSettings.Port();

	iHostAddr8 = CnvUtfConverter::ConvertFromUnicodeToUtf8L(hostAddress);	
	TPtr8 addr8ptr = iHostAddr8->Des();

	iSocketConnector = &(iTransportManager->SocketFactory().ConnectL(*this, addr8ptr, hostPort));

	iState = EStateConnectingSocket;
	iStatus = KRequestPending;
	SetActive();
	}

/**
Start creation of a secure socket.
*/
void CImapSessionManager::CreateSecureSocket()
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::CreateSecureSocket");

	iOutputStream->BindSecure(*this);

	TPtrC8 domainName;
	if (iImapSettings.TlsSslDomain().Length() > 0)
		{
		domainName.Set(iImapSettings.TlsSslDomain());
		}
	else
		{
		domainName.Set(*iHostAddr8);
		}

	iOutputStream->SecureClientReq(domainName);

	iStatus = KRequestPending;
	SetActive();
	}

/**
Create a new session, and wait for the greeting response from the IMAP server.
*/
void CImapSessionManager::StartSessionL()
	{
	__LOG_TEXT(KDefaultLog, "CImapSessionManager::StartSessionL");

	// Create the IMAP Session. 
	iImapSession = CImapSession::NewL(iImapSettings, iImapMailStore, *iInputStream, *iOutputStream);

	// Connected, request consumption of Server Greeting
	iState = EStateGreetingWait;
	iImapSession->ReadServerGreetingL(iStatus);
	SetActive();
	}

/**
Send a login command to the IMAP server.
*/
void CImapSessionManager::SendLoginL()
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::SendLoginL");
	
	// Turn off logging so that user name and password are hidden
	__LOG_ON(LogId(), EFalse);

	iImapSession->LoginL(iStatus, iImapSettings.LoginName(), iImapSettings.Password());
	iState = EStateLoginWait;
	SetActive();
	}

/**
Request the hierarchy separator from the IMAP server by sending a list command
with no folders specified.
*/
void CImapSessionManager::GetHierarchySeparatorL()
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::GetHierarchySeparatorL");

	// Always update the path separator on connection.
	iSeparatorFolderList.ResetAndDestroy();
	iImapSession->ListL(iStatus, KNullDesC(), KNullDesC(), iSeparatorFolderList);
	iState = EStateSeparatorWait;
	SetActive();
	}

/**
We are disconnecting a list of sessions. If there are any left in the list,
send a logout command for the first one.
*/
void CImapSessionManager::DisconnectFirstSessionInList()
	{
	if (iDisconnectList.Count() == 0)
		{
		iDisconnectList.Reset();
		Complete(KErrNone);
		return;
		}

	__LOG_TEXT(LogId(), "CImapSessionManager::Logout session");

	iState = EStateLogoutWait;
	TRAPD(err, iDisconnectList[0]->LogoutL(iStatus));

	if (err == KErrNone)
		{
		SetActive();
		}
	else
		{
		DisconnectSocket();
		}
	}

/**
Disconnect a socket.
*/
void CImapSessionManager::DisconnectSocket()
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::DisconnectSocket");

	// Just close the streams as this will delete the socket to.
	CloseSessionStreams(iDisconnectList[0]);

	iState = EStateDisconnectingSocket;

	// Complete ourselves to keep the state machine moving
	iStatus = KRequestPending;
	CompleteSelf(KErrNone);
	SetActive();
	}

/**
Immediately disconnect all the sessions in the disconnect list.
No logout command is sent. We just disconnect the socket.
*/
void CImapSessionManager::ImmediateDisconnect()
	{
	while (iDisconnectList.Count() > 0)
		{
		__LOG_TEXT(LogId(), "CImapSessionManager::ImmediateDisconnect");

		// Just close the streams as this will delete the socket to.
		CloseSessionStreams(iDisconnectList[0]);
		iDisconnectList.Remove(0);
		}

	iDisconnectList.Reset();
	}

/**
Close the session streams. This will also delete the streams and their
associated socket.
*/
void CImapSessionManager::CloseSessionStreams(CImapSession* aSession)
	{
	__LOG_TEXT(aSession->LogId(), "CImapSessionManager::CloseSessionStreams");

	// Closing the output stream will close the input stream too.
	MOutputStream* outputStream = aSession->OutputStream();

	if (outputStream != NULL)
		{
		outputStream->Close();
		}
	}

/**
Gets the connection stage information from the connection.

@return Connection stage or error code.
*/
TInt CImapSessionManager::GetConnectionStage()
	{
	if (iImConnect)
		{
		TNifProgress progress;
		TInt err = iImConnect->Progress(progress);
		return (err == KErrNone) ? (progress.iStage) : (err);
		
		}

	return KErrNotFound;
	}

/**
Converts an error code to one that is more meaningful for the state we are in.

@param aError Error code to convert.
*/
void CImapSessionManager::ConvertError(TInt& aError)
	{
	if (aError <= KErrNone)
		{
		return;
		}

	switch (iState)
		{
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)			

		case EStateCapabilityWait:
			{
			__LOG_TEXT(LogId(), "CImapSessionManager - Convert error to bad  authenticate");
			aError = KErrImapAuthenticationFailed;
			break;
			}
#endif
		case EStateLoginWait:
			{
			__LOG_TEXT(LogId(), "CImapSessionManager - Convert error to bad logon");
			aError = KErrImapBadLogon;
			break;
			}
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)			
		case EStateAuthenticateWait:
			{
			__LOG_TEXT(LogId(), "CImapSessionManager - Convert error to bad logon");
			aError = KErrImapBadLogon;
			break;
			}
#endif
		case EStateStartTLSWait:
			{
			__LOG_TEXT(LogId(), "CImapSessionManager - Convert error to TLS negotiate failed");
			aError = KErrImapTLSNegotiateFailed;
			break;
			}

		case EStateLogoutWait:
		case EStateDisconnectingSocket:
			{
			// Do nothing
			break;
			}

		default:
			{
			__LOG_TEXT(LogId(), "CImapSessionManager - Convert error to Imap connect error");
			// This is a postive error code (defined in cimapsession, thus allowing further
			// handling in the protocol controller. Specifically, this is used in the case
			// of initial connection failure where brearer mobility is configured.
			aError = KErrImapConnectError;
			break;
			}
		}
	}

/**
Self complete with specified error code.

@param aError Error code to complete with.
*/
void CImapSessionManager::CompleteSelf(TInt aError)
	{
	// We use callbacks from the transport handler to receive error
	// notifications and then self complete with those error codes. As it is
	// possible to receive more than one callback at a time, we have to make
	// sure we don't try to self complete more than once.
	if (!iCompletedSelf)
		{
		TRequestStatus* status = &iStatus;
		User::RequestComplete(status, aError);
		iCompletedSelf = ETrue;
		}
	}

/**
Gets the log ID to use for debug logging.

@return Log ID
*/
TInt CImapSessionManager::LogId()
	{
#ifdef __IMAP_LOGGING
	if (iImapSession)
		{
		return iImapSession->LogId();
		}
	else if (iDisconnectList.Count() > 0)
		{
		return iDisconnectList[0]->LogId();
		}
#endif //__IMAP_LOGGING
	return KDefaultLog;
	}

/**
Returns ETrue if the session manager has access to an RConnection

@return TBool
*/
EXPORT_C TBool CImapSessionManager::HasConnection()
	{
	if (iImConnect)
		{
		return ETrue;
		}

	return EFalse;
	}

/**
Gets the RConnection used for all the sessions

@return RConnection reference
*/
EXPORT_C RConnection& CImapSessionManager::GetConnectionL()
	{
	__ASSERT_DEBUG(iImConnect,
	               TImapServerPanic::ImapPanic(TImapServerPanic::ERConnectionNotDefined));

	if (!iImConnect)
		{
		User::Leave(KErrNotFound);		
		}

	return iImConnect->GetConnection();
	}

/**
Gets the access point ID in use for the connection to the server

@param aAccessPointId On return stores the access point ID value

@return KErrNone if successful, or a system wide error code
*/
EXPORT_C TInt CImapSessionManager::GetAccessPointIdForConnection(TUint32& aAccessPointId) const
	{
	if (iImConnect)
		{
		return iImConnect->GetIAPValue(aAccessPointId);
		}

	return KErrNotFound;
	}
	
/**
Close the current network connection since all the sessions will be closed before this
*/
EXPORT_C void CImapSessionManager::CloseNetworkConnection()
 	{
 	iCreatedNetworkConnection = EFalse;	
	delete iTransportManager;
	iTransportManager = NULL;
 	delete iImConnect;
	iImConnect = NULL ;
 	iSocketServ.Close();
 	}

/**
Send a Authenticate  command to the IMAP server.
@param aCurrentAuthProfile
*/
#if (defined SYMBIAN_EMAIL_CAPABILITY_SUPPORT)

void CImapSessionManager::SendAuthenticateL(CImapAuthMechanismHelper::TImapAuthProfileFlag aCurrentAuthProfile) 
	{
	__LOG_TEXT(LogId(), "CImapSessionManager::SendAuthenticateL");
	
	iImapSession->AuthenticateL(iStatus,aCurrentAuthProfile);

	iState =  EStateAuthenticateWait;
	SetActive();
	}

/**
State handler called when a authenticate response has been received.
*/
void CImapSessionManager::ProcessAuthenticateResponseL()
	{
	__LOG_ON(LogId(), ETrue);
	__LOG_TEXT(LogId(), "CImapSessionManager::ProcessLoginResponseL");

	GetHierarchySeparatorL();
	}

/**
Selects  Authentication extension profile depending on authentication and fallback flags
@param aCapabilityInfo
*/	
void CImapSessionManager::SelectAuthExtensionProfileL(const CImapCapabilityInfo& aCapabilityInfo)
	{
	if (aCapabilityInfo.QueryFlag(CImapCapabilityInfo::EAuthCramMd5))	
		{
		__LOG_TEXT(LogId(), "CImapSessionManager - ECramMD5");
		iCurrentAuthProfile = CImapAuthMechanismHelper::ECramMD5;
		SendAuthenticateL(iCurrentAuthProfile);
		}
	else if(iImapSettings.FallBack())
    	{
      	if (aCapabilityInfo.QueryFlag(CImapCapabilityInfo::EAuthPlain))
      		{
      		__LOG_TEXT(LogId(), "CImapSessionManager - EAuthPlain");
      		iCurrentAuthProfile=CImapAuthMechanismHelper::EPlain;
			SendAuthenticateL(iCurrentAuthProfile);
      		}
		else if (aCapabilityInfo.QueryFlag(CImapCapabilityInfo::EAuthLogin))
			{
			__LOG_TEXT(LogId(), "CImapSessionManager - ELogin");
			iCurrentAuthProfile = CImapAuthMechanismHelper::ELogin;
			SendAuthenticateL(iCurrentAuthProfile);
			}
    	else
			{
			__LOG_TEXT(LogId(), "CImapSessionManager - ELogin");
			SendLoginL();	
			}
		}
	else 
		{
		TInt status = 1;
		Complete(status);
		}
	}
#endif
