// 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:
//

/**
 @file
 @internalTechnology
 @prototype
*/

#include <comms-infras/ss_log.h>
#include <elements/sm_core.h>
#include <comms-infras/linkmessages.h>
#include <elements/nm_messages_base.h>

#include <comms-infras/ss_nodemessages_dataclient.h>

#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS
#include <comms-infras/nifagt_internal.h>
#endif

#include "agentscpr.h"
#include "agentscprstates.h"
#include "agentscpractivities.h"
#include "CAgentAdapter.h"
#include "agentmessages.h"

#include <comms-infras/ss_msgintercept.h>


#ifdef _DEBUG
// Panic category for "absolutely impossible!" vanilla ASSERT()-type panics from this module
// (if it could happen through user error then you should give it an explicit, documented, category + code)
_LIT(KSpecAssert_NifManAgtPrCgnts, "NifManAgtPrCgnts");
#endif

#if defined __CFLOG_ACTIVE || defined SYMBIAN_TRACE_ENABLE
#define KAgentSCprTag KESockSubConnectionTag
_LIT8(KAgentSCprSubTag, "agentscpr");
#endif


using namespace Messages;
using namespace MeshMachine;
using namespace ESock;
using namespace NetStateMachine;

//We reserve space for two preallocated activities that may start concurrently on the SCPR
//node: destroy and data client stop.
static const TUint KDefaultMaxPreallocatedActivityCount = 2;
static const TUint KMaxPreallocatedActivitySize = sizeof(MeshMachine::CNodeRetryParallelActivity) + sizeof(MeshMachine::APreallocatedOriginators<4>);
static const TUint KAgentSCPRPreallocatedActivityBufferSize = KDefaultMaxPreallocatedActivityCount * KMaxPreallocatedActivitySize;

/**
Creates an Agent SubConnection Provider
@param aFactory The parent factory which has created the SCPr
@return Pointer to the newly created SCPr
*/
EXPORT_C CAgentSubConnectionProvider* CAgentSubConnectionProvider::NewL(ESock::CSubConnectionProviderFactoryBase& aFactory)
	{
	CAgentSubConnectionProvider* self = new (ELeave) CAgentSubConnectionProvider(aFactory, AgentSCprActivities::agentSCprActivities::Self());
	CleanupStack::PushL(self);
	self->ConstructL(KAgentSCPRPreallocatedActivityBufferSize);
	CleanupStack::Pop(self);
	return self;
	}


/**
Constructor for the Agent SubConnection Provider
@param aFactory The parent factory which created this SCPr
*/
EXPORT_C CAgentSubConnectionProvider::CAgentSubConnectionProvider(CSubConnectionProviderFactoryBase& aFactory,
    const MeshMachine::TNodeActivityMap& aActivityMap)
   : CCoreSubConnectionProvider(aFactory, aActivityMap)
	{
	LOG_NODE_CREATE(KAgentSCprTag, CAgentSubConnectionProvider);
	}


/**
D'tor
*/
EXPORT_C CAgentSubConnectionProvider::~CAgentSubConnectionProvider()
    {
	CleanupProvisioningInfo();
    LOG_NODE_DESTROY(KAgentSCprTag, CAgentSubConnectionProvider);
	}


EXPORT_C void CAgentSubConnectionProvider::CleanupProvisioningInfo()
    {
    if (iAuthenticateInProgress && AgentProvisionInfo()->AgentAdapter())
	   {
	   iAuthenticateInProgress = EFalse;
	   AgentProvisionInfo()->AgentAdapter()->CancelAuthenticate();
	   }

	CAgentAdapter* agentAdapter(AgentProvisionInfo()->AgentAdapter());
	const_cast<CAgentProvisionInfo*>(AgentProvisionInfo())->SetAgentAdapter(NULL);
	delete agentAdapter;

	// Remove ourselves from the notification handler, or delete it entirely if we own it
	CAgentNotificationHandler* handler = AgentProvisionInfo()->AgentNotificationHandler();
	if (handler)
	    {
	    if (iScprOwnedNotificationHandler)
	        {
	        const_cast<CAgentProvisionInfo*>(AgentProvisionInfo())->SetAgentNotificationHandler(NULL);
	        delete handler;
	        }
        else
            {
            handler->Initialise(NULL);
            }
	    }
    }

/**
Mesh machine message entry point
*/
EXPORT_C void CAgentSubConnectionProvider::ReceivedL(const TRuntimeCtxId& aSender, const TNodeId& aRecipient, TSignatureBase& aMessage)
	{
	ESOCK_DEBUG_MESSAGE_INTERCEPT(aSender, aMessage, aRecipient);

	TNodeContext<CAgentSubConnectionProvider> ctx(*this, aMessage, aSender, aRecipient);
	CCoreSubConnectionProvider::Received(ctx);
	User::LeaveIfError(ctx.iReturn);
	}


