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


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

#include <OpenGL/gl.h>
#include <OpenGL/glu.h>


#include "../../core/ApplicationInterfaces/psychlops_app_info.h"
#include "../../core/devices/psychlops_io_file.h"
#include "../../core/math/psychlops_math.h"
#include "../../core/graphic/psychlops_g_fundamental.h"
#include "../../core/graphic/psychlops_g_color.h"
#include "../../core/graphic/psychlops_g_module.h"
#include "../../core/graphic/psychlops_g_shape.h"
#include "../../core/graphic/psychlops_g_canvas.h"
#include "../../core/graphic/psychlops_g_image.h"

#include "psychlops_g_API_OSX.h"


namespace Psychlops {



	void Canvas::cacheImageBody(Image &img) {
		APIImageCache *api_ = 0;
		int tex_width = 0, tex_height = 0;

		if(img.caches.count(this)==0) {
			int i;
			i=0;
			while(pow(2.0,++i)<img.width_) {}
			tex_width = pow(2.0,i);
			i=0;
			while(pow(2.0,++i)<img.height_) {}
			tex_height = pow(2.0,i);
		} else {
			api_ = img.caches[this].id;
			tex_width = api_->tex_width;
			tex_height = api_->tex_height;
		}
		Image *tmp = new Image(tex_width, tex_height, img.pixcomp_, img.pixprec_);
		for(int y=0; y<img.height_; y++) Image::line_direct_copy_(img, *tmp, y);

		if(img.caches.count(this)!=0) {
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, api_->VRAMoffset);
			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, api_->tex_width, api_->tex_height, APIImageProperties::PixCompGL_[img.pixcomp_], APIImageProperties::PixPrecGL_[img.pixprec_], tmp->getBitmapPtr());
			glDisable(GL_TEXTURE_2D);
		} else {
			api_ = new APIImageCache;
			img.caches.insert(std::pair<DrawableWithCache*, ImageCache_>(this, ImageCache_(api_, false)));
			api_->tex_width = tex_width;
			api_->tex_height = tex_height;
			glEnable(GL_TEXTURE_2D);
			glGenTextures(1, &api_->VRAMoffset);
			glBindTexture(GL_TEXTURE_2D, api_->VRAMoffset);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
//				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
//				glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
			glTexImage2D(GL_TEXTURE_2D, 0, Image::PixCompSize_[img.pixcomp_], api_->tex_width, api_->tex_height, 0, APIImageProperties::PixCompGL_[img.pixcomp_], APIImageProperties::PixPrecGL_[img.pixprec_], tmp->getBitmapPtr());
			glDisable(GL_TEXTURE_2D);

//				APIImageProperties::regist((int)Display::getWidth(), (int)Display::getHeight(), width_, height_, VRAMleft_, VRAMtop_);
//				glDisable(GL_BLEND);
//				glDrawBuffer(GL_AUX1);
//				for(int y=0; y<height_; y++) {
//					for(int x=0; x<width_; x++) {
//						Display::pix(x+VRAMleft_, y+VRAMtop_, getPix(x, y));
//					}
//				}
//				glDrawBuffer(GL_BACK);
//				glEnable(GL_BLEND);

		}
		glDrawBuffer(GL_AUX1);
		glColor4f(1.0,1.0,1.0,1.0);
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, api_->getTexIndex());
		glBegin(GL_QUADS);
			glTexCoord2d(0 , img.height_);
				glVertex2f(img.targetarea_.getLeft() , img.targetarea_.getTop());
			glTexCoord2d(img.width_ , img.height_);
				glVertex2f(img.targetarea_.getRight()+1, img.targetarea_.getTop());
			glTexCoord2d(img.width_ , 0);
				glVertex2f(img.targetarea_.getRight()+1, img.targetarea_.getBottom()+1);
			glTexCoord2d(0, 0);
				glVertex2f(img.targetarea_.getLeft() , img.targetarea_.getBottom()+1);
		glEnd();
		glDisable(GL_TEXTURE_2D);
		glDrawBuffer(GL_BACK);
		delete tmp;
	}

	void Canvas::uncacheImageBody(Image &img) {
		APIImageCache *api_ = 0;
		if(img.caches.count(this)!=0) {
			api_ = img.caches[this].id;
			if(api_->VRAMoffset!=0) glDeleteTextures(1, &(api_->VRAMoffset));
			api_->VRAMoffset = 0;
			img.caches.erase(this);
		}
	}


	void Canvas::drawImage(const Image &img, const double left, const double top) {
		//int left = Math::round(left__);
		//int top = Math::round(top__);

		if(img.caches.count(this)!=0) {
			APIImageCache *api_ = img.caches[this].id;
			double tex_top = 1.0;
			double tex_bottom = ((double)api_->tex_height-img.height_)/api_->tex_height;

			glColor4f(1.0,1.0,1.0,1.0);
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, api_->getTexIndex());
			glBegin(GL_QUADS);
/*				glTexCoord2d(0 , img.height_/img.api->tex_height);
					glVertex2f(img.targetarea_.getLeft() , img.targetarea_.getTop());
				glTexCoord2d(img.width_/img.api->tex_width , img.height_/img.api->tex_height);
					glVertex2f(img.targetarea_.getRight()+1 , img.targetarea_.getTop());
				glTexCoord2d(img.width_/img.api->tex_width , 0);
					glVertex2f(img.targetarea_.getRight()+1, img.targetarea_.getBottom()+1);
				glTexCoord2d(0, 0);
					glVertex2f(img.targetarea_.getLeft() , img.targetarea_.getBottom()+1);
*/
				glTexCoord2d(0, tex_top);
					glVertex2f(left, top);
				glTexCoord2d((double)img.width_/api_->tex_width , tex_top);
					glVertex2f(left+img.targetarea_.getWidth(), top);
				glTexCoord2d((double)img.width_/api_->tex_width , tex_bottom);
					glVertex2f(left+img.targetarea_.getWidth(), top+img.targetarea_.getHeight());
				glTexCoord2d(0 , tex_bottom);
					glVertex2f(left, top+img.targetarea_.getHeight());

			glEnd();
			glDisable(GL_TEXTURE_2D);
		} else {
			glRasterPos2d(left,img.height_+top);
			if( img.pixcomp_==Image::RGBA ) {
				glPushAttrib(GL_ALL_ATTRIB_BITS);
				glEnable(GL_BLEND);
				glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
				glDrawPixels((GLsizei)img.getWidth(), (GLsizei)img.getHeight(), APIImageProperties::PixCompGL_[img.pixcomp_], APIImageProperties::PixPrecGL_[img.pixprec_], img.getBitmapPtr());
				glDisable(GL_BLEND);
				glPopAttrib();
			} else {
				glDrawPixels((GLsizei)img.getWidth(), (GLsizei)img.getHeight(), APIImageProperties::PixCompGL_[img.pixcomp_], APIImageProperties::PixPrecGL_[img.pixprec_], img.getBitmapPtr());
			}
		}
	}

	void Canvas::drawImage(Image &img, const Rectangle &target_area)
	{
		if(img.caches.count(this)==0) {
			img.cache(*this);
		}
		APIImageCache *api_ = img.caches[this].id;
		double tex_top = 1.0;
		double tex_bottom = ((double)api_->tex_height-img.height_)/api_->tex_height;
		
		glColor4f(1.0,1.0,1.0,1.0);
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, api_->getTexIndex());
		glBegin(GL_QUADS);
		glTexCoord2d(0, tex_top);
		glVertex2f(target_area.left, target_area.top);
		glTexCoord2d((double)img.width_/api_->tex_width , tex_top);
		glVertex2f(target_area.right, target_area.top);
		glTexCoord2d((double)img.width_/api_->tex_width , tex_bottom);
		glVertex2f(target_area.right, target_area.bottom);
		glTexCoord2d(0 , tex_bottom);
		glVertex2f(target_area.left, target_area.bottom);
		
		glEnd();
		glDisable(GL_TEXTURE_2D);
	}
	
	Canvas& Canvas::image(Image &img, const Rectangle &target_area, const Rectangle &source_rect)
	{
		if(img.caches.count(this)==0) {
			img.cache(*this);
		}
		APIImageCache *api_ = img.caches[this].id;
//		double right = left + source_rect.getWidth();
//		double bottom = top + source_rect.getHeight();
		double tex_top = 1.0;
		double tex_bottom = ((double)api_->tex_height-img.height_)/api_->tex_height;
		double tex_left = 0.0;
		double tex_right = (double)img.width_/api_->tex_width;
		double tex_height = tex_top - tex_bottom;
		double tex_width  = tex_right - tex_left;
		tex_left = tex_width * source_rect.getLeft() / (img.getWidth()-1.0);
		tex_top = 1.0 - tex_height * source_rect.getTop() / (img.getHeight()-1.0);
		tex_right = tex_width * source_rect.getRight() / (img.getWidth()-1.0);
		tex_bottom = 1.0 - tex_height * source_rect.getBottom() / (img.getHeight()-1.0);
//std::cout << "D   " << tex_left << " " << tex_top << " " << tex_right << " " << tex_bottom << std::endl;
		
		glColor4f(1.0,1.0,1.0,1.0);
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, api_->getTexIndex());
		glBegin(GL_QUADS);

		glTexCoord2d(tex_left, tex_top);
