#include "TextRasterizer.h"

#include <windows.h>

#include <algorithm>
#include <cmath>
#include <cstring>
#include <gdiplus.h>
#include <memory>

namespace
{
constexpr int kTextSdfSpread = 20;
constexpr float kTextFontPixelSize = 144.0f;
constexpr float kTextLayoutPadding = 48.0f;
constexpr float kSdfInfinity = 1.0e20f;

class GdiplusSession
{
public:
	GdiplusSession()
	{
		Gdiplus::GdiplusStartupInput startupInput;
		mStarted = Gdiplus::GdiplusStartup(&mToken, &startupInput, NULL) == Gdiplus::Ok;
	}

	~GdiplusSession()
	{
		if (mStarted)
			Gdiplus::GdiplusShutdown(mToken);
	}

	GdiplusSession(const GdiplusSession&) = delete;
	GdiplusSession& operator=(const GdiplusSession&) = delete;

	bool started() const { return mStarted; }

private:
	ULONG_PTR mToken = 0;
	bool mStarted = false;
};

std::wstring Utf8ToWide(const std::string& text)
{
	if (text.empty())
		return std::wstring();
	const int required = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, NULL, 0);
	if (required <= 1)
		return std::wstring();
	std::wstring wide(static_cast<std::size_t>(required - 1), L'\0');
	MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, wide.data(), required);
	return wide;
}

void DistanceTransform1D(const std::vector<float>& input, std::vector<float>& output, unsigned count)
{
	std::vector<unsigned> locations(count, 0);
	std::vector<float> boundaries(static_cast<std::size_t>(count) + 1, 0.0f);

	unsigned segment = 0;
	locations[0] = 0;
	boundaries[0] = -kSdfInfinity;
	boundaries[1] = kSdfInfinity;

	for (unsigned q = 1; q < count; ++q)
	{
		float intersection = 0.0f;
		for (;;)
		{
			const unsigned location = locations[segment];
			intersection =
				((input[q] + static_cast<float>(q * q)) - (input[location] + static_cast<float>(location * location))) /
				(2.0f * static_cast<float>(q) - 2.0f * static_cast<float>(location));
			if (intersection > boundaries[segment] || segment == 0)
				break;
			--segment;
		}

		++segment;
		locations[segment] = q;
		boundaries[segment] = intersection;
		boundaries[segment + 1] = kSdfInfinity;
	}

	segment = 0;
	for (unsigned q = 0; q < count; ++q)
	{
		while (boundaries[segment + 1] < static_cast<float>(q))
			++segment;
		const unsigned location = locations[segment];
		const float delta = static_cast<float>(q) - static_cast<float>(location);
		output[q] = delta * delta + input[location];
	}
}

std::vector<float> DistanceTransform2D(const std::vector<unsigned char>& targetMask, unsigned width, unsigned height)
{
	std::vector<float> rowInput(width, 0.0f);
	std::vector<float> rowOutput(width, 0.0f);
	std::vector<float> columnInput(height, 0.0f);
	std::vector<float> columnOutput(height, 0.0f);
	std::vector<float> rowDistance(static_cast<std::size_t>(width) * height, 0.0f);
	std::vector<float> distance(static_cast<std::size_t>(width) * height, 0.0f);

	for (unsigned y = 0; y < height; ++y)
	{
		for (unsigned x = 0; x < width; ++x)
			rowInput[x] = targetMask[static_cast<std::size_t>(y) * width + x] ? 0.0f : kSdfInfinity;
		DistanceTransform1D(rowInput, rowOutput, width);
		for (unsigned x = 0; x < width; ++x)
			rowDistance[static_cast<std::size_t>(y) * width + x] = rowOutput[x];
	}

	for (unsigned x = 0; x < width; ++x)
	{
		for (unsigned y = 0; y < height; ++y)
			columnInput[y] = rowDistance[static_cast<std::size_t>(y) * width + x];
		DistanceTransform1D(columnInput, columnOutput, height);
		for (unsigned y = 0; y < height; ++y)
			distance[static_cast<std::size_t>(y) * width + x] = columnOutput[y];
	}

	return distance;
}