TInt CAgentSubConnectionProvider::PostMessageToFlow(const TRuntimeCtxId& aSender, const TSignatureBase& aMessage)
   {
   // There can only ever be one flow for an AgentSCpr
   RNodeInterface* dataClient = this->GetFirstClient<TDefaultClientMatchPolicy>(TClientType(TCFClientType::EData));
   if (!dataClient)
      {
      return KErrNotFound;
      }
   dataClient->PostMessage(aSender, aMessage);
   return KErrNone;
   }


/**
Retrieves the Agent Provider specific provisioning information as given by the MCPr
transition.

@internalTechnology
*/
EXPORT_C const CAgentProvisionInfo* CAgentSubConnectionProvider::AgentProvisionInfo() const
    {
    const CAgentProvisionInfo* agentProvisionInfo = static_cast<const CAgentProvisionInfo*>(AccessPointConfig().FindExtension(CAgentProvisionInfo::TypeId()));
	__ASSERT_DEBUG(agentProvisionInfo, User::Panic(KSpecAssert_NifManAgtPrCgnts, 1));
    return agentProvisionInfo;
    }


/**
Starts the agent connection process
*/
EXPORT_C void CAgentSubConnectionProvider::ConnectAgent(TAgentConnectType aConnectType)
    {
    AgentProvisionInfo()->AgentAdapter()->ConnectAgent(aConnectType);
    }


/**
Notifies an event to the AgentSCpr's Agent via the AgentAdapter
*/
EXPORT_C TInt CAgentSubConnectionProvider::NotificationToAgent(TFlowToAgentEventType aEvent, TAny* aInfo)
    {
    return AgentProvisionInfo()->AgentAdapter()->NotificationToAgent(aEvent, aInfo);
    }


/**
Called from the StartAgent state transition
*/
void CAgentSubConnectionProvider::StartAgentL()
    {
    SetActivityIdForAdapter(ECFActivityStartDataClient);

    if (!AgentProvisionInfo()->AgentAdapter())
        {
        CAgentNotificationHandler* handler = AgentProvisionInfo()->AgentNotificationHandler();
        if (!handler)
            {
            // Ensure something is present to forward msgs to the flow
            // Normally the MCPr will own the handler, we must only delete if we created it
            handler = CAgentNotificationHandler::NewL();
            iScprOwnedNotificationHandler = ETrue;
            const_cast<CAgentProvisionInfo*>(AgentProvisionInfo())->SetAgentNotificationHandler(handler);
            }
        handler->Initialise (this);

        const_cast<CAgentProvisionInfo*>(AgentProvisionInfo())->SetAgentAdapter (CAgentAdapter::NewL(*this, AgentProvisionInfo()->AgentName()));
        }

	CAgentAdapter::TAgentState agtState = AgentProvisionInfo()->AgentAdapter()->AgentState();

    // Should never get a TStart whilst we are disconnecting
    __ASSERT_DEBUG(agtState != CAgentAdapter::EDisconnecting, User::Panic(KSpecAssert_NifManAgtPrCgnts, 2));

	if (agtState == CAgentAdapter::EDisconnected)
        {
        ConnectAgent(EAgentStartDialOut);
        }
    else if (agtState == CAgentAdapter::EConnected)
        {
        RClientInterface::OpenPostMessageClose(Id(), TNodeCtxId(ECFActivityStartDataClient, Id()), TCFServiceProvider::TStarted().CRef());
        iActivityIdForAdapter = KActivityNull;
        }
    // else if AgentState() is EConnecting or EReconnecting we wait for the TStarted
    //      from that operation
   }

/**
Called from the StopAgent state transition
*/
void CAgentSubConnectionProvider::StopAgent(TInt aReason)
    {
    // Agent should have been started, so the agent adapter should exist
    __ASSERT_DEBUG(AgentProvisionInfo()->AgentAdapter(), User::Panic(KSpecAssert_NifManAgtPrCgnts, 3));

    if (AgentProvisionInfo()->AgentAdapter()->AgentState() == CAgentAdapter::EDisconnected)
        {
        RClientInterface::OpenPostMessageClose(Id(), TNodeCtxId(iActivityIdForAdapter, Id()), TCFDataClient::TStopped(KErrDisconnected).CRef());
        iActivityIdForAdapter = KActivityNull;
        }
    else
        {
        iStopRequested = ETrue;
        iStoppingReason = aReason;
        AgentProvisionInfo()->AgentAdapter()->DisconnectAgent(aReason);
        }
    }

/**
Upcall from the agent
*/
void CAgentSubConnectionProvider::ServiceStarted()
   {
   CAgentNotificationHandler* handler = AgentProvisionInfo()->AgentNotificationHandler();
   if (handler)
      {
      handler->ServiceStarted ();
      }
   }


