#include <psemek/fonts/kerned_font.hpp>

#include <psemek/util/unicode.hpp>
#include <psemek/util/exception.hpp>

namespace psemek::fonts::inline v1
{

	kerned_font::kerned_font(bmfont_data data, gfx::texture_2d atlas)
		: data_(std::move(data))
		, atlas_{std::move(atlas)}
	{
		if (!supports_character('?'))
			throw util::exception("Kerned font must support '?' character");
		if (!supports_character(' '))
			throw util::exception("Kerned font must support ' ' character");

		std::vector<char32_t> chars;
		for (auto const & g : data_.glyphs)
			chars.push_back(g.first);
		std::sort(chars.begin(), chars.end());

		for (auto c : chars)
		{
			if (!ranges_.empty() && ranges_.back().end == c)
				++ranges_.back().end;
			else
				ranges_.push_back({c, c + 1});
		}
	}

	static math::vector<float, 2> advance_dir(shape_options::direction_t direction)
	{
		switch (direction)
		{
		case shape_options::left_to_right: return {1.f, 0.f};
		case shape_options::right_to_left: return {-1.f, 0.f};
		case shape_options::top_to_bottom: return {0.f, 1.f};
		case shape_options::bottom_to_top: return {0.f, -1.f};
		}

		return {1.f, 0.f};
	}

	bool kerned_font::supports_character(char32_t c) const
	{
		return data_.glyphs.contains(c);
	}

	std::vector<glyph> kerned_font::shape(std::string_view str, shape_options const & options, math::point<float, 2> & pen) const
	{
		return shape_impl(util::utf8_range(str), options, pen);
	}

	std::vector<glyph> kerned_font::shape(std::u32string_view str, shape_options const & options, math::point<float, 2> & pen) const
	{
		return shape_impl(str, options, pen);
	}

	template <typename String>
	std::vector<glyph> kerned_font::shape_impl(String const & str, shape_options const & options, math::point<float, 2> & pen) const
	{
		char32_t const unknown = supports_character(options.unknown_character) ? options.unknown_character : '?';
		math::vector<float, 2> const advance_mask = advance_dir(options.direction);

		std::vector<glyph> result;

		for (char32_t c : str)
		{
			if (!supports_character(c))
				c = unknown;

			auto const & data = data_.glyphs.at(c);

			glyph g;
			g.character = c;
			g.position[0].min = pen[0] + data.offset_x * options.scale;
			g.position[1].min = pen[1] - (data.offset_y + data.size_y) * options.scale;
			g.position[0].max = pen[0] + (data.offset_x + data.size_x) * options.scale;
			g.position[1].max = pen[1] - data.offset_y * options.scale;
			result.push_back(g);

			math::vector<float, 2> advance{data.advance * options.scale, data_.size[1] * options.scale};

			pen += math::pointwise_mult(advance_mask, advance);
		}

		return result;
	}

	std::optional<math::box<float, 2>> kerned_font::texcoords(char32_t c) const
	{
		auto it = data_.glyphs.find(c);
		if (it == data_.glyphs.end())
			return std::nullopt;

		math::box<float, 2> box;
		box[0].min = it->second.start_x;
		box[1].min = it->second.start_y;
		box[0].max = box[0].min + it->second.size_x;
		box[1].max = box[1].min + it->second.size_y;

		return box;
	}

	std::unique_ptr<font> make_bitmap_font(io::istream && description, io::istream && texture)
	{
		auto data = bmfont_data::parse(std::move(description));

		auto pixmap = gfx::read_image<std::uint8_t>(std::move(texture));
		gfx::texture_2d atlas;
		atlas.load(pixmap);
		atlas.linear_filter();
		atlas.clamp();
		gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_R, gl::ONE);
		gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_G, gl::ONE);
		gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_B, gl::ONE);
		gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_A, gl::RED);

		return std::make_unique<kerned_font>(std::move(data), std::move(atlas));
	}

}