std::vector<unsigned char> BuildTextSdfTexture(const std::vector<unsigned char>& alpha, unsigned width, unsigned height)
{
	std::vector<unsigned char> insideMask(static_cast<std::size_t>(width) * height, 0);
	std::vector<unsigned char> outsideMask(static_cast<std::size_t>(width) * height, 0);
	for (std::size_t index = 0; index < alpha.size(); ++index)
	{
		const bool inside = alpha[index] > 127;
		insideMask[index] = inside ? 1 : 0;
		outsideMask[index] = inside ? 0 : 1;
	}

	const std::vector<float> distanceToInside = DistanceTransform2D(insideMask, width, height);
	const std::vector<float> distanceToOutside = DistanceTransform2D(outsideMask, width, height);
	std::vector<unsigned char> sdf(static_cast<std::size_t>(width) * height * 4, 0);

	for (unsigned y = 0; y < height; ++y)
	{
		const unsigned flippedY = height - 1 - y;
		for (unsigned x = 0; x < width; ++x)
		{
			const std::size_t source = static_cast<std::size_t>(y) * width + x;
			const float signedDistance = std::sqrt(distanceToOutside[source]) - std::sqrt(distanceToInside[source]);
			const float normalized = std::clamp(
				0.5f + signedDistance / static_cast<float>(kTextSdfSpread * 2),
				0.0f,
				1.0f);
			const unsigned char value = static_cast<unsigned char>(normalized * 255.0f + 0.5f);
			const std::size_t out = (static_cast<std::size_t>(flippedY) * width + x) * 4;
			sdf[out + 0] = value;
			sdf[out + 1] = value;
			sdf[out + 2] = value;
			sdf[out + 3] = value;
		}
	}

	return sdf;
}

}

bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector<unsigned char>& sdf, std::string& error)
{
	GdiplusSession gdiplus;
	if (!gdiplus.started())
	{
		error = "Could not start GDI+ for text rendering.";
		return false;
	}

	Gdiplus::PrivateFontCollection fontCollection;
	Gdiplus::FontFamily fallbackFamily(L"Arial");
	Gdiplus::FontFamily* fontFamily = &fallbackFamily;
	std::unique_ptr<Gdiplus::FontFamily[]> families;
	const std::wstring wideFontPath = fontPath.empty() ? std::wstring() : fontPath.wstring();
	if (!wideFontPath.empty())
	{
		if (fontCollection.AddFontFile(wideFontPath.c_str()) != Gdiplus::Ok)
		{
			error = "Could not load packaged font file for text rendering: " + fontPath.string();
			return false;
		}

		const INT familyCount = fontCollection.GetFamilyCount();
		if (familyCount <= 0)
		{
			error = "Packaged font did not contain a usable font family: " + fontPath.string();
			return false;
		}

		families.reset(new Gdiplus::FontFamily[familyCount]);
		INT found = 0;
		if (fontCollection.GetFamilies(familyCount, families.get(), &found) != Gdiplus::Ok || found <= 0)
		{
			error = "Could not read the packaged font family: " + fontPath.string();
			return false;
		}
		fontFamily = &families[0];
	}

	Gdiplus::Bitmap bitmap(kTextTextureWidth, kTextTextureHeight, PixelFormat32bppARGB);
	Gdiplus::Graphics graphics(&bitmap);
	graphics.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
	graphics.Clear(Gdiplus::Color(255, 0, 0, 0));
	graphics.SetCompositingMode(Gdiplus::CompositingModeSourceOver);
	graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAlias);
	graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
	Gdiplus::Font font(fontFamily, kTextFontPixelSize, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel);
	Gdiplus::SolidBrush brush(Gdiplus::Color(255, 255, 255, 255));
	Gdiplus::StringFormat format;
	format.SetAlignment(Gdiplus::StringAlignmentNear);
	format.SetLineAlignment(Gdiplus::StringAlignmentCenter);
	format.SetFormatFlags(Gdiplus::StringFormatFlagsNoWrap | Gdiplus::StringFormatFlagsMeasureTrailingSpaces);
	const Gdiplus::RectF layout(
		kTextLayoutPadding,
		0.0f,
		static_cast<Gdiplus::REAL>(kTextTextureWidth) - (kTextLayoutPadding * 2.0f),
		static_cast<Gdiplus::REAL>(kTextTextureHeight));
	const std::wstring wideText = Utf8ToWide(text);
	graphics.DrawString(wideText.c_str(), -1, &font, layout, &format, &brush);

	std::vector<unsigned char> alpha(static_cast<std::size_t>(kTextTextureWidth) * kTextTextureHeight, 0);
	for (unsigned y = 0; y < kTextTextureHeight; ++y)
	{
		for (unsigned x = 0; x < kTextTextureWidth; ++x)
		{
			Gdiplus::Color pixel;
			bitmap.GetPixel(x, y, &pixel);
			BYTE luminance = pixel.GetRed();
			if (pixel.GetGreen() > luminance)
				luminance = pixel.GetGreen();
			if (pixel.GetBlue() > luminance)
				luminance = pixel.GetBlue();
			alpha[static_cast<std::size_t>(y) * kTextTextureWidth + x] = static_cast<unsigned char>(luminance);
		}
	}
	sdf = BuildTextSdfTexture(alpha, kTextTextureWidth, kTextTextureHeight);
	return true;
}
