//From https://bisqwit.iki.fi/story/howto/dither/jy/

//~ #include <gd.h>
#include <stdio.h>
#include <math.h>
#include <algorithm> /* For std::sort() */
#include <vector>
#include <map>       /* For associative container, std::map<> */

////////////////////////////////////////////////////
#if 0
#include <utility> // for std::pair
#include "alloc/FSBAllocator.hh"

/* kd-tree implementation translated to C++
 * from java implementation in VideoMosaic
 * at http://www.intelegance.net/video/videomosaic.shtml.
 */

template<typename V, unsigned K = 3>
class KDTree {
public:
	struct KDPoint {
		double coord[K];
	
		KDPoint() { }
	
		KDPoint(double a,double b,double c) {
			coord[0] = a;
			coord[1] = b;
			coord[2] = c;
		}
	
		KDPoint(double v[K]) {
			for(unsigned n=0; n<K; ++n)
				coord[n] = v[n];
		}
	
		bool operator==(const KDPoint& b) const {
			for(unsigned n=0; n<K; ++n)
				if(coord[n] != b.coord[n]) return false;
			return true;
		}
		double sqrdist(const KDPoint& b) const {
			double result = 0;
			for(unsigned n=0; n<K; ++n) {
				double diff = coord[n] - b.coord[n];
				result += diff*diff;
			}
			return result;
		}
	};
private:
	struct KDRect {
		KDPoint min, max;
	
		KDPoint bound(const KDPoint& t) const {
			KDPoint p;
			for(unsigned i=0; i<K; ++i)
				if(t.coord[i] <= min.coord[i])
					p.coord[i] = min.coord[i];
				else if(t.coord[i] >= max.coord[i])
					p.coord[i] = max.coord[i];
				else
					p.coord[i] = t.coord[i];
			return p;
		}
		void MakeInfinite() {
			for(unsigned i=0; i<K; ++i) {
				min.coord[i] = -1e99;
				max.coord[i] =  1e99;
			}
		}
	};

	struct KDNode {
		KDPoint k;
		V       v;
		KDNode  *left, *right;
	public:
		KDNode() : k(),v(),left(0),right(0) { }
		KDNode(const KDPoint& kk, const V& vv) : k(kk), v(vv), left(0), right(0) { }
	
		virtual ~KDNode() {
			delete (left);
			delete (right);
		}
	
		static KDNode* ins( const KDPoint& key, const V& val,
				    KDNode*& t, int lev) {
			if(!t)
				return (t = new KDNode(key, val));
			else if(key == t->k)
				return 0; /* key duplicate */
			else if(key.coord[lev] > t->k.coord[lev])
				return ins(key, val, t->right, (lev+1)%K);
			else
				return ins(key, val, t->left,  (lev+1)%K);
		}
		struct Nearest {
			const KDNode* kd;
			double        dist_sqd;
		};
		// Method Nearest Neighbor from Andrew Moore's thesis. Numbered
		// comments are direct quotes from there. Step "SDL" is added to
		// make the algorithm work correctly.
		static void nnbr(const KDNode* kd, const KDPoint& target,
				 KDRect& hr, // in-param and temporary; not an out-param.
				 int lev,
				 Nearest& nearest) {
			// 1. if kd is empty then set dist-sqd to infinity and exit.
			if (!kd) return;
		
			// 2. s := split field of kd
			int s = lev % K;
		
			// 3. pivot := dom-elt field of kd
			const KDPoint& pivot = kd->k;
			double pivot_to_target = pivot.sqrdist(target);
		
			// 4. Cut hr into to sub-hyperrectangles left-hr and right-hr.
			//    The cut plane is through pivot and perpendicular to the s
			//    dimension.
			KDRect& left_hr = hr; // optimize by not cloning
			KDRect right_hr = hr;
			left_hr.max.coord[s]  = pivot.coord[s];
			right_hr.min.coord[s] = pivot.coord[s];
		
			// 5. target-in-left := target_s <= pivot_s
			bool target_in_left = target.coord[s] < pivot.coord[s];
		
			const KDNode* nearer_kd;
			const KDNode* further_kd;
			KDRect nearer_hr;
			KDRect further_hr;
		
			// 6. if target-in-left then nearer is left, further is right
			if (target_in_left) {
				nearer_kd = kd->left;
				nearer_hr = left_hr;
				further_kd = kd->right;
				further_hr = right_hr;
			}
			// 7. if not target-in-left then nearer is right, further is left
			else {
				nearer_kd = kd->right;
				nearer_hr = right_hr;
				further_kd = kd->left;
				further_hr = left_hr;
			}
		
			// 8. Recursively call Nearest Neighbor with parameters
			//    (nearer-kd, target, nearer-hr, max-dist-sqd), storing the
			//    results in nearest and dist-sqd
			nnbr(nearer_kd, target, nearer_hr, lev + 1, nearest);
		
			// 10. A nearer point could only lie in further-kd if there were some
			//     part of further-hr within distance sqrt(max-dist-sqd) of
			//     target.  If this is the case then
			const KDPoint closest = further_hr.bound(target);
			if (closest.sqrdist(target) < nearest.dist_sqd) {
				// 10.1 if (pivot-target)^2 < dist-sqd then
				if (pivot_to_target < nearest.dist_sqd) {
					// 10.1.1 nearest := (pivot, range-elt field of kd)
					nearest.kd = kd;
					// 10.1.2 dist-sqd = (pivot-target)^2
					nearest.dist_sqd = pivot_to_target;
				}
			
				// 10.2 Recursively call Nearest Neighbor with parameters
				//      (further-kd, target, further-hr, max-dist_sqd)
				nnbr(further_kd, target, further_hr, lev + 1, nearest);
			}
			// SDL: otherwise, current point is nearest
			else if (pivot_to_target < nearest.dist_sqd) {
				nearest.kd       = kd;
				nearest.dist_sqd = pivot_to_target;
			}
		}
	private:
		void operator=(const KDNode&);
	public:
		KDNode(const KDNode& b)
			: k(b.k), v(b.v),
			  left( b.left ? new KDNode(*b.left) : 0),
			  right( b.right ? new KDNode(*b.right) : 0 ) { }
	};
private:
	KDNode* m_root;
public:
	KDTree() : m_root(0) { }
	virtual ~KDTree() {
		delete (m_root);
	}

