/*
 * Copyright 2009 Funambol, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id$ */

#include "commontypes.h"
#include "daemon/Configuration.h"
#include "daemon/ComponentInfo.h"
#include "daemon/config_def.h"
#include "daemon/components.h"
#include "daemon/ProfileComponentsHolder.h"
#include "daemon/Profile.h"
#include "daemon/NotificationCenter.h"
#include "DaemonEngineRequest.h"

#include "platform.h"

#include "DataStorage/IDataStorage.h"
#include "executionqueue/ExecutionQueue.h"
#include "serverexchange/ServerExchangeManager.h"
#include "treemanager/MOTreeManager.h"
#include "NotificationListener/NotificationListener.h"
#include "DeviceAdapter/IDeviceAdapter.h"

#include <base/util/XMLProcessor.h>
#include <base/util/ArrayList.h>

#include <Logger/LoggerMacroses.h>
const char* c_LogName = "DaemonEngine";

// Dynamic Library loading API
#include "DynamicLoader.h"

#include <memory>

using namespace NS_DM_Client;
using namespace NS_DM_Client::NS_Daemon;
using namespace NS_DM_Client::NS_Configuration;
using namespace NS_DM_Client::NS_ExecutionQueue;
using namespace NS_DM_Client::NS_Communication;
using namespace NS_DM_Client::NS_NotificationListener;

using Funambol::XMLProcessor;
using Funambol::ArrayList;

using std::auto_ptr;

static inline char* _strtok_x(char *str, const char *delim, char **saveptr)
{
return
#ifdef _MSC_VER
    strtok_s(str, delim, saveptr);
#else
    strtok_r(str, delim, saveptr);
#endif
;
}



Configuration::Configuration(NS_Logging::Logger& log)
: m_logger(log)
{
}

Configuration::~Configuration(void)
{
    reset();

}


void Configuration::reset()
{
    for (Profiles::iterator i = m_Profiles.begin(); i < m_Profiles.end(); ++i)
    {
        delete *i;
    }
    m_Profiles.clear();
}


bool Configuration::Read(StringBuffer& xml)
{
LINFO("Enter");

    bool brc = false;

    reset();

    do
    {
        StringBuffer confRootNode;
        if (!S_getTagInfo(xml, c_OMADMClientTag, confRootNode))
        {
            LERROR("Failed to get %s tag", c_OMADMClientTag);
            break;
        }

        StringBuffer daemonConfXml;
        if (!S_getTagInfo(confRootNode, c_DaemonTag, daemonConfXml))
        {
            LERROR("Failed to get %s tag", c_DaemonTag);
            break;
        }

        StringBuffer profilesXml;
        if (!S_getTagInfo(confRootNode, c_ProfilesTag, profilesXml))
        {
            LERROR("Failed to get %s tag", c_ProfilesTag);
            break;
        }

        if (!readProfiles(profilesXml))
        {
            LERROR("Failed to read profiles");
            break;
        }

        // TODO enable Logger instances
        // Logger interface should be extended

        brc = true;
    } while (false);


    return brc;
}


bool Configuration::S_getTagInfo(const char* xml, const char* tagName,
                                 StringBuffer& content, StringBuffer* attributes,
                                 unsigned int* endPos
                                 )
{
    unsigned int start = 0;
    unsigned int end = 0;
    const char* ptr = XMLProcessor::getElementContent(xml, tagName, NULL, &start, &end);
    if (ptr)
    {
        XMLProcessor::copyContent(xml, start, end, content);
        if (endPos)
        {
            *endPos = end;
        }
        if (attributes)
        {
            start = end = 0;
            *attributes = XMLProcessor::getElementAttributes(xml, tagName, &start, &end);
        }
    }

    return ptr != NULL;
}


bool Configuration::S_getAttribute(const char* xml, const char* attName, StringBuffer& attrValue)
{
    static const char delimitersSpaceTag[] = " \t\n\r/><";
    static const char paramValAssign = '=';
    static const char c_DoubleQuotes = '\"' ;

    bool brc = false;
    char *context = NULL;
    char* ptr = NULL;
    StringBuffer buf = xml;
    char* strToken = const_cast<char*>(buf.c_str());
    const int attrNameLen = strlen(attName);
    bool paramValAssignFound = false;
    do
    {
        ptr = _strtok_x(strToken, delimitersSpaceTag, &context);
        if (ptr && strncmp(ptr, attName, attrNameLen) == 0 && (ptr[attrNameLen] == 0 || ptr[attrNameLen] == paramValAssign))
        {
            // Attribute found

            if (ptr[attrNameLen] == 0)
            {
                ptr = _strtok_x(NULL, delimitersSpaceTag, &context);
            }
            else
            {
                ptr += attrNameLen;
            }

            // find assign character
            if (*ptr == paramValAssign)
            {
                paramValAssignFound = true;
                if (ptr[1] == 0)
                {
                    ptr = _strtok_x(NULL, delimitersSpaceTag, &context);
                }
                else
                {
                    ptr++;
                }
            }

            if (!paramValAssignFound)
            {
                // assign character is missing
                break;
            }

            // check double quotes around param value
            const int valLen = strlen(ptr);
            if (ptr[0] == c_DoubleQuotes
                && ptr[valLen - 1] == c_DoubleQuotes)
            {
                ptr[valLen - 1] = '\0';   // overwrite last "
                attrValue = ++ptr;    // skip first "
                brc = true;
                break;
            }

        }

        strToken = NULL;
    } while (ptr);

    return brc;
}


