/*
 * 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 "treemanager/MOTreeGetCommand.h"
#include "treemanager/MOTreeGetResponseCommand.h"
#include "treemanager/IMOTreeManager.h"

#include "syncml/core/Get.h"
#include "syncml/core/Item.h"
#include "syncml/core/Source.h"
#include "syncml/core/Property.h"
#include <syncml/core/ObjectDel.h>

#include "commontypes.h"
#include "Errors.h"
#include "Logger/LoggerMacroses.h"

#include "executionqueue/IExecutionQueue.h"
#include "Logger/Logger.h"

const char* const c_LogName = "MOTreeGetCommand";

const char* const c_PropertyDelimiter = "?prop=";
const char* const c_ListDelimiter = "?list=";

const char* const c_ListTNDSDelimiter = "TNDS";
const char* const c_ListTNDSPlusDelimiter = "+";
const char* const c_ListTNDSMinusDelimiter = "-";

#define LOG_INFO_ NS_Logging::FunctionNameLogHelper(__FUNCTION__, NS_Logging::e_info).Log

using namespace NS_DM_Client;


MOTreeGetCommand::MOTreeGetCommand(ProfileComponentsHolder* prholder,
								   GetPtr& cmd,
								   const String& msgID,
								   const char* serverId)
    : ActionCommand(prholder, cmd, msgID, serverId), m_maxObjSize(UINT_MAX), m_GetCommand(cmd)
{
}


MOTreeGetCommand::~MOTreeGetCommand(void)
{
}


MOTreeResponseCommand * MOTreeGetCommand::CreateResponse()
{
	MOTreeGetResponseCommand *pResponse = 
		new MOTreeGetResponseCommand(m_pProfile, m_GetCommand, m_resCode, m_messageID, m_serverID);
	if (m_pCommandsSink)
		pResponse->SetCommandsSink(*m_pCommandsSink);
	return pResponse;
}


bool MOTreeGetCommand::Execute()
{
    LOG_INFO_(NS_Logging::GetLogger(c_LogName), "FUNCTION STARTED");

    bool res = m_pProfile && m_GetCommand.get();
    if (res)
    {
		res = false;
		m_resCode = performOperation();
		res = InvokeResult();
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_LogName),
            "component holder or command instanse not valid");
    }

    LOG_INFO_(NS_Logging::GetLogger(c_LogName), "FUNCTION END. Res: %d", res);
    return res;
}


StatusCode MOTreeGetCommand::performOperation()
{
    StatusCode res = e_Ok;
    Funambol::ArrayList* items = m_GetCommand->getItems();
    if (items)
    {
    	items = items->clone();
    	m_GetCommand->setItems(NULL);

    	Funambol::ArrayList newItemsList;
        Funambol::Item* item = 0;
        int count = items->size();
        for (int i = 0; i < count; ++i)
        {
            if ((item = dynamic_cast<Funambol::Item*> ((*items)[i])) != 0)
            {
                const URI uri = item->getTarget()->getLocURI();
				GDLINFO("Execute Get on %s", uri.c_str());

                URI res_uri = uri;

                // check if uri represent list
                size_t pos = uri.find(c_ListDelimiter);
                if (pos != URI::npos)
                {
                	// NOTE: result items from motreemanager are added to another arraylist
                    res = performListExecution(&newItemsList, item, uri, pos, m_serverID);
                    if (res != e_Ok)
                    {
                        break;
                    }
                }
                else
                {
                    // check if uri represent property
                    pos = uri.find(c_PropertyDelimiter);
                    URI property_name;
                    String property_value;
                    if (pos != URI::npos)
                    {   // we have property request
                        res_uri = uri.substr(0, pos);
                        property_name = uri.substr(pos + strlen(c_PropertyDelimiter));
                        if (e_Ok != (res = m_pProfile->GetMOTreeManager()->GetPropertyValue(res_uri, property_name, property_value, m_serverID)))
                        {
                            LOG_ERROR_(NS_Logging::GetLogger(c_LogName),
                                "get item property from MO Tree Manager failed. Res=%d", res);
                            break;
                        }
                    }
                    else
                    {
                        if (e_Ok == (res = m_pProfile->GetMOTreeManager()->Get(res_uri, *item, m_serverID)))
                        {
                        	if (item->getData() && item->getData()->getData() && strlen(item->getData()->getData()) > m_maxObjSize)
							{
								res = e_EntityTooLarge;
								break;
							}
                        }
                        else
                        {
                            LOG_ERROR_(NS_Logging::GetLogger(c_LogName),
                                "get item from MO Tree Manager failed. Res=%d", res);
                            break;
                        }
                    }
                    if (!property_name.empty())
                    {   // we have property request
                        Funambol::Target old_target(uri.c_str(), 0, 0);
                        item->setTarget(&old_target);
                        Funambol::ComplexData new_data(property_value.c_str());
                        item->setData(&new_data);
                        Funambol::Meta new_meta;
                        new_meta.setFormat("chr");
                        new_meta.setType("text/plain");
                        item->setMeta(&new_meta);
                    }
                }
                newItemsList.add(*item);
            }
            else
            {
                LOG_ERROR_(NS_Logging::GetLogger(c_LogName),
                    "casting to Funambol::Item failed");
                break;
            }
        }

        m_GetCommand->setItems(&newItemsList);
        delete items;
	}
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_LogName),
            "get items from ItemizedCommand failed");
    }
    return res;
}

StatusCode ConvertStringRepresentationToRequiredProperties(const String& property, IMOTreeManager::RequiredProperties& requiredProp)
{
    if (property.compare("ACL") == 0)
    {
        requiredProp.push_back(e_TNDS_ACL);
    }
    else if (property.compare("Format") == 0)
    {
        requiredProp.push_back(e_TNDS_format);
    }
    else if (property.compare("NodeName") == 0)
    {
        requiredProp.push_back(e_TNDS_name);
    }
    else if (property.compare("Size") == 0)
    {
        requiredProp.push_back(e_TNDS_size);
    }
    else if (property.compare("Title") == 0)
    {
        requiredProp.push_back(e_TNDS_title);
    }
    else if (property.compare("TStamp") == 0)
    {
        requiredProp.push_back(e_TNDS_tstamp);
    }
    else if (property.compare("Type") == 0)
    {
        requiredProp.push_back(e_TNDS_type);
    }
    else if (property.compare("VerNo") == 0)
    {
        requiredProp.push_back(e_TNDS_verNo);
    }
    else if (property.compare("Value") == 0)
    {
        requiredProp.push_back(e_TNDS_value);
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_LogName), "TNDS property not valid. Property name: %s", property.c_str());
        return e_OptionalFeatureNotSupported;
    }
    return e_Ok;
}

bool isListTNDS(const String& property_name)
{
    size_t pos_beg = 0;
    size_t pos_end = 0;
    pos_end = property_name.find(c_ListTNDSDelimiter, pos_beg);
    if ((pos_end != String::npos) && (pos_end == 0))
    {
        return true;
    }
    return false;
}

StatusCode checkTNDSDelimiters(const String& property_name, bool& plusDelimiter)
{
    bool plusPresent = false;
    bool minusPresent = false;
    if (property_name.find(c_ListTNDSPlusDelimiter, 0) != String::npos)
    {
        plusPresent = true;
    }
    if (property_name.find(c_ListTNDSMinusDelimiter, 0) != String::npos)
    {
        minusPresent = true;
    }
    if (plusPresent && minusPresent)
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_LogName),
            "TNDS property list combine include and remove delimiters. Valid only include or remove delimiters");
        return e_ForbiddenCommand;
    }
    if ((!plusPresent) && (!minusPresent))
    {
        plusDelimiter = true;
        LOG_INFO_(NS_Logging::GetLogger(c_LogName),
            "TNDS property list not contain any include or remove properties. By default set all avialable properties");

        return e_Ok;
    }

    plusDelimiter = (plusPresent) ? true : false;
    return e_Ok;
}

StatusCode extractListTNDSDProperties(const String& property_name, const String& delimiter,
    IMOTreeManager::RequiredProperties& requiredProp)
{
    StatusCode res = e_CommandFailed;
    size_t pos_beg = 0;
    size_t pos_end = 0;

    if (property_name.compare(c_ListTNDSDelimiter) == 0)
    {
        requiredProp.push_back(e_TNDS_ACL);
        requiredProp.push_back(e_TNDS_format);
        requiredProp.push_back(e_TNDS_name);
        requiredProp.push_back(e_TNDS_size);
        requiredProp.push_back(e_TNDS_title);
        requiredProp.push_back(e_TNDS_tstamp);
        requiredProp.push_back(e_TNDS_type);
        requiredProp.push_back(e_TNDS_verNo);
        requiredProp.push_back(e_TNDS_value);
        res = e_Ok;
    }
    else
    {
        String property_with_end_separator = property_name.substr(strlen(c_ListTNDSDelimiter) + 1);
        property_with_end_separator = property_with_end_separator + delimiter;

        String prop;
        while ((pos_end = property_with_end_separator.find(delimiter, pos_beg)) != String::npos )
        {
            prop = property_with_end_separator.substr(pos_beg, pos_end - pos_beg);
            if ((res = ConvertStringRepresentationToRequiredProperties(prop, requiredProp)) != e_Ok)
            {
                return res;
            }
            pos_beg = pos_end + strlen(delimiter.c_str());
        }
    }
    return res;
}

StatusCode MOTreeGetCommand::performListExecution(Funambol::ArrayList* items, Funambol::Item* item, const URI uri, size_t pos, const char* serverId)
{
    StatusCode res = e_Failed;
    Funambol::ArrayList list_items;
    String property_name = uri.substr(pos + strlen(c_PropertyDelimiter));

    IMOTreeManager::RequiredProperties requiredProp;
    if (isListTNDS(property_name))
    {
        bool plusDelimiter = false;
        if ((res = checkTNDSDelimiters(property_name,plusDelimiter)) == e_Ok)
        {
            IMOTreeManager::RequiredProperties requiredProp;
            res = extractListTNDSDProperties(property_name,
                plusDelimiter ? c_ListTNDSPlusDelimiter : c_ListTNDSMinusDelimiter, requiredProp);
            if (res == e_Ok)
            {
                Funambol::StringBuffer* xml_res = 0;
                res = m_pProfile->GetMOTreeManager()->GetAttributeTNDS(uri.substr(0, pos), requiredProp, xml_res, m_serverID);
                if (res != e_Ok)
                {
                    LOG_ERROR_(NS_Logging::GetLogger(c_LogName), "IMOTreeManager::GetAttributeTNDS failed. URI: %s", (uri.substr(0, pos)).c_str());
                }
                else
                {
					Funambol::Target target(uri.c_str(), 0, 0);
					item->setTarget(&target);

					Funambol::ComplexData cdata(xml_res->c_str());
					item->setData(&cdata);
					Funambol::deleteStringBuffer(&xml_res);

					Funambol::Meta meta;
					meta.setFormat("chr");
					meta.setType("text/plain");
					item->setMeta(&meta);
                }
                return res;
            }
            else
            {
                return res;
            }
        }
        else
        {
            return res;
        }
    }

    if ((res = m_pProfile->GetMOTreeManager()->GetAttributeStructData(uri.substr(0, pos), list_items, m_serverID)) == e_Ok)
    {
        if (property_name.compare("Struct") == 0)
        {
            // remove all data from result items
            for (int i = 0; i < list_items.size(); ++i)
            {
                Funambol::Item *retitem = (Funambol::Item*)list_items.get(i);
                if (retitem)
                {
                    retitem->setData(NULL);
                }
            }
        }

        if (items->add(&list_items) == -1)
        {
            LOG_ERROR_(NS_Logging::GetLogger(c_LogName),
                "add ArrayList to base array list failed. Res=%d", res);
            res = e_CommandFailed;
            return res;
        }
    }
    else
    {
        LOG_ERROR_(NS_Logging::GetLogger(c_LogName),
            "get list on URI from MO Tree Manager failed. Res=%d", res);
        return res;
    }
    return res;
}
