/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

#include <log4cxx/logstring.h>
#include <log4cxx/xml/xmllayout.h>
#include <log4cxx/spi/loggingevent.h>
#include <log4cxx/helpers/optionconverter.h>
#include <log4cxx/level.h>
#include <log4cxx/helpers/transform.h>
#include <log4cxx/helpers/iso8601dateformat.h>
#include <log4cxx/helpers/stringhelper.h>
#include <log4cxx/helpers/transcoder.h>
#include <log4cxx/ndc.h>


using namespace LOG4CXX_NS;
using namespace LOG4CXX_NS::helpers;
using namespace LOG4CXX_NS::spi;
using namespace LOG4CXX_NS::xml;

struct XMLLayout::XMLLayoutPrivate
{
	XMLLayoutPrivate()
		: locationInfo(false)
		, properties(false)
		, expectedPatternLength(100)
		{}

	// Print no location info by default
	bool locationInfo; //= false
	bool properties; // = false

	// Expected length of a formatted event excluding the message text
	size_t expectedPatternLength;
};

IMPLEMENT_LOG4CXX_OBJECT(XMLLayout)

XMLLayout::XMLLayout()
	: m_priv(std::make_unique<XMLLayoutPrivate>())
{
	m_priv->expectedPatternLength = getFormattedEventCharacterCount() * 2;
}

XMLLayout::~XMLLayout() {}

void XMLLayout::setOption(const LogString& option,
	const LogString& value)
{
	if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("LOCATIONINFO"), LOG4CXX_STR("locationinfo")))
	{
		setLocationInfo(OptionConverter::toBoolean(value, false));
		m_priv->expectedPatternLength = getFormattedEventCharacterCount() * 2;
	}

	if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("PROPERTIES"), LOG4CXX_STR("properties")))
	{
		setProperties(OptionConverter::toBoolean(value, false));
		m_priv->expectedPatternLength = getFormattedEventCharacterCount() * 2;
	}
}

void XMLLayout::format(LogString& output,
	const spi::LoggingEventPtr& event,
	Pool& p) const
{
	auto& lsMsg = event->getRenderedMessage();
	output.reserve(m_priv->expectedPatternLength + lsMsg.size());
	output.append(LOG4CXX_STR("<log4j:event logger=\""));
	Transform::appendEscapingTags(output, event->getLoggerName());
	output.append(LOG4CXX_STR("\" timestamp=\""));
	StringHelper::toString(event->getTimeStamp() / 1000L, p, output);
	output.append(LOG4CXX_STR("\" level=\""));
	Transform::appendEscapingTags(output, event->getLevel()->toString());
	output.append(LOG4CXX_STR("\" thread=\""));
	Transform::appendEscapingTags(output, event->getThreadName());
	output.append(LOG4CXX_STR("\">"));
	output.append(LOG4CXX_EOL);

	output.append(LOG4CXX_STR("<log4j:message><![CDATA["));
	// Append the rendered message. Also make sure to escape any
	// existing CDATA sections.
	Transform::appendEscapingCDATA(output, lsMsg);
	output.append(LOG4CXX_STR("]]></log4j:message>"));
	output.append(LOG4CXX_EOL);

	LogString ndc;

	if (event->getNDC(ndc))
	{
		output.append(LOG4CXX_STR("<log4j:NDC><![CDATA["));
		Transform::appendEscapingCDATA(output, ndc);
		output.append(LOG4CXX_STR("]]></log4j:NDC>"));
		output.append(LOG4CXX_EOL);
	}

	if (m_priv->locationInfo)
	{
		output.append(LOG4CXX_STR("<log4j:locationInfo class=\""));
		const LocationInfo& locInfo = event->getLocationInformation();
		LOG4CXX_DECODE_CHAR(className, locInfo.getClassName());
		Transform::appendEscapingTags(output, className);
		output.append(LOG4CXX_STR("\" method=\""));
		LOG4CXX_DECODE_CHAR(method, locInfo.getMethodName());
		Transform::appendEscapingTags(output, method);
		output.append(LOG4CXX_STR("\" file=\""));
		LOG4CXX_DECODE_CHAR(fileName, locInfo.getFileName());
		Transform::appendEscapingTags(output, fileName);
		output.append(LOG4CXX_STR("\" line=\""));
		StringHelper::toString(locInfo.getLineNumber(), p, output);
		output.append(LOG4CXX_STR("\"/>"));
		output.append(LOG4CXX_EOL);
	}

	if (m_priv->properties)
	{
		LoggingEvent::KeySet propertySet(event->getPropertyKeySet());
		LoggingEvent::KeySet keySet(event->getMDCKeySet());

		if (!(keySet.empty() && propertySet.empty()))
		{
			output.append(LOG4CXX_STR("<log4j:properties>"));
			output.append(LOG4CXX_EOL);

			for (auto key : keySet)
			{
				LogString value;

				if (event->getMDC(key, value))
				{
					output.append(LOG4CXX_STR("<log4j:data name=\""));
					Transform::appendEscapingTags(output, key);
					output.append(LOG4CXX_STR("\" value=\""));
					Transform::appendEscapingTags(output, value);
					output.append(LOG4CXX_STR("\"/>"));
					output.append(LOG4CXX_EOL);
				}
			}

			for (auto key : propertySet)
			{
				LogString value;

				if (event->getProperty(key, value))
				{
					output.append(LOG4CXX_STR("<log4j:data name=\""));
					Transform::appendEscapingTags(output, key);
					output.append(LOG4CXX_STR("\" value=\""));
					Transform::appendEscapingTags(output, value);
					output.append(LOG4CXX_STR("\"/>"));
					output.append(LOG4CXX_EOL);
				}
			}

			output.append(LOG4CXX_STR("</log4j:properties>"));
			output.append(LOG4CXX_EOL);
		}
	}

	output.append(LOG4CXX_STR("</log4j:event>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_EOL);
}

void XMLLayout::setLocationInfo(bool locationInfo1)
{
	m_priv->locationInfo = locationInfo1;
}

bool XMLLayout::getLocationInfo() const
{
	return m_priv->locationInfo;
}

void XMLLayout::setProperties(bool flag)
{
	m_priv->properties = flag;
}

bool XMLLayout::getProperties()
{
	return m_priv->properties;
}

