#pragma once

#include <psemek/util/atlas.hpp>
#include <psemek/gfx/texture.hpp>
#include <psemek/gfx/texture_view.hpp>

namespace psemek::gfx
{

	template <typename Pixel, typename Key, typename Compare = std::less<Key>>
	struct texture_atlas_2d
	{
		using atlas_type = util::atlas<Pixel, 2, Key, Compare>;

		texture_atlas_2d(Pixel background = Pixel{}, std::size_t padding = 0, typename atlas_type::padding_mode mode = atlas_type::padding_mode::default_value, Compare compare = Compare{});

		using texture_view = texture_view_2d;

		texture_2d & texture() { return texture_; }
		texture_2d const & texture() const { return texture_; }

		util::ndarray<Pixel, 2> const & pixmap() const { return atlas_.array(); }

		std::pair<texture_view, bool> insert(Key const & key, util::ndarray<Pixel, 2> const & data);
		texture_view find(Key const & key) const;
		texture_view at(Key const & key) const;

		std::size_t used_pixels() const { return used_pixels_; }

	private:
		atlas_type atlas_;
		texture_2d texture_;
		std::size_t used_pixels_ = 0;

		void update_texture();

		texture_view to_view(typename util::atlas<Pixel, 2, Key, Compare>::atlas_part const & part) const;
	};

	template <typename Pixel, typename Key, typename Compare>
	texture_atlas_2d<Pixel, Key, Compare>::texture_atlas_2d(Pixel background, std::size_t padding, typename atlas_type::padding_mode mode, Compare compare)
		: atlas_(std::move(background), padding, mode, std::move(compare))
	{}

	template <typename Pixel, typename Key, typename Compare>
	std::pair<texture_view_2d, bool> texture_atlas_2d<Pixel, Key, Compare>::insert(Key const & key, util::ndarray<Pixel, 2> const & data)
	{
		auto result = atlas_.insert(key, data);
		if (result.second)
			update_texture();

		used_pixels_ += data.size();

		return {to_view(result.first), result.second};
	}

	template <typename Pixel, typename Key, typename Compare>
	texture_view_2d texture_atlas_2d<Pixel, Key, Compare>::find(Key const & key) const
	{
		auto result = atlas_.find(key);
		if (result)
			return to_view(*result);
		return texture_view{};
	}

	template <typename Pixel, typename Key, typename Compare>
	texture_view_2d texture_atlas_2d<Pixel, Key, Compare>::at(Key const & key) const
	{
		return to_view(atlas_.at(key));
	}

	template <typename Pixel, typename Key, typename Compare>
	void texture_atlas_2d<Pixel, Key, Compare>::update_texture()
	{
		texture_.load(atlas_.array());
	}

	template <typename Pixel, typename Key, typename Compare>
	texture_view_2d texture_atlas_2d<Pixel, Key, Compare>::to_view(typename util::atlas<Pixel, 2, Key, Compare>::atlas_part const & part) const
	{
		return texture_view{&texture_, {{{part.begin[0], part.end[0]}, {part.begin[1], part.end[1]}}}};
	}

}
