/*
 *  psychlops_g_image_Win32_GL.cpp
 *  Psychlops Standard Library (Universal)
 *
 *  Last Modified 2007/07/07 by Kenchi HOSOKAWA
 *  (C) 2006 Kenchi HOSOKAWA, Kazushi MARUYA and Takao SATO
 */



#include <stdlib.h>
#include <Math.h>


#define PSYCHLOPS_WINDOW_API_PLATFORM
#include "../psychlops_platform_selector.h"
#include "../../core/graphic/psychlops_g_image.h"



namespace Psychlops {

	const int APIImageProperties::PixCompGL_[3] = { GL_LUMINANCE, GL_RGB, GL_RGBA };
	const int APIImageProperties::PixPrecGL_[2] = { GL_UNSIGNED_BYTE, GL_FLOAT };


	class ImageManipulator {
		friend class Image;
		inline static int pix_offset(const Image &img, const int x, const int iy) {
			int y = img.height_-iy-1;
			if(0>x || x>img.width_-1 || y<0 || y>img.height_-1) throw Exception(typeid(Image), "Out Of Bound Exception", "Specified coordinate is out of bound.");
			return y*img.lineValue_ + x*img.PixCompSize_[img.pixcomp_];
		}
		inline static unsigned char round8bit(double value) {
			double val = value*255.0+0.5, integer_part, frac;
			frac = modf(val, &integer_part);
			if(frac!=0.0) return (unsigned char)integer_part;
			if((int)(integer_part)%2==0) return (unsigned char)integer_part; else return (unsigned char)integer_part-1;
		}



		inline static void pix_gen_ub_1(unsigned char *p, const Color &col) { *(p) = round8bit(col.Red); }
		inline static void pix_gen_ub_3(unsigned char *p, const Color &col) { *(p) = round8bit(col.Red); *(++p) = round8bit(col.Green); *(++p) = round8bit(col.Blue); }
		inline static void pix_gen_ub_4(unsigned char *p, const Color &col) { *(p) = round8bit(col.Red); *(++p) = round8bit(col.Green); *(++p) = round8bit(col.Blue); *(++p) = round8bit(col.Alpha); }
		inline static void pix_gen_f_1(float *p, const Color &col) { *(p) = (float)(col.Red); }
		inline static void pix_gen_f_3(float *p, const Color &col) { *(p) = (float)(col.Red); *(++p) = (float)(col.Green); *(++p) = (float)(col.Blue); }
		inline static void pix_gen_f_4(float *p, const Color &col) { *(p) = (float)(col.Red); *(++p) = (float)(col.Green); *(++p) = (float)(col.Blue); *(++p) = (float)(col.Alpha); }

//	through

		static void (*(pix_direct_[2][3]))(const Image &, const int, const int, const Color&);// = { { &pix_d_ub_l_, &pix_d_ub_rgb_ , &pix_d_ub_rgba_ } , { &pix_d_f_l_, &pix_d_f_rgb_ , &pix_d_f_rgba_ }  };

		static void pix_d_ub_l_   (const Image &img, const int ix, const int iy, const Color &col) { pix_gen_ub_1( img.bitmapub_ + pix_offset(img, ix, iy), col ); }
		static void pix_d_f_l_    (const Image &img, const int ix, const int iy, const Color &col) { pix_gen_f_1 ( img.bitmapf_  + pix_offset(img, ix, iy), col ); }
		static void pix_d_ub_rgb_ (const Image &img, const int ix, const int iy, const Color &col) { pix_gen_ub_3( img.bitmapub_ + pix_offset(img, ix, iy), col ); }
		static void pix_d_f_rgb_  (const Image &img, const int ix, const int iy, const Color &col) { pix_gen_f_3 ( img.bitmapf_  + pix_offset(img, ix, iy), col ); }
		static void pix_d_ub_rgba_(const Image &img, const int ix, const int iy, const Color &col) { pix_gen_ub_4( img.bitmapub_ + pix_offset(img, ix, iy), col ); }
		static void pix_d_f_rgba_ (const Image &img, const int ix, const int iy, const Color &col) { pix_gen_f_4 ( img.bitmapf_  + pix_offset(img, ix, iy), col ); }


// ALPHA BLENDING INCLUDED

		static void (*(pix_[2][3]))(const Image &, const int, const int, const Color&);// = { { &pix_ub_l_, &pix_ub_rgb_ , &pix_ub_rgba_ } , { &pix_f_l_, &pix_f_rgb_ , &pix_f_rgba_ }  };

