//------------------------------------------------------------------------------
// <copyright file="LqrSMACXmlCmdOutputFormatter.cpp" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
// </summary>
//
// <owner></owner>
//------------------------------------------------------------------------------

#include "LqrSMACXmlCmdOutputFormatter.h"
#include "LqrSMACBase64.h"
#include "pugixml.hpp"
#include <ctype.h>

using namespace std;
using namespace LiquidRobotics::SMACFramework::XmlDOM;
using namespace pugi;

namespace LiquidRobotics {
namespace SMACFramework {

XmlCmdOutputFormatter::XmlCmdOutputFormatter(const string& _options)
: options(_options)
{
}

XmlCmdOutputFormatter::~XmlCmdOutputFormatter()
{
}

void XmlCmdOutputFormatter::AddAttribute(const string& attributeName, const string& attributeValue)
{
	MutexLock lock(collectionsMutex);
	if (!attributeName.length()) throw "Cannot add an attribute with an empty name";
	attributes[attributeName] = attributeValue;
}

void XmlCmdOutputFormatter::AppendOutput(const void* pStart, unsigned len)
{
	AppendTag(outputTag, pStart, len);
}

void XmlCmdOutputFormatter::AppendResult(const void* pStart, unsigned len)
{
	results.push_back(string((const char*) pStart, len));
}


void XmlCmdOutputFormatter::AppendTag(const string& tagName, const void* pStart, unsigned len)
{
	MutexLock lock(collectionsMutex);
	StringStreamMap::iterator it = tags.find(tagName);
	streamstreamptr ssp;

	if (it == tags.end())
	{
		ssp.reset(new stringstream);
		tags[tagName] = ssp;
	}
	else
		ssp = it->second;

	(*ssp).write((const char*) pStart, len);
}

string XmlCmdOutputFormatter::GetAttribute(const string& attributeName) const
{
	MutexLock lock(const_cast<Mutex&>(collectionsMutex));
	StringStringMap::const_iterator ci = attributes.find(attributeName);
	string attributeValue;

	if (ci != attributes.end())
		attributeValue = ci->second;

	return attributeValue;
}

string XmlCmdOutputFormatter::GetOutput() const
{
	return GetTag(outputTag);
}

string XmlCmdOutputFormatter::GetTag(const string& tagName) const
{
	MutexLock lock(const_cast<Mutex&>(collectionsMutex));
	StringStreamMap::const_iterator ci = tags.find(tagName);
	string tagValue;

	if (ci != tags.end())
		tagValue = ci->second->str();

	return tagValue;
}

XmlCmdOutputFormatter::Ptr XmlCmdOutputFormatter::NewChild()
{
	Ptr result;
	result.reset(new XmlCmdOutputFormatter(options));
	result->SetParent(this);
	Base::NewChild(result);
	return result;
}

XmlCmdOutputFormatter::OutputStream& XmlCmdOutputFormatter::ToStream(OutputStream& stm, unsigned nesting, unsigned maxLength) const
{
	XmlDocument xdoc;
	XmlNode rootNode = AppendChildNode("root", xdoc);

	//xml_node docNode = xdoc.document_element();

	//WriteNode(docNode, nesting, maxLength);
	WriteNode(rootNode, nesting, maxLength);

	xml_writer_stream writer(stm);
	xdoc.save(stm, PUGIXML_TEXT("\t"), format_no_declaration);

	return stm;
}

inline void SetAttribute(XmlNode& node, const string& attrName, const string& newValue)
{
	XmlAttribute newAtt = AppendAttribute(attrName, node);
	newAtt.set_value(newValue.c_str());
}

template<typename T,typename U>
void SetAttribute(T& node, const string& attrName, const U& newValue)
{
	XmlAttribute newAtt = AppendAttribute(attrName, node);
	newAtt.set_value(newValue);
}

#if 0
template<typename T>
void XmlCmdOutputFormatter::SetValue(T& node, const string& content) const
{
	if (RequiresBinaryEncoding(content))
	{
		string encodingType;
		string encodedContent = EncodeField(content, encodingType);

		if (encodingType.length())
			SetAttribute(node, "dt:dt", encodingType);

		node.set_value(encodedContent.c_str());
		//node << encodedContent;
	}
	else
		node.set_value(content.c_str());
		//node << content;
}
#endif

template<typename T>
void XmlCmdOutputFormatter::SetValueTag(T& node, const string& tagName, const string& content) const
{
	XmlNode newNode = AppendChildNode(tagName, node);

	if ((content.length() > 40) && RequiresBinaryEncoding(content))
	{
		string encodingType;
		string encodedContent = EncodeField(content, encodingType);

		if (encodingType.length())
			SetAttribute(newNode, "dt:dt", encodingType);

		XmlNode textField = AppendChildNode("unnamed", newNode, node_pcdata);
		textField.set_value(encodedContent.c_str());
	}
	else
		SetAttribute(newNode, valueTag, content);
}

void XmlCmdOutputFormatter::WriteNode(XmlNode& node, unsigned nesting, unsigned maxLength) const
{
	MutexLock lock(const_cast<Mutex&>(collectionsMutex));

	if (commandName.length())
		SetAttribute(node, commandTag, commandName);

	if (!succeeded)
		SetAttribute(node, succeededTag, false);

	if (caughtException)
	{
		SetAttribute(node, exceptionNameTag, exceptionName);
		SetAttribute(node, exceptionTextTag, exceptionText);
	}

	if (CreateTime().WasSet())
	{
		string tempStr = CreateTime().ToString();
		SetAttribute(node, createdTimeTag, tempStr);
	}

	if (StartTime().WasSet())
	{
		string tempStr = StartTime().ToString();
		SetAttribute(node, startTimeTag, tempStr);
	}

	if (EndTime().WasSet())
	{
		string tempStr = EndTime().ToString();
		SetAttribute(node, endTimeTag, tempStr);
	}

	for (StringStringMap::const_iterator ci = attributes.begin(); ci != attributes.end(); ++ci)
	{
		string attrName = ci->first;
		string attrVal = ci->second;

		SetAttribute(node, attrName, attrVal);
	}

	for (StringStreamMap::const_iterator ci = tags.begin(); ci != tags.end(); ++ci)
	{
		string nodeName = ci->first;
		string nodeValue = ci->second->str();

		SetValueTag(node, nodeName, nodeValue);
	}

	for (StringList::const_iterator ci = results.begin(); ci != results.end(); ++ci)
	{
		string nodeName = resultTag;
		string nodeValue = *ci;

		SetValueTag(node, nodeName, nodeValue);
	}

	lock.unlock();

	MutexLock lock2(const_cast<Mutex&>(childrenMutex));

	if (children.size())
	{
		XmlNode childrenNode = AppendChildNode(childrenTag, node);

		for (PtrArray::const_iterator ci = children.begin(); ci != children.end(); ++ci)
		{
			Ptr ptr = *ci;

			try
			{
				const XmlCmdOutputFormatter& child = dynamic_cast<XmlCmdOutputFormatter&>(*ptr);
				XmlNode childNode = AppendChildNode(childTag, childrenNode);

				child.WriteNode(childNode, nesting+1, maxLength);
			}
			catch(bad_cast& bcx)
			{
				stringstream childStm;
				ptr->ToStream(childStm, nesting+1, maxLength);

				string addition = childStm.str();
				SetValueTag(childrenNode, childTag, addition);
			}
		}
	}
}

XmlCmdOutputFormatter::string XmlCmdOutputFormatter::EncodeField(const string& fieldContent, string& encodingType) const
{
	encodingType = "binary.base64";

	stringstream stm;
	base64_encode((unsigned char*) fieldContent.data(), fieldContent.length(), stm);

	string result = stm.str();
	return result;
}

bool XmlCmdOutputFormatter::RequiresBinaryEncoding(const string& fieldContent)
{
	bool requires = false;

	for (string::const_iterator ci = fieldContent.begin(); !requires && (ci != fieldContent.end()); ++ci)
	{
		const char c = *ci;
		if (!isprint(c))
			requires = true;
	}

	return requires;
}


}
}
