#include <psemek/gfx/pixmap.hpp>
#include <psemek/gfx/detail/stb_image.h>
#include <psemek/gfx/detail/stb_image_write.h>
#include <psemek/util/exception.hpp>

namespace psemek::gfx
{

	template <typename Pixel>
	basic_pixmap<Pixel> read_image(io::istream && is)
	{
		stbi_io_callbacks callbacks;

		callbacks.read = [](void * user, char * data, int size) -> int
		{
			return reinterpret_cast<io::istream *>(user)->read(data, size);
		};

		callbacks.skip = [](void * user, int count)
		{
			if (count < 0)
				throw util::exception("unget is not supported in stb_image skip callback");

			char buffer[1024];
			while (count > 0)
			{
				int read_count = std::min(1024, count);
				count -= reinterpret_cast<io::istream *>(user)->read(buffer, read_count);
			}
		};

		callbacks.eof = [](void * user)
		{
			return reinterpret_cast<io::istream *>(user)->finished() ? 1 : 0;
		};

		int width, height, channels;
		auto data = stbi_load_from_callbacks(&callbacks, &is, &width, &height, &channels, sizeof(Pixel));

		if (!data)
			throw util::exception(stbi_failure_reason());

		basic_pixmap<Pixel> result({width, height});
		std::copy(data, data + width * height * sizeof(Pixel), reinterpret_cast<stbi_uc *>(result.data()));

		stbi_image_free(data);

		return result;
	}

	template pixmap_monochrome read_image<std::uint8_t>(io::istream && is);
	template pixmap_rgb read_image<color_rgb>(io::istream && is);
	template pixmap_rgba read_image<color_rgba>(io::istream && is);

	namespace
	{

		void write_func(void * context, void * data, int size)
		{
			reinterpret_cast<io::ostream *>(context)->write(reinterpret_cast<char *>(data), size);
		}

	}

	void write_image_png(pixmap_monochrome const & p, io::ostream && os)
	{
		if (stbi_write_png_to_func(write_func, &os, p.width(), p.height(), 1, p.data(), p.width() * 1) == 0)
			throw util::exception("Failed to write monochrome png");
	}

	void write_image_png(pixmap_rgb const & p, io::ostream && os)
	{
		if (stbi_write_png_to_func(write_func, &os, p.width(), p.height(), 3, p.data(), p.width() * 3) == 0)
			throw util::exception("Failed to write rgb png");
	}

	void write_image_png(pixmap_rgba const & p, io::ostream && os)
	{
		if (stbi_write_png_to_func(write_func, &os, p.width(), p.height(), 4, p.data(), p.width() * 4) == 0)
			throw util::exception("Failed to write rgba png");
	}

	void write_image_tga(pixmap_monochrome const & p, io::ostream && os)
	{
		if (stbi_write_tga_to_func(write_func, &os, p.width(), p.height(), 1, p.data()) == 0)
			throw util::exception("Failed to write monochrome tga");
	}

	void write_image_tga(pixmap_rgb const & p, io::ostream && os)
	{
		if (stbi_write_tga_to_func(write_func, &os, p.width(), p.height(), 3, p.data()) == 0)
			throw util::exception("Failed to write rgb tga");
	}

	void write_image_tga(pixmap_rgba const & p, io::ostream && os)
	{
		if (stbi_write_tga_to_func(write_func, &os, p.width(), p.height(), 4, p.data()) == 0)
			throw util::exception("Failed to write rgba tga");
	}

	void write_image_bmp(pixmap_rgb const & p, io::ostream && os)
	{
		if (stbi_write_bmp_to_func(write_func, &os, p.width(), p.height(), 3, p.data()) == 0)
			throw util::exception("Failed to write rgb bmp");
	}

	void write_image_jpg(pixmap_rgb const & p, io::ostream && os, int quality)
	{
		if (stbi_write_jpg_to_func(write_func, &os, p.width(), p.height(), 3, p.data(), quality) == 0)
			throw util::exception("Failed to write rgb jpg");
	}

	void write_image_hdr(basic_pixmap<color_3f> const & p, io::ostream && os)
	{
		if (stbi_write_hdr_to_func(write_func, &os, p.width(), p.height(), 1, reinterpret_cast<float const *>(p.data())) == 0)
			throw util::exception("Failed to write rgb hdr");
	}

	void write_image_pgm(pixmap_monochrome const & p, io::ostream && os)
	{
		char header_buffer[256];
		int header_size = std::snprintf(header_buffer, 256, "P5\n%lu %lu\n255\n", (unsigned long)p.width(), (unsigned long)p.height());
		os.write(header_buffer, header_size);
		os.write(reinterpret_cast<char const *>(p.data()), p.width() * p.height());
	}

	void write_image_ppm(pixmap_rgb const & p, io::ostream && os)
	{
		char header_buffer[256];
		int header_size = std::snprintf(header_buffer, 256, "P6\n%lu %lu\n255\n", (unsigned long)p.width(), (unsigned long)p.height());
		os.write(header_buffer, header_size);
		os.write(reinterpret_cast<char const *>(p.data()), p.width() * p.height() * 3);
	}

}