bool Configuration::readProfiles(StringBuffer& xml)
{
LINFO("Enter");

    StringBuffer profileContent;
    StringBuffer attributes;
    StringBuffer profileName;
    StringBuffer profileType;
    StringBuffer profileDescription;
    StringBuffer enabledVal;
    unsigned int endPos = 0;
    
    do
    {
        unsigned int endPosCur = 0;
        if (!S_getTagInfo(&xml.c_str()[endPos], c_ProfileTag, profileContent, &attributes, &endPosCur))
        {
            break;
        }
        endPos += endPosCur;

        if (!S_getAttribute(attributes, c_NameAttribute, profileName))
        {
            LWARN("Profile do not have Name attribute. Skipping. ");
            continue;
        }

        if (!S_getAttribute(attributes, c_TypeAttribute, profileType))
        {
            LWARN("Profile do not have Type attribute. Skipping. ");
            continue;
        }

        if (!S_getAttribute(attributes, c_DescriptionAttribute, profileDescription))
        {
            LWARN("Profile do not have Description attribute. Use name instead");
            profileDescription = profileName;
        }

        bool enabled = true;
        if (S_getAttribute(attributes, c_EnabledAttribute, enabledVal))
        {
            enabled = enabledVal.icmp(c_ValueTrue);
        }

        auto_ptr<Profile> profile(new(std::nothrow) Profile);
        if (profile.get() == NULL)
        {
            LERROR("Failed to allocate memory for Profile. ");
            return false;
        }

        profile->SetName(profileName);
        profile->SetType(profileType);
        profile->SetDescription(profileDescription);
        LINFO("Adding profile: %s ", profileName.c_str());
        if (!addProfile(*profile, profileContent, enabled))
        {
            LERROR("Incorrect profile: %s ", profileName.c_str());
            continue;
        }
        profile.release();

    }while(xml.length() > endPos);

    return !m_Profiles.empty();
}


