#ifndef ENCODING_HPP_44AE7032_AF79_4A6F_A2ED_529BC1A38758
#define ENCODING_HPP_44AE7032_AF79_4A6F_A2ED_529BC1A38758
#pragma once

/*
encoding.hpp

Работа с кодовыми страницами
*/
/*
Copyright © 1996 Eugene Roshal
Copyright © 2000 Far Group
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.
3. The name of the authors may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

// Internal:

// Platform:

// Common:
#include "common/bytes_view.hpp"
#include "common/range.hpp"
#include "common/utility.hpp"

// External:

//----------------------------------------------------------------------------

namespace encoding
{
	struct diagnostics
	{
		enum: unsigned
		{
			error_position = 0_bit,
			incomplete_bytes = 1_bit,

			all = ~0u
		};

		unsigned EnabledDiagnostics{ all };
		std::optional<size_t> ErrorPosition;
		size_t IncompleteBytes{};
	};

	namespace codepage
	{
		namespace detail
		{
			struct utf8 { [[nodiscard]] static uintptr_t id(); };
			struct ansi { [[nodiscard]] static uintptr_t id(); };
			struct oem  { [[nodiscard]] static uintptr_t id(); };
		}

		[[nodiscard]] inline uintptr_t utf8() { return detail::utf8::id(); }
		[[nodiscard]] inline uintptr_t ansi() { return detail::ansi::id(); }
		[[nodiscard]] inline uintptr_t oem()  { return detail::oem::id(); }

		[[nodiscard]] uintptr_t normalise(uintptr_t Codepage);
	}

	[[nodiscard]] size_t get_bytes(uintptr_t Codepage, string_view Str, span<char> Buffer, diagnostics* Diagnostics = {});
	void get_bytes(uintptr_t Codepage, string_view Str, std::string& Buffer, diagnostics* Diagnostics = {});
	[[nodiscard]] std::string get_bytes(uintptr_t Codepage, string_view Str, diagnostics* Diagnostics = {});

	[[nodiscard]] size_t get_bytes_count(uintptr_t Codepage, string_view Str, diagnostics* Diagnostics = {});

	//-------------------------------------------------------------------------

	[[nodiscard]] size_t get_chars(uintptr_t Codepage, std::string_view Str, span<wchar_t> Buffer, diagnostics* Diagnostics = {});
	void get_chars(uintptr_t Codepage, std::string_view Str, string& Buffer, diagnostics* Diagnostics = {});
	[[nodiscard]] size_t get_chars(uintptr_t Codepage, bytes_view Str, span<wchar_t> Buffer, diagnostics* Diagnostics = {});
	void get_chars(uintptr_t Codepage, bytes_view Str, string& Buffer, diagnostics* Diagnostics = {});
	[[nodiscard]] string get_chars(uintptr_t Codepage, std::string_view Str, diagnostics* Diagnostics = {});
	[[nodiscard]] string get_chars(uintptr_t Codepage, bytes_view Str, diagnostics* Diagnostics = {});

	[[nodiscard]] size_t get_chars_count(uintptr_t Codepage, std::string_view Str, diagnostics* Diagnostics = {});
	[[nodiscard]] size_t get_chars_count(uintptr_t Codepage, bytes_view Str, diagnostics* Diagnostics = {});

	//-------------------------------------------------------------------------

	[[noreturn]]
	void raise_exception(uintptr_t Codepage, string_view Str, size_t Position);

	namespace detail
	{
		namespace cp = codepage;

		template<typename T>
		class codepage
		{
		public:
			[[nodiscard]] static auto get_bytes(string_view const Str, span<char> const Buffer, diagnostics* const Diagnostics = {})
			{
				return encoding::get_bytes(T::id(), Str, Buffer, Diagnostics);
			}

			static auto get_bytes(string_view const Str, std::string& Buffer, diagnostics* const Diagnostics = {})
			{
				return encoding::get_bytes(T::id(), Str, Buffer, Diagnostics);
			}

			[[nodiscard]] static auto get_bytes(string_view const Str, diagnostics* const Diagnostics = {})
			{
				return encoding::get_bytes(T::id(), Str, Diagnostics);
			}

			[[nodiscard]] static auto get_bytes_count(string_view const Str, diagnostics* const Diagnostics = {})
			{
				return encoding::get_bytes_count(T::id(), Str, Diagnostics);
			}

			[[nodiscard]] static auto get_chars(std::string_view const Str, span<wchar_t> const Buffer, diagnostics* const Diagnostics = {}, size_t* IncompleteBytes = {})
			{
				return encoding::get_chars(T::id(), Str, Buffer, Diagnostics);
			}

			static auto get_chars(std::string_view const Str, string& Buffer, diagnostics* const Diagnostics = {}, size_t* IncompleteBytes = {})
			{
				return encoding::get_chars(T::id(), Str, Buffer, Diagnostics);
			}

			[[nodiscard]] static auto get_chars(bytes_view const Str, span<wchar_t> const Buffer, diagnostics* const Diagnostics = {}, size_t* IncompleteBytes = {})
			{
				return encoding::get_chars(T::id(), Str, Buffer, Diagnostics);
			}

			static auto get_chars(bytes_view const Str, string& Buffer, diagnostics* const Diagnostics = {}, size_t* IncompleteBytes = {})
			{
				return encoding::get_chars(T::id(), Str, Buffer, Diagnostics);
			}

			[[nodiscard]] static auto get_chars(std::string_view const Str, diagnostics* const Diagnostics = {}, size_t* IncompleteBytes = {})
			{
				return encoding::get_chars(T::id(), Str, Diagnostics);
			}

			[[nodiscard]] static auto get_chars(bytes_view const Str, diagnostics* const Diagnostics = {}, size_t* IncompleteBytes = {})
			{
				return encoding::get_chars(T::id(), Str, Diagnostics);
			}

			[[nodiscard]] static auto get_chars_count(std::string_view const Str, diagnostics* const Diagnostics = {}, size_t* IncompleteBytes = {})
			{
				return encoding::get_chars_count(T::id(), Str, Diagnostics);
			}

			[[nodiscard]] static auto get_chars_count(bytes_view const Str, diagnostics* const Diagnostics = {}, size_t* IncompleteBytes = {})
			{
				return encoding::get_chars_count(T::id(), Str, Diagnostics);
			}
		};
	}

	using utf8 = detail::codepage<codepage::detail::utf8>;
	using ansi = detail::codepage<codepage::detail::ansi>;
	using oem = detail::codepage<codepage::detail::oem>;

	[[nodiscard]] std::string_view get_signature_bytes(uintptr_t Cp);

	class writer
	{
	public:
		NONCOPYABLE(writer);
		writer(std::ostream& Stream, uintptr_t Codepage, bool AddSignature = true, bool IgnoreEncodingErrors = false);

		template<typename... args>
		void write(string_view Str, const args&... Args)
		{
			write_impl(Str);
			(..., write_impl(Args));
		}

	private:
		void write_impl(string_view Str);

		std::string m_Buffer;
		std::ostream* m_Stream;
		uintptr_t m_Codepage;
		bool m_AddSignature;
		bool m_IgnoreEncodingErrors;
	};

	bool is_valid_utf8(std::string_view Str, bool PartialContent, bool& PureAscii);

	inline constexpr wchar_t bom_char      = L'﻿'; // Zero Length Space
	inline constexpr wchar_t replace_char  = L'�'; // Replacement
	inline constexpr wchar_t continue_char = L'›'; // Single Right-Pointing Angle Quotation Mark

	namespace utf16
	{
		bool is_high_surrogate(wchar_t Char);
		bool is_low_surrogate(wchar_t Char);
		bool is_valid_surrogate_pair(wchar_t First, wchar_t Second);
		char32_t extract_codepoint(wchar_t First, wchar_t Second);
		char32_t extract_codepoint(string_view Str);
		void remove_first_codepoint(string_view& Str);
		void remove_last_codepoint(string_view& Str);
		std::pair<wchar_t, wchar_t> to_surrogate(char32_t Codepoint);
	}
}

void swap_bytes(const void* Src, void* Dst, size_t SizeInBytes);

[[nodiscard]] bool IsVirtualCodePage(uintptr_t cp);
[[nodiscard]] bool IsUnicodeCodePage(uintptr_t cp);
[[nodiscard]] bool IsStandardCodePage(uintptr_t cp);
[[nodiscard]] bool IsUnicodeOrUtfCodePage(uintptr_t cp);
[[nodiscard]] bool IsNoFlagsCodepage(uintptr_t cp);

[[nodiscard]] string ShortReadableCodepageName(uintptr_t cp);

//#############################################################################

class MultibyteCodepageDecoder
{
public:
	NONCOPYABLE(MultibyteCodepageDecoder);

	MultibyteCodepageDecoder() = default;

	bool SetCP(uintptr_t Codepage);
	uintptr_t GetCP() const { return m_Codepage; }
	size_t GetSize() const { return m_Size; }
	size_t GetChar(std::string_view Str, wchar_t& Char, bool* End = nullptr) const;

private:
	std::vector<char> len_mask; //[256]
	std::vector<wchar_t> m1;    //[256]
	std::vector<wchar_t> m2;  //[65536]

	uintptr_t m_Codepage {};
	size_t m_Size {};
};

//#############################################################################

namespace Utf8
{
	// returns the number of decoded chars, 1 or 2. Moves StrIterator and FullyConsumedIterator forward:
	// StrIterator: including incomplete characters
	// FullyConsumedIterator: only fully decoded characters. If it's not the same as StrIterator, you might want to drop the results and try again
	[[nodiscard]] size_t get_char(
		std::string_view::const_iterator& StrIterator,
		std::string_view::const_iterator& FullyConsumedIterator,
		std::string_view::const_iterator StrEnd,
		wchar_t& First,
		wchar_t& Second
	);
	// returns the number of decoded chars, up to Buffer.size(). Stops on buffer overflow. Tail contains the number of unprocessed bytes.
	[[nodiscard]] size_t get_chars(std::string_view Str, span<wchar_t> Buffer, int& Tail);
}

//#############################################################################

class [[nodiscard]] raw_eol
{
public:
	raw_eol(): m_Cr('\r'), m_Lf('\n') {}
	explicit raw_eol(uintptr_t Codepage): m_Cr(to(Codepage, L'\r')), m_Lf(to(Codepage, L'\n')) {}

	template<class T>
	[[nodiscard]] T cr() const { return value<T>(L'\r', m_Cr); }

	template<class T>
	[[nodiscard]] T lf() const { return value<T>(L'\n', m_Lf); }

private:
	static char to(uintptr_t Codepage, wchar_t WideChar)
	{
		char Char;
		return encoding::get_bytes(Codepage, { &WideChar, 1 }, { &Char, 1 })? Char : WideChar;
	}

	template<typename T>
	static T value(
		[[maybe_unused]] wchar_t const WideChar,
		[[maybe_unused]] char const Char
	)
	{
		if constexpr (std::same_as<T, wchar_t>)
			return WideChar;
		else
		{
			static_assert(std::same_as<T, char>);
			return Char;
		}
	}

	const char m_Cr;
	const char m_Lf;
};

struct cp_info
{
	string Name;
	unsigned char MaxCharSize;
};

using cp_map = std::unordered_map<unsigned, cp_info>;
[[nodiscard]] const cp_map& InstalledCodepages();
[[nodiscard]] cp_info const* GetCodePageInfo(uintptr_t cp);

#endif // ENCODING_HPP_44AE7032_AF79_4A6F_A2ED_529BC1A38758
