/* === S Y N F I G ========================================================= */
/*!	\file synfig/rendering/surface.cpp
**	\brief Surface
**
**	\legal
**	......... ... 2015-2018 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
*/
/* ========================================================================= */

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

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <cstring>

#include "surface.h"

#include "common/surfacememoryreadwrapper.h"

#endif

using namespace synfig;
using namespace rendering;

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

/* === G L O B A L S ======================================================= */

/* === P R O C E D U R E S ================================================= */

/* === M E T H O D S ======================================================= */

synfig::Token Surface::token;
int SurfaceResource::last_id = 0;

Surface::Surface():
	blank(true),
	width(),
	height()
{ }

Surface::~Surface()
{ }

void
Surface::set_desc(int width, int height, bool blank)
{
	if (width > 0 && height > 0) {
		this->blank  = blank;
		this->width  = width;
		this->height = height;
	} else {
		this->blank  = true;
		this->width  = 0;
		this->height = 0;
	}
}

bool
Surface::get_pixels_vfunc(Color *dest) const
{
	const Color *src = get_pixels_pointer();
	if (!src) return false;
	memcpy(dest, src, get_buffer_size());
	return true;
}

bool
Surface::create(int width, int height)
{
	if (is_exists() && width == this->width && height == this->height && this->blank)
		return true;
	if (is_read_only() || width <= 0 || height <= 0)
		return false;
	if (is_exists())
		reset();
	if (!create_vfunc(width, height))
	{
		if (!reset()) assert(false);
		return false;
	}
	this->width = width;
	this->height = height;
	blank = true;
	return true;
}

bool
Surface::assign(const Surface &other)
{
	if (&other == this)
		return true;
	if (is_read_only())
		return false;
	if (!other.is_exists())
		return reset();
	if (other.is_blank())
		return create(other.get_width(), other.get_height());
	if (!assign_vfunc(other))
	{
		if (!reset()) assert(false);
		return false;
	}
	width = other.get_width();
	height = other.get_height();
	blank = false;
	return true;
}

bool
Surface::assign(const Color *pixels, int width, int height)
{
	if (pixels && pixels == get_pixels_pointer())
		return true;
	if (is_read_only() || !pixels)
		return false;
	return assign(SurfaceMemoryReadWrapper(pixels, width, height));
}

bool
Surface::clear()
{
	if (is_blank())
		return true;
	if (is_read_only())
		return false;
	if (!clear_vfunc())
	{
		int w = get_width();
		int h = get_height();
		reset();
		return create(w, h);
	}
	blank = true;
	return true;
}

bool
Surface::reset()
{
	if (!is_exists())
		return true;
	if (is_read_only())
		return false;
	if (!reset_vfunc())
		return false;
	width = 0;
	height = 0;
	blank = true;
	return true;
}

const Color*
Surface::get_pixels_pointer() const
{
	if (!is_exists())
		return nullptr;
	return get_pixels_pointer_vfunc();
}

bool
Surface::get_pixels(Color *dest) const
{
	if (!is_exists() || !dest)
		return false;
	return get_pixels_vfunc(dest);
}

bool
Surface::equals_to(synfig::rendering::Surface::Handle s) const
{
	if (!s)
		return false;

	if (width != s->width || height != s->height)
		return false;

	// Since both surfaces have the same dimensions, if either dimension is <=0, they are considered equal.
	if (width <= 0 || height <= 0)
		return true;

	std::vector<Color> buffer1(get_pixels_count()), buffer2(s->get_pixels_count());
	get_pixels(buffer1.data());
	s->get_pixels(buffer2.data());

	return buffer1 == buffer2;
}

bool
Surface::touch()
{
	if (is_read_only() || !is_exists())
		return false;
	blank = false;
	return true;
}



SurfaceResource::SurfaceResource():
	id(++last_id),
	width(),
	height(),
	blank(true)
{ }

SurfaceResource::SurfaceResource(Surface::Handle surface):
	width(),
	height(),
	blank(true)
{ assign(surface); }

SurfaceResource::~SurfaceResource()
	{ reset(); }

Surface::Handle
SurfaceResource::get_surface(
	const Surface::Token::Handle &token,
	bool exclusive, // for write access
	bool full,
	const RectInt &rect,
	bool create,
	bool any )
{
	if (!token && !any)
		return Surface::Handle();
	if (!full && !rect.is_valid())
		return Surface::Handle();

	std::lock_guard<std::mutex> lock(mutex);

	if (width <= 0 || height <= 0)
		return Surface::Handle();
	if (!full && !rect_contains(RectInt(0, 0, width, height), rect))
		return Surface::Handle();

	Surface::Handle surface;

	Map::const_iterator i = surfaces.find(token);
	if (i != surfaces.end())
		surface = i->second;
	else
	if (any && !surfaces.empty())
		surface = surfaces.begin()->second;
	else
	if (!create)
		return Surface::Handle();

	if (!surface) {
		surface = token->fabric();
		if (!surface)
			return Surface::Handle();

		if (blank) {
			if (!surface->create(width, height))
				return Surface::Handle();
		} else {
			bool found = false;
			for(Map::const_iterator i = surfaces.begin(); i != surfaces.end() && !found; ++i)
				if (i->second->get_pixels_pointer() && surface->assign(*i->second))
					found = true;
			for(Map::const_iterator i = surfaces.begin(); i != surfaces.end() && !found; ++i)
				if (!i->second->get_pixels_pointer() && surface->assign(*i->second))
					found = true;
			if (!found)
				return Surface::Handle();
		}

		if (exclusive) surfaces.clear(); // all other surfaces invalidated
		surfaces[token] = surface;
	}

	if (exclusive) {
		if (surfaces.size() != 1) // keep only current surface in map
			{ surfaces.clear(); surfaces[token] = surface; }
		surface->touch();
		blank = false;
	}
	return surface;
}

void
SurfaceResource::create(int width, int height)
{
	Glib::Threads::RWLock::WriterLock lock(rwlock);
	std::lock_guard<std::mutex> short_lock(mutex);
	if (width > 0 && height > 0) {
		this->width  = width;
		this->height = height;
	} else {
		this->width  = 0;
		this->height = 0;
	}
	blank = true;
	surfaces.clear();
}

void
SurfaceResource::assign(Surface::Handle surface)
{
	Glib::Threads::RWLock::WriterLock lock(rwlock);
	std::lock_guard<std::mutex> short_lock(mutex);

	for(Map::const_iterator i = surfaces.begin(); i != surfaces.end(); ++i)
		if (i->second == surface)
			return;

	width = 0;
	height = 0;
	blank = true;
	surfaces.clear();
	if (!surface->is_exists())
		return;

	surfaces[surface->get_token()] = surface;
	width = surface->get_width();
	height = surface->get_height();
	blank = surface->is_blank();
}

void
SurfaceResource::clear()
{
	Glib::Threads::RWLock::WriterLock lock(rwlock);
	std::lock_guard<std::mutex> short_lock(mutex);
	blank = true;
	surfaces.clear();
}

void
SurfaceResource::reset()
{
	Glib::Threads::RWLock::WriterLock lock(rwlock);
	std::lock_guard<std::mutex> short_lock(mutex);
	width = 0;
	height = 0;
	blank = true;
	surfaces.clear();
}

/* === E N T R Y P O I N T ================================================= */
