/*
 *  psychlops_g_color.cpp
 *  Psychlops Standard Library (Universal)
 *
 *  Last Modified 2006/01/04 by Kenchi HOSOKAWA
 *  (C) 2006 Kenchi HOSOKAWA, Kazushi MARUYA and Takao SATO
 */


#include <Math.h>
#include <float.h>
#include <vector>
#include <map>

#include "../ApplicationInterfaces/psychlops_code_exception.h"
#include "../math/psychlops_m_util.h"
#include "psychlops_g_color.h"
#include "psychlops_g_canvas.h"


namespace Psychlops {

	bool Color::strict_match = false;
	bool Color::force_software_calibration_ = false;
	Color::CALIBRATION_MODE Color::calibration_mode_ = NOTHING;
	double Color::gamma_r_inversed_=1.0, Color::gamma_g_inversed_=1.0, Color::gamma_b_inversed_=1.0;
	double Color::gamma_r_=1.0, Color::gamma_g_=1.0, Color::gamma_b_=1.0;
	int Color::table_size_=0;
	std::vector<double> Color::table_r_, Color::table_g_, Color::table_b_;
	std::multimap<double, double> Color::restore_table_r_, Color::restore_table_g_, Color::restore_table_b_;

	void Color::forceSoftwareCalibration(bool on_off) {
		//bool mismatched = false;
		force_software_calibration_ = on_off;
		if(force_software_calibration_) {
			switch(calibration_mode_) {
			case GAMMA_VALUE:
				set_ = &set_correct_value;
				get_ = &get_restore_gamma;
				calibration_mode_ = SOFTWARE_GAMMA_VALUE;
				break;
			case TABLE:
				set_ = &set_correct_table;
				get_ = &get_restore_table;
				calibration_mode_ = SOFTWARE_TABLE;
				break;
			default:
				break;
			}
		} else {
			switch(calibration_mode_) {
			case SOFTWARE_GAMMA_VALUE:
				setGammaValue(gamma_r_, gamma_g_, gamma_b_);
				break;
			case SOFTWARE_TABLE:
				setGammaTable(table_r_, table_g_, table_b_);
				break;
			default:
				break;
			}
		}
	}
	void Color::setGammaValue(double r, double g, double b) {
		bool mismatched = force_software_calibration_;
		gamma_r_ = r;
		gamma_g_ = g;
		gamma_b_ = b;
		if(!force_software_calibration_) {
			try {
				Display::setGammaValue(r, g, b);
				set_ = &set_direct;
				get_ = &get_direct;
				calibration_mode_ = GAMMA_VALUE;
			} catch(Exception e) {
				mismatched = true;
			}
		}
		if(mismatched) {
			if(strict_match) throw Exception(typeid(Color), "API ERROR", "Failed to set color calibration table for the video renderer.");
			set_ = &set_correct_value;
			get_ = &get_restore_gamma;
			gamma_r_inversed_ = 1.0/r;
			gamma_g_inversed_ = 1.0/g;
			gamma_b_inversed_ = 1.0/b;
			gray.set(0.5,0.5,0.5);
			calibration_mode_ = SOFTWARE_GAMMA_VALUE;
		}
	}
	void Color::setGammaValue(const double r, const double g, const double b, const CALIBRATION_MODE mode) {
		switch(mode) {
			case GAMMA_VALUE:
				force_software_calibration_ = false;
				break;
			case SOFTWARE_GAMMA_VALUE:
				force_software_calibration_ = true;
				break;
			case BITS_MONO:
				force_software_calibration_ = true;
				break;
			case NOTHING:
			case TABLE:
			case SOFTWARE_TABLE:
			default:
				throw Exception("Color::setGammaValue: Specified color calibration mode and value type are mismatched.");
				break;
		}

		setGammaValue(r, g, b);

		switch(mode) {
			case BITS_MONO:
				calibration_mode_ = BITS_MONO;
				set_ = &set_correct_bits_mono;
				get_ = &get_restore_bits_mono;
				break;
			case NOTHING:
			case GAMMA_VALUE:
			case TABLE:
			case SOFTWARE_GAMMA_VALUE:
			case SOFTWARE_TABLE:
				break;
		}
	}
	void Color::setGammaTable(const double * const table_r, const double * const table_g, const double * const table_b, const int num_steps) {
		std::vector<double> tr(num_steps), tg(num_steps), tb(num_steps);
		for(int i=0; i<num_steps; i++) {
			tr[i] = table_r[i];
			tg[i] = table_g[i];
			tb[i] = table_b[i];
		}
		setGammaTable(tr, tg, tb);
	}
inline int max_int(int a, int b) {
	return (a>b ? a : b);
}
	void Color::setGammaTable(const std::vector<double> &table_r, const std::vector<double> &table_g, const std::vector<double> &table_b) {
		bool mismatched = force_software_calibration_;

		if(strict_match && (table_r.size()!=table_g.size() || table_r.size()!=table_b.size()))
			 throw Exception(typeid(Color), "API ERROR", "Failed to set color calibration table because table sizes were unmatched between R/G/B tables.");
		table_size_ = (max_int(table_r.size(), max_int( table_g.size(), table_b.size() ) ), 256);

		interpolateDiscreteSamplesInLinearFunction(table_r, table_r_);
		interpolateDiscreteSamplesInLinearFunction(table_g, table_g_);
		interpolateDiscreteSamplesInLinearFunction(table_b, table_b_);

		for(int i=0; i<table_size_; i++) {
			restore_table_r_.insert( std::pair<double, double>(table_r_[i], i/(table_size_-1.0)) );
			restore_table_g_.insert( std::pair<double, double>(table_g_[i], i/(table_size_-1.0)) );
			restore_table_b_.insert( std::pair<double, double>(table_b_[i], i/(table_size_-1.0)) );
		}

		if(!force_software_calibration_) {
			try {
				Display::setGammaTable(table_r_, table_g_, table_b_);
				set_ = &set_direct;
				get_ = &get_direct;
				calibration_mode_ = TABLE;
			} catch(Exception e) {
				mismatched = true;
			}
		}
		if(mismatched) {
			if(strict_match) throw Exception(typeid(Color), "API ERROR", "Failed to set color calibration table for the video renderer.");
			set_ = &set_correct_table;
			get_ = &get_restore_table;
			calibration_mode_ = SOFTWARE_TABLE;
		}
	}
	void Color::interpolateDiscreteSamplesInLinearFunction(const std::vector<double> &source, std::vector<double> &target) {
		double full, integer, fraction;
		if(source.size()<table_size_) {
			target.clear();
			target.resize(table_size_);
			for(int i=0; i<table_size_; i++) {
				full = ((double)i/(table_size_-1))*(source.size()-1);
				fraction = modf(full, &integer);
				if( integer==source.size()-1) {
					target[i] = limitvalue(source[(int)integer]);
				} else {
					target[i] = limitvalue((1.0-fraction)*source[(int)integer] + (fraction)*source[(int)integer+1]);
				}
			}
		} else {
			target.clear();
			target.resize(table_size_);
			for(int i=0; i<table_size_; i++) {
				target[i] = limitvalue(source[i]);
			}
		}
	}
	Color::CALIBRATION_MODE Color::getCalibrationMode() {
		return calibration_mode_;
	}