	bool insert(const KDPoint& key, const V& val) {
		return KDNode::ins(key, val, m_root, 0);
	}

	const std::pair<V,double> nearest(const KDPoint& key) const {
		KDRect hr;
		hr.MakeInfinite();
	
		typename KDNode::Nearest nn;
		nn.kd       = 0;
		nn.dist_sqd = 1e99;
		KDNode::nnbr(m_root, key, hr, 0, nn);
		if(!nn.kd) return std::pair<V,double> ( V(), 1e99 );
		return std::pair<V,double> ( nn.kd->v, nn.dist_sqd);
	}
public:
	KDTree& operator=(const KDTree&b) {
		if(this != &b) {
			if(m_root) delete (m_root);
			m_root = b.m_root ? new KDNode(*b.m_root) : 0;
			m_count = b.m_count;
		}
		return *this;
	}
	KDTree(const KDTree& b)
		: m_root( b.m_root ? new KDNode(*b.m_root) : 0 ),
		  m_count( b.m_count ) { }
};
#endif
////////////////////////////////////////////////////

#define COMPARE_RGB 1


static const double Gamma = 2.2; // Gamma correction we use.

double GammaCorrect(double v)   {
	return pow(v, Gamma);
}
double GammaUncorrect(double v) {
	return pow(v, 1.0 / Gamma);
}

/* CIE C illuminant */
static const double illum[3*3] = {
	0.488718, 0.176204, 0.000000,
	0.310680, 0.812985, 0.0102048,
	0.200602, 0.0108109, 0.989795
};
struct LabItem { // CIE L*a*b* color value with C and h added.
	double L,a,b,C,h;

	LabItem() { }
	LabItem(double R,double G,double B) {
		Set(R,G,B);
	}
	void Set(double R,double G,double B) {
		const double* const i = illum;
		double X = i[0]*R + i[3]*G + i[6]*B, x = X / (i[0] + i[1] + i[2]);
		double Y = i[1]*R + i[4]*G + i[7]*B, y = Y / (i[3] + i[4] + i[5]);
		double Z = i[2]*R + i[5]*G + i[8]*B, z = Z / (i[6] + i[7] + i[8]);
		const double threshold1 = (6*6*6.0)/(29*29*29.0);
		const double threshold2 = (29*29.0)/(6*6*3.0);
		double x1 = (x > threshold1) ? pow(x, 1.0/3.0) : (threshold2*x)+(4/29.0);
		double y1 = (y > threshold1) ? pow(y, 1.0/3.0) : (threshold2*y)+(4/29.0);
		double z1 = (z > threshold1) ? pow(z, 1.0/3.0) : (threshold2*z)+(4/29.0);
		L = (29*4)*y1 - (4*4);
		a = (500*(x1-y1) );
		b = (200*(y1-z1) );
		C = sqrt(a*a + b+b);
		h = atan2(b, a);
	}
	LabItem(unsigned rgb) {
		Set(rgb);
	}
	void Set(unsigned rgb) {
		Set( (rgb>>16)/255.0, ((rgb>>8)&0xFF)/255.0, (rgb&0xFF)/255.0 );
	}
};

