/*
 * Copyright (C) 2009 - 2010 Funambol, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */

/* $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>

// 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)
{
	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] = NULL;   // overwrite last "
                attrValue = ++ptr;    // skip first "
                brc = true;
                break;
            }

        }

        strToken = NULL;
    } while (ptr);

    return brc;
}


bool Configuration::readProfiles(StringBuffer& xml)
{
    StringBuffer profileContent;
    StringBuffer attributes;
    StringBuffer profileName;
    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_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 Profile);
        if (!profile.get())
        {
		    LERROR("Failed to allocate memory for Profile. ");
            return false;
        }

        profile->SetName(profileName);
        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)
{
    auto_ptr<ProfileComponentsHolder>    compHolder(new ProfileComponentsHolder);
    if (!compHolder.get())
    {
	    LERROR("Failed to allocate memory for ProfileComponentsHolder. ");
        return false;
    }

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

    NS_DataStorage::IDataStorage* dataStorage = NS_DataStorage::CreateDataStorage(profile.GetName());
    if (!dataStorage)
    {
        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 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 && comp)
            {
				StringMap params;
				if (!S_readSettings(tagContent, params))
				{
					LDEBUG("Failed to read settings. ");
				}
				IDaemonEngineRequest* request = new DaemonEngineRequest(*(compHolder.get()));
				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)
                {
					LERROR("DynamicLoader::Load error: '%s'", dl->GetLoadErrorDescription());
                }
                delete dl;
                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 ExecutionQueue;
		LDUMP("Component instance = %p. ", comp);
        if (comp)
        {
            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 ServerExchangeManager;
		LDUMP("Component instance = %p. ", comp);
        if (comp)
        {
            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 MOTreeManager;
		LDUMP("Component instance = %p. ", comp);
        if (comp)
        {
            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 NotificationListener;
		LDUMP("Component instance = %p. ", comp);
        if (comp)
        {
            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)
{
    Profile* p = NULL;
    if (i < GetProfilesCount())
    {
        p = m_Profiles[i];
    }

    return p;
}

Profile* Configuration::GetProfile(const NS_DM_Client::String& profileName)
{
    Profile* p = 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();
}


