/*
 * 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.
 */
#define NOMINMAX /* tell windows not to define min/max macros */
#include <log4cxx/private/string_c11.h>
#include <log4cxx/logstring.h>
#include <log4cxx/helpers/charsetdecoder.h>
#include <log4cxx/helpers/bytebuffer.h>
#include <log4cxx/helpers/exception.h>
#include <log4cxx/helpers/pool.h>
#include <log4cxx/helpers/loglog.h>
#include <apr_xlate.h>
#if !defined(LOG4CXX)
	#define LOG4CXX 1
#endif
#include <log4cxx/private/log4cxx_private.h>
#include <locale.h>
#include <apr_portable.h>
#include <log4cxx/helpers/stringhelper.h>
#include <log4cxx/helpers/transcoder.h>
#include <mutex>

using namespace LOG4CXX_NS;
using namespace LOG4CXX_NS::helpers;

IMPLEMENT_LOG4CXX_OBJECT(CharsetDecoder)


namespace LOG4CXX_NS
{
namespace helpers
{

#if APR_HAS_XLATE
/**
 *  Converts from an arbitrary encoding to LogString
 *    using apr_xlate.  Requires real iconv implementation,
*    apr-iconv will crash in use.
 */
class APRCharsetDecoder : public CharsetDecoder
{
	public:
		/**
		 *  Creates a new instance.
		 *  @param frompage name of source encoding.
		 */
		APRCharsetDecoder(const LogString& frompage) : pool()
		{
#if LOG4CXX_LOGCHAR_IS_WCHAR
			const char* topage = "WCHAR_T";
#endif
#if LOG4CXX_LOGCHAR_IS_UTF8
			const char* topage = "UTF-8";
#endif
#if LOG4CXX_LOGCHAR_IS_UNICHAR
			const char* topage = "UTF-16";
#endif
			std::string fpage(Transcoder::encodeCharsetName(frompage));
			apr_status_t stat = apr_xlate_open(&convset,
					topage,
					fpage.c_str(),
					pool.getAPRPool());

			if (stat != APR_SUCCESS)
			{
				throw IllegalArgumentException(frompage);
			}
		}

		/**
		 *  Destructor.
		 */
		virtual ~APRCharsetDecoder()
		{
		}

		virtual log4cxx_status_t decode(ByteBuffer& in,
			LogString& out)
		{
			enum { BUFSIZE = 256 };
			logchar buf[BUFSIZE];
			const apr_size_t initial_outbytes_left = BUFSIZE * sizeof(logchar);
			apr_status_t stat = APR_SUCCESS;

			if (in.remaining() == 0)
			{
				size_t outbytes_left = initial_outbytes_left;
				{
					std::lock_guard<std::mutex> lock(mutex);
					stat = apr_xlate_conv_buffer((apr_xlate_t*) convset,
							NULL, NULL, (char*) buf, &outbytes_left);
				}
				out.append(buf, (initial_outbytes_left - outbytes_left) / sizeof(logchar));
			}
			else
			{
				while (in.remaining() > 0 && stat == APR_SUCCESS)
				{
					size_t inbytes_left = in.remaining();
					size_t initial_inbytes_left = inbytes_left;
					apr_size_t outbytes_left = initial_outbytes_left;
					{
						std::lock_guard<std::mutex> lock(mutex);
						stat = apr_xlate_conv_buffer((apr_xlate_t*) convset,
								in.current(),
								&inbytes_left,
								(char*) buf,
								&outbytes_left);
					}
					out.append(buf, (initial_outbytes_left - outbytes_left) / sizeof(logchar));
					if (inbytes_left == initial_inbytes_left && stat == APR_SUCCESS)
					{
						stat = APR_BADCH;
						break;
					}
					in.increment_position(initial_inbytes_left - inbytes_left);
				}
			}

			return stat;
		}

