/*
 * 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/helpers/asyncbuffer.h>
#include <log4cxx/helpers/transcoder.h>
#if LOG4CXX_CONCEPTS
#include <variant>
#endif // LOG4CXX_CONCEPTS

namespace LOG4CXX_NS
{

namespace helpers
{

struct AsyncBuffer::Private
{
#if LOG4CXX_CONCEPTS && LOG4CXX_WCHAR_T_API
    using value_t = std::variant<MessageBufferAppender, WideMessageBufferAppender>;
#else // !(defined(__cpp_concepts) && 202002 <= __cpp_concepts && LOG4CXX_WCHAR_T_API)
    using value_t = MessageBufferAppender;
#endif // !(defined(__cpp_concepts) && 202002 <= __cpp_concepts && LOG4CXX_WCHAR_T_API)
	std::vector<value_t> data;

	Private(const value_t& f)
		: data{ f }
	{}

#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
	StringViewType fmt_string;
	FmtArgStore    fmt_args;

	Private(StringViewType&& format_string, FmtArgStore&& args)
		: fmt_string{ std::move(format_string) }
		, fmt_args{ std::move(args) }
	{}

#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
	WideStringViewType fmt_wstring;
	WideFmtArgStore    fmt_wargs;

	Private(WideStringViewType&& format_string, WideFmtArgStore&& args)
		: fmt_wstring{ std::move(format_string) }
		, fmt_wargs{ std::move(args) }
	{}
#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT

};

#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
void AsyncBuffer::initializeForFmt(StringViewType&& format_string, FmtArgStore&& args)
{
	if (!m_priv)
		m_priv = std::make_unique<Private>(std::move(format_string), std::move(args));
	else
	{
		m_priv->fmt_string = std::move(format_string);
		m_priv->fmt_args = std::move(args);
	}
}

#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
void AsyncBuffer::initializeForFmt(WideStringViewType&& format_string, WideFmtArgStore&& args)
{
	if (!m_priv)
		m_priv = std::make_unique<Private>(std::move(format_string), std::move(args));
	else
	{
		m_priv->fmt_wstring = std::move(format_string);
		m_priv->fmt_wargs = std::move(args);
	}
}
#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT

/** An empty buffer.
*/
AsyncBuffer::AsyncBuffer()
{}

/** A new buffer with the content of \c other
*/
AsyncBuffer::AsyncBuffer(AsyncBuffer&& other)
	: m_priv(std::move(other.m_priv))
{
}

/** Release resources.
*/
AsyncBuffer::~AsyncBuffer()
{
}

/**
* Has no item been added to this?
*/
bool AsyncBuffer::empty() const
{
	bool result{ true };
	if (m_priv)
	{
		result = m_priv->data.empty();
#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
		if (result)
			result = (0 == m_priv->fmt_string.size());
#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
		if (result)
			result = (0 == m_priv->fmt_wstring.size());
#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
#endif
	}
	return result;
}

/**
* Add text version of buffered values to \c msg
*/
void AsyncBuffer::renderMessage(LogCharMessageBuffer& msg) const
{
	if (m_priv)
	{
		for (auto& renderer : m_priv->data)
#if LOG4CXX_CONCEPTS && LOG4CXX_WCHAR_T_API
		{
#if LOG4CXX_LOGCHAR_IS_UTF8
			if (auto pRenderer = std::get_if<MessageBufferAppender>(&renderer))
				(*pRenderer)(msg);
			else
			{
				WideMessageBuffer wideBuf;
				std::get<WideMessageBufferAppender>(renderer)(wideBuf);
				LOG4CXX_DECODE_WCHAR(lsMsg, wideBuf.extract_str(wideBuf));
				msg << lsMsg;
			}
#else // !LOG4CXX_LOGCHAR_IS_UTF8
			if (auto pRenderer = std::get_if<WideMessageBufferAppender>(&renderer))
				(*pRenderer)(msg);
			else
			{
				CharMessageBuffer narrowBuf;
				std::get<MessageBufferAppender>(renderer)(narrowBuf);
				LOG4CXX_DECODE_CHAR(lsMsg, narrowBuf.extract_str(narrowBuf));
				msg << lsMsg;
			}
#endif // !LOG4CXX_LOGCHAR_IS_UTF8
		}
#else // !LOG4CXX_CONCEPTS
			renderer(msg);
#endif // !LOG4CXX_CONCEPTS

#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
#if LOG4CXX_LOGCHAR_IS_UTF8
		if (0 < m_priv->fmt_string.size())
			msg << fmt::vformat(m_priv->fmt_string, m_priv->fmt_args);
#if LOG4CXX_WCHAR_T_API
		if (0 < m_priv->fmt_wstring.size())
		{
			LOG4CXX_DECODE_WCHAR(lsMsg, fmt::vformat(m_priv->fmt_wstring, m_priv->fmt_wargs));
			msg << lsMsg;
		}
#endif // LOG4CXX_WCHAR_T_API
#endif // LOG4CXX_LOGCHAR_IS_UTF8

#if LOG4CXX_LOGCHAR_IS_WCHAR
		if (0 < m_priv->fmt_wstring.size())
			msg << fmt::vformat(m_priv->fmt_wstring, m_priv->fmt_wargs);
		if (0 < m_priv->fmt_string.size())
		{
			LOG4CXX_DECODE_CHAR(lsMsg, fmt::vformat(m_priv->fmt_string, m_priv->fmt_args));
			msg << lsMsg;
		}
#endif // LOG4CXX_LOGCHAR_IS_WCHAR
#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
	}
}

/**
* Remove all message appenders
*/
void AsyncBuffer::clear()
{
	if (m_priv)
	{
		m_priv->data.clear();
#if LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
		m_priv->fmt_string = {};
#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
		m_priv->fmt_wstring = {};
#endif // LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR
#endif // LOG4CXX_ASYNC_BUFFER_SUPPORTS_FMT
	}
}

#if LOG4CXX_CONCEPTS
/**
 *   Append \c function to this buffer.
 */
void AsyncBuffer::append(const MessageBufferAppender& f)
{
	if (!m_priv)
		m_priv = std::make_unique<Private>(f);
	else
		m_priv->data.push_back(f);
}

#if LOG4CXX_WCHAR_T_API
/**
 *   Append \c function to this buffer.
 */
void AsyncBuffer::append(const WideMessageBufferAppender& f)
{
	if (!m_priv)
		m_priv = std::make_unique<Private>(f);
	else
		m_priv->data.push_back(f);
}
#endif // LOG4CXX_WCHAR_T_API
#else // !LOG4CXX_CONCEPTS
/**
 *   Append \c function to this buffer.
 */
void AsyncBuffer::append(const MessageBufferAppender& f)
{
	if (!m_priv)
		m_priv = std::make_unique<Private>(f);
	else
		m_priv->data.push_back(f);
}
#endif // !LOG4CXX_CONCEPTS

} // namespace helpers
} // namespace LOG4CXX_NS

