/* === S Y N F I G ========================================================= */
/*!	\file synfig/rendering/software/function/packedsurface.h
**	\brief PackedSurface Header
**
**	\legal
**	......... ... 2016 Ivan Mahonin
**
**	This file is part of Synfig.
**
**	Synfig is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 2 of the License, or
**	(at your option) any later version.
**
**	Synfig is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with Synfig.  If not, see <https://www.gnu.org/licenses/>.
**	\endlegal
*/
/* ========================================================================= */

/* === S T A R T =========================================================== */

#ifndef __SYNFIG_RENDERING_SOFTWARE_PACKEDSURFACE_H
#define __SYNFIG_RENDERING_SOFTWARE_PACKEDSURFACE_H

/* === H E A D E R S ======================================================= */

#include <set>

#include <synfig/real.h>
#include <synfig/color.h>
#include <synfig/surface.h>

#include "../../primitive/contour.h"
#include "../../primitive/polyspan.h"

/* === M A C R O S ========================================================= */

/* === T Y P E D E F S ===================================================== */

/* === C L A S S E S & S T R U C T S ======================================= */

namespace synfig
{
namespace rendering
{
namespace software
{

class PackedSurface
{
public:
	enum ChannelType {
		ChannelUInt8,
		ChannelFloat32
	};

	enum {
		ChunkSize = 32,
		CacheRows = 2
	};

	class Reader
	{
	private:
		struct CacheEntry {
			int chunk_index;
			CacheEntry *prev;
			CacheEntry *next;
			void *data(int offset = 0) { return (char*)this + sizeof(*this) + offset; }
		};

		const PackedSurface *surface;
		mutable CacheEntry* first;
		mutable CacheEntry* last;
		mutable std::vector<CacheEntry*> chunks;
		char* cache;

	public:

		Reader();
		explicit Reader(const PackedSurface &surface);
		~Reader();

		void open(const PackedSurface &surface);
		void close();
		bool is_opened() const { return surface != nullptr; }

		Color get_pixel(int x, int y) const;

		inline static Color reader(const void *surf, int x, int y)
		{
			const Reader &r = *(const Reader*)surf;
			return clamping::clamp(x, r.surface->width) && clamping::clamp(y, r.surface->height)
			     ? r.get_pixel(x, y) : Color();
		}

		inline static Color reader_cook(const void *surf, int x, int y)
		{
			const Reader &r = *(const Reader*)surf;
			return clamping::clamp(x, r.surface->width) && clamping::clamp(y, r.surface->height)
				 ? ColorPrep::cook_static(r.get_pixel(x, y)) : Color();
		}
	};

	struct DiscreteHelper {
		Color::value_type value;
		Color::value_type min;
		Color::value_type max;
		int count;
		DiscreteHelper(): value(), min(), max(), count() { }
		DiscreteHelper(Color::value_type c):
			value(c),
			min(c - real_low_precision<Color::value_type>()),
			max(c + real_low_precision<Color::value_type>()),
			count(1)
		{ }
		bool in_range(Color::value_type c) const
			{ return min <= c && c <= max; }
	};

	typedef sampler<Color, float, Reader::reader_cook> Sampler;

private:
	mutable std::mutex mutex;
	mutable std::set<Reader*> readers;

	int width;
	int height;

	ChannelType channel_type;
	int channels[4];
	Color::value_type discrete_to_float[256];
	Color constant;

	int pixel_size;
	int row_size;

	int chunk_size;
	int chunk_row_size;
	int chunks_width;
	int chunks_height;

	std::vector<char> data;

	static Color::value_type get_channel(const void *pixel, int offset, ChannelType type, Color::value_type constant, const Color::value_type *discrete_to_float);
	static void set_channel(void *pixel, int offset, ChannelType type, Color::value_type color, const Color::value_type *discrete_to_float);

	Color get_pixel(const void *pixel) const;
	void set_pixel(void *pixel, const Color &color);

	void get_compressed_chunk(int index, const void *&data, int &size, bool &compressed) const;

public:
	PackedSurface();
	~PackedSurface();

	void clear();
	void set_pixels(const Color *pixels, int width, int height, int pitch = 0);
	int get_width() const { return width; }
	int get_height() const { return height; }
	void get_pixels(Color *target) const;
};

} /* end namespace software */
} /* end namespace rendering */
} /* end namespace synfig */

/* -- E N D ----------------------------------------------------------------- */

#endif