	inline double Color::Int8toFloat64(int value) {
		return value/255.0;
	}
	inline double Color::limitvalue(double value) {
		if( value<0.0 ) {
			if(strict_match) throw new Exception("Color.set : STRICT MATCH FAILED : Argument was under 0.0 .");
			value = 0.0;
		} else if( value>1.0 ) {
			if(strict_match) throw new Exception("Color.set : STRICT MATCH FAILED : Argument was over 1.0 .");
			value = 1.0;
		}
		return value;
	}
//	inline double Color::restoregamma_r(double value) { return pow(value,gamma_r_); }
//	inline double Color::restoregamma_g(double value) { return pow(value,gamma_g_); }
//	inline double Color::restoregamma_b(double value) { return pow(value,gamma_b_); }
//	inline double Color::restoretable_r(double value) { return restore_table_r_.find(val)->second; }
//	inline double Color::restoretable_g(double value) { return restore_table_r_.find(val)->second; }
//	inline double Color::restoretable_b(double value) { return restore_table_r_.find(val)->second; }

	void Color::set_direct(Color * col, double r, double g, double b, double a) {
		col->Red = limitvalue(r);
		col->Green = limitvalue(g);
		col->Blue = limitvalue(b);
		col->Alpha = limitvalue(a);
	}
	void Color::set_correct_value(Color * col, double r, double g, double b, double a) {
		col->Red = limitvalue(pow(r,gamma_r_inversed_));
		col->Green = limitvalue(pow(g,gamma_g_inversed_));
		col->Blue = limitvalue(pow(b,gamma_b_inversed_));
		col->Alpha = limitvalue(a);
	}
	void Color::set_correct_table(Color * col, double r, double g, double b, double a) {
		col->Red = table_r_[(int)(Math::round((table_size_-1)*r))];
		col->Green = table_g_[(int)(Math::round((table_size_-1)*g))];
		col->Blue = table_b_[(int)(Math::round((table_size_-1)*b))];
		col->Alpha = limitvalue(a);
	}
	void (*Color::set_)(Color * col, double r, double g, double b, double a) = &set_direct;