/**
Called from the CAgentAdapter. Gets information required by flow into a TCommsBinder
and posts it into this node's mesh machine with a TStarted message
*/
void CAgentSubConnectionProvider::ConnectionUpL()
	{
	__CFLOG_VAR((KAgentSCprTag, KAgentSCprSubTag, _L8("CAgentSubConnectionProvider::ConnectionUpL() - Agent has started")));

    CAgentNotificationHandler* handler = AgentProvisionInfo()->AgentNotificationHandler();
    if (handler)
        {
        handler->ConnectCompleteL();
        }

    // Send connection up message to the SCPr
    RClientInterface::OpenPostMessageClose(Id(), TNodeCtxId(ECFActivityStartDataClient, Id()), TCFServiceProvider::TStarted().CRef());
    iActivityIdForAdapter = KActivityNull;
	}



/**
Called from the CAgentAdapter. Indicates that the agent is now disconnected.
*/
void CAgentSubConnectionProvider::ConnectionDownL()
	{
	// Only send ourselves a DataClientStopped message if we really want to forward
	// it up. That translates to: have we been asked to StopConnection and are
	// we expecting the agent to disconnect, if so then kick the mesh machine
	// with a TStopped message
	if (iStopRequested)
	    {
    	__CFLOG_VAR((KAgentSCprTag, KAgentSCprSubTag, _L8("CAgentSubConnectionProvider::ConnectionDownL() - Agent has stopped by request")));
    	// Send connection down message to self (this is really like the TStopped message coming up from a lower layer)

    	RClientInterface::OpenPostMessageClose(Id(), TNodeCtxId(iActivityIdForAdapter, Id()), TCFServiceProvider::TStopped(iStoppingReason).CRef());
    	iActivityIdForAdapter = KActivityNull;
    	iStopRequested = EFalse;
	    }
	else
	    {
	    // Agent has stopped and disconnected without this node telling it to
	    // Could be an error in the agent initiating a disconnect
    	__CFLOG_VAR((KAgentSCprTag, KAgentSCprSubTag, _L8("CAgentSubConnectionProvider::ConnectionDownL() - Agent has stopped unexpectedly")));
	    ControlProvider()->PostMessage(Id(), TCFControlProvider::TDataClientGoneDown(KErrDisconnected).CRef());
	    }
	}


/**
Called from the CAgentAdapter. Indicates that the agent has finished getting
authentication data by whatever means it chooses.
*/
void CAgentSubConnectionProvider::AuthenticateCompleteL(TInt aStatus)
   {
   __ASSERT_DEBUG(iActivityIdForAdapter == ECFActivityAuthentication, User::Panic(KSpecAssert_NifManAgtPrCgnts, 4));

   CCredentialsConfig* credentials = AgentProvisionInfo()->Credentials();
   credentials->SetResult (aStatus);

   if (aStatus == KErrNone)
      {
      credentials->SetUserName (iUsername);
      credentials->SetPassword (iPassword);
      }
      else
      {
      credentials->SetUserName (_L(""));
      credentials->SetPassword (_L(""));
      }

   RClientInterface::OpenPostMessageClose(Id(), TNodeCtxId(ECFActivityAuthentication, Id()), TLinkMessage::TAuthenticateResponse().CRef());
   iAuthenticateInProgress = EFalse;
   iActivityIdForAdapter = KActivityNull;
   }


/**
Called from the CAgentAdapter. Indicates that the agent has had a response from
the user deciding whether or not to reconnect
*/
void CAgentSubConnectionProvider::PromptForReconnectComplete(TInt aStatus)
    {
    if (aStatus == KErrNone)
        {
        // Response was to reconnect
        ConnectAgent(EAgentReconnect);
        }
    else
        {
        AgentProvisionInfo()->AgentAdapter()->DisconnectAgent(aStatus);
        }
    }


/**
Posts the progress to the Control Client (SCPr above). There should only ever be
one Control Client for this SCPr.
*/
void CAgentSubConnectionProvider::ProgressL(TInt aStage)
    {
    iLastProgress.iStage = aStage;
    iLastProgress.iError = KErrNone;
    RClientInterface::OpenPostMessageClose(Id(), Id(),
    	TCFMessage::TStateChange(iLastProgress).CRef());
    }