		static void pix_ub_l_(const Image &img, const int ix, const int iy, const Color &col) {
			pix_gen_ub_1(img.bitmapub_ + pix_offset(img, ix, iy), col);
		}
		static void pix_f_l_(const Image &img, const int ix, const int iy, const Color &col) {
			pix_gen_f_1(img.bitmapf_ + pix_offset(img, ix, iy), col);
		}
		static void pix_ub_rgb_(const Image &img, const int ix, const int iy, const Color &col) {
			unsigned char *p = img.bitmapub_ + pix_offset(img, ix, iy);
			if(Color::getCalibrationMode()==Color::SOFTWARE_GAMMA_VALUE || Color::getCalibrationMode()==Color::SOFTWARE_TABLE) {
				double red, green, blue, alpha;
				col.get(red, green, blue, alpha);
				Color xcol = getpix_ub_rgb_(img, ix, iy);
				double xred, xgreen, xblue, xalpha;
				xcol.get(xred, xgreen, xblue, xalpha);
				Color ycol(
					(1.0-alpha)*xred   + alpha*red,
					(1.0-alpha)*xgreen + alpha*green,
					(1.0-alpha)*xblue  + alpha*blue,
					(1.0-alpha)*xalpha + alpha*alpha
				);
				*(p)   = round8bit(ycol.Red);
				*(++p) = round8bit(ycol.Green);
				*(++p) = round8bit(ycol.Blue);
			} else {
				*(p) = round8bit(((1.0-col.Alpha)*(*p/255.0) + col.Alpha*col.Red)); p++;
				*(p) = round8bit(((1.0-col.Alpha)*(*p/255.0) + col.Alpha*col.Green)); p++;
				*(p) = round8bit(((1.0-col.Alpha)*(*p/255.0) + col.Alpha*col.Blue)); p++;
			}
		}
		static void pix_f_rgb_(const Image &img, const int ix, const int iy, const Color &col) {
			float *p = img.bitmapf_ + pix_offset(img, ix, iy);
			if(Color::getCalibrationMode()==Color::SOFTWARE_GAMMA_VALUE || Color::getCalibrationMode()==Color::SOFTWARE_TABLE) {
				double red, green, blue, alpha;
				col.get(red, green, blue, alpha);
				Color xcol = getpix_f_rgba_(img, ix, iy);
				double xred, xgreen, xblue, xalpha;
				xcol.get(xred, xgreen, xblue, xalpha);
				Color ycol(
					(1.0-alpha)*xred   + alpha*red,
					(1.0-alpha)*xgreen + alpha*green,
					(1.0-alpha)*xblue  + alpha*blue,
					(1.0-alpha)*xalpha + alpha*alpha
				);
				*(p)   = (float)(ycol.Red);
				*(++p) = (float)(ycol.Green);
				*(++p) = (float)(ycol.Blue);
			} else {
				*(p)   = (float)((1.0-col.Alpha)*(*p) + col.Alpha*col.Red);
				*(++p) = (float)((1.0-col.Alpha)*(*p) + col.Alpha*col.Green);
				*(++p) = (float)((1.0-col.Alpha)*(*p) + col.Alpha*col.Blue);
			}
		}
		static void pix_ub_rgba_(const Image &img, const int ix, const int iy, const Color &col) {
			unsigned char *p = img.bitmapub_ + pix_offset(img, ix, iy);
			if(Color::getCalibrationMode()==Color::SOFTWARE_GAMMA_VALUE || Color::getCalibrationMode()==Color::SOFTWARE_TABLE) {
				double red, green, blue, alpha;
				col.get(red, green, blue, alpha);
				Color xcol = getpix_ub_rgba_(img, ix, iy);
				double xred, xgreen, xblue, xalpha;
				xcol.get(xred, xgreen, xblue, xalpha);
				Color ycol(
					(1.0-alpha)*xred   + alpha*red,
					(1.0-alpha)*xgreen + alpha*green,
					(1.0-alpha)*xblue  + alpha*blue,
					(1.0-alpha)*xalpha + alpha*alpha
				);
				*(p)   = round8bit(ycol.Red);
				*(++p) = round8bit(ycol.Green);
				*(++p) = round8bit(ycol.Blue);
				*(++p) = round8bit(ycol.Alpha);
			} else {
				*(p) = round8bit(((1.0-col.Alpha)*(*p/255.0) + col.Alpha*col.Red)); p++;
				*(p) = round8bit(((1.0-col.Alpha)*(*p/255.0) + col.Alpha*col.Green)); p++;
				*(p) = round8bit(((1.0-col.Alpha)*(*p/255.0) + col.Alpha*col.Blue)); p++;
				*(p) = round8bit(((1.0-col.Alpha)*(*p/255.0) + col.Alpha*col.Alpha));
			}
		}
		static void pix_f_rgba_(const Image &img, const int ix, const int iy, const Color &col) {
			float *p = img.bitmapf_ + pix_offset(img, ix, iy);
			if(Color::getCalibrationMode()==Color::SOFTWARE_GAMMA_VALUE || Color::getCalibrationMode()==Color::SOFTWARE_TABLE) {
				double red, green, blue, alpha;
				col.get(red, green, blue, alpha);
				Color xcol = getpix_f_rgba_(img, ix, iy);
				double xred, xgreen, xblue, xalpha;
				xcol.get(xred, xgreen, xblue, xalpha);
				Color ycol(
					(1.0-alpha)*xred   + alpha*red,
					(1.0-alpha)*xgreen + alpha*green,
					(1.0-alpha)*xblue  + alpha*blue,
					(1.0-alpha)*xalpha + alpha*alpha
				);
				*(p)   = (float)(ycol.Red);
				*(++p) = (float)(ycol.Green);
				*(++p) = (float)(ycol.Blue);
				*(++p) = (float)(ycol.Alpha);
			} else {
				*(p)   = (float)((1.0-col.Alpha)*(*p) + col.Alpha*col.Red);
				*(++p) = (float)((1.0-col.Alpha)*(*p) + col.Alpha*col.Green);
				*(++p) = (float)((1.0-col.Alpha)*(*p) + col.Alpha*col.Blue);
				*(++p) = (float)((1.0-col.Alpha)*(*p) + col.Alpha*col.Alpha);
			}
		}