	private:
		APRCharsetDecoder(const APRCharsetDecoder&);
		APRCharsetDecoder& operator=(const APRCharsetDecoder&);
		LOG4CXX_NS::helpers::Pool pool;
		std::mutex mutex;
		apr_xlate_t* convset;
};

#endif

#if LOG4CXX_LOGCHAR_IS_WCHAR && LOG4CXX_HAS_MBSRTOWCS
/**
*    Converts from the default multi-byte string to
*        LogString using mbstowcs.
*
*/
class MbstowcsCharsetDecoder : public CharsetDecoder
{
	public:
		MbstowcsCharsetDecoder()
		{
		}

		virtual ~MbstowcsCharsetDecoder()
		{
		}

	private:
		inline log4cxx_status_t append(LogString& out, const wchar_t* buf)
		{
			out.append(buf);
			return APR_SUCCESS;
		}

		virtual log4cxx_status_t decode(ByteBuffer& in,
			LogString& out)
		{
			log4cxx_status_t stat = APR_SUCCESS;
			enum { BUFSIZE = 256 };
			wchar_t wbuf[BUFSIZE];
			char cbuf[BUFSIZE*4];

			mbstate_t mbstate;
			memset(&mbstate, 0, sizeof(mbstate));

			while (in.remaining() > 0)
			{
				const char* src = in.current();

				if (*src == 0)
				{
					out.append(1, (logchar) 0);
					in.increment_position(1);
				}
				else
				{
					auto available = std::min(sizeof (cbuf) - 1, in.remaining());
					strncpy(cbuf, src, available);
					cbuf[available] = 0;
					src = cbuf;
					size_t wCharCount = mbsrtowcs(wbuf,
							&src,
							BUFSIZE - 1,
							&mbstate);
					// mbsrtowcs sets *src to nullptr when it consumes a null wide character.
					// Performing pointer arithmetic on that nullptr (src - cbuf) is undefined
					// behaviour, so recover the consumed byte count from the position of the
					// null that stopped the conversion instead.
					size_t converted;
					if (src == nullptr)
					{
						size_t nullPos = 0;
						while (nullPos < available && cbuf[nullPos] != 0)
						{
							++nullPos;
						}
						// If the null came from the input bytes, it was consumed too;
						// if it is the sentinel we wrote at cbuf[available], stop at available.
						converted = (nullPos < available) ? nullPos + 1 : available;
					}
					else
					{
						converted = static_cast<size_t>(src - cbuf);
					}
					in.increment_position(converted);

					if (wCharCount == (size_t) -1) // Illegal byte sequence?
					{
						LogString msg(LOG4CXX_STR("Illegal byte sequence at "));
						msg.append(std::to_wstring(in.position()));
						msg.append(LOG4CXX_STR(" of "));
						msg.append(std::to_wstring(in.limit()));
						LogLog::warn(msg);
						stat = APR_BADCH;
						break;
					}
					else
					{
						// FIX: Check for incomplete sequence infinite loop.
						// If mbsrtowcs returns success (>=0) but converted 0 bytes while data remains,
						// we are stuck (e.g. incomplete multibyte char at EOF).
						if (converted == 0 && in.remaining() > 0)
						{
							LogString msg(LOG4CXX_STR("Incomplete multibyte sequence at end of buffer"));
							LogLog::warn(msg);
							stat = APR_BADCH;
							break; // Break the infinite loop
						}

						wbuf[wCharCount] = 0;
						stat = append(out, wbuf);
					}
				}
			}

			return stat;
		}



	private:
		MbstowcsCharsetDecoder(const MbstowcsCharsetDecoder&);
		MbstowcsCharsetDecoder& operator=(const MbstowcsCharsetDecoder&);
};
#endif


/**
*    Decoder used when the external and internal charsets
*    are the same.
*
*/
class TrivialCharsetDecoder : public CharsetDecoder
{
	public:
		TrivialCharsetDecoder()
		{
		}

		virtual ~TrivialCharsetDecoder()
		{
		}

