/*
 *  psychlops_g_canvas_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>
#include <string>


#include "psychlops_g_GL_h.h"

#include "../../core/ApplicationInterfaces/psychlops_app_info.h"
#include "../../core/ApplicationInterfaces/psychlops_app_thread.h"
#include "../../core/devices/psychlops_io_hid.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"

#define PSYCHLOPS_WINDOW_API_PLATFORM
#include "../../platform/psychlops_platform_selector.h"


namespace Psychlops {



	GLuint font_LCD[128];
	GLuint fillOvalPrimitives[4], drawOvalPrimitives[4];
	int Canvas::font_LCD_height;


	////////	Construct and Destruct  ////////
	class DirectGetColor {
		public:
		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;
		}
		static void setColor4ub(const Color &color) {
			glColor4ub(round8bit(color.Red), round8bit(color.Green), round8bit(color.Blue), round8bit(color.Alpha));
		}
		static void setColorClear4ub(const Color &color) {
			glClearColor((GLclampf)round8bit(color.Red)/255.0, (GLclampf)round8bit(color.Green)/255.0, (GLclampf)round8bit(color.Blue)/255.0, (GLclampf)round8bit(color.Alpha)/255.0);
		}
		static void setColor4Through(const Color &color) {
			glColor4d(color.Red, color.Green, color.Blue, color.Alpha);
		}
		static void setColorClaer4Through(const Color &color) {
			glClearColor(color.Red, color.Green, color.Blue, color.Alpha);
		}
	};

	void Canvas::popMatrices() {
		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
	}
	void Canvas::pushPixToPixProjection() {
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		glOrtho(-0.375, Width-0.375, Height-0.375, -0.375, -1000, 1000);
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadIdentity();
		glDisable(GL_LIGHTING);
		glDisable(GL_DEPTH_TEST);
	}
	void Canvas::RenderModeSmooth2D() {
		glShadeModel(GL_SMOOTH);
		glEnable(GL_POINT_SMOOTH);
		glEnable(GL_LINE_SMOOTH);
		//        glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
		//        glEnable(GL_POLYGON_SMOOTH_HINT);
		//        glEnable(GL_POLYGON_SMOOTH);
	}
	void Canvas::RenderModeSmooth3D(double left, double right, double top, double bottom, double near, double far) {
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		//gluPerspective(left, top, near, far);
		//glFrustum(left, right, top, bottom, near, far);
		glRotated(180, 0, 1, 0);

		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadIdentity();

		//glShadeModel(GL_SMOOTH);
		//glEnable(GL_POINT_SMOOTH);
		//glEnable(GL_LINE_SMOOTH);
		//        glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
		//        glEnable(GL_POLYGON_SMOOTH_HINT);
		//        glEnable(GL_POLYGON_SMOOTH);
		glEnable(GL_DEPTH_TEST);
		//        glEnable(GL_FOG);
		//        glEnable(GL_LIGHTING);
		glDisable(GL_LIGHTING);
	}

	void Canvas::initAPIprop() {
		switch(colordepth) {
		case 24:
		case 32:
			APIsetColor = &(DirectGetColor::setColor4ub);
			APIsetColorClear = &(DirectGetColor::setColorClear4ub);
			break;
		default:
			APIsetColor = &(DirectGetColor::setColor4Through);
			APIsetColorClear = &(DirectGetColor::setColorClaer4Through);
			break;
		}

		glDrawBuffer(GL_BACK);
		glReadBuffer(GL_BACK);
		//glMatrixMode(GL_PROJECTION);
		//glLoadIdentity();
//		glOrtho(0, Width, Height, 0, -1, 1);
		//glOrtho(-0.375, Width-0.375, Height-0.375, 0-0.375, -1, 1);
		//glMatrixMode(GL_MODELVIEW);
		//glLoadIdentity();
//		glTranslated(0.375, 0.375, 0.0);
//		glPushMatrix();

        pushPixToPixProjection();
		initAPIattributes();

		GLclampf red=0.0, green=0.0, blue=0.0, alpha=1.0;
//		default_drawing_mode_ = GL_POINTS;
		glClearColor(red,green,blue,alpha);
		clear();
		flip(2);
		clear();
		flip(2);
	}

	void Canvas::initAPIattributes() {
		glPushAttrib(GL_ALL_ATTRIB_BITS);

		////	default settings	////
		glShadeModel(GL_FLAT);
		glEnable(GL_ALPHA_TEST);
		glEnable(GL_BLEND);
		glEnable(GL_LINE_STIPPLE);
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
		glDisable(GL_POINT_SMOOTH);
		glDisable(GL_LINE_SMOOTH);
		glDisable(GL_POLYGON_SMOOTH);
		glDisable(GL_DEPTH_TEST);
		glDisable(GL_DITHER);
		glDisable(GL_FOG);
		glDisable(GL_LIGHTING);
		glDisable(GL_LOGIC_OP);
		glDisable(GL_STENCIL_TEST);
		glDisable(GL_TEXTURE_1D);
		glDisable(GL_TEXTURE_2D);
		glPixelTransferi(GL_MAP_COLOR, GL_FALSE);
		glPixelTransferi(GL_MAP_STENCIL, GL_FALSE);
		glPixelTransferi(GL_RED_SCALE, 1);
		glPixelTransferi(GL_RED_BIAS, 0);
		glPixelTransferi(GL_GREEN_SCALE, 1);
		glPixelTransferi(GL_GREEN_BIAS, 0);
		glPixelTransferi(GL_BLUE_SCALE, 1);
		glPixelTransferi(GL_BLUE_BIAS, 0);
		glPixelTransferi(GL_ALPHA_SCALE, 1);
		glPixelTransferi(GL_ALPHA_BIAS, 0);
		glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
		glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
		glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
		glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
		glPixelStoref(GL_UNPACK_ALIGNMENT, 4);
		glPixelStorei(GL_PACK_ALIGNMENT, 4);
		glPixelStoref(GL_PACK_ALIGNMENT, 4);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}
	void Canvas::popAPIattributes() {
		glPopAttrib();
	}


	////	release

	////	Prepare Primitives





	////////	Drawing Graphical Elements   ////////
	inline void Canvas::setStrokeState(const Stroke& strk) {
		APIsetColor(strk.color);
		glLineWidth(strk.width);
		glLineStipple((int)Math::round(strk.width), strk.pattern);
	}


	Drawable& Canvas::pix(double x, double y, const Color &col) {
		APIsetColor(col);
		glBegin(GL_POINTS);
			glVertex2d(x,y);
		glEnd();
		return *this;
	}
	Canvas& Canvas::pix(const Point &po, const Color &col) {
		pix(po.x, po.y, col);
		return *this;
	}
	void Canvas::pix(int dotsCnt, double x[], double y[], const Color col[]) {
		glBegin(GL_POINTS);
		for(int i=0; i<dotsCnt; i++) {
			APIsetColor(col[i]);
			glVertex2d(x[i], y[i]);
		}
		glEnd();
	}
	void Canvas::pix(int dotsCnt, double x[], double y[], const Color &col=Color::white) {
		APIsetColor(col);
		glBegin(GL_POINTS);
		for(int i=0; i<dotsCnt; i++) {
			glVertex2d(x[i], y[i]);
		}
		glEnd();
	}
	void Canvas::pix(int dotsCnt, const Point po[], const Color col[]) {
		glBegin(GL_POINTS);
		for(int i=0; i<dotsCnt; i++) {
			APIsetColor(col[i]);
			glVertex2d(po[i].x, po[i].y);
		}
		glEnd();
	}
	void Canvas::line(double x1, double y1, double x2, double y2, const Color &col) {
		APIsetColor(col);
		glBegin(GL_LINES);
			glVertex2d(x1, y1);
			glVertex2d(x2, y2);
		glEnd();
	}
	void Canvas::line(const Point &po1, const Point &po2, const Color &col) {
		line(po1.x, po1.y, po2.x, po2.y, col);
	}
	void Canvas::line(double x1, double y1, double x2, double y2, const Stroke &strk) {
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		setStrokeState(strk);
		glBegin(GL_LINES);
			glVertex2d(x1, y1);
			glVertex2d(x2, y2);
		glEnd();
		glPopAttrib();
	}
	void Canvas::line(const Point &po1, const Point &po2, const Stroke &strk) {
		line(po1.x, po1.y, po2.x, po2.y, strk);
	}
	Canvas& Canvas::line(const Line &drawee, const Color &col) {
		line(drawee.datum.x, drawee.datum.y, drawee.end.x, drawee.end.y, col);
		return *this;
	}
	Canvas& Canvas::line(const Line &drawee, const Stroke &strk) {
		line(drawee.datum.x, drawee.datum.y, drawee.end.x, drawee.end.y, strk);
		return *this;
	}
	Canvas& Canvas::rect(const Rectangle &rec, const Color &col) {
		if(APIsetColor!=0) APIsetColor(col);
//		DirectGetColor::setColor4ub(col);
		glRectd(rec.left, rec.top, rec.right+1, rec.bottom+1);
		return *this;
	}
	Canvas& Canvas::rect(const int nRects, const Rectangle *rec, const Color &col) {
		APIsetColor(col);
		for(unsigned int i=0; i<nRects; i++) {
			glRectd(rec[i].left, rec[i].top, rec[i].right+1, rec[i].bottom+1);
		}
		return *this;
	}
	Canvas& Canvas::rect(const int nRects, const Rectangle *rec, const Color *col) {
		for(unsigned int i=0; i<nRects; i++) rect(rec[i], col[i]);
		return *this;
	}
	Canvas& Canvas::rect(const unsigned int nRectsBegin, const unsigned int nRectsEnd, const Rectangle *rec, const Color *col) {
		for(unsigned int i=nRectsBegin; i<nRectsEnd; i++) rect(rec[i], col[i]);
		return *this;
	}
	Canvas& Canvas::rect(const Rectangle &rec, const Stroke &strk) {
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		setStrokeState(strk);
		glBegin(GL_LINE_LOOP);
		glVertex2d(rec.left, rec.top);
		glVertex2d(rec.right, rec.top);
		glVertex2d(rec.right, rec.bottom);
		glVertex2d(rec.left, rec.bottom);
		glEnd();
		glPopAttrib();
		return *this;
	}
	Canvas& Canvas::rect(const int nRects, const Rectangle *rec, const Stroke &strk) {
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		setStrokeState(strk);
		for(int i=0; i<nRects; i++) {
			glBegin(GL_LINE_LOOP);
			glVertex2d(rec[i].left, rec[i].top);
			glVertex2d(rec[i].right, rec[i].top);
			glVertex2d(rec[i].right, rec[i].bottom);
			glVertex2d(rec[i].left, rec[i].bottom);
			glEnd();
		}
		glPopAttrib();
		return *this;
	}
	Canvas& Canvas::rect(const int nRects, const Rectangle *rec, const Stroke *strk) {
		for(int i=0; i<nRects; i++) rect(rec[i], strk[i]);
		return *this;
	}
	Canvas& Canvas::rect(const unsigned int nRectsBegin, const unsigned int nRectsEnd, const Rectangle *rec, const Stroke *strk) {
		for(unsigned int i=nRectsBegin; i<nRectsEnd; i++) rect(rec[i], strk[i]);
		return *this;
	}
	Canvas& Canvas::ellipse(const Ellipse &ovl, const Color &col) {
		Ellipse o;
		Rectangle rect(ovl.radius, ovl.v_radius);
		rect.centering(ovl);
		return oval(rect, col);
	}
	Canvas& Canvas::ellipse(const Ellipse &ovl, const Stroke &strk) {
		Rectangle rect(ovl.radius, ovl.v_radius);
		rect.centering(ovl);
		return oval(rect, strk);
	}
	Canvas& Canvas::ellipse(const Rectangle &rect, const Color &col) {
		const double wi = rect.getWidth();
		glPushMatrix();
		glPushAttrib(GL_ALL_ATTRIB_BITS);
			APIsetColor(col);
			glTranslatef(rect.left, rect.top, 0);
			glScalef(wi, rect.getHeight()+1, 1.0);
			if(wi<8) {
				glCallList(fillOvalPrimitives[0]);
			} else if(wi<64) {
				glCallList(fillOvalPrimitives[1]);
			} else if(wi<512) {
				glCallList(fillOvalPrimitives[2]);
			} else {
				glCallList(fillOvalPrimitives[3]);
			}
		glPopAttrib();
		glPopMatrix();
		return *this;
	}
	Canvas& Canvas::ellipse(const Rectangle &rect, const Stroke &strk) {
		const double wi = rect.getWidth();
		glPushMatrix();
		glPushAttrib(GL_ALL_ATTRIB_BITS);
			setStrokeState(strk);
			glTranslatef(rect.left, rect.top, 0);
			glScalef(wi, rect.getHeight()+1, 1.0);
			if(wi<8) {
				glCallList(drawOvalPrimitives[0]);
			} else if(wi<64) {
				glCallList(drawOvalPrimitives[1]);
			} else if(wi<512) {
				glCallList(drawOvalPrimitives[2]);
			} else {
				glCallList(drawOvalPrimitives[3]);
			}
		glPopAttrib();
		glPopMatrix();
		return *this;
	}
	void Canvas::polygon(Point *vertices, unsigned int nVertices, const Color &col) {
		APIsetColor(col);
		glBegin(GL_POLYGON);
			for(unsigned int i=0; i<nVertices; i++) {
				glVertex2d(vertices[i].x, vertices[i].y);
			}
		glEnd();
	}
	void Canvas::polygon(Point *vertices, unsigned int nVertices, const Stroke &strk) {
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		setStrokeState(strk);
		glBegin(GL_LINE_LOOP);
			for(unsigned int i=0; i<nVertices; i++) {
				glVertex2d(vertices[i].x, vertices[i].y);
			}
		glEnd();
		glPopAttrib();
	}
	Canvas& Canvas::polygon(const Polygon &drawee) {
		if(drawee.vertices.empty()) return *this;
		glPushMatrix();
		const Point datum = drawee.getDatum();
		glTranslatef(datum.x, datum.y, datum.z);
		glBegin(GL_POLYGON);
		for(std::deque<Point>::const_iterator v=drawee.vertices.begin(); v!=drawee.vertices.end(); v++) {
			glVertex3d((*v).x, (*v).y, (*v).z);
		}
		glEnd();
		glPopMatrix();
		return *this;
	}
	Canvas& Canvas::polygon(const Polygon &drawee, const Color &col) {
		if(drawee.vertices.empty()) return *this;
		APIsetColor(col);
		glPushMatrix();
		const Point datum = drawee.getDatum();
		glTranslatef(datum.x, datum.y, datum.z);
		glBegin(GL_POLYGON);
		for(std::deque<Point>::const_iterator v=drawee.vertices.begin(); v!=drawee.vertices.end(); v++) {
			glVertex3d((*v).x, (*v).y, (*v).z);
		}
		glEnd();
		glPopMatrix();
		return *this;
	}
	Canvas& Canvas::polygon(const Polygon &drawee, const Stroke &strk) {
		if(drawee.vertices.empty()) return *this;
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		setStrokeState(strk);
		glPushMatrix();
		const Point datum = drawee.getDatum();
		glTranslatef(datum.x, datum.y, datum.z);
		glBegin(GL_LINE_LOOP);
		for(std::deque<Point>::const_iterator v=drawee.vertices.begin(); v!=drawee.vertices.end(); v++) {
			glVertex3d((*v).x, (*v).y, (*v).z);
		}
		glEnd();
		glPopMatrix();
		glPopAttrib();
		return *this;
	}
	Canvas& Canvas::polyline(const PolyLine &drawee) {
		polyline(drawee, drawee.stroke);
		return *this;
	}
	Canvas& Canvas::polyline(const PolyLine &drawee, const Color &col) {
		Stroke strk(col, 1, Stroke::SOLID);
		polyline(drawee, strk);
		return *this;
	}
	Canvas& Canvas::polyline(const PolyLine &drawee, const Stroke &strk) {
		if(drawee.vertices.empty()) return *this;
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		setStrokeState(strk);
		glPushMatrix();
		const Point datum = drawee.getDatum();
		glTranslatef(datum.x, datum.y, datum.z);
		glBegin(GL_LINE_STRIP);
		for(std::deque<Point>::const_iterator v=drawee.vertices.begin(); v!=drawee.vertices.end(); v++) {
			glVertex3d((*v).x, (*v).y, (*v).z);
		}
		glEnd();
		glPopMatrix();
		glPopAttrib();
		return *this;
	}

	Canvas& Canvas::figures(const Group &drawee) {
		const Color zero_alpha(1,1,1,0,false);
		const Point datum = drawee.getDatum();
		bool clipper_cached ;
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		if(drawee.clip_type==Group::ALPHA) {
			clipper_cached = drawee.alpha_mask->caches.count(this);
			if(!clipper_cached) drawee.alpha_mask->cache(*this);
			glEnable(GL_BLEND);
			// nulling alpha value in buffer
			glBlendFunc(GL_ZERO, GL_SRC_COLOR);
			Rectangle fullcanvas(getWidth(), getHeight());
			fullcanvas.draw(zero_alpha);
		}
		glPushMatrix();
		if(drawee.clip_type==Group::RECT) {
			glEnable(GL_SCISSOR_TEST);
			glScissor((int)(drawee.scissor_left+datum.x), (int)(drawee.scissor_top+datum.y),
			          (int)(drawee.scissor_width), (int)(drawee.scissor_height));
		}
		glTranslated(datum.x, datum.y, datum.z);
		if(drawee.scaling.x!=0.0 || drawee.scaling.y!=0.0 || drawee.scaling.z!=0.0) glScaled(drawee.scaling.x, drawee.scaling.y, drawee.scaling.z);
		if(drawee.rotation!=0.0) glRotated(drawee.rotation, drawee.axis.x, drawee.axis.y, drawee.axis.z);
		if(drawee.clip_type==Group::ALPHA) {
			// nulling alpha value in buffer
			//glBlendFunc(GL_ZERO, GL_SRC_COLOR);
			//drawee.alpha_mask->targetarea_.draw(zero_alpha);
			// write alpha value to buffer
			glBlendFunc(GL_ONE, GL_ONE);
			drawee.alpha_mask->draw();
			//
			glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA);
			if(!clipper_cached) drawee.alpha_mask->uncache(*this);
		}
		if(!drawee.contents.empty()) {
			for(int i=0; i<drawee.contents.size(); i++) {
				drawee.contents[i]->draw(*this);
			}
		}
		if(drawee.clip_type==Group::RECT) {
			glDisable(GL_SCISSOR_TEST);
		}
		if(drawee.clip_type==Group::ALPHA) {
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  		}
		glPopMatrix();
		glPopAttrib();
		return *this;
	}

	Canvas& Canvas::image(const Image &img, const double alpha) {
		const Color zero_alpha(1,1,1,0,false), alpha_mask(0,0,0,alpha);
		Rectangle area = img.targetarea_;
		area.set(area.left-1, area.top-1, area.right+1, area.bottom+1);
		glPushAttrib(GL_ALL_ATTRIB_BITS);

		glEnable(GL_BLEND);
		// nulling alpha value in buffer
		glBlendFunc(GL_ZERO, GL_SRC_COLOR);
		rect(area, zero_alpha);
		// write alpha value to buffer
		glBlendFunc(GL_ONE, GL_ONE);
		rect(area, alpha_mask);
		glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA);

		image(img);

		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glPopAttrib();
		//glPopMatrix(); can't found corresponding glPushMatrix
		return *this;
	}


	////	Drawing Images


	void Canvas::copy(const Rectangle &source, const Rectangle &target, bool doesdeletesource, const Color &delcol) {
		glReadBuffer(GL_BACK);
		glDrawBuffer(GL_BACK);
		glRasterPos2d(target.left, target.bottom);
		glCopyPixels((GLuint)source.left, (GLuint)(Height-source.bottom-1), (GLuint)source.getWidth(), (GLuint)source.getHeight(), GL_COLOR);
		if(doesdeletesource) {
			fillRect(source, delcol);
		}
	}
	void Canvas::to(Image &img, const Rectangle &srcrect) const {
		Rectangle src = srcrect;
		if(img.hasInstance()) img.release();
		img.set(srcrect);
		GLuint sw=(GLuint)srcrect.getWidth(), sh=(GLuint)srcrect.getHeight(), tw=(GLuint)img.getWidth(), th=(GLuint)img.getHeight();
		if(tw!=sw || th!=sh) {
			src.resize(tw<sw?tw:sw, th<sh?th:sh).centering(srcrect);
		} else if(tw<sw || th<sh) {
			throw Exception(typeid(*this), "Sorry", "This version can not copy bitmap to pertial image.");
		} else {
			src.resize(tw, th);
		}
//		glReadBuffer(GL_BACK);
		glReadPixels((GLuint)(src.getLeft()), (GLuint)(Height-src.getBottom()-1), (GLuint)src.getWidth(), (GLuint)src.getHeight(), APIImageProperties::PixCompGL_[img.pixcomp_], APIImageProperties::PixPrecGL_[img.pixprec_], const_cast<void *>(img.getBitmapPtr()) );
	}

/*	void Canvas::to(Image &img, const Rectangle &srcrect, const Rectangle &tgtrect) const {
		if(!img.hasInstance()) img.set(srcrect);
		Rectangle src = srcrect, tgt = tgtrect, imgrect(img.getWidth(), img.getHeight());
		tgt.cripped(img);
		GLuint sw=(GLuint)srcrect.getWidth(), sh=(GLuint)srcrect.getHeight(), tw=(GLuint)tgt.getWidth(), th=(GLuint)tgt.getHeight(), iw=(GLuint)img.getWidth(), ih=(GLuint)img.getHeight();
		if(tw!=sw || th!=sh) {
			src.resize(tw<sw?tw:sw, th<sh?th:sh).centering(srcrect);
		}
		glReadBuffer(GL_BACK);
		glReadPixels((GLuint)(srcrect.left), (GLuint)(Height-srcrect.bottom), (GLuint)src.getWidth(), (GLuint)src.getHeight(), Image::PixCompGL_[img.pixcomp_], Image::PixPrecGL_[img.pixprec_], const_cast<void *>(img.getBitmapPtr()) );
		unsigned int lines, sl, sr, sb, tl, tr, tb;
		for(int l=0; l<lines; l++)

	}*/


	////	Drawing String

	void Canvas::var_(std::string str, double x, double y, const Color &col, bool fillForward) {
		glPushMatrix();
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		APIsetColor(col);
		if(fillForward) {
			std::string::iterator itr=str.begin(), end=str.end();
			glTranslatef(x-12,y,0);
			do {
				glTranslatef(12,0,0);
				glCallList(font_LCD[(int)(*itr)]);
				itr++;
			} while(itr<end);
		} else {
			std::string::reverse_iterator itr=str.rbegin(), end=str.rend();
			glTranslatef(x,y,0);
			do {
				glTranslatef(-12,0,0);
				glCallList(font_LCD[(int)(*itr)]);
				itr++;
			} while(itr<end);
		}
		glPopAttrib();
		glPopMatrix();
	}
	int Canvas::msg(const char* str, const double x, const double y, const Color &col, const int horiz_align, const double max__width) {
		Rectangle mask;
		const Color zero_alpha(1,1,1,0,false), alpha_mask(1,1,1,col.getA(),false);
//		glPushMatrix();
		glPushAttrib(GL_COLOR_BUFFER_BIT | GL_TEXTURE_BIT);
		glEnable(GL_BLEND);

		int i=0, line_begin_i=0, num_lines=0, cursol_xpos=0;
		double line_x, line_y=0, max_width=0;

		switch(horiz_align) {
		case Letters::TEXT_ALIGN_RIGHT:
			max_width = (max__width>0) ? (int)max__width : (int)(x);
			break;
		case Letters::TEXT_ALIGN_CENTER:
			if(max__width>0) max_width = (int)max__width;
			else max_width = (x>Width/2) ? (int)(Width-x)*2 : (int)(x)*2;
			break;
		case Letters::TEXT_ALIGN_LEFT:
		default:
			max_width = (max__width>0) ? (int)max__width : (int)(Width-x);
			break;
		}

		while(str[i]!=0) {
			//line count
			line_begin_i = i;
			cursol_xpos = 0;
			while(cursol_xpos<max_width && str[i]!=0 && str[i]!='\r' && str[i]!='\n') {
				cursol_xpos += Font::font_minimum_proportion[str[i++]];
			}
			switch(horiz_align) {
			case Letters::TEXT_ALIGN_RIGHT:
				line_x = x - cursol_xpos + 3;
				break;
			case Letters::TEXT_ALIGN_CENTER:
				line_x = x - cursol_xpos/2.0 + 3;
				break;
			case Letters::TEXT_ALIGN_LEFT:
			default:
				line_x = x + 3;
				break;
			}

			// draw
			line_y = y + num_lines*8 - 8;
			mask.set(line_x-4, line_y, line_x+cursol_xpos, line_y+8);
			glBlendFunc(GL_ZERO, GL_SRC_COLOR);	// clear destination alpha
			rect(mask, zero_alpha);
			glBlendFunc(GL_ONE, GL_ONE); // draw font in alpha channel
			cursol_xpos = 0;
			for(int c=line_begin_i; c<i; c++) {
				if(str[c]<128) {
					drawImage(Font::font_minimum[str[c]], line_x+cursol_xpos, line_y);
					cursol_xpos+=Font::font_minimum_proportion[str[c]];
				}
			}

			if(col.getA()!=1) { // apply alpha factor
				glBlendFunc(GL_ZERO, GL_SRC_COLOR);
				rect(mask, alpha_mask);
			}
			glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA); // drawing in specified color by destination alpha
			rect(mask, col);
			if( str[i]=='\r') i++;
			if( str[i]=='\n') i++;
			num_lines++;
		}

		glPopAttrib();