/* From the paper "The CIEDE2000 Color-Difference Formula: Implementation Notes, */
/* Supplementary Test Data, and Mathematical Observations", by */
/* Gaurav Sharma, Wencheng Wu and Edul N. Dalal, */
/* Color Res. Appl., vol. 30, no. 1, pp. 21-30, Feb. 2005. */
/* Return the CIEDE2000 Delta E color difference measure squared, for two Lab values */
double ColorCompare(const LabItem& lab1, const LabItem& lab2) {
#define RAD2DEG(xx) (180.0/M_PI * (xx))
#define DEG2RAD(xx) (M_PI/180.0 * (xx))
	/* Compute Cromanance and Hue angles */
	double C1,C2, h1,h2;
	{
		double Cab = 0.5 * (lab1.C + lab2.C);
		double Cab7 = pow(Cab,7.0);
		double G = 0.5 * (1.0 - sqrt(Cab7/(Cab7 + 6103515625.0)));
		double a1 = (1.0 + G) * lab1.a;
		double a2 = (1.0 + G) * lab2.a;
		C1 = sqrt(a1 * a1 + lab1.b * lab1.b);
		C2 = sqrt(a2 * a2 + lab2.b * lab2.b);
	
		if (C1 < 1e-9)
			h1 = 0.0;
		else {
			h1 = RAD2DEG(atan2(lab1.b, a1));
			if (h1 < 0.0)
				h1 += 360.0;
		}
	
		if (C2 < 1e-9)
			h2 = 0.0;
		else {
			h2 = RAD2DEG(atan2(lab2.b, a2));
			if (h2 < 0.0)
				h2 += 360.0;
		}
	}

	/* Compute delta L, C and H */
	double dL = lab2.L - lab1.L, dC = C2 - C1, dH;
	{
		double dh;
		if (C1 < 1e-9 || C2 < 1e-9) {
			dh = 0.0;
		} else {
			dh = h2 - h1;
			/**/ if (dh > 180.0)  dh -= 360.0;
			else if (dh < -180.0) dh += 360.0;
		}
	
		dH = 2.0 * sqrt(C1 * C2) * sin(DEG2RAD(0.5 * dh));
	}

	double h;
	double L = 0.5 * (lab1.L  + lab2.L);
	double C = 0.5 * (C1 + C2);
	if (C1 < 1e-9 || C2 < 1e-9) {
		h = h1 + h2;
	} else {
		h = h1 + h2;
		if (fabs(h1 - h2) > 180.0) {
			/**/ if (h < 360.0)  h += 360.0;
			else if (h >= 360.0) h -= 360.0;
		}
		h *= 0.5;
	}
	double T = 1.0
		   - 0.17 * cos(DEG2RAD(h - 30.0))
		   + 0.24 * cos(DEG2RAD(2.0 * h))
		   + 0.32 * cos(DEG2RAD(3.0 * h + 6.0))
		   - 0.2 * cos(DEG2RAD(4.0 * h - 63.0));
	double hh = (h - 275.0)/25.0;
	double ddeg = 30.0 * exp(-hh * hh);
	double C7 = pow(C,7.0);
	double RC = 2.0 * sqrt(C7/(C7 + 6103515625.0));
	double L50sq = (L - 50.0) * (L - 50.0);
	double SL = 1.0 + (0.015 * L50sq) / sqrt(20.0 + L50sq);
	double SC = 1.0 + 0.045 * C;
	double SH = 1.0 + 0.015 * C * T;
	double RT = -sin(DEG2RAD(2 * ddeg)) * RC;
	double dLsq = dL/SL, dCsq = dC/SC, dHsq = dH/SH;
	return dLsq*dLsq + dCsq*dCsq + dHsq*dHsq + RT*dCsq*dHsq;
#undef RAD2DEG
#undef DEG2RAD
}

double ColorCompare(int r1,int g1,int b1, int r2,int g2,int b2) {
	double luma1 = (r1*299 + g1*587 + b1*114) / (255.0*1000);
	double luma2 = (r2*299 + g2*587 + b2*114) / (255.0*1000);
	double lumadiff = luma1-luma2;
	double diffR = (r1-r2)/255.0, diffG = (g1-g2)/255.0, diffB = (b1-b2)/255.0;
	return (diffR*diffR*0.299 + diffG*diffG*0.587 + diffB*diffB*0.114)*0.75
	       + lumadiff*lumadiff;
}


