#include "stdafx.h"
#include "overlay_fonts.h"
#include "Emu/System.h"
#include "Emu/vfs_config.h"

#ifndef _WIN32
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>

#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
#include <sys/sysctl.h>
#endif
#endif

namespace rsx
{
	namespace overlays
	{
		void codepage::initialize_glyphs(char32_t codepage_id, f32 font_size, const std::vector<u8>& ttf_data)
		{
			glyph_base = (codepage_id * 256);
			glyph_data.resize(bitmap_width * bitmap_height);
			pack_info.resize(256);

			stbtt_pack_context context;
			if (!stbtt_PackBegin(&context, glyph_data.data(), bitmap_width, bitmap_height, 0, 1, nullptr))
			{
				rsx_log.error("Font packing failed");
				return;
			}

			stbtt_PackSetOversampling(&context, oversample, oversample);

			if (!stbtt_PackFontRange(&context, ttf_data.data(), 0, font_size, (codepage_id * 256), 256, pack_info.data()))
			{
				rsx_log.error("Font packing failed");
				stbtt_PackEnd(&context);
				return;
			}

			stbtt_PackEnd(&context);
		}

		stbtt_aligned_quad codepage::get_char(char32_t c, f32& x_advance, f32& y_advance)
		{
			stbtt_aligned_quad quad;
			stbtt_GetPackedQuad(pack_info.data(), bitmap_width, bitmap_height, (c - glyph_base), &x_advance, &y_advance, &quad, false);

			quad.t0 += sampler_z;
			quad.t1 += sampler_z;
			return quad;
		}

		font::font(std::string_view ttf_name, f32 size)
		{
			// Convert pt to px
			size_px = ceilf(size * 96.f / 72.f);
			size_pt = size;

			font_name = std::string(ttf_name);
			initialized = true;
		}

		language_class font::classify(char32_t codepage_id)
		{
			switch (codepage_id)
			{
			case 00: // Extended ASCII
			case 04: // Cyrillic
			{
				return language_class::default_;
			}
			case 0x11: // Hangul jamo
			case 0x31: // Compatibility jamo 3130-318F
			// case 0xA9: // Hangul jamo extended block A A960-A97F
			{
				return language_class::hangul;
			}
			case 0xFF: // Halfwidth and Fullwidth Forms
			{
				// Found in SCE-PS3-SR-R-JPN.TTF, so we'll use cjk_base for now
				return language_class::cjk_base;
			}
			default:
			{
				if (codepage_id >= 0xAC && codepage_id <= 0xD7)
				{
					// Hangul syllables + jamo extended block B
					return language_class::hangul;
				}

				if (codepage_id >= 0x2E && codepage_id <= 0x9F)
				{
					// Generic CJK blocks, mostly chinese and japanese kana
					// Should typically be compatible with JPN ttf fonts
					return language_class::cjk_base;
				}

				// TODO
				return language_class::default_;
			}
			}
		}