		static void (*(pix_alpha_[2][3]))(const Image &, const int, const int, const double);//    = { { &alpha_null_, &alpha_null_ , &alpha_ub_rgba_ } , { &alpha_null_, &alpha_null_ , &alphs_f_rgba_ }  };

		static void alpha_null_(const Image &img, const int ix, const int iy, const double alpha) {
			int x = ix, y = img.height_-iy-1;
			if(0>x || x>img.width_-1 || y<0 || y>img.height_-1) throw Exception(typeid(Image), "Out Of Bound Exception", "Specified coordinate is out of bound.");
		}
		static void alpha_ub_rgba_(const Image &img, const int ix, const int iy, const double alpha) {
			int x = ix, y = img.height_-iy-1;
			if(0>x || x>img.width_-1 || y<0 || y>img.height_-1) throw Exception(typeid(Image), "Out Of Bound Exception", "Specified coordinate is out of bound.");
			int offset = y*img.lineValue_ + x*img.PixCompSize_[img.pixcomp_];
			unsigned char *p = img.bitmapub_ + offset;
			*(p+3) = round8bit(alpha);
		}
		static void alpha_f_rgba_(const Image &img, const int ix, const int iy, const double alpha) {
			int x = ix, y = img.height_-iy-1;
			if(0>x || x>img.width_-1 || y<0 || y>img.height_-1) throw Exception(typeid(Image), "Out Of Bound Exception", "Specified coordinate is out of bound.");
			int offset = y*img.lineValue_ + x*img.PixCompSize_[img.pixcomp_];
			float *p = img.bitmapf_ + offset;
			*(p+3) = (float)alpha;
		}



		static Color (*(getpix_[2][3]))(const Image &, int, int);// = { { &getpix_ub_l_, &getpix_ub_rgb_ , &getpix_ub_rgba_ } , { &getpix_f_l_, &getpix_f_rgb_ , &getpix_f_rgba_ }  };

