/*
* 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 "Logger/Logger.h"
#include "Node.h"
#include "Utils.h"
#include "DataStorage/IDataStorage.h"
#include "common/Buffer.h"

#include <string>

namespace ds = NS_DM_Client::NS_DataStorage;

// TODO: add "MO_" prefix to paths

namespace NS_DM_Client
{

    const char* S_propertyName[] = {"ACL", "Format", "Name", "Size", "Title", "TStamp", "Type", "VerNo"};

    const char* S_contentFile = "content";
    const char* S_permanentValue = "true";

    const char* S_seperator = "/";
    const char* S_rootNode = "./";
    const char* S_interiorNodeFormat = "node";
    const char* S_interiorNodeDefType = "";
    const char* S_leafNodeDefType = "text/plain";
    const char* S_leafNodeDefFormat = "chr";
    const char* S_permanentFile = "permanent";

    //-------------------------------------------------------------------------------------------

    Node::Node(const URI& path, ds::IDataStorage* dataStorage): m_path(path), m_dataStorage(dataStorage),
        m_logger(NS_Logging::GetLogger("MOTreeManager"))
    {
        if (!m_dataStorage)
        {
            LOG_ERROR_(m_logger, "Failed to create data storage");
        }
    }
    //-------------------------------------------------------------------------------------------

    Node::~Node()
    {
    }
    //-------------------------------------------------------------------------------------------

    void Node::SetContent(const String& content)
    {
        if (m_dataStorage)
        {
            bool res = m_dataStorage->SavePrivateData(m_path + S_seperator + S_contentFile, content.c_str(), content.size());
            if (!res)
            {
                LOG_ERROR_(m_logger, "Failed to save data through data storage");
            }
        }
    }
    //-------------------------------------------------------------------------------------------

    bool Node::GetContent(String& content) const
    {
        if (!m_dataStorage || !IsLeaf())
        {
            LOG_WARNING_(m_logger, "Node is interior node - no content is available");
            return false;
        }
//      LOG_(m_logger, "Node is leaf node");
        Buffer buffer;
        bool res = m_dataStorage->LoadPrivateData(m_path + S_seperator + S_contentFile, buffer);
        if (!res)
        {
            LOG_ERROR_(m_logger, "Failed to load data through data storage");
            return false;
        }
        else
        {
            copyFromBuffer(content, buffer.GetPointer(), buffer.Size());
            buffer.Release();
            return true;
        }
    }
    //-------------------------------------------------------------------------------------------

    void Node::SetProperty(Property name, const String& value)
    {
        if (m_dataStorage)
        {
            bool res = m_dataStorage->SavePrivateData(m_path + S_seperator + S_propertyName[name], value.c_str(), value.size());
            if (!res)
            {
                LOG_ERROR_(m_logger, "Failed to save properties' data through data storage");
            }
        }
    }
    //-------------------------------------------------------------------------------------------

    bool Node::GetProperty(Property name, String& value) const
    {
        value.clear();
        if (m_dataStorage)
        {
            if (e_name == name)
            {
                String path_n;
                ParseNodePath(m_path, path_n, value);
                return true;
            }
            if (e_size == name)
            {
                size_t size;
                bool res = GetContentSize(size);
                if (!res)
                {
                    return false;
                }
                value = IntToString((int)size);
                return true;
            }
            Buffer buffer;
            bool res = m_dataStorage->LoadPrivateData(m_path + S_seperator + S_propertyName[name], buffer);
            if (!res)
            {
                LOG_WARNING_(m_logger, "there is no property with name \"%s\" in data storage", S_propertyName[name]);
                return false;
            }
            copyFromBuffer(value, buffer.GetPointer(), buffer.Size());
            LOG_DEBUG_(m_logger, "%s property \"%s\" has value \"%s\"", m_path.c_str(), S_propertyName[name], value.c_str());
            buffer.Release();
            return true;
        }
        return false;
    }
    //-------------------------------------------------------------------------------------------

    bool Node::GetContentSize(size_t& size) const
    {
        if (m_dataStorage && IsLeaf())
        {
            bool res = m_dataStorage->GetPrivateDataSize(m_path + S_seperator + S_contentFile, size);
            if (!res)
            {
                LOG_WARNING_(m_logger, "Failed to get content's size property");
                return false;
            }
            return true;
        }
        return false;
    }

    void Node::copyFromBuffer(String& destination, void* source, size_t size) const
    {
/****************
        char* tmpContent = new char[size + 1];
         memset(tmpContent, 0, size + 1); // last 0 added
        memcpy(tmpContent, source, size);
        destination = tmpContent;
         delete [] tmpContent;
*************/
        if(size == 0)
        {
            destination = (char *)"";
            return;
        }
        char* tmpContent = (char *)malloc(size);
        if(tmpContent == NULL)
        {
            destination = (char *)"";
            LOG_ERROR_(m_logger, "malloc:tmpContent");
            return;
        }
        memset(tmpContent, 0, size );
        memcpy(tmpContent, source, size);
        if(*(tmpContent + size-1) == '\n')
        {
            *(tmpContent + size-1) = '\0';
            destination = tmpContent;
        }
        else if(*(tmpContent + size-1) == '\0')
        {
            destination = tmpContent;
        }
        else // NULL terminate
        {
            free(tmpContent);
            tmpContent = (char *)malloc(size + 1);
            if(tmpContent == NULL)
            {
                LOG_ERROR_(m_logger, "malloc:tmpContent2");
                destination = (char *)NULL;
                return;
            }
            memset(tmpContent, 0, size + 1);
            memcpy(tmpContent, source, size);
            destination = tmpContent;
        }
        free(tmpContent);
    }
    //-------------------------------------------------------------------------------------------

    Node* Node::GetParent() const
    {
        if (m_path.compare(S_rootNode) == 0)
        {
            return 0;
        }
        size_t pos = m_path.rfind(S_seperator);
        if (pos == String::npos)
        {
            return 0;
        }
        else
        {
            return new(std::nothrow) Node(m_path.substr(0, pos), m_dataStorage);
        }
        return 0;
    }
    //-------------------------------------------------------------------------------------------

    const URI Node::GetName() const
    {
        String path;
        String name;
        ParseNodePath(m_path, path, name);
        return name;
    }
    //-------------------------------------------------------------------------------------------

    Node* Node::GetExistingParent() const
    {
        Node* parent = GetParent();
        while (parent && (!parent->Exist()))
        {
            Node* old = parent;
            parent = parent->GetParent();
            delete old;
        }
        if (!parent)
        {
            LOG_ERROR_(m_logger, "Failed to find existing property. At last must be permanent properties");
        }
        return parent;
    }
    //-------------------------------------------------------------------------------------------

    bool Node::IsPermanent() const
    {
        Buffer buffer;
        if (m_dataStorage->LoadPrivateData(m_path + S_seperator + S_permanentFile, buffer))
        {
            String value;
            copyFromBuffer(value, buffer.GetPointer(), buffer.Size());
            if (value.compare(S_permanentValue) == 0)
            {
                return true;
            }
        }
        return false;
    }
    //-------------------------------------------------------------------------------------------

    void Node::SetPermanent()
    {
        if (m_dataStorage)
        {
            Buffer buffer(strlen(S_permanentValue) + 1, '\0');
            memcpy(buffer.GetPointer(), S_permanentValue, buffer.Size());

            bool res = m_dataStorage->SavePrivateData(m_path + S_seperator + S_permanentFile, buffer);
            if (!res)
            {
                LOG_ERROR_(m_logger, "Failed to save permanent status through data storage");
            }
        }
    }
    //-------------------------------------------------------------------------------------------

    bool Node::Exist() const
    {
        return m_dataStorage->Exist(m_path);
    }
    //-------------------------------------------------------------------------------------------

    bool Node::Delete()
    {
        bool res = m_dataStorage->RemovePrivateData(m_path);
        if (!res)
        {
            LOG_ERROR_(m_logger, "Failed to remove node with uri = \"%s\"", m_path.c_str());
        }
        return res;
    }
    //-------------------------------------------------------------------------------------------

    bool Node::IsLeaf() const
    {
        String formatValue;
        bool res = GetProperty(e_format, formatValue);
        if (!res || formatValue == S_interiorNodeFormat)
        {
            return false;
        }
        return true;
    }
    //-------------------------------------------------------------------------------------------

    bool Node::GetChildren(ChildrenPaths& children) const
    {
        return m_dataStorage->GetChildList(m_path, children);
    }
    //-------------------------------------------------------------------------------------------

    bool GetPropertyByName(const String& name, Property& property)
    {
        size_t count = sizeof(S_propertyName) / sizeof(S_propertyName[0]);
        for (size_t i = 0; i < count; ++i)
        {
            if (name.compare(S_propertyName[i]) == 0)
            {
                property = Property(e_ACL + (Property)i);
                return true;
            }
        }
        return false;
    }
    //-------------------------------------------------------------------------------------------

    bool IsPropertyReplacementSupported(Property property)
    {
        bool res = false;
        if ((property == e_ACL) || (property == e_title) || (property == e_name))
        {
            res = true;
        }
        return res;
    }
    //-------------------------------------------------------------------------------------------

    void ParseNodePath(const URI& full_name, String& path_name, String& node_name)
    {
        if (full_name.compare(S_rootNode) == 0)
        {
            path_name = S_rootNode;
            node_name.clear();
            return;
        }
        size_t pos = full_name.rfind(S_seperator);
        if (pos == String::npos)
        {
            node_name = full_name;
            path_name.clear();
        }
        else
        {
            node_name = full_name.substr(pos + 1);
            path_name = full_name.substr(0, pos + 1);
        }
    }
    //-------------------------------------------------------------------------------------------
    Node* GetExistingParentWithACL(const Node* node)
    {
        Node* parent = node->GetExistingParent();
        String acl;
        if (parent)
        {
            parent->GetProperty(e_ACL, acl);
        }
        while (parent && (acl.empty()))
        {
            Node* old = parent;
            parent = parent->GetExistingParent();
            delete old;
            if (parent)
            {
                parent->GetProperty(e_ACL, acl);
            }
        }
        return parent;
    }
    //-------------------------------------------------------------------------------------------

    String IntToString(int value)
    {
        const int maxSize = 1024;
        char buffer[maxSize];
        memset(buffer, 0, maxSize);
        __sprintf(buffer, "%i", value);
        //_itoa(value, buffer, 10);
        return buffer;
    }
    //-------------------------------------------------------------------------------------------
}