bool Configuration::addProfile(Profile& profile, StringBuffer& xml, bool enable)
{
LINFO("Enter");

    auto_ptr<ProfileComponentsHolder> compHolder(new(std::nothrow) ProfileComponentsHolder(profile));
    if (compHolder.get() == NULL)
    {
        LERROR("Failed to allocate memory for ProfileComponentsHolder. ");
        return false;
    }

    INotificationCenter* nc = new(std::nothrow) NotificationCenter;
    if (nc != (INotificationCenter*)NULL)
    {
        compHolder->SetNotificationCenter(nc);
    }
    else
    {
        LERROR("Failed to create NotificationCenter. ");
    }

    NS_DataStorage::IDataStorage* dataStorage = NS_DataStorage::CreateDataStorage(profile.GetName());
    if (dataStorage == (NS_DataStorage::IDataStorage*)NULL)
    {
        LERROR("Failed to create DataStorage instance. ");
        return false;
    }
    compHolder->SetDataStorage(dataStorage);

    // TODO write initialization helper classes. Base IComponentInitializer
    // params: ComponentName
    //  virtual IComponent* CreateInstance();
    //  virtual bool PutToComponentHolder(ProfileComponentsHolder*) = 0;

    StringBuffer tagContent;
    StringBuffer settingsInfo;
    if (S_getTagInfo(xml, c_NativeAPIDeviceAdapter, tagContent))
    {
        LINFO("Read %s configuration", c_NativeAPIDeviceAdapter);

        StringBuffer modulename;
        StringBuffer createFuncName;
        if (S_readLocation(tagContent, modulename, createFuncName))
        {
            DynamicLoader* dl = new(std::nothrow) DynamicLoader;
            if(dl == (DynamicLoader*)NULL)
            {
                LERROR("new DynamicLoader");
            }
            IDeviceAdapter* comp = (IDeviceAdapter*)dl->Load(modulename.c_str(), createFuncName.c_str(), 0);
            LDEBUG("%s (%p) loaded from lib='%s' (%s). ", c_NativeAPIDeviceAdapter, comp,
                modulename.c_str(), createFuncName.c_str());

            if ((dl != (DynamicLoader*)NULL) && (comp != (IDeviceAdapter*)NULL))
            {
                StringMap params;
                if (!S_readSettings(tagContent, params))
                {
                    LDEBUG("Failed to read settings. ");
                }
                IDaemonEngineRequest* request = new(std::nothrow) DaemonEngineRequest(*(compHolder.get()));
                if(request == (IDaemonEngineRequest*)NULL)
                {
                    LERROR("new DaemonEngineRequest");
                }
                compHolder->SetDaemonEngineRequest(request);
                bool brc = comp->Init(params, request);
                LDEBUG("%s Init result=%d. ", c_NativeAPIDeviceAdapter, brc);

                compHolder->SetDeviceAdapter(comp, dl);
            }
            else
            {
                LERROR("Failed to create instance of %s. DynamicLoader=%p, object instance=%p", c_NativeAPIDeviceAdapter, dl, comp);
                if (dl != (DynamicLoader*)NULL)
                {
                    LERROR("DynamicLoader::Load error: '%s'", dl->GetLoadErrorDescription());
                }
                if (dl != (DynamicLoader*)NULL) delete dl;
                if (comp != (IDeviceAdapter*)NULL) delete comp;
            }

        }
        else
        {
            LDEBUG("Failed to read DeviceAdapter location. ");
        }

    }

    if (!compHolder->GetDeviceAdapter())
    {
        LERROR("There is no DeviceAdapter instance ready. ");
        return false;
    }

    if (S_getTagInfo(xml, c_ExecutionQueueTitle, tagContent))
    {
        LINFO("Read %s configuration", c_ExecutionQueueTitle);

        IExecutionQueue* comp = new(std::nothrow) ExecutionQueue;
        LDUMP("Component instance = %p. ", comp);
        if (comp != (IExecutionQueue*)NULL)
        {
            StringMap params;
            if (!S_readSettings(tagContent, params))
            {
                LDEBUG("Failed to read settings. ");
            }

            StringBuffer loggerInstanceName;
            if (!S_readLoggerInfo(tagContent, loggerInstanceName))
            {
                LDEBUG("Failed to read logger. ");
            }

            if (comp->Init(params, loggerInstanceName.c_str(), *compHolder))
            {
                compHolder->SetExecutionQueue(comp);
            }
            else
            {
                LERROR("%s init failed. ", c_ExecutionQueueTitle);
                comp->Release();
            }
        }
        else
        {
            LERROR("Failed to create instance of %s . ", c_ExecutionQueueTitle);
        }
    }

    if (!compHolder->GetExecutionQueue())
    {
        LERROR("There is no ExecutionQueue instance ready. ");
        return false;
    }

    if (S_getTagInfo(xml, c_ServerExchangeManagerTitle, tagContent))
    {
        LINFO("Read %s configuration", c_ServerExchangeManagerTitle);

        IServerExchangeManager* comp = new(std::nothrow) ServerExchangeManager;
        LDUMP("Component instance = %p. ", comp);
        if (comp != (IServerExchangeManager*)NULL)
        {
            StringMap params;
            if (!S_readSettings(tagContent, params))
            {
                LDEBUG("Failed to read settings. ");
            }

            StringBuffer loggerInstanceName;
            if (!S_readLoggerInfo(tagContent, loggerInstanceName))
            {
                LDEBUG("Failed to read logger. ");
            }

            if (comp->Init(params, loggerInstanceName.c_str(), *compHolder))
            {
                compHolder->SetServerExchangeManager(comp);
            }
            else
            {
                LERROR("%s init failed. ", c_ServerExchangeManagerTitle);
                comp->Release();
            }
        }
        else
        {
            LERROR("Failed to create instance of %s . ", c_ServerExchangeManagerTitle);
        }
    }

    if (!compHolder->GetServerExchangeManager())
    {
        LERROR("There is no ServerExchangeManager instance ready. ");
        return false;
    }

    if (S_getTagInfo(xml, c_MOTreeManagerTitle, tagContent))
    {
        LINFO("Read %s configuration", c_MOTreeManagerTitle);

        IMOTreeManager* comp = new(std::nothrow) MOTreeManager;
        LDUMP("Component instance = %p. ", comp);
        if (comp != (IMOTreeManager*)NULL)
        {
            StringMap params;
            if (!S_readSettings(tagContent, params))
            {
                LDEBUG("Failed to read settings. ");
            }

            StringBuffer loggerInstanceName;
            if (!S_readLoggerInfo(tagContent, loggerInstanceName))
            {
                LDEBUG("Failed to read logger. ");
            }

            if (comp->Init(params, loggerInstanceName .c_str(), *compHolder))
            {
                compHolder->SetMOTreeManager(comp);
            }
            else
            {
                LERROR("%s init failed. ", c_MOTreeManagerTitle);
                comp->Release();
            }
        }
        else
        {
            LERROR("Failed to create instance of %s . ", c_MOTreeManagerTitle);
        }
    }

    if (!compHolder->GetMOTreeManager())
    {
        LERROR("There is no MOTreeManager instance ready. ");
        return false;
    }

    if (S_getTagInfo(xml, c_NotificationListenerTitle, tagContent))
    {
        LINFO("Read %s configuration", c_NotificationListenerTitle);

        INotificationListener* comp = new(std::nothrow) NotificationListener;
        LDUMP("Component instance = %p. ", comp);
        if (comp != (INotificationListener*)NULL)
        {
            StringMap params;
            if (!S_readSettings(tagContent, params))
            {
                LDEBUG("Failed to read settings. ");
            }

            StringBuffer loggerInstanceName;
            if (!S_readLoggerInfo(tagContent, loggerInstanceName))
            {
                LDEBUG("Failed to read logger. ");
            }

            if (comp->Init(params, loggerInstanceName .c_str(), *compHolder))
            {
                compHolder->SetNotificationListener(comp);
            }
            else
            {
                LERROR("%s init failed. ", c_NotificationListenerTitle);
                comp->Release();
                SAFE_DELETE(comp);
            }
        }
        else
        {
            LERROR("Failed to create instance of %s . ", c_NotificationListenerTitle);
        }
    }

    profile.SetComponentsHolder(compHolder.get());
    compHolder.release();

    if (!profile.Enable(enable))
    {
        LERROR("Failed to enable profile. ");
    }

    m_Profiles.push_back(&profile);

    return true;
}