		virtual log4cxx_status_t decode(ByteBuffer& in,
			LogString& out)
		{
			size_t remaining = in.remaining();

			if ( remaining > 0)
			{
				auto src = in.current();
				auto count = remaining / sizeof(logchar);
				out.append(reinterpret_cast<const logchar*>(src), count);
				in.increment_position(remaining);
			}

			return APR_SUCCESS;
		}



	private:
		TrivialCharsetDecoder(const TrivialCharsetDecoder&);
		TrivialCharsetDecoder& operator=(const TrivialCharsetDecoder&);
};

/**
*    Converts from UTF-8 to LogString
*
*/
class UTF8CharsetDecoder : public CharsetDecoder
{
	public:
		UTF8CharsetDecoder()
		{
		}

		virtual ~UTF8CharsetDecoder()
		{
		}

	private:
		virtual log4cxx_status_t decode(ByteBuffer& in,
			LogString& out)
		{
			auto availableByteCount = in.remaining();
			while (0 < availableByteCount)
			{
				auto sv = getUTF8CodePoint(in);
				auto nextAvailableByteCount = in.remaining();
				if (sv == 0xFFFF || nextAvailableByteCount == availableByteCount)
					return APR_BADCH;
				Transcoder::encode(sv, out);
				availableByteCount = nextAvailableByteCount;
			}
			return APR_SUCCESS;
		}

	private:
		UTF8CharsetDecoder(const UTF8CharsetDecoder&);
		UTF8CharsetDecoder& operator=(const UTF8CharsetDecoder&);
};

/**
*    Converts from ISO-8859-1 to LogString.
*
*/
class ISOLatinCharsetDecoder : public CharsetDecoder
{
	public:
		ISOLatinCharsetDecoder()
		{
		}

		virtual ~ISOLatinCharsetDecoder()
		{
		}

	private:
		virtual log4cxx_status_t decode(ByteBuffer& in,
			LogString& out)
		{
			auto availableByteCount = in.remaining();
			auto src = in.current();
			auto srcEnd = src + availableByteCount;

			while (src < srcEnd)
			{
				auto sv = static_cast<unsigned int>(static_cast<unsigned char>(*src++));
				Transcoder::encode(sv, out);
			}
			in.increment_position(availableByteCount);

			return APR_SUCCESS;
		}



	private:
		ISOLatinCharsetDecoder(const ISOLatinCharsetDecoder&);
		ISOLatinCharsetDecoder& operator=(const ISOLatinCharsetDecoder&);
};


/**
*    Converts from US-ASCII to LogString.
*
*/
class USASCIICharsetDecoder : public CharsetDecoder
{
	public:
		USASCIICharsetDecoder()
		{
		}

		virtual ~USASCIICharsetDecoder()
		{
		}

	private:

		virtual log4cxx_status_t decode(ByteBuffer& in,
			LogString& out)
		{
			log4cxx_status_t stat = APR_SUCCESS;

			auto availableByteCount = in.remaining();
			auto src = in.current();
			auto srcEnd = src + availableByteCount;
			size_t byteCount = 0;
			while (src < srcEnd)
			{
				auto sv = static_cast<unsigned int>(*src++);

				if (sv < 0x80)
				{
					++byteCount;
					Transcoder::encode(sv, out);
				}
				else
				{
					stat = APR_BADCH;
					break;
				}
			}
			in.increment_position(byteCount);

			return stat;
		}



