#ifndef __COLOR_H_
#define __COLOR_H_

#define _USE_MATH_DEFINES
#include <cmath>
#include "png++/png.hpp"
#include "color/color.hpp"
#include <cstddef>
#include "hash.h"
#include "intset.h"

struct hdr_color {
	float red, green, blue;
	hdr_color(float r = 0, float g = 0, float b = 0) : red(r), green(g), blue(b) {}
	hdr_color(hdr_color const& other) : red(other.red), green(other.green), blue(other.blue) {}
	hdr_color(hdr_color& other) : red(other.red), green(other.green), blue(other.blue) {}
	//size_t operator()(const hdr_color& c) const { return (std::hash<float>()(c.red)) ^ (std::hash<float>()(c.green)) ^ (std::hash<float>()(c.blue)); }
};

inline hdr_color from(png::rgb_pixel p) { return hdr_color(p.red, p.green, p.blue); }
inline hdr_color from(png::color c) { return hdr_color(c.red, c.green, c.blue); }
inline png::rgb_pixel from(const hdr_color& c) { return png::rgb_pixel(c.red, c.green, c.blue); }
inline hdr_color diff(const hdr_color& a, const hdr_color& b) { return hdr_color(a.red-b.red,a.green-b.green,a.blue-b.blue); }
inline hdr_color add(const hdr_color& a, const hdr_color& b) { return hdr_color(a.red+b.red,a.green+b.green,a.blue+b.blue); }
inline hdr_color multidiv(const hdr_color& a, int num, int den) { return hdr_color(a.red*num/den,a.green*num/den,a.blue*num/den); }
inline hdr_color multi(const hdr_color& a, float scale) { return hdr_color(a.red*scale,a.green*scale,a.blue*scale); }
inline hdr_color clamp(const hdr_color& c) {
	return hdr_color(
			std::max(std::min(c.red,255.f),0.f),
			std::max(std::min(c.green,255.f),0.f),
			std::max(std::min(c.blue,255.f),0.f)
	);
}
inline int modulo(int a, int b) { return (a % b + b) % b; }

/*
#include <unordered_map>
std::unordered_map<std::tuple<float,float,float,float,float,float,std::string>, float> compare_cache;

inline float compare_colors(const hdr_color& c1, hdr_color& c2, const std::string colorspace) {
	std::tuple<float,float,float,float,float,float,std::string> args = std::make_tuple(c1.red, c1.green, c1.blue, c2.red, c2.green, c2.blue, colorspace);
	if(compare_cache.count(args)>0) { return compare_cache[args]; } else {
		color::rgb<float> a({c1.red, c1.green, c1.blue});
		color::rgb<float> b({c2.red, c2.green, c2.blue});
		float res;
		if(colorspace=="cie76") res = ::color::operation::distance< ::color::constant::distance::CIE76_entity >( a, b );
		else if(colorspace=="cmc1984") res = ::color::operation::distance< ::color::constant::distance::CMC1984_entity >( a, b );
		else if(colorspace=="cie94g") res = ::color::operation::distance< ::color::constant::distance::CIE94_graphics_entity >( a, b );
		else if(colorspace=="cie94t") res = ::color::operation::distance< ::color::constant::distance::CIE94_textile_entity >( a, b );
		else if(colorspace=="ciede2000") res = ::color::operation::distance< ::color::constant::distance::CIEDE2000_entity >( a, b );
		else if(colorspace=="delta_gray") res = ::color::operation::distance< ::color::constant::distance::delta_gray_entity >( a, b );
		else res = ::color::operation::distance< ::color::constant::distance::euclid_entity >( a, b );
		compare_cache[args] = res;
		return res;
	}
}
*/

inline float compare_colors(const hdr_color& c1, hdr_color& c2, const std::string colorspace) {
	color::rgb<float> a({c1.red, c1.green, c1.blue});
	color::rgb<float> b({c2.red, c2.green, c2.blue});
	float res;
	if(colorspace=="cie76") res = ::color::operation::distance< ::color::constant::distance::CIE76_entity >( a, b );
	else if(colorspace=="cmc1984") res = ::color::operation::distance< ::color::constant::distance::CMC1984_entity >( a, b );
	else if(colorspace=="cie94g") res = ::color::operation::distance< ::color::constant::distance::CIE94_graphics_entity >( a, b );
	else if(colorspace=="cie94t") res = ::color::operation::distance< ::color::constant::distance::CIE94_textile_entity >( a, b );
	else if(colorspace=="ciede2000") res = ::color::operation::distance< ::color::constant::distance::CIEDE2000_entity >( a, b );
	else if(colorspace=="delta_gray") res = ::color::operation::distance< ::color::constant::distance::delta_gray_entity >( a, b );
	else res = ::color::operation::distance< ::color::constant::distance::euclid_entity >( a, b );
	return res;
}

inline int closest(png::palette& pal, hdr_color& ref, const std::string colorspace, const std::set<int> &palette_mask) {
	float best_d = std::numeric_limits<float>::infinity();
	int best_i = -1;
	for(size_t i=0; i<pal.size(); i++) {
		if(palette_mask.count(i)>0) {
			float dist = compare_colors(from(pal[i]), ref, colorspace);
			if(dist < best_d) {
				best_d = dist;
				best_i = i;
			}
		}
	}
	return best_i;
}

#endif // __COLOR_H_
