/*
* Copyright (c) 2008 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 "pushserverconnpluginmanager.h"
#include "logger.h"
#include "applicationstatecontroller.h"
#include "serverconnectionfactory.h"
#include "serverconnection.h"
#include "pusherrorcodes.h"
#include "pushexception.h"
#include "pushconstant.h"

using namespace java::push;
using namespace java::util;
using namespace java::runtime;

/**
 *
 */
PushServerConnPluginManager::PushServerConnPluginManager
(ConnectionListener* aListener,PendingConnectionListener* aPendingConnListener,
 ConnManagedInterface* aConnManagedInterface)
        : mConnListener(aListener),mPendingConnListener(aPendingConnListener),
        mConnManagedInterface(aConnManagedInterface)
{
    JELOG2(EJavaPush);
}

/**
 *
 */
PushServerConnPluginManager::~PushServerConnPluginManager()
{
    JELOG2(EJavaPush);
    try
    {
        for (SrvConnContainerIter_t iter = mSrvConnContainer.begin(); iter != mSrvConnContainer.end(); ++iter)
        {
            iter->second.mServerConnFactory.closeAllConnections();
            delete(iter->second.mSrvConnLibAccess);
            iter->second.mSrvConnLibAccess = 0;
        }
        mSrvConnContainer.clear();
    }
    catch (...)
    {
        ELOG(EJavaPush,"ERROR!!! ~PushServerConnPluginManager(): Unexpected exception was caught");
    }
}

/**
 *
 */
void PushServerConnPluginManager::startListen(UriList_t& aUriList,bool aThrowException,
        bool aCallConnectionHandled)
{
    JELOG2(EJavaPush);

    if (0 == aUriList.size())
        return;

    for (UriListIter_t iter = aUriList.begin(); iter != aUriList.end(); ++iter)
    {
        try
        {
            LOG1WSTR(EJavaPush,EInfo, "startListen() URI: %s",iter->mUri);
            std::wstring schemeStr;
            loadAndStoreDll(iter->mUri,schemeStr);
            createAndOpenConn((*iter),schemeStr);
            if (true == aCallConnectionHandled)
                mConnManagedInterface->connCompleted(iter->mUri);
        }
        catch (ExceptionBase& ex)
        {
            //std::exception is caught here to get a debug print.
            ELOG1(EJavaPush,"ERROR!!! PushServerConnPluginManager::startListen(): %s",
                  ex.toString().c_str());
            if (true == aThrowException)
                throw;
        }
        catch (...)
        {
            //Exception is not thrown from this situation. It is better just ignore unsupported
            //URI.
            ELOG(EJavaPush,
                 "ERROR!!! PushServerConnPluginManager::startListen() Cannot open plugin for push URI");
            if (true == aThrowException)
                throw;
        }
    }//end for

    if (0 == getNumOfSuccessfulPushConns())
    {
        throw PushException(INIT_OF_ALL_PUSH_CONNS_FAILED,"Creation of all push connections failed",
                            __FILE__,__FUNCTION__,__LINE__);
    }
}

/**
 *
 */
void PushServerConnPluginManager::updatePushRegs(UriList_t& aUriList,
        PushControllerErrorHandlerInterface& aErrorHandler)
{
    JELOG2(EJavaPush);

    //First is retrieved current connections. This is needed to find out whether
    //push connection was registered or unregistered.
    std::list<std::wstring> existingUriList;
    listConnections(false,existingUriList);

    //We can stop execution of this operation if size of both list is equal.
    //This should not never happened.
    if (aUriList.size() == existingUriList.size())
        return;

    if (aUriList.size() < existingUriList.size())
    {
        //In this case one push req has been removed from other MIDlet.
        findAndDeleteUnregPushConns(existingUriList,aUriList,aErrorHandler);
    }
    else if (aUriList.size() > existingUriList.size())
    {
        //In this case push req has been added to the MIDlet from the other MIDlet.
        findAndAddNewPushConns(existingUriList,aUriList);
    }
}

/**
 *
 */
void PushServerConnPluginManager::listConnections(bool aAvailable,
        std::list<std::wstring>& aUriList)
{
    JELOG2(EJavaPush);
    for (SrvConnContainerIter_t iter = mSrvConnContainer.begin(); iter != mSrvConnContainer.end(); ++iter)
    {
        iter->second.mServerConnFactory.getPushConnections(aAvailable,aUriList);
    }//end for
}

/**
 *
 */
