/*
 * 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 <limits.h>
#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 "Utils.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_ListStruct             = "Struct";
const char* const c_ListStructData         = "StructData";
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

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


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)
{
    if(cmd.get() == NULL)
    {
        GDLWARN("cmd is NULL");
    }
}


MOTreeGetCommand::~MOTreeGetCommand(void)
{
}


MOTreeResponseCommand * MOTreeGetCommand::CreateResponse()
{
    MOTreeGetResponseCommand *pResponse =
        new(std::nothrow) MOTreeGetResponseCommand(*m_pProfile->GetServerExchangeManager(), m_GetCommand, m_resCode, m_messageID, m_serverID);
    if(pResponse == (MOTreeGetResponseCommand*)NULL)
    {
        GDLWARN("new MOTreeGetResponseCommand");
    }
    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)
    {
        IMOTreeManager* treeManager = m_pProfile ? m_pProfile->GetMOTreeManager() : 0;
        if (!treeManager)
        {
            GDLERROR("can't get tree manager instance");
            return e_Failed;
        }

        items = items->clone();

        if (notifyProvisionUpdate(e_Get, e_sessionStart, e_Ok))
        {
            res = processGetOnItems(items);
        }
        else
        {
            GDLERROR("NotifyProvisionUpdate failed");
            res = e_Failed;
        }

        if (!notifyProvisionUpdate(e_Get, e_sessionEnd, res))
        {
            GDLERROR("NotifyProvisionUpdate failed");
        }
    }
    else
    {
        GDLERROR("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;
}


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, const URI uri, size_t pos, const char* serverId)
{
    StatusCode res = e_Failed;

    String property_name = uri.substr(pos + strlen(c_PropertyDelimiter));
    IMOTreeManager::RequiredProperties requiredProp;
    IMOTreeManager &treeManager = *m_pProfile->GetMOTreeManager();
    
    if (isListTNDS(property_name))
    {
        bool plusDelimiter = false;
        if (e_Ok == (res = checkTNDSDelimiters(property_name, plusDelimiter)))
        {
            IMOTreeManager::RequiredProperties requiredProp;
            res = extractListTNDSDProperties(property_name, 
                                             plusDelimiter ? c_ListTNDSPlusDelimiter : c_ListTNDSMinusDelimiter, 
                                             requiredProp);
            if (e_Ok == res)
            {
                Funambol::StringBuffer* xml_res = 0;
                if (e_Ok == (res = treeManager.GetAttributeTNDS(uri.substr(0, pos), requiredProp, xml_res, m_serverID)))
                {
                    Funambol::Item item;
                    Utils::ConstructItem(item, uri, xml_res->c_str());
                    Funambol::deleteStringBuffer(&xml_res);
                    items.add(item);
                }
                else
                {
                    GDLERROR("IMOTreeManager::GetAttributeTNDS failed. URI: %s", uri.substr(0, pos).c_str());
                }
            }
        }
    }
    else if (property_name == c_ListStruct)
    {
        const bool keepData = false;
        res = treeManager.GetAttributeStructData(uri.substr(0, pos), items, keepData, m_serverID);
    }
    else if (property_name == c_ListStructData)
    {
        const bool keepData = true;
        res = treeManager.GetAttributeStructData(uri.substr(0, pos), items, keepData, m_serverID);
    }
    else
    {
        GDLERROR("get list on URI from MO Tree Manager failed. Res=%d", res);
    }
    
    return res;
}


StatusCode MOTreeGetCommand::processGetOnItems(Funambol::ArrayList* items)
{
    StatusCode res = e_Failed;

    Funambol::ArrayList newItemsList;
    int count = items->size();
    IMOTreeManager &treeManager = *m_pProfile->GetMOTreeManager();
    URI uri;

    for (int i = 0; i < count; ++i)
    {
        Funambol::Item* item = static_cast<Funambol::Item*>(items->get(i));
        if (!item)
        {
            GDLERROR("null pointer in items collection");
            break;
        }
        
        
        if (!item->getTarget() || !item->getTarget()->getLocURI())
        {
            GDLERROR("badly constructed item: target or locuri of target is null");
            break;
        }
        
        uri = item->getTarget()->getLocURI();
        GDLINFO("Execute Get on %s", uri.c_str());

        size_t pos = URI::npos;
        if (URI::npos != (pos = uri.find(c_ListDelimiter)))
        {
            // ?list= detected, get requests list
            // NOTE: result items from motreemanager are added to another arraylist
            res = performListExecution(newItemsList, uri, pos, m_serverID);
            if (res != e_Ok)
            {
                break;
            }
        }
        else if (URI::npos != (pos = uri.find(c_PropertyDelimiter)))
        {
            // ?prop= detected, get requests property
            URI property_name(uri.substr(pos + strlen(c_PropertyDelimiter)));
            URI res_uri(uri.substr(0, pos));
            String property_value;
            
            if (e_Ok == (res = treeManager.GetPropertyValue(res_uri, property_name, property_value, m_serverID)))
            {
                Utils::ConstructItem(*item, uri, property_value.c_str());
                newItemsList.add(*item);
            }
            else
            {
                GDLERROR("get item property from MO Tree Manager failed. Res=%d", res);
                break;
            }
        }
        else
        {
            if (e_Ok == (res = treeManager.Get(uri, *item, m_serverID)))
            {
                if (item->getData() && item->getData()->getData())
                {
                    size_t size = strlen(item->getData()->getData());
                    if (0 == size)
                    {
                        // set Meta::Format to "null"
                        Funambol::Meta *pMeta = item->getMeta();
                        if (pMeta)
                            pMeta->setFormat("null");
                        item->setData(NULL);
                    }
                    else if (size > m_maxObjSize)
                    {
                        res = e_EntityTooLarge;
                        GDLERROR("found item with too large data", res);
                        break;
                    }
                }
                newItemsList.add(*item);
            }
            else
            {
                GDLERROR("get item from MO Tree Manager failed. Res=%d", res);
                break;
            }
        }
    } // for

    m_GetCommand->setItems(NULL);
    
    // TODO - items that are result of Get are set instead of original Get items - 
    // not very good
    m_GetCommand->setItems(&newItemsList);
    delete items;

    return res;
}


Funambol::ArrayList* MOTreeGetCommand::getListOfItems()
{
    return m_GetCommand->getItems();
}