/* Palette */
static const unsigned palettesize = 16;

/* Luminance for each palette entry, to be initialized as soon as the program begins */
static unsigned luma[palettesize];
static LabItem  meta[palettesize];
static double   pal_g[palettesize][3]; // Gamma-corrected palette entry

inline bool PaletteCompareLuma(unsigned index1, unsigned index2) {
	return luma[index1] < luma[index2];
}

typedef std::vector<unsigned> MixingPlan;
MixingPlan DeviseBestMixingPlan(unsigned color, size_t limit) {
	// Input color in RGB
	int input_rgb[3] = { (int)((color>>16)&0xFF),
			     (int)((color>>8)&0xFF),
			     (int)(color&0xFF)
			   };
			   
	// Input color in CIE L*a*b*
	LabItem input(color);

	// Tally so far (gamma-corrected)
	double so_far[3] = { 0,0,0 };

	MixingPlan result;
	while(result.size() < limit) {
		unsigned chosen_amount = 1;
		unsigned chosen        = 0;
	
		const unsigned max_test_count = result.empty() ? 1 : result.size();
	
		double least_penalty = -1;
		for(unsigned index=0; index<palettesize; ++index) {
			//~ const unsigned color = pal[index];
			double sum[3] = { so_far[0], so_far[1], so_far[2] };
			double add[3] = { pal_g[index][0], pal_g[index][1], pal_g[index][2] };
		
			for(unsigned p=1; p<=max_test_count; p*=2) {
				for(unsigned c=0; c<3; ++c) sum[c] += add[c];
				for(unsigned c=0; c<3; ++c) add[c] += add[c];
				double t = result.size() + p;
			
				double test[3] = { GammaUncorrect(sum[0]/t),
						   GammaUncorrect(sum[1]/t),
						   GammaUncorrect(sum[2]/t)
						 };
						 
#if COMPARE_RGB
				double penalty = ColorCompare(
							 input_rgb[0],input_rgb[1],input_rgb[2],
							 test[0]*255, test[1]*255, test[2]*255);
#else
				LabItem test_lab( test[0], test[1], test[2] );
				double penalty = ColorCompare(test_lab, input);
#endif
				if(penalty < least_penalty || least_penalty < 0) {
					least_penalty = penalty;
					chosen        = index;
					chosen_amount = p;
				}
			}
		}
	
		// Append "chosen_amount" times "chosen" to the color list
		result.resize(result.size() + chosen_amount, chosen);
	
		for(unsigned c=0; c<3; ++c)
			so_far[c] += pal_g[chosen][c] * chosen_amount;
	}
	// Sort the colors according to luminance
	std::sort(result.begin(), result.end(), PaletteCompareLuma);
	return result;
}
#if 0
int main(int argc, char**argv) {
	FILE* fp = fopen(argv[1], "rb");
	gdImagePtr srcim = gdImageCreateFromPng(fp);
	fclose(fp);

	unsigned w = gdImageSX(srcim), h = gdImageSY(srcim);
	gdImagePtr im = gdImageCreate(w, h);
	for(unsigned c=0; c<palettesize; ++c) {
		unsigned r = pal[c]>>16, g = (pal[c]>>8) & 0xFF, b = pal[c] & 0xFF;
		gdImageColorAllocate(im, r,g,b);
		luma[c] = r*299 + g*587 + b*114;
		meta[c].Set(pal[c]);
		pal_g[c][0] = GammaCorrect(r/255.0);
		pal_g[c][1] = GammaCorrect(g/255.0);
		pal_g[c][2] = GammaCorrect(b/255.0);
	}
	#pragma omp parallel for
	for(unsigned y=0; y<h; ++y)
		for(unsigned x=0; x<w; ++x) {
			unsigned color = gdImageGetTrueColorPixel(srcim, x, y);
			unsigned map_value = map[(x & 7) + ((y & 7) << 3)];
			MixingPlan plan = DeviseBestMixingPlan(color, 16);
			map_value = map_value * plan.size() / 64;
			gdImageSetPixel(im, x,y, plan[ map_value ]);
		}
	fp = fopen(argv[2], "wb");
	gdImagePng(im, fp);
	fclose(fp);
	gdImageDestroy(im);
	gdImageDestroy(srcim);
	return 0;
}
#endif
