/* === S Y N F I G ========================================================= */
/*!	\file palette.cpp
**	\brief Template File
**
**	\legal
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2010 Nikita Kitaev
**	Copyright (c) 2010 Carlos López
**
**	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 "palette.h"

#include "surface.h"
#include "general.h"
#include "filesystemnative.h"
#include <synfig/localization.h>
#include <fstream>
#include <iostream>
#include <sstream>

#endif

/* === U S I N G =========================================================== */

using namespace synfig;

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

#define PALETTE_SYNFIG_FILE_COOKIE	"SYNFIGPAL1.0"
#define PALETTE_SYNFIG_EXT ".spal"
#define PALETTE_GIMP_FILE_COOKIE "GIMP Palette"
#define PALETTE_GIMP_EXT ".gpl"

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

bool weight_less_than(const PaletteItem& lhs,const PaletteItem& rhs)
{
	return lhs.weight<rhs.weight;
}

bool luma_less_than(const PaletteItem& lhs,const PaletteItem& rhs)
{
	return lhs.color.get_y()<rhs.color.get_y();
}

bool luma_less_than(const PaletteItem& lhs,const float& rhs)
{
	return lhs.color.get_y()<rhs;
}

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

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

Palette::Palette():
	name_(_("Unnamed"))
{
}

Palette::Palette(const String& name_):
	name_(name_)
{
}

void
PaletteItem::add(const Color& x,int xweight)
{
	color=(color*weight+x*xweight)/(weight+xweight);
	weight+=xweight;
}

Palette::Palette(const Surface& surface, int max_colors, const Gamma &gamma):
	name_(_("Surface Palette"))
{
	max_colors-=2;
	for(int i=0;(signed)size()<(max_colors-1) && i<max_colors*16;++i) {
        int x=rand()%surface.get_w();
        int y=rand()%surface.get_h();

			float dist;
			Color color(surface[y][x]);

			if(empty())
			{
				push_back(color);
				continue;
			}

			if(color.get_a()==0)
			{
				if(front().color.get_a()!=0)
					insert(begin(),Color(1,0,1,0));
				front().weight+=400;
				continue;
			}

			iterator iter(find_closest(color, gamma, &dist));
			if(sqrt(dist)<0.005)
			{
				iter->add(color);
				continue;
			}

			/*if(size()>=max_colors)
			{
				iterator iterlight(find_light());
				PaletteItem light(*iterlight);
				erase(iterlight);
				find_closest(light.color, gamma)->add(light.color,light.weight);
			}
			*/

			push_back(color);
			continue;
    }

/*

	max_colors-=2;
	for(int y=0;y<surface.get_h();y++)
		for(int x=0;x<surface.get_w();x++)
		{
			float dist;
			Color color(surface[y][x]);

			if(empty())
			{
				push_back(color);
				continue;
			}

			if(color.get_a()==0)
			{
				if(front().color.get_a()!=0)
					insert(begin(),Color(1,0,1,0));
				front().weight+=400;
				continue;
			}

			iterator iter(find_closest(color, gamma, &dist));
			if(sqrt(dist)<0.005)
			{
				iter->add(color);
				continue;
			}


			push_back(color);
			continue;
		}
	sort(rbegin(),rend());

	iterator iter;

	iterator best_match(begin());
	while((signed)size()>max_colors)
	{
		PaletteItem item(back());
		pop_back();
		find_closest(item.color, gamma)->add(item.color,item.weight);
	}
*/
	push_back(Color::black());
	push_back(Color::white());

//	sort(begin(),end(),&luma_less_than);
}

Palette::const_iterator
Palette::find_closest(const Color& color, const Gamma &gamma, float* dist)const
{
	// For the sake of avoiding cut-and-paste
	// bugs, we'll just use the non-const
	// find_closest()... It doesn't change anything
	// anyway.
	return const_cast<Palette*>(this)->find_closest(color, gamma, dist);
}

Palette::iterator
Palette::find_closest(const Color& color, const Gamma &gamma, float* dist)
{
	iterator iter;

	iterator best_match(begin());
	float best_dist(1000000);

	const Color prep = gamma.apply(color);
	const float prep_y(prep.get_y()*prep.get_a());
	const float prep_u(prep.get_u());
	const float prep_v(prep.get_v());

	for(iter=begin();iter!=end();++iter)
	{
		const Color ic = gamma.apply(iter->color);
		const float diff_y(prep_y - ic.get_y()*ic.get_a());
		const float diff_u(prep_u - ic.get_u());
		const float diff_v(prep_v - ic.get_v());
		const float diff_a(prep.get_a() - ic.get_a());


		const float dist(
			diff_y*diff_y*1.5f+
			diff_a*diff_a+

			diff_u*diff_u+
			diff_v*diff_v

			// cross product
			/*abs(
				prep_u*ic.get_u()-
				prep_v*ic.get_v()
			)*/
		);
		if(dist<best_dist)
		{
			best_dist=dist;
			best_match=iter;
		}
	}
	if(dist)
		*dist=best_dist;

	return best_match;
}