		glyph_load_setup font::get_glyph_files(language_class class_) const
		{
			glyph_load_setup result;
			result.font_names.push_back(font_name);

			const std::vector<std::string> font_dirs = Emu.GetCallbacks().get_font_dirs();
			result.lookup_font_dirs.insert(result.lookup_font_dirs.end(), font_dirs.begin(), font_dirs.end());
			// Search dev_flash for the font too
			result.lookup_font_dirs.push_back(g_cfg_vfs.get_dev_flash() + "data/font/");
			result.lookup_font_dirs.push_back(g_cfg_vfs.get_dev_flash() + "data/font/SONY-CC/");

			switch (class_)
			{
			case language_class::default_:
			{
				result.font_names.emplace_back("Arial.ttf");
				result.font_names.emplace_back("arial.ttf");
#ifdef __APPLE__
				result.font_names.emplace_back("DejaVuSans.ttf");
				result.font_names.emplace_back("NotoSans-Regular.ttf");
				result.font_names.emplace_back("Roboto-Regular.ttf");
				result.font_names.emplace_back("OpenSans-Regular.ttf");
				result.font_names.emplace_back("FreeSans.ttf");
#elif !defined(_WIN32)
				result.font_names.emplace_back("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); // ubuntu
				result.font_names.emplace_back("/usr/share/fonts/TTF/DejaVuSans.ttf");             // arch
#endif
				// Attempt to load a font from dev_flash as a last resort
				result.font_names.emplace_back("SCE-PS3-VR-R-LATIN.TTF");
				break;
			}
			case language_class::cjk_base:
			{
				// Skip loading font files directly
				result.font_names.clear();

				// Attempt to load a font from dev_flash before any other source
				result.font_names.emplace_back("SCE-PS3-SR-R-JPN.TTF");
				result.font_names.emplace_back("SCE-PS3-DH-R-CGB.TTF");

				// Known system font as last fallback
				result.font_names.emplace_back("Yu Gothic.ttf");
				result.font_names.emplace_back("YuGothR.ttc");
#ifdef _WIN32
				result.font_names.emplace_back("msyh.ttc");
				result.font_names.emplace_back("simsunb.ttc");
				result.font_names.emplace_back("simsun.ttc");
				result.font_names.emplace_back("SimsunExtG.ttf");
#endif
				break;
			}
			case language_class::hangul:
			{
				// Skip loading font files directly
				result.font_names.clear();

				// Attempt to load a font from dev_flash before any other source
				result.font_names.emplace_back("SCE-PS3-YG-R-KOR.TTF");

				// Known system font as last fallback
				result.font_names.emplace_back("Malgun Gothic.ttf");
				result.font_names.emplace_back("malgun.ttf");
				break;
			}
			}

			return result;
		}

		codepage* font::initialize_codepage(char32_t c)
		{
			// Init glyph
			const auto codepage_id = get_page_id(c);
			const auto class_ = classify(codepage_id);
			const auto fs_settings = get_glyph_files(class_);

			// Attemt to load requested font
			std::vector<u8> bytes;
			std::vector<u8> fallback_bytes;
			std::string fallback_file;
			bool font_found = false;

			const auto get_font = [&](const std::string& file_path) -> bool
			{
				// Read font
				fs::file f(file_path);
				f.read(bytes, f.size());

				// Check if the character exists in the font
				stbtt_fontinfo info;
				if (stbtt_InitFont(&info, bytes.data(), stbtt_GetFontOffsetForIndex(bytes.data(), 0)) != 0)
				{
					font_found = stbtt_FindGlyphIndex(&info, c) != 0;
				}

				if (!font_found)
				{
					if (fallback_bytes.empty())
					{
						// Save this font as a fallback so we don't get a segfault or exception
						fallback_bytes = std::move(bytes);
						fallback_file = file_path;
					}

					bytes.clear();
				}

				return font_found;
			};

			for (const std::string& font_file : fs_settings.font_names)
			{
				if (fs::is_file(font_file))
				{
					// Check for absolute paths or fonts 'installed' to executable folder
					if (get_font(font_file))
					{
						break;
					}

					continue;
				}

				std::string extension;
				if (const auto extension_start = font_file.find_last_of('.');
					extension_start != umax)
				{
					extension = font_file.substr(extension_start + 1);
				}

				std::string file_name = font_file;
				if (extension.length() != 3)
				{
					// Allow other extensions to support other truetype formats
					file_name += ".ttf";
				}

				for (const auto& font_dir : fs_settings.lookup_font_dirs)
				{
					const std::string file_path = font_dir + file_name;
					if (fs::is_file(file_path))
					{
						if (get_font(file_path))
						{
							break;
						}
					}
				}

				if (font_found)
				{
					break;
				}
			}

			if (!font_found)
			{
				if (fallback_bytes.empty())
				{
					fmt::throw_exception("Failed to initialize font for character 0x%x on codepage %d.\nLookup dirs:\n%s\nTarget fonts:\n%s", static_cast<u32>(c), static_cast<u32>(codepage_id), fmt::merge(fs_settings.lookup_font_dirs, "\n"), fmt::merge(fs_settings.font_names, "\n"));
				}

				rsx_log.error("Failed to initialize font for character 0x%x on codepage %d. Falling back to font '%s'", static_cast<u32>(c), static_cast<u32>(codepage_id), fallback_file);
				bytes = std::move(fallback_bytes);
			}

			codepage_cache.page = nullptr;
			auto page = std::make_unique<codepage>();
			page->initialize_glyphs(codepage_id, size_px, bytes);
			page->sampler_z = static_cast<f32>(m_glyph_map.size());

			auto ret = page.get();
			m_glyph_map.emplace_back(codepage_id, std::move(page));

			if (codepage_id == 0)
			{
				// Latin-1
				f32 unused;
				get_char('m', em_size, unused);
			}

			return ret;
		}

		stbtt_aligned_quad font::get_char(char32_t c, f32& x_advance, f32& y_advance)
		{
			if (!initialized)
				return {};

			const auto page_id = get_page_id(c);

			if (codepage_cache.codepage_id == page_id && codepage_cache.page) [[likely]]
			{
				return codepage_cache.page->get_char(c, x_advance, y_advance);
			}
			else
			{
				codepage_cache.codepage_id = page_id;
				codepage_cache.page = nullptr;

				for (const auto& e : m_glyph_map)
				{
					if (e.first == page_id)
					{
						codepage_cache.page = e.second.get();
						break;
					}
				}

				if (!codepage_cache.page) [[unlikely]]
				{
					codepage_cache.page = initialize_codepage(c);
				}

				return codepage_cache.page->get_char(c, x_advance, y_advance);
			}
		}

		std::vector<vertex> font::render_text_ex(f32& x_advance, f32& y_advance, const char32_t* text, usz char_limit, u16 max_width, bool wrap)
		{
			x_advance = 0.f;
			y_advance = 0.f;
			std::vector<vertex> result;

			if (!initialized)
			{
				return result;
			}

			// Render as many characters as possible as glyphs.
			for (usz i = 0u, begin_of_word = 0u; i < char_limit; i++)
			{
				switch (const auto& c = text[i])
				{
				case '\0':
				{
					// We're done.
					return result;
				}
				case '\n':
				{
					// Reset x to 0 and increase y to advance to the new line.
					x_advance = 0.f;
					y_advance += size_px + 2.f;
					begin_of_word = result.size();
					continue;
				}
				case '\r':
				{
					// Reset x to 0.
					x_advance = 0.f;
					begin_of_word = result.size();
					continue;
				}
				default:
				{
					const bool is_whitespace = c == ' ';
					stbtt_aligned_quad quad{};

					if (is_whitespace)
					{
						// Skip whitespace if we are at the start of a line.
						if (x_advance <= 0.f)
						{
							// Set the glyph to the current position.
							// This is necessary for downstream linewidth calculations.
							quad.x0 = quad.x1 = x_advance;
							quad.y0 = quad.y1 = y_advance;
						}
						else
						{
							const f32 x_advance_old = x_advance;
							const f32 y_advance_old = y_advance;

							// Get the glyph size.
							quad = get_char(c, x_advance, y_advance);

							// Reset the result if the glyph would protrude out of the given space anyway.
							if (x_advance > max_width)
							{
								// Set the glyph to the previous position.
								// This is necessary for downstream linewidth calculations.
								quad.x0 = quad.x1 = x_advance_old;
								quad.y0 = quad.y1 = y_advance_old;
							}
						}
					}
					else
					{
						// No whitespace. Get the glyph size.
						quad = get_char(c, x_advance, y_advance);
					}

					// Add the glyph's vertices.
					result.emplace_back(quad.x0, quad.y0, quad.s0, quad.t0);
					result.emplace_back(quad.x1, quad.y0, quad.s1, quad.t0);
					result.emplace_back(quad.x0, quad.y1, quad.s0, quad.t1);
					result.emplace_back(quad.x1, quad.y1, quad.s1, quad.t1);

					// The next word will begin after any whitespaces.
					if (is_whitespace)
					{
						begin_of_word = result.size();
					}

					// Check if we reached the end of the available space.
					if (x_advance > max_width)
					{
						// Try to wrap the protruding text
						if (wrap)
						{
							// Increase y to advance to the next line.
							y_advance += size_px + 2.f;

							// We can just reset x and move on to the next character if this is a whitespace.
							if (is_whitespace)
							{
								x_advance = 0.f;
								break;
							}

							// Get the leftmost offset of the current word.
							const f32 base_x = result[begin_of_word].x();

							// Move all characters of the current word one line down and to the left.
							for (usz n = begin_of_word; n < result.size(); ++n)
							{
								result[n].x() -= base_x;
								result[n].y() += size_px + 2.f;
							}

							// Set x offset to the rightmost position of the current word
							x_advance = result.back().x();
						}
						else
						{
							// TODO: Ellipsize
						}
					}

					break;
				}
				} // switch
			}
			return result;
		}

		std::vector<vertex> font::render_text(const char32_t* text, u16 max_width, bool wrap)
		{
			f32 unused_x, unused_y;

			return render_text_ex(unused_x, unused_y, text, -1, max_width, wrap);
		}

		std::pair<f32, f32> font::get_char_offset(const char32_t* text, usz max_length, u16 max_width, bool wrap)
		{
			f32 loc_x, loc_y;

			render_text_ex(loc_x, loc_y, text, max_length, max_width, wrap);
			return {loc_x, loc_y};
		}

		const std::vector<u8>& font::get_glyph_data() const
		{
			constexpr u32 page_size = codepage::bitmap_width * codepage::bitmap_height;
			const auto size = page_size * m_glyph_map.size();

			static thread_local std::vector<u8> bytes;
			bytes.resize(size);

			u8* data = bytes.data();

			for (const auto& e : m_glyph_map)
			{
				std::memcpy(data, e.second->glyph_data.data(), page_size);
				data += page_size;
			}
			return bytes;
		}
	} // namespace overlays
} // namespace rsx