//		glPopMatrix();
		return num_lines;
	}


	////////	Critical Operations ////////
	void Canvas::flip(const int frame_duration_by_vsyncs) {
		Clock time_thisFrame, elapsed;
		double wait_time;

		if(frame_duration_by_vsyncs<1) throw Exception(typeid(*this), "ARGUMENT ERROR", "frame duration must be more than zero.");

		if(!billboard.empty()) {
			for(int i=0; i<billboard.size(); i++) billboard[i]->draw(*this);
		}
		if(displayingFPS) displayFPS();
		if(Mouse::showLocalPointer()) Mouse::drawPointer(mouse(), *this);


		glFinish();

		if(rest_wating_vsyncs_>1) {

			time_thisFrame.update();
			elapsed = time_thisFrame - time_lastFrame_before_flip_;

			//	HYBRID ROUTINE
/*			do {
				api->waitRefresh();
				time_thisFrame.update();
				elapsed = time_thisFrame - time_lastFrame_before_flip_;
			} while(elapsed.at_msec()<frameIntervalAccurate*(rest_wating_vsyncs_-0.5));
*/
			//	TIME BASED ROUTINE
			wait_time = frameIntervalAccurate*(rest_wating_vsyncs_);
			do {
				if(not_strict) Prototype::Thread::sleep(1000);
				time_thisFrame.update();
				elapsed = time_thisFrame - time_lastFrame_before_flip_;
			} while(elapsed.at_msec()<wait_time);

			//	VSYNC BASED ROUTINE
/*			int rest_frames = rest_wating_vsyncs_ - (int)( elapsed.at_msec() / frameIntervalAccurate );
			//int rest_frames = (int)(((rest_wating_vsyncs_)*frameIntervalAccurate - elapsed.at_msec())/frameIntervalAccurate);
			if(rest_frames>0) {
				glReadBuffer(GL_BACK);
				glDrawBuffer(GL_AUX0);
				glRasterPos2d(0, Height-0.625);
				glCopyPixels(0, 0, Width, Height, GL_COLOR);
				glReadBuffer(GL_FRONT);
				glDrawBuffer(GL_BACK);
				glRasterPos2d(0, Height-0.625);
				glCopyPixels(0, 0, Width, Height, GL_COLOR);
//				glDrawBuffer(GL_AUX0);
				for(int i=0; i<rest_frames; i++) {
				 rect(Rectangle(Width, Height), Color(0.0,0.0,0.0,0.5)); glFinish(); api->flip(); }
				glReadBuffer(GL_AUX0);
				glDrawBuffer(GL_BACK);
				glRasterPos2d(0, Height-0.625);
				glCopyPixels(0, 0, Width, Height, GL_COLOR);
			}
*/
		} else {
			wait_time = frameIntervalAccurate*0.1;
			do {
				if(not_strict) Prototype::Thread::sleep(1000);
				time_thisFrame.update();
				elapsed = time_thisFrame - time_lastFrame_before_flip_;
			} while(elapsed.at_msec()<wait_time);
		}


		time_lastFrame_before_flip_.update();

		api->flip();

		//int rest_wating_vsyncs_before = rest_wating_vsyncs_;
		rest_wating_vsyncs_ = frame_duration_by_vsyncs;

		time_thisFrame.update();
		elapsed = time_thisFrame - time_lastFrame;
		frameInterval = elapsed.at_msec();
		time_lastFrame = time_thisFrame;

		if(checkingFPS) {
			displayedFrames+=frame_duration_by_vsyncs;
			if(checkingFPS_last) {
				lastFailedFrames_ = (long)floor( ((frameInterval-1.0-(frameIntervalAccurate*((double)rest_wating_vsyncs_-1)) ) / frameIntervalAccurate) );
				failedFrames += lastFailedFrames_;
				DISPLAYED_FRAMES = displayedFrames;
				FAILED_FRAMES = failedFrames;
				FAILED_FLIPS += ( lastFailedFrames_==0 ? 0 : 1);
				TOTAL_REFRESH = failedFrames+displayedFrames;
				LAST_FAILED_FRAMES = lastFailedFrames_;
			} else {
				checkingFPS_last = true;
			}
		}
	}

	Canvas& Canvas::clear(const Color &col, TargetSurface target) {
		APIsetColorClear(col);
		switch(target) {
			case ALL:
				glDrawBuffer(GL_FRONT || GL_BACK);
				//rect(clear_area_, col);
				glClear(GL_COLOR_BUFFER_BIT);
				break;
			case FRONT:
				glDrawBuffer(GL_FRONT);
				//rect(clear_area_, col);
				glClear(GL_COLOR_BUFFER_BIT);
				break;
			case BACK:
				glDrawBuffer(GL_BACK);
				//rect(clear_area_, col);
				glClear(GL_COLOR_BUFFER_BIT);
				break;
		}
		glDrawBuffer(GL_BACK);
		return *this;
	}


	////////	Utilities   ////////

	void Canvas::progressbar(double percentage) {
		Rectangle filledrect;
		Color fillcolor = progressbarfillcolor;
		if( percentage>1.0 ) {
			 fillcolor = progressbarerrorcolor;
			 percentage=1.0;
		}
		glDrawBuffer(GL_FRONT);
			fillRect(progressbarrect, progressbaremptycolor);
			if(percentage>0.0) {
				filledrect.set(progressbarrect.left, progressbarrect.top, progressbarrect.left+(long)(progressbarrect.getWidth()*percentage), progressbarrect.bottom);
				fillRect(filledrect, progressbarfillcolor);
			}
			glFlush();
		glDrawBuffer(GL_BACK);
	}



	////////	Properties   ////////

	////////	Element Preparation	////////

	void Canvas::setfontLCD() {
		/*
			LCD FONT ....
			LIST:   0123456789AbCdEF+-* /=<>.,%
		*/
		int len = 8;

		// 0
		//1 2
		// 3
		//4 5
		// 6
		//int font_LCD_vertices[7*4] = { 2,2+len+len,2+len,2+len+len , 1,2+len,1,2+len+len , 2+len,2+len,2+len,2+len+len , 2,1+len,2+len,1+len , 1,1,1,1+len , 2+len,1,2+len,1+len , 2,0,2+len,0 };
		int font_LCD_vertices[7*4] = {
					0,0,1,0,
			0,0,0,1,		1,0,1,1,
					0,1,1,1,
			0,1,0,2,		1,1,1,2,
					0,2,1,2
		};
		for(int i=0; i<7*4; i+=4) {
			if(font_LCD_vertices[i+0]==font_LCD_vertices[i+2]) {
				font_LCD_vertices[i+0] = font_LCD_vertices[i+0]*len;
				font_LCD_vertices[i+2] = font_LCD_vertices[i+2]*len;
			} else {
				font_LCD_vertices[i+0] = font_LCD_vertices[i+0]*len+1;
				font_LCD_vertices[i+2] = font_LCD_vertices[i+2]*len;
			}
			if(font_LCD_vertices[i+1]==font_LCD_vertices[i+3]) {
				font_LCD_vertices[i+1] = font_LCD_vertices[i+1]*len;
				font_LCD_vertices[i+3] = font_LCD_vertices[i+3]*len;
			} else {
				font_LCD_vertices[i+1] = font_LCD_vertices[i+1]*len+1;
				font_LCD_vertices[i+3] = font_LCD_vertices[i+3]*len;
			}
			font_LCD_vertices[i+1] -= len*2;
			font_LCD_vertices[i+3] -= len*2;
		}
		//int font_LCD_GEN[16] = { 119,36,93,109,46,107,123,39,127,47,63,122,83,124,91,27 };
		int font_LCD_GEN[128] = {
			0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
			0,// SPC
			119,// !
			0,// "
			0,// #
			0,// $
			0,// %
			0,// &
			0,// '
			0,// (
			0,// )
			0,// *
			0,// +
			0,// ,
			8,// -
			0,// .
			0,// /
			119,36,93,109,46,107,123,39,127,47,// 0123456789
			0,0,0,0,0,0,0,// :;<=>?@
			63,122,83,124,91,27,115,62,32,100,// AbCdEFGHiJ
			0,0,0,0,119,//KLMNO
			0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
			0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
			0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		};

		font_LCD[0] = glGenLists(128);
		if(font_LCD[0]!=0) {
			for(int i=0; i<128; i++) {
				font_LCD[i] = font_LCD[0]+i;
				if(font_LCD_GEN[i] != 0) {
					glNewList(font_LCD[i], GL_COMPILE);
						glBegin(GL_LINES);
							for(int j=0; j<7; j++) {
								if( font_LCD_GEN[i] >> j & 1 != 0 ) {
									glVertex2i(font_LCD_vertices[j*4 + 0], font_LCD_vertices[j*4 + 1]);
									glVertex2i(font_LCD_vertices[j*4 + 2], font_LCD_vertices[j*4 + 3]);
								}
							}
						glEnd();
					glEndList();
				} else {
					glNewList(font_LCD[i], GL_COMPILE);
						glBegin(GL_POINTS);
							glVertex2i(font_LCD_vertices[6*4 + 2], font_LCD_vertices[6*4 + 3]);
						glEnd();
					glEndList();
				}
			}
		}
		font_LCD_height = len*2+3;
	}

	void Canvas::setFillOvalPrimitives() {
		//  set display-lists index
		fillOvalPrimitives[0] = glGenLists(4);
		for(int i=1; i<4; i++) { fillOvalPrimitives[i]=fillOvalPrimitives[0]+i; }
		drawOvalPrimitives[0] = glGenLists(4);
		for(int i=1; i<4; i++) { drawOvalPrimitives[i]=drawOvalPrimitives[0]+i; }

		//  implement
		double k=0.0;
		for(int i=0; i<4; i++) {
			glNewList(fillOvalPrimitives[i], GL_COMPILE);
			glBegin(GL_POLYGON);
				for(int j=0; j<(k=pow(5.0, 2.0+i*0.2)); j++) {
					glVertex2f(cos(2.0*(j/k)*PI)/2.0+0.5, sin(2.0*(j/k)*PI)/2.0+0.5);	}
					glVertex2f(cos(2.0*(0)*PI)/2.0+0.5, sin(2.0*(0)*PI)/2.0+0.5);
			glEnd();
			glEndList();
		}
		for(int i=0; i<4; i++) {
			glNewList(drawOvalPrimitives[i], GL_COMPILE);
			glBegin(GL_LINE_LOOP);
				for(int j=0; j<(k=pow(5.0, 2.0+i*0.2)); j++) {
					glVertex2f(cos(2.0*(j/k)*PI)/2.0+0.5, sin(2.0*(j/k)*PI)/2.0+0.5);	}
					glVertex2f(cos(2.0*(0)*PI)/2.0+0.5, sin(2.0*(0)*PI)/2.0+0.5);
			glEnd();
			glEndList();
		}
	}



}	/*	<- namespace Psycholops 	*/

