/*
 * 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/htmllayout.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/helpers/date.h>

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

struct HTMLLayout::HTMLLayoutPrivate
{
	HTMLLayoutPrivate()
		: locationInfo(false)
		, title(LOG4CXX_STR("Log4cxx Log Messages"))
		, dateFormat()
		, expectedPatternLength(100)
		{}

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

	LogString title;

	helpers::ISO8601DateFormat dateFormat;

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

IMPLEMENT_LOG4CXX_OBJECT(HTMLLayout)


HTMLLayout::HTMLLayout()
	: m_priv(std::make_unique<HTMLLayoutPrivate>())
{
	m_priv->dateFormat.setTimeZone(TimeZone::getGMT());
	m_priv->expectedPatternLength = getFormattedEventCharacterCount() * 2;
}

HTMLLayout::~HTMLLayout() {}


void HTMLLayout::setOption(const LogString& option,
	const LogString& value)
{

	if (StringHelper::equalsIgnoreCase(option,
			LOG4CXX_STR("TITLE"), LOG4CXX_STR("title")))
	{
		setTitle(value);
	}
	else if (StringHelper::equalsIgnoreCase(option,
			LOG4CXX_STR("LOCATIONINFO"), LOG4CXX_STR("locationinfo")))
	{
		setLocationInfo(OptionConverter::toBoolean(value, false));
		m_priv->expectedPatternLength = getFormattedEventCharacterCount() * 2;
	}
}

void HTMLLayout::format(LogString& output,
	const spi::LoggingEventPtr& event,
	Pool& p) const
{
	auto& lsMsg = event->getRenderedMessage();
	output.reserve(m_priv->expectedPatternLength + lsMsg.size());
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<tr>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<td>"));

	m_priv->dateFormat.format(output, event->getTimeStamp(), p);


	output.append(LOG4CXX_STR("</td>"));
	output.append(LOG4CXX_EOL);

	output.append(LOG4CXX_STR("<td title=\""));
	LogString threadName(event->getThreadName());
	Transform::appendEscapingTags(output, threadName);
	output.append(LOG4CXX_STR(" thread\">"));
	Transform::appendEscapingTags(output, threadName);
	output.append(LOG4CXX_STR("</td>"));
	output.append(LOG4CXX_EOL);

	output.append(LOG4CXX_STR("<td title=\"Level\">"));

	if (event->getLevel()->equals(Level::getDebug()))
	{
		output.append(LOG4CXX_STR("<font color=\"#339933\">"));
		Transform::appendEscapingTags(output, event->getLevel()->toString());
		output.append(LOG4CXX_STR("</font>"));
	}
	else if (event->getLevel()->isGreaterOrEqual(Level::getWarn()))
	{
		output.append(LOG4CXX_STR("<font color=\"#993300\"><strong>"));
		Transform::appendEscapingTags(output, event->getLevel()->toString());
		output.append(LOG4CXX_STR("</strong></font>"));
	}
	else
	{
		Transform::appendEscapingTags(output, event->getLevel()->toString());
	}

	output.append(LOG4CXX_STR("</td>"));
	output.append(LOG4CXX_EOL);

	output.append(LOG4CXX_STR("<td title=\""));
	Transform::appendEscapingTags(output, event->getLoggerName());
	output.append(LOG4CXX_STR(" logger\">"));
	Transform::appendEscapingTags(output, event->getLoggerName());
	output.append(LOG4CXX_STR("</td>"));
	output.append(LOG4CXX_EOL);

	if (m_priv->locationInfo)
	{
		output.append(LOG4CXX_STR("<td>"));
		const LocationInfo& locInfo = event->getLocationInformation();
		LOG4CXX_DECODE_CHAR(fileName, locInfo.getFileName());
		Transform::appendEscapingTags(output, fileName);
		output.append(1, (logchar) 0x3A /* ':' */);
		int line = event->getLocationInformation().getLineNumber();

		if (line != 0)
		{
			StringHelper::toString(line, p, output);
		}

		output.append(LOG4CXX_STR("</td>"));
		output.append(LOG4CXX_EOL);
	}

	output.append(LOG4CXX_STR("<td title=\"Message\">"));
	Transform::appendEscapingTags(output, lsMsg);
	output.append(LOG4CXX_STR("</td>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("</tr>"));
	output.append(LOG4CXX_EOL);

	LogString ndcVal;

	if (event->getNDC(ndcVal))
	{
		output.append(LOG4CXX_STR("<tr><td bgcolor=\"#EEEEEE\" "));
		output.append(LOG4CXX_STR("style=\"font-size : xx-small;\" colspan=\"6\" "));
		output.append(LOG4CXX_STR("title=\"Nested Diagnostic Context\">"));
		output.append(LOG4CXX_STR("NDC: "));
		Transform::appendEscapingTags(output, ndcVal);
		output.append(LOG4CXX_STR("</td></tr>"));
		output.append(LOG4CXX_EOL);
	}
}

void HTMLLayout::appendHeader(LogString& output, Pool& p)
{
	output.append(LOG4CXX_STR("<!DOCTYPE HTML PUBLIC "));
	output.append(LOG4CXX_STR("\"-//W3C//DTD HTML 4.01 Transitional//EN\" "));
	output.append(LOG4CXX_STR("\"http://www.w3.org/TR/html4/loose.dtd\">"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<html>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<head>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<title>"));
	Transform::appendEscapingTags(output, m_priv->title);
	output.append(LOG4CXX_STR("</title>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<style type=\"text/css\">"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<!--"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("body, table {font-family: arial,sans-serif; font-size: x-small;}"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("th {background: #336699; color: #FFFFFF; text-align: left;}"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("-->"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("</style>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("</head>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<body bgcolor=\"#FFFFFF\" topmargin=\"6\" leftmargin=\"6\">"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<hr size=\"1\" noshade>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("Log session start time "));

	m_priv->dateFormat.format(output, Date::currentTime(), p);

	output.append(LOG4CXX_STR("<br>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<br>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<table cellspacing=\"0\" cellpadding=\"4\" border=\"1\" bordercolor=\"#224466\" width=\"100%\">"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<tr>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<th>Time</th>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<th>Thread</th>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<th>Level</th>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<th>Logger</th>"));
	output.append(LOG4CXX_EOL);

	if (m_priv->locationInfo)
	{
		output.append(LOG4CXX_STR("<th>File:Line</th>"));
		output.append(LOG4CXX_EOL);
	}

	output.append(LOG4CXX_STR("<th>Message</th>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("</tr>"));
	output.append(LOG4CXX_EOL);
}

void HTMLLayout::appendFooter(LogString& output, Pool& /* pool */ )
{
	output.append(LOG4CXX_STR("</table>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("<br>"));
	output.append(LOG4CXX_EOL);
	output.append(LOG4CXX_STR("</body></html>"));
}

void HTMLLayout::setLocationInfo(bool locationInfoFlag)
{
	m_priv->locationInfo = locationInfoFlag;
}

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

void HTMLLayout::setTitle(const LogString& title1)
{
	m_priv->title.assign(title1);
}

const LogString& HTMLLayout::getTitle() const
{
	return m_priv->title;
}

LogString HTMLLayout::getContentType() const
{
	return LOG4CXX_STR("text/html");
}

bool HTMLLayout::ignoresThrowable() const
{
	return false;
}