ServerConnection& PushServerConnPluginManager::getPushConnection(const std::wstring& aUri)
{
    JELOG2(EJavaPush);
    std::wstring schemeStr = resolveScheme(aUri);
    ServerConnectionFactory* srvConnFac = getDllFromContainer(schemeStr);
    if (0 == srvConnFac)
    {
        std::string errTxt("Server Connection Plugin by URI is not found");
        ELOG1(EJavaPush, "ERROR!!! %s",errTxt.c_str());
        throw PushException(SRV_CONN_NOT_FOUND,errTxt,__FILE__,__FUNCTION__,__LINE__);
    }
    ServerConnection* srvConn = srvConnFac->getPushConnection(aUri);
    if (0 == srvConn)
    {
        std::string errTxt("ServerConnection object by URI is not found");
        ELOG1(EJavaPush, "ERROR!!! %s",errTxt.c_str());
        throw PushException(SRV_CONN_NOT_FOUND,errTxt,__FILE__,__FUNCTION__,__LINE__);
    }
    return *srvConn;
}

/**
 *
 */
void PushServerConnPluginManager::unregisterDynamicPushConn(const std::wstring& aUri)
{
    JELOG2(EJavaPush);

    std::wstring schemeStr = resolveScheme(aUri);
    ServerConnectionFactory* srvConnFac = getDllFromContainer(schemeStr);
    if (0 != srvConnFac)
        srvConnFac->deletePushConnection(aUri);
    //Unregisterd connection must be removed from "is connection opened successfully"
    //list.
    mSuccessPushConnList.erase(aUri);
}

/**
 *
 */
int PushServerConnPluginManager::setConnFailed(const std::wstring& aUri)
{
    JELOG2(EJavaPush);
    ScopedLock lockObj(mMutex);

    SuccessPushConnListIter_t iter = mSuccessPushConnList.find(aUri);
    if (iter != mSuccessPushConnList.end())
    {
        iter->second = false;
    }
    return getNumOfSuccessfulPushConns();
}

/**
 *
 */
void PushServerConnPluginManager::deletePushServerConn(const std::wstring& aUri)
{
    JELOG2(EJavaPush);

    try
    {
        std::wstring schemeStr = resolveScheme(aUri);
        ServerConnectionFactory* srvConnFac = getDllFromContainer(schemeStr);
        if (0 != srvConnFac)
            srvConnFac->deletePushConnection(aUri);
    }
    catch (...)
    {
        ELOG(EJavaPush,"ERROR!!! deletePushServerConn() Unexpected exception was caught");
    }
}

/**
 *
 */
bool PushServerConnPluginManager::isValidPushUri(const std::wstring& aUri)
{
    JELOG2(EJavaPush);

    ServerConnection* srvConn = 0;
    std::wstring schemeStr;
    try
    {
        ServerConnectionFactory& srvConnFac = loadAndStoreDll(aUri,schemeStr);

        //This checks that push connetion is not reserved by this MIDlet.
        srvConn = srvConnFac.getPushConnection(aUri);
        if (0 != srvConn)
            return false;

        //If creation of the server connection succeeds then URI is unique.
        //Connection is closed immediately.
        srvConn = srvConnFac.create(aUri);
        srvConn->open(mConnListener);
        srvConn->close();
        srvConnFac.releaseConnection(aUri);
        return true;
    }
    catch (...)
    {
        ELOG(EJavaPush,"ERROR!!! checkUniquenessOfUri() Exception was caught");
        try
        {
            if (0 != srvConn)
            {
                srvConn->close();
                ServerConnectionFactory& srvConnFac = loadAndStoreDll(aUri,schemeStr);
                srvConnFac.releaseConnection(aUri);
            }
        }
        catch (...) {}
        return false;
    }
}

/**
 *
 */
ServerConnectionFactory* PushServerConnPluginManager::getDllFromContainer(const std::wstring& aScheme)
{
    JELOG2(EJavaPush);

    SrvConnContainerIter_t iter = mSrvConnContainer.find(aScheme);
    if (iter != mSrvConnContainer.end())
        return &(iter->second.mServerConnFactory);
    return 0;
}

/**
 *
 */
ServerConnectionFactory& PushServerConnPluginManager::loadAndStoreDll(const std::wstring& aUri,
        std::wstring& aOutScheme)
{
    JELOG2(EJavaPush);
    aOutScheme = resolveScheme(aUri);
    ServerConnectionFactory* srvConnFac = getDllFromContainer(aOutScheme);
    if (0 != srvConnFac)
        return *srvConnFac;

    std::string dllName = resolveDllName(aOutScheme);
    std::auto_ptr<java::util::DynamicLibLoader> tmpLoader(0);
    ServerConnectionFactory& conn =
        ServerConnectionFactory::getServerConnectionFactory(dllName,tmpLoader);

    ServerConnectionPluginData pluginData(tmpLoader.release(),conn);
    std::pair<std::wstring,ServerConnectionPluginData> pairObj(aOutScheme,pluginData);
    mSrvConnContainer.insert(pairObj);
    return conn;
}

/**
 *
 */