	private:
		USASCIICharsetDecoder(const USASCIICharsetDecoder&);
		USASCIICharsetDecoder& operator=(const USASCIICharsetDecoder&);
};

/**
 *    Charset decoder that uses current locale settings.
 */
class LocaleCharsetDecoder : public CharsetDecoder
{
	public:
		LocaleCharsetDecoder() : state()
		{
		}
		log4cxx_status_t decode(ByteBuffer& in, LogString& out) override
		{
			log4cxx_status_t result = APR_SUCCESS;
			auto p = in.current();
			auto availableByteCount = in.remaining();
			size_t byteCount = 0;
#if !LOG4CXX_CHARSET_EBCDIC
			if (std::mbsinit(&this->state)) // ByteBuffer not partially decoded?
			{
				// Copy single byte characters
				for (; byteCount < availableByteCount && static_cast<unsigned int>(*p) < 0x80; ++byteCount, ++p)
				{
					out.append(1, *p);
				}
			}
#endif
			// Decode characters that may be represented by multiple bytes
			while (byteCount < availableByteCount)
			{
				wchar_t ch = 0;
				size_t n = std::mbrtowc(&ch, p, availableByteCount - byteCount, &this->state);
				if (0 == n) // NULL encountered?
				{
					++byteCount;
					break;
				}
				if (static_cast<std::size_t>(-1) == n) // decoding error?
				{
					result = APR_BADCH;
					break;
				}
				if (static_cast<std::size_t>(-2) == n) // incomplete sequence?
				{
					break;
				}
				Transcoder::encode(static_cast<unsigned int>(ch), out);
				byteCount += n;
				p += n;
			}
			in.increment_position(byteCount);
			return result;
		}

	private:
		std::mbstate_t state;
};



} // namespace helpers

}  //namespace log4cxx


CharsetDecoder::CharsetDecoder()
{
}


CharsetDecoder::~CharsetDecoder()
{
}

CharsetDecoder* CharsetDecoder::createDefaultDecoder()
{
#if LOG4CXX_CHARSET_UTF8
#if LOG4CXX_LOGCHAR_IS_UTF8
	return new TrivialCharsetDecoder();
#else
	return new UTF8CharsetDecoder();
#endif
#elif LOG4CXX_CHARSET_ISO88591 || defined(_WIN32_WCE)
	return new ISOLatinCharsetDecoder();
#elif LOG4CXX_CHARSET_USASCII
	return new USASCIICharsetDecoder();
#elif LOG4CXX_LOGCHAR_IS_WCHAR && LOG4CXX_HAS_MBSRTOWCS
	return new MbstowcsCharsetDecoder();
#else
	return new LocaleCharsetDecoder();
#endif
}

CharsetDecoderPtr CharsetDecoder::getDefaultDecoder()
{
	static WideLife<CharsetDecoderPtr> decoder(createDefaultDecoder());

	//
	//  if invoked after static variable destruction
	//     (if logging is called in the destructor of a static object)
	//     then create a new decoder.
	//
	if (decoder.value() == 0)
	{
		return CharsetDecoderPtr( createDefaultDecoder() );
	}

	return decoder;
}

CharsetDecoderPtr CharsetDecoder::getUTF8Decoder()
{
	return std::make_shared<UTF8CharsetDecoder>();
}

CharsetDecoderPtr CharsetDecoder::getISOLatinDecoder()
{
	return std::make_shared<ISOLatinCharsetDecoder>();
}


CharsetDecoderPtr CharsetDecoder::getDecoder(const LogString& charset)
{
	if (StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("UTF-8"), LOG4CXX_STR("utf-8")) ||
		StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("UTF8"), LOG4CXX_STR("utf8")) ||
		StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("CP65001"), LOG4CXX_STR("cp65001")))
	{
#if LOG4CXX_LOGCHAR_IS_UTF8
		return std::make_shared<TrivialCharsetDecoder>();
#else
		return std::make_shared<UTF8CharsetDecoder>();
#endif
	}
	else if (StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("C"), LOG4CXX_STR("c")) ||
		charset == LOG4CXX_STR("646") ||
		StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("US-ASCII"), LOG4CXX_STR("us-ascii")) ||
		StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("ISO646-US"), LOG4CXX_STR("iso646-US")) ||
		StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("ANSI_X3.4-1968"), LOG4CXX_STR("ansi_x3.4-1968")) ||
		StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("CP20127"), LOG4CXX_STR("cp20127")))
	{
		return std::make_shared<USASCIICharsetDecoder>();
	}
	else if (StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("ISO-8859-1"), LOG4CXX_STR("iso-8859-1")) ||
		StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("ISO-LATIN-1"), LOG4CXX_STR("iso-latin-1")) ||
		StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("CP1252"), LOG4CXX_STR("cp1252")))
	{
		return std::make_shared<ISOLatinCharsetDecoder>();
	}
	else if (StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("LOCALE"), LOG4CXX_STR("locale")))
	{
		return std::make_shared<LocaleCharsetDecoder>();
	}