		static Color getpix_ub_l_(const Image &img, int ix, int iy) {
			int x = ix, y = img.height_-iy-1;
			if(0>x || x>img.width_-1 || y<0 || y>img.height_-1) throw Exception(typeid(Image), "Out Of Bound Exception", "Specified coordinate is out of bound.");
			int offset = y*img.lineValue_ + x*img.PixCompSize_[img.pixcomp_];
			return Color(*(img.bitmapub_+offset)/255.0, *(img.bitmapub_+offset)/255.0, *(img.bitmapub_+offset)/255.0, 1.0, false);
		}
		static Color getpix_f_l_(const Image &img, int ix, int iy) {
			int x = ix, y = img.height_-iy-1;
			if(0>x || x>img.width_-1 || y<0 || y>img.height_-1) throw Exception(typeid(Image), "Out Of Bound Exception", "Specified coordinate is out of bound.");
			int offset = y*img.lineValue_ + x*img.PixCompSize_[img.pixcomp_];
			return Color(*(img.bitmapf_+offset), *(img.bitmapf_+offset), *(img.bitmapf_+offset), 1.0, false);
		}
		static Color getpix_ub_rgb_(const Image &img, int ix, int iy) {
			int x = ix, y = img.height_-iy-1;
			if(0>x || x>img.width_-1 || y<0 || y>img.height_-1) throw Exception(typeid(Image), "Out Of Bound Exception", "Specified coordinate is out of bound.");
			int offset = y*img.lineValue_ + x*img.PixCompSize_[img.pixcomp_];
			unsigned char *bitmapub_ = img.bitmapub_ + offset;
			return Color(*(bitmapub_)/255.0, *(bitmapub_+1)/255.0, *(bitmapub_+2)/255.0, 1.0, false);
		}
		static Color getpix_f_rgb_(const Image &img, int ix, int iy) {
			int x = ix, y = img.height_-iy-1;
			if(0>x || x>img.width_-1 || y<0 || y>img.height_-1) throw Exception(typeid(Image), "Out Of Bound Exception", "Specified coordinate is out of bound.");
			int offset = y*img.lineValue_ + x*img.PixCompSize_[img.pixcomp_];
			float *bitmapf_ = img.bitmapf_ + offset;
			return Color(*(bitmapf_), *(bitmapf_+1), *(bitmapf_+2), 1.0, false);
		}
		static Color getpix_ub_rgba_(const Image &img, int ix, int iy) {
			int x = ix, y = img.height_-iy-1;
			if(0>x || x>img.width_-1 || y<0 || y>img.height_-1) throw Exception(typeid(Image), "Out Of Bound Exception", "Specified coordinate is out of bound.");
			int offset = y*img.lineValue_ + x*img.PixCompSize_[img.pixcomp_];
			unsigned char *bitmapub_ = img.bitmapub_ + offset;
			return Color(*(bitmapub_)/255.0, *(bitmapub_+1)/255.0, *(bitmapub_+2)/255.0, *(bitmapub_+3)/255.0 , false);
		}
		static Color getpix_f_rgba_(const Image &img, int ix, int iy) {
			int x = ix, y = img.height_-iy-1;
			if(0>x || x>img.width_-1 || y<0 || y>img.height_-1) throw Exception(typeid(Image), "Out Of Bound Exception", "Specified coordinate is out of bound.");
			int offset = y*img.lineValue_ + x*img.PixCompSize_[img.pixcomp_];
			float *bitmapf_ = img.bitmapf_ + offset;
			return Color(*(bitmapf_), *(bitmapf_+1), *(bitmapf_+2), *(bitmapf_+3), false);
		}
	};
	void (*(ImageManipulator::pix_[2][3]))(const Image &, const int, const int, const Color&)    = { { &pix_ub_l_, &pix_ub_rgb_ , &pix_ub_rgba_ } , { &pix_f_l_, &pix_f_rgb_ , &pix_f_rgba_ }  };
	void (*(ImageManipulator::pix_direct_[2][3]))(const Image &, const int, const int, const Color&)    = { { &pix_d_ub_l_, &pix_d_ub_rgb_ , &pix_d_ub_rgba_ } , { &pix_d_f_l_, &pix_d_f_rgb_ , &pix_d_f_rgba_ }  };
	void (*(ImageManipulator::pix_alpha_[2][3]))(const Image &, const int, const int, const double)    = { { &alpha_null_, &alpha_null_ , &alpha_ub_rgba_ } , { &alpha_null_, &alpha_null_ , &alpha_f_rgba_ }  };
	Color (*(ImageManipulator::getpix_[2][3]))(const Image &, int, int) = { { &getpix_ub_l_, &getpix_ub_rgb_ , &getpix_ub_rgba_ } , { &getpix_f_l_, &getpix_f_rgb_ , &getpix_f_rgba_ }  };

/*
	Image& Image::pix(const int ix, const int iy, const Color &col) {
		pix_(*this, ix, iy, col);
		return *this;
	}

	Color Image::getPix(int ix, int iy) const {
		return getpix_(*this, ix, iy);
	}
*/