//		glVertex2f(left, top);
		glVertex2f(target_area.left, target_area.top);

		glTexCoord2d(tex_right, tex_top);
//		glVertex2f(right, top);
		glVertex2f(target_area.right+1, target_area.top);

		glTexCoord2d(tex_right , tex_bottom);
//		glVertex2f(right, bottom);
		glVertex2f(target_area.right+1, target_area.bottom+1);

		glTexCoord2d(tex_left , tex_bottom);
//		glVertex2f(left, bottom);
		glVertex2f(target_area.left, target_area.bottom+1);


		glEnd();
		glDisable(GL_TEXTURE_2D);
		return *this;
	}

	
/*
	void Canvas::cacheImageBody(Image &img) {
		APIImageCache *api_ = 0;
		if(img.caches.count(this)!=0) {
			api_ = img.caches[this].id;
			glEnable(GL_TEXTURE_RECTANGLE_EXT);
			glBindTexture(GL_TEXTURE_RECTANGLE_EXT, api_->VRAMoffset);
			glTexSubImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, img.width_, img.height_, APIImageProperties::PixCompGL_[img.pixcomp_], APIImageProperties::PixPrecGL_[img.pixprec_], img.getBitmapPtr());
			glDisable(GL_TEXTURE_RECTANGLE_EXT);
		} else {
			api_ = new APIImageCache;
			img.caches.insert(std::pair<DrawableWithCache*, ImageCache_>(this, ImageCache_(api_, false)));
			glEnable(GL_TEXTURE_RECTANGLE_EXT);
			glGenTextures(1, &api_->VRAMoffset);
			glBindTexture(GL_TEXTURE_RECTANGLE_EXT, api_->VRAMoffset);
			glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
			glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, 0, Image::PixCompSize_[img.pixcomp_], img.width_, img.height_, 0, APIImageProperties::PixCompGL_[img.pixcomp_], APIImageProperties::PixPrecGL_[img.pixprec_], img.getBitmapPtr());
			glDisable(GL_TEXTURE_RECTANGLE_EXT);
			//quickened_ = true;
			/*
			APIImageProperties::regist((int)Display::getWidth(), (int)Display::getHeight(), width_, height_, VRAMleft_, VRAMtop_);
			glDisable(GL_BLEND);
			glDrawBuffer(GL_AUX1);
			for(int y=0; y<height_; y++) {
				for(int x=0; x<width_; x++) {
					Display::pix(x+VRAMleft_, y+VRAMtop_, getPix(x, y));
				}
			}
			glDrawBuffer(GL_BACK);
			glEnable(GL_BLEND);
			* /
		}
		glDrawBuffer(GL_AUX1);
		glColor4f(1.0,1.0,1.0,1.0);
		glEnable(GL_TEXTURE_RECTANGLE_EXT);
		glBindTexture(GL_TEXTURE_RECTANGLE_EXT, api_->getTexIndex());
		glBegin(GL_QUADS);
			glTexCoord2d(0 , img.height_);
				glVertex2f(img.targetarea_.getLeft() , img.targetarea_.getTop());
			glTexCoord2d(img.width_ , img.height_);
				glVertex2f(img.targetarea_.getRight()+1 , img.targetarea_.getTop());
			glTexCoord2d(img.width_ , 0);
				glVertex2f(img.targetarea_.getRight()+1, img.targetarea_.getBottom()+1);
			glTexCoord2d(0, 0);
				glVertex2f(img.targetarea_.getLeft() , img.targetarea_.getBottom()+1);
		glEnd();
		glDisable(GL_TEXTURE_RECTANGLE_EXT);
		glDrawBuffer(GL_BACK);
	}

	void Canvas::uncacheImageBody(Image &img) {
		APIImageCache *api_ = 0;
		if(img.caches.count(this)!=0) {
			api_ = img.caches[this].id;			
			if(api_->VRAMoffset!=0) glDeleteTextures(1, &(api_->VRAMoffset));
			api_->VRAMoffset = 0;
			img.caches.erase(this);
		}
	}

	void Canvas::drawImage(const Image &img, Point lt, Point rt, Point rb, Point lb) {
		if(img.caches.count(this)!=0) {
			glColor4f(1.0,1.0,1.0,1.0);
			glEnable(GL_TEXTURE_RECTANGLE_EXT);
			glBindTexture(GL_TEXTURE_RECTANGLE_EXT, img.caches[this].id->getTexIndex());
			glBegin(GL_QUADS);
			glTexCoord2d(0 , img.height_);
			glVertex2f(lt.x, lt.y);
			glTexCoord2d(img.width_ , img.height_);
			glVertex2f(rt.x , rt.y);
			glTexCoord2d(img.width_ , 0);
			glVertex2f(rb.x, rb.y);
			glTexCoord2d(0, 0);
			glVertex2f(lb.x, lb.y);
			glEnd();
			glDisable(GL_TEXTURE_RECTANGLE_EXT);
		}
	}
	void Canvas::drawImage(const Image &img, const double left, const double top) {
		if(img.caches.count(this)!=0) {
			//			glReadBuffer(GL_AUX1);
//			glDrawBuffer(GL_BACK);
//			glCopyPixels(img.VRAMleft_, (int)getHeight()-img.VRAMtop_-img.height_, img.width_, img.height_, GL_COLOR);
			glColor4f(1.0,1.0,1.0,1.0);
//			glEnable(GL_BLEND);
			glEnable(GL_TEXTURE_RECTANGLE_EXT);
			glBindTexture(GL_TEXTURE_RECTANGLE_EXT, img.caches[this].id->getTexIndex());
			glBegin(GL_QUADS);
				glTexCoord2d(0 , img.height_);
					glVertex2f(left, top);
				glTexCoord2d(img.width_ , img.height_);
					glVertex2f(left+img.targetarea_.getWidth() , top);
				glTexCoord2d(img.width_ , 0);
					glVertex2f(left+img.targetarea_.getWidth(), top+img.targetarea_.getHeight());
				glTexCoord2d(0, 0);
					glVertex2f(left , top+img.targetarea_.getHeight());
			glEnd();
			glDisable(GL_TEXTURE_RECTANGLE_EXT);
		} else {
			glRasterPos2d(left,img.height_+top);
			if( img.pixcomp_==Image::RGBA ) {
				glPushAttrib(GL_ALL_ATTRIB_BITS);
				glEnable(GL_BLEND);
				glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
				glDrawPixels((GLsizei)img.getWidth(), (GLsizei)img.getHeight(), APIImageProperties::PixCompGL_[img.pixcomp_], APIImageProperties::PixPrecGL_[img.pixprec_], img.getBitmapPtr());
				glDisable(GL_BLEND);
				glPopAttrib();
			} else {
				glDrawPixels((GLsizei)img.getWidth(), (GLsizei)img.getHeight(), APIImageProperties::PixCompGL_[img.pixcomp_], APIImageProperties::PixPrecGL_[img.pixprec_], img.getBitmapPtr());
			}
		}
	}
*/


	int Canvas::msg(Letters &letters, const double x, const double y, const Color &col, const int horiz_align, const int vertical_align, const double max_width) {
		double x_begin_shift;
		switch(horiz_align) {
		case Letters::TEXT_ALIGN_RIGHT:
			x_begin_shift = -letters.width_;
			break;
		case Letters::TEXT_ALIGN_CENTER:
			x_begin_shift = -letters.width_/2;
			break;
		case Letters::TEXT_ALIGN_LEFT:
		default:
			x_begin_shift = 0.0;
			break;
		}
		api->drawLetters(letters, x+x_begin_shift, y, col, horiz_align, vertical_align, max_width);
		return 1;
	}


}	/*	<- namespace Psycholops 	*/