Palette::iterator
Palette::find_heavy()
{
	iterator iter;

	iterator best_match(begin());

	for(iter=begin();iter!=end();++iter)
	{
		if(iter->weight>best_match->weight)
			best_match=iter;
	}

	return best_match;
}

Palette::iterator
Palette::find_light()
{
	iterator iter;

	iterator best_match(begin());

	for(iter=begin();iter!=end();++iter)
	{
		if(iter->weight<best_match->weight)
			best_match=iter;
	}

	return best_match;
}

Palette
Palette::grayscale(int steps, ColorReal gamma)
{
	Palette ret;
	for(int i=0;i<steps;i++)
	{
		ColorReal amount = i/ColorReal(steps-1);
		ColorReal y = Gamma::calculate(amount, gamma);
		ret.push_back(
			PaletteItem(
				Color(y, y, y),
				strprintf(_("%0.2f%% Gray"), amount) ));
	}
	return ret;
}

void
Palette::save_to_file(const synfig::filesystem::Path& filename) const
{
	FileSystem::WriteStream::Handle file = FileSystemNative::instance()->get_write_stream(filename.u8string());

	if(!file)
		throw strprintf(_("Unable to open %s for write"),filename.u8_str());

	*file << PALETTE_SYNFIG_FILE_COOKIE << "\n" << name_ << "\n";
	for (const_iterator iter=begin();iter!=end();++iter) {
		*file << iter->name << "\n"
		      << iter->color.get_r() << "\n"
			  << iter->color.get_g() << "\n"
			  << iter->color.get_b() << "\n"
			  << iter->color.get_a() << "\n";
	}
}

Palette
Palette::load_from_file(const synfig::filesystem::Path& filename)
{
	FileSystem::ReadStream::Handle file = FileSystemNative::instance()->get_read_stream(filename.u8string());

	if(!file)
		throw strprintf(_("Unable to open %s for read"), filename.u8_str());

	Palette ret;
	std::string line("");
	const std::string ext(filename.extension().u8string());


	if (ext==PALETTE_SYNFIG_EXT)
	{
		getline(*file, line);

		if(line!=PALETTE_SYNFIG_FILE_COOKIE)
			throw strprintf(_("%s does not appear to be a valid %s palette file"), filename.u8_str(), "Synfig");

		getline(*file, ret.name_);

		std::string line_trail;

		while(file->good())	{
			PaletteItem item;
			float r, g, b, a;
			getline(*file, item.name);
			*file >> r >> g >> b >> a;
			item.color.set_r(r).set_g(g).set_b(b).set_a(a);

			// file ends in new line
			if (file->good())
				ret.push_back(item);

			getline(*file, line_trail);
		}
	}
	else if (ext==PALETTE_GIMP_EXT)
	{
		/*
		file format: GPL (GIMP Palette) file should have the following layout:
		GIMP Palette
		Name: <palette name>
		[Columns: <number>]
		[#]
		[# Optional comments]
		[#]
		<value R> <value G> <value B> <swatch name>
		<value R> <value G> <value B> <swatch name>
		... ...
		[<new line>]
		*/

		int line_num = 0;

		// skip initial empty lines on malformed files
		do {
			getline(*file, line);
			line_num++;
		} while (file->good() && line.empty());

		if (line != PALETTE_GIMP_FILE_COOKIE)
			throw strprintf(_("%s does not appear to be a valid %s palette file"), filename.u8_str(), "GIMP");

		while (file->good())
		{
			getline(*file, line);
			line_num++;

			if (line.empty())
				continue;

			if (line.substr(0,5) == "Name:")
				ret.name_ = String(line.substr(6));
			else if (line.substr(0,8) == "Columns:")
				; // Ignore columns
			else if (line.substr(0,1) == "#")
				; // Ignore comments
			else
				break;
		}

		while(file->good())
		{
			PaletteItem item;
			int r, g, b;

			std::stringstream ss;

			if (!line.empty())
			{
				ss << line.c_str();

			 	ss >> r >> g >> b;
				getline(ss, item.name);

				item.color.set_r(r/255.f).set_g(g/255.f).set_b(b/255.f);
				// Alpha is 1 by default
				item.color.set_a(1);

				ret.push_back(item);
			}

			getline(*file, line);
			line_num++;
			if (!file->eof() && file->fail())
				warning(_("could not properly load palette: error parsing line #%i (%s)"), line_num, filename.u8_str());
		}
	}
	else
		throw strprintf(_("%s does not appear to be a supported palette file"), filename.u8_str());

	return ret;
}