	void Image::line_direct_copy_(const Image &src, Image &tgt, const int iy, const int jy) {
		int sy = src.height_-iy-1, ty = jy<0 ? tgt.height_-iy-1 : tgt.height_-jy-1;
		if(sy<0 || sy>src.height_-1) throw Exception(typeid(Image), "Out Of Bound Exception", "Specified coordinate is out of bound.");
		size_t bytes = ( src.lineBytes_>tgt.lineBytes_ ? tgt.lineBytes_ : src.lineBytes_ );
		switch(src.pixprec_) {
			case Image::BYTE:
				memcpy((void *)(tgt.bitmapub_+ty*tgt.lineValue_), (void *)(src.bitmapub_+sy*src.lineValue_), bytes);
				break;
			case Image::FLOAT:
				memcpy((void *)(tgt.bitmapf_+ty*tgt.lineValue_), (void *)(src.bitmapf_+sy*src.lineValue_), bytes);
				break;
			default:
				throw Exception(typeid(Image), "Format Error", "Class Image recieved unknown format for pixels.");
				break;
		}
	}


	void Image::set(long ix, long iy, PixelComponentsCnt pixcomp, PixelComponentsPrecision pixprec) {
		if(have_instance_) throw Exception(typeid(*this), "Memory Error", "Image object already has its bitmap.");

		//	init basic properties
		if(ix<=0) throw Exception(typeid(*this), "FORMAT ERROR", "The specified width is illegal");
			else width_ = ix;
		if(iy<=0) throw Exception(typeid(*this), "FORMAT ERROR", "The specified height is illegal");
			else height_ = iy;
		if(pixcomp<0) throw Exception(typeid(*this), "FORMAT ERROR", "The specified pixel component is illegal"); else
		if(pixcomp>=3) throw Exception(typeid(*this), "FORMAT ERROR", "The specified pixel component is illegal"); else
			pixcomp_ = pixcomp;
		if(pixprec<0) throw Exception(typeid(*this), "FORMAT ERROR", "The specified precision is illegal"); else
		if(pixprec>=2) throw Exception(typeid(*this), "FORMAT ERROR", "The specified precision is illegal"); else
			pixprec_ = pixprec;

		//	init calculable parameters
		targetarea_ = localarea_ = area_ = Rectangle(width_, height_);
		pixBytes_    = PixCompSize_[pixcomp_] * PixPrecSize_[pixprec_];
		lineBytes_   = pixBytes_ * width_;
		lineBytes_   = ( (lineBytes_%lineBytesAlingnment_==0 ? 0 : 1) + ((int)lineBytes_ / (int)lineBytesAlingnment_) ) * lineBytesAlingnment_;
		bitmapBytes_ = lineBytes_ * height_;
		lineValue_   = lineBytes_ / PixPrecSize_[pixprec_];
		bitmapValue_ = lineValue_ * height_;

		switch(pixprec_) {
			case BYTE:
				if(NULL==(bitmapub_ = new GLubyte[bitmapValue_])) throw Exception(typeid(*this), "Memory Error", "Image failed to allocate memory.");
				bitmapf_ = NULL;
				break;
			case FLOAT:
				if(NULL==(bitmapf_ = new GLfloat[bitmapValue_])) throw Exception(typeid(*this), "Memory Error", "Image failed to allocate memory.");
				bitmapub_ = NULL;
				break;
			default:
				throw Exception(typeid(*this), "Format Error", "Class Image recieved unknown format for pixels.");
				break;
		}
		api_ = new APIImageProperties(this);
		pix_ = ImageManipulator::pix_[pixprec_][pixcomp_];
		pix_direct_ = ImageManipulator::pix_direct_[pixprec_][pixcomp_];
		pix_alpha_ = ImageManipulator::pix_alpha_[pixprec_][pixcomp_];
		getpix_ = ImageManipulator::getpix_[pixprec_][pixcomp_];
		have_instance_ = true;
		clear(Color::black);
	}

	void Image::from(const Image &readimage) {
		release();
		*this = readimage;
		api_ = new APIImageProperties(this);
		caches.clear();
		void *target_bitmap, *source_bitmap;
		if(have_instance_) {
			switch(pixprec_) {
				case BYTE:
					if(NULL==(bitmapub_ = new GLubyte[bitmapValue_])) throw Exception(typeid(*this), "Memory Error", "Image failed to allocate memory.");
					target_bitmap = (void *)(bitmapub_);
					source_bitmap = (void *)(readimage.bitmapub_);
					break;
				case FLOAT:
					if(NULL==(bitmapf_ = new GLfloat[bitmapValue_])) throw Exception(typeid(*this), "Memory Error", "Image failed to allocate memory.");
					target_bitmap = (void *)(bitmapf_);
					source_bitmap = (void *)(readimage.bitmapf_);
					break;
				default:
					throw Exception(typeid(*this), "Format Error", "Class Image recieved unknown format for pixels.");
					break;
			}
			memcpy(target_bitmap, source_bitmap, bitmapBytes_);
			have_instance_ = true;
		}
	}



}	/*	<- namespace Psycholops 	*/