/**
Posts the error to the control clients (Up)
*/
void CAgentSubConnectionProvider::Error(const Elements::TStateChange& aProgress)
    {
    __ASSERT_DEBUG(aProgress.iError, User::Panic(KSpecAssert_NifManAgtPrCgnts, 5));

    iLastProgress = aProgress;
    if (iActivityIdForAdapter != KActivityNull)
        {
        RClientInterface::OpenPostMessageClose(Id(), TNodeCtxId(iActivityIdForAdapter, Id()),
        	TEBase::TError(TCFDataClient::TStart::Id(), iLastProgress.iError).CRef());
        iActivityIdForAdapter = KActivityNull;
        }

    if (CountActivities(ECFActivityStartDataClient))
        {
        __ASSERT_DEBUG(CountActivities(ECFActivityStartDataClient) == 1, User::Panic(KSpecAssert_NifManAgtPrCgnts, 6));
        MeshMachine::CNodeActivityBase* startDataClientActivty = FindActivityById(ECFActivityStartDataClient);
        startDataClientActivty->SetError(iLastProgress.iError);
        TEBase::TCancel cancelMsg;
		TNodeCtxId sender(startDataClientActivty->ActivityId(), Id());
		TNodeCtxId recipient(0, Id());
	    TNodeContext<CAgentSubConnectionProvider> ctx(*this, cancelMsg, sender, recipient);
        startDataClientActivty->Cancel(ctx);
        }
    }


void CAgentSubConnectionProvider::NetworkAdaptorEvent(TNetworkAdaptorEventType aEventType, TUint aEvent, const TDesC8& aEventData, TAny* /*aSource*/)
    {
	if(aEventType == EAgentOriginatedConnectionCommand)
	// added to allow agent to generate connection control commands, such as Stop()
		{
		if(aEvent == EAgentConnectionCommandStop)
			{
			__ASSERT_DEBUG(aEventData.Length()==sizeof(TInt), User::Panic(KSpecAssert_NifManAgtPrCgnts, 7));
			TInt errorCode = *(reinterpret_cast<const TInt*>(aEventData.Ptr()));
			CancelStartOrSendStopToSelf(errorCode);
			}
		}
	else if(aEventType == EEtelEvent)
		{
		if (aEvent == ECurrentNetworkChangeEvent)
			{
			// This is a very specific event for CDMA2000 which is essentially for backwards
			// compatibility with old architecture.  The PSD Agent generates this event to indicate
			// a zone change.  This event needs to be propagated to the Network Config Daemon (NetCfgExt),
			// which is done here.  Technically, the PSD Agent should not be listening for this
			// specific ETel event - the NetCfgExt should be listening directly for the event and handling it
			// itself.  However, this change isn't currently the case hence this forwarding code is present.
			// This should be removed if zone change events are (ever) moved from PSD Agent to NetCfg Daemon.
			TLinkMessage::TAgentEventNotification msg(aEventType, aEvent);
	        PostToClients<TDefaultClientMatchPolicy>(Id(), msg, TClientType(TCFClientType::ECtrl));
			}
		}
    }

void CAgentSubConnectionProvider:: CancelStartOrSendStopToSelf(TInt aError)
/**
Issue a TCFDataClient::TStop message to ourselves.

Used to initiate the stop activity internally when requested from an Agent.

@param aError error code in TCFDataClient::TStop message.
*/
	{
#ifdef SYMBIAN_NETWORKING_CSDAGENT_BCA_SUPPORT	
	if(CountActivities(ECFActivityStartDataClient))
		{
		Elements::TStateChange aProgress;
		aProgress.iError=aError;
		Error(aProgress);
		}
	else
		{
		RClientInterface::OpenPostMessageClose(Id(), Id(), TCFDataClient::TStop(aError).CRef());
		}
#else
		RClientInterface::OpenPostMessageClose(Id(), Id(), TCFDataClient::TStop(aError).CRef());
#endif	
	}

TInt CAgentSubConnectionProvider::NotificationFromAgent(TAgentToFlowEventType aEvent, TAny* aInfo)
    {
    if (aEvent == EAgentToNifEventTypeDisableConnection)
    	{
#ifdef SYMBIAN_NETWORKING_CSDAGENT_BCA_SUPPORT		
		TInt errorCode = KErrCancel;
		if (NULL != aInfo)
			{			
			errorCode = reinterpret_cast<const TInt>(aInfo);
			__ASSERT_DEBUG(errorCode < KErrNone, User::Panic(KSpecAssert_NifManAgtPrCgnts, 8));
			}
		// Request from agent to disconnect using error code specified from agent
		CancelStartOrSendStopToSelf(errorCode);
#else
		CancelStartOrSendStopToSelf(KErrCancel);
#endif
    	}
    else
    	{
	    CAgentNotificationHandler* handler = AgentProvisionInfo()->AgentNotificationHandler();
	    if (handler)
	        {
	        return handler->NotificationFromAgent(aEvent, aInfo);
	        }
    	}

    return KErrNotSupported;
    }


void CAgentSubConnectionProvider::NotificationFromFlow(TFlowToAgentEventType aEvent)
    {
   	CAgentNotificationHandler* handler = AgentProvisionInfo()->AgentNotificationHandler();
   	if (handler)
       	{
       	handler->NotificationFromFlow(aEvent);
       	}
    }