void PushServerConnPluginManager::createAndOpenConn(const PushRegData& aData,
        const std::wstring& aScheme)
{
    JELOG2(EJavaPush);

    SrvConnContainerIter_t connIter;
    try
    {
        connIter = mSrvConnContainer.find(aScheme);
        if (mSrvConnContainer.end() == connIter)
            return;

        connIter->second.mServerConnFactory.createPushServerConn
        (aData.mUri,aData.mFilter,mConnListener,mPendingConnListener);

        ScopedLock lockObj(mMutex);
        mSuccessPushConnList.insert(std::pair<std::wstring,bool>(aData.mUri,true));
    }
    catch (...)
    {
        ELOG(EJavaPush,"ERROR!!! PushServerConnPluginManager::createAndOpenConn()");
        throw;
    }
}

/**
 *
 */
std::wstring PushServerConnPluginManager::resolveScheme(const std::wstring& aUri) const
{
    JELOG2(EJavaPush);

    std::wstring::size_type schemeIndex = aUri.find(L":");
    if (std::string::npos == schemeIndex)
    {
        std::string errTxt("URI does not contain scheme");
        ELOG1(EJavaPush, "ERROR!!! %s",errTxt.c_str());
        throw PushException(INVALID_URI,errTxt,__FILE__,__FUNCTION__,__LINE__);
    }
    std::wstring schemeStr = aUri.substr(0,schemeIndex);
    if (L"secure-element" == schemeStr)
        return L"secureelement";
    return schemeStr;
}

/**
 *
 */
std::string PushServerConnPluginManager::resolveDllName(const std::wstring& aScheme) const
{
    JELOG2(EJavaPush);
    std::string schemeStr(aScheme.begin(),aScheme.end());
    std::string dllNameStr(PREFIX_OF_SRV_CONN_PLUGIN);
    dllNameStr.append(schemeStr);
    dllNameStr.append(SUFFIX_OF_SRV_CONN_PLUGIN);
    WLOG1(EJavaPush,"Name of the loaded dll: %s",dllNameStr.c_str());
    return dllNameStr;
}

/**
 *
 */
int PushServerConnPluginManager::getNumOfSuccessfulPushConns()
{
    JELOG2(EJavaPush);
    ScopedLock lockObj(mMutex);

    int successConnections = 0;
    for (SuccessPushConnListIter_t iter = mSuccessPushConnList.begin();
            iter != mSuccessPushConnList.end(); ++iter)
    {
        if (true == iter->second)
            successConnections++;
    }
    return successConnections;
}

/**
 *
 */
void PushServerConnPluginManager::findAndDeleteUnregPushConns
(std::list<std::wstring>& aExistingUriList,UriList_t& aNewUriListFromStorage,
 PushControllerErrorHandlerInterface& aErrorHandler)
{
    JELOG2(EJavaPush);

    //First is removed all valid push connections(=not push regs which will be unregistered)
    //from the 'existingUriList' list.
    //After the loop 'aExistingUriList' list contains connections which will be removed.
    for (UriListIter_t iter = aNewUriListFromStorage.begin();
            iter != aNewUriListFromStorage.end(); ++iter)
    {
        aExistingUriList.remove(iter->mUri);
    }//end for

    //Unregistering connections. Actually this list should contains only one item because
    //dynamic push regs are unregistered one by one from PushRegstry class.
    for (std::list<std::wstring>::iterator iter2 = aExistingUriList.begin();
            iter2 != aExistingUriList.end(); ++iter2)
    {
        unregisterDynamicPushConn((*iter2));
        mConnManagedInterface->connCompleted((*iter2));
    }//end for

    //Next is checked whether this MIDlet has any push connections. Below
    //is also checked whether listening of all push connections are failed.
    std::list<std::wstring> existingPushConns;
    listConnections(false,existingPushConns);
    if (0 < existingPushConns.size())
    {
        //MIDlet has push connections. Next is checked whether opening
        //of all MIDlets has been done successfully.
        if (0 == getNumOfSuccessfulPushConns())
        {
            //Initialization of all connections has been failed.
            aErrorHandler.sendAllConnsFailedMsg();
            //No active connections so we can inform runtime to close a process.
            aErrorHandler.closeRuntime();
        }
    }
    else if (0 == existingPushConns.size())
    {
        //No connections so runtime can be informed to close an application.
        aErrorHandler.closeRuntime();
    }
}

/**
 *
 */
void PushServerConnPluginManager::findAndAddNewPushConns
(std::list<std::wstring>& aExistingUriList,UriList_t& aNewUriListFromStorage)
{
    JELOG2(EJavaPush);

    //First is removed all push connections from 'aNewUriListFromStorage' list which
    //already exists in the push plugins.
    for (std::list<std::wstring>::iterator iter = aExistingUriList.begin();
            iter != aExistingUriList.end(); ++iter)
    {
        PushRegData tmpObj((*iter),L"*");
        aNewUriListFromStorage.remove(tmpObj);
    }//end for

    //Now 'aNewUriListFromStorage' list contains only new dynamic push regs.
    startListen(aNewUriListFromStorage,false,true);
}