	void Color::get_direct(const Color &col, double &r, double &g, double &b, double &a) {
		r = col.Red;
		g = col.Green;
		b = col.Blue;
		a = col.Alpha;
	}
	void Color::get_restore_gamma(const Color &col, double &r, double &g, double &b, double &a) {
		r = pow(col.Red,gamma_r_);
		g = pow(col.Green,gamma_g_);
		b = pow(col.Blue,gamma_b_);
		a = col.Alpha;
	}
	void Color::get_restore_table(const Color &col, double &r, double &g, double &b, double &a) {
		r = restore_table_r_.find(col.Red)->second;
		g = restore_table_g_.find(col.Green)->second;
		b = restore_table_b_.find(col.Blue)->second;
		a = col.Alpha;
	}
	void (*Color::get_)(const Color &col, double &r, double &g, double &b, double &a) = &get_direct;



	Color::Color() { set_(this, 0.0, 0.0, 0.0, 1.0); }
	Color::Color(double r, double g, double b, double a) { set_(this,r,g,b,a); }
	Color::Color(double r, double g, double b, double a, bool dummy) { set_direct(this,r,g,b,a); }
	Color::Color(double gray) { set_(this,gray,gray,gray,1.0); }
	Color &Color::set(double r, double g, double b, double a) { set_(this,r,g,b,a); return *this; }
	Color &Color::set(double r, double g, double b, double a, bool dummy) { set_direct(this,r,g,b,a); return *this; }
	Color &Color::set(double gray) { set_(this,gray,gray,gray,1.0); return *this; }

	double Color::getR() const {
		double r, g, b, a;
		get_(*this, r, g, b, a);
		return r;
	}
	double Color::getG() const {
		double r, g, b, a;
		get_(*this, r, g, b, a);
		return g;
	}
	double Color::getB() const {
		double r, g, b, a;
		get_(*this, r, g, b, a);
		return b;
	}
	double Color::getA() const {
		return Alpha;
	}
	void Color::get(double &r, double &g, double &b, double &a) const {
		get_(*this, r, g, b, a);
	}


	Color Color::dup() const {
		return *this;
	}

	bool Color::operator ==(Color rhs) {
		if(Red==rhs.Red && Green==rhs.Green && Blue==rhs.Blue && Alpha==rhs.Alpha) {
			return true;
		} else {
			return false;
		}
	}
	Color Color::operator +(Color rhs) {
		return dup()+=rhs;
	}
	Color Color::operator -(Color rhs) {
		return dup()-=rhs;
	}
	Color & Color::operator +=(Color rhs) {
		Red += limitvalue(rhs.Red);
		Green += limitvalue(rhs.Green);
		Blue += limitvalue(rhs.Blue);
		Alpha += limitvalue(rhs.Alpha);
		return (*this);
	}
	Color & Color::operator -=(Color rhs) {
		Red -= limitvalue(rhs.Red);
		Green -= limitvalue(rhs.Green);
		Blue -= limitvalue(rhs.Blue);
		Alpha -= limitvalue(rhs.Alpha);
		return (*this);
	}


	Color Color::white(1.0);
	Color Color::black(0.0);
	Color Color::gray(0.5);
	Color Color::red(1.0,0.0,0.0);
	Color Color::green(0.0,1.0,0.0);
	Color Color::blue(0.0,0.0,1.0);
	Color Color::cyan(0.0,1.0,1.0);
	Color Color::magenta(1.0,0.0,1.0);
	Color Color::yellow(1.0,1.0,0.0);

	const Color Color::null_color(0,0,0,0,false);



	void Color::set_correct_bits_mono(Color * col, double r, double g, double b, double a) {
		if(a!=1) throw new Exception("Color.set : alpha value is disabled for Bits++.");
		unsigned short wordlum = (unsigned short)(limitvalue(pow(r,gamma_r_inversed_))*65535);
		col->Red = (double)((wordlum>>8)/255.0);
		col->Green = (double)((wordlum&255)/255.0);
		col->Blue = 0;
		col->Alpha = 1;
	}
	void Color::get_restore_bits_mono(const Color &col, double &r, double &g, double &b, double &a) {
		double lum = ( ((unsigned short)(col.Red*255) << 8) + (unsigned short)(col.Green*255) ) / 65535.0;
		r = pow(lum,gamma_r_);
		g = r;
		b = r;
		a = 1;
	}

}	/*	<- namespace Psycholops 	*/


