/*
 * 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 "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_interiorNode = "node";
	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;
	}
	//-------------------------------------------------------------------------------------------

	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 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);
			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_interiorNode)
		{
			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(char*);
		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] = {0};
		__sprintf(buffer, "%i", value);
		//_itoa(value, buffer, 10);
		return buffer;
	}
	//-------------------------------------------------------------------------------------------
}