#if APR_HAS_XLATE
	return std::make_shared<APRCharsetDecoder>(charset);
#else
	throw IllegalArgumentException(charset);
#endif
}

log4cxx_status_t CharsetDecoder::decode(const char* in, size_t maxByteCount, LogString& out)
{
	ByteBuffer buf((char*)in, strnlen_s(in, maxByteCount));
	return decode(buf, out);
}

unsigned int CharsetDecoder::getUTF8CodePoint(ByteBuffer& in)
{
	auto availableByteCount = in.remaining();
	if (0 == availableByteCount)
		return 0xFFFF;

	auto pChar = in.current();
	auto ch1 = static_cast<unsigned char>(*pChar);
	if (ch1 <= 0x7F)
	{
		in.increment_position(1);
		return ch1;
	}

	//
	//   should not have continuation character here
	//
	if ((ch1 & 0xC0) != 0x80 && 1 < availableByteCount)
	{
		auto ch2 = static_cast<unsigned char>(*(pChar + 1));
		if ((ch2 & 0xC0) != 0x80) // not a continuation?
			return 0xFFFF;

		if ((ch1 & 0xE0) == 0xC0)
		{
			unsigned int rv = ((ch1 & 0x1F) << 6) + (ch2 & 0x3F);
			if (rv >= 0x80)
			{
				in.increment_position(2);
				return rv;
			}
			return 0xFFFF;
		}

		if (2 < availableByteCount)
		{
			auto ch3 = static_cast<unsigned char>(*(pChar + 2));
			if ((ch3 & 0xC0) != 0x80) // not a continuation?
				return 0xFFFF;

			if ((ch1 & 0xF0) == 0xE0)
			{
				unsigned int rv = ((ch1 & 0x0F) << 12)
					+ ((ch2 & 0x3F) << 6)
					+ (ch3 & 0x3F);

				// RFC 3629 §3 prohibits UTF-8 encodings of the UTF-16 surrogate
				// halves (U+D800..U+DFFF); accepting them lets malformed Unicode
				// cross the decode boundary into LogString and downstream output.
				if (rv < 0x800 || (0xD800 <= rv && rv <= 0xDFFF))
					return 0xFFFF;

				in.increment_position(3);
				return rv;
			}

			if (3 < availableByteCount)
			{
				auto ch4 = static_cast<unsigned char>(*(pChar + 3));
				if ((ch4 & 0xC0) != 0x80) // not a continuation?
					return 0xFFFF;

				unsigned int rv = ((ch1 & 0x07) << 18)
					+ ((ch2 & 0x3F) << 12)
					+ ((ch3 & 0x3F) << 6)
					+ (ch4 & 0x3F);

				// RFC 3629 §3 caps UTF-8 at U+10FFFF; lead bytes F5..F7 (and
				// F4 with an over-high trailer) produce rv > 0x10FFFF, which
				// is not a Unicode code point. Without this bound, encodeUTF16
				// later silently aliases the bogus value to a valid in-range
				// code point — a substitution-collision filter-bypass primitive.
				// Lead bytes F8..FF are never valid UTF-8, but the & 0x07 mask
				// discards their high bits, so without the (ch1 & 0xF8) == 0xF0
				// guard F8 BF BF BF would alias to U+3FFFF instead of being
				// rejected.
				if ((ch1 & 0xF8) == 0xF0 && rv > 0xFFFF && rv <= 0x10FFFF)
				{
					in.increment_position(4);
					return rv;
				}

			}
		}
	}
	return 0xFFFF;
}