bool Configuration::S_readLocation(StringBuffer& xmlParent, StringBuffer& modulename, StringBuffer& createFuncName)
{
    StringBuffer xml;
    if (!S_getTagInfo(xmlParent, c_LocationTag, xml))
    {
        return false;
    }

    if (!S_getTagInfo(xml, c_ModuleTag, modulename))
    {
        return false;
    }

    if (!S_getTagInfo(xml, c_CreateFuncTag, createFuncName))
    {
        return false;
    }

    return true;
}

bool Configuration::S_readSettings(StringBuffer& xmlParent, StringMap& params)
{
    StringBuffer xml;
    if (!S_getTagInfo(xmlParent, c_SettingsTag, xml))
    {
        return false;
    }

    unsigned int endPos = 0;
    StringBuffer paramContent;
    StringBuffer attributes;
    StringBuffer paramName;

    do
    {
        unsigned int endPosCur = 0;

        if (!S_getTagInfo(&xml.c_str()[endPos], c_ParamTag, paramContent, &attributes, &endPosCur))
        {
            break;
        }
        endPos += endPosCur;

        if (!S_getAttribute(attributes, c_NameAttribute, paramName))
        {
            // ERROR write log - param not have Name attribute. Skipping.
            continue;
        }

        if (params.put(paramName, paramContent))
        {
            // INFO write log - param overwritten
        }

    }while(xml.length() > endPos);


    return true;
}


bool Configuration::S_readLoggerInfo(StringBuffer& xmlParent, StringBuffer& loggerInstance)
{
    StringBuffer xml;
    StringBuffer attributes;
    if (!S_getTagInfo(xmlParent, c_LoggerInstanceTag, xml, &attributes))
    {
        return false;
    }

    if (!S_getAttribute(attributes, c_NameAttribute, loggerInstance))
    {
        // ERROR write log - Name attribute is missing. Skipping.
        return false;
    }

    return true;
}


Profile* Configuration::GetProfile(int i)
{
LINFO("Enter:%d",i);

    Profile* p = (Profile*)NULL;
    if (i < GetProfilesCount())
    {
        p = m_Profiles[i];
    }

    return p;
}

Profile* Configuration::GetProfile(const String& profileName)
{
LINFO("Enter:%d",profileName.c_str());

    Profile* p = (Profile*)NULL;
    for (int i = 0; i < GetProfilesCount(); ++i)
    {
        if (m_Profiles[i]->GetName() == profileName)
        {
            p = m_Profiles[i];
        }
    }

    return p;
}

int Configuration::GetProfilesCount() const
{
    return m_Profiles.size();
}


