// This code is D 1.0

/**
	Part of my old D 1.0 game helper code that used SDL. I keep it compiling on new D compilers too, but it is not meant to be used in new projects.
*/
module arsd.screen;

import sdl.SDL;
import sdl.SDL_image;
import sdl.SDL_ttf;
import std.string;

import std.stdio;
import std.format;

import arsd.engine;

version(D_Version2)
static import stdcstring = core.stdc.string;
else
static import stdcstring = std.c.string;

version(none)
char[] fmt(...){
    char[] o;
    void putc(dchar c)
    {
    	o ~= c;
    }

    std.format.doFormat(&putc, _arguments, _argptr);

	return o;
}


extern(C){
	void glGetIntegerv(int, void*);
	void glMatrixMode(int);
	void glPushMatrix();
	void glLoadIdentity();
	void glOrtho(double, double, double, double, double, double);
	void glPopMatrix();
	void glEnable(int);
	void glDisable(int);
	void glClear(int);
	void glBegin(int);
	void glVertex2f(float, float);
	void glEnd();
	void glColor3b(ubyte, ubyte, ubyte);
	void glColor3i(int, int, int);
	void glColor3f(float, float, float);
	void glColor4f(float, float, float, float);
	void glTranslatef(float, float, float);

	void glRotatef(float, float, float, float);

	uint glGetError();

	void glDeleteTextures(int, uint*);

	char* gluErrorString(uint);

	void glRasterPos2i(int, int);
	void glDrawPixels(int, int, uint, uint, void*);
	void glClearColor(float, float, float, float);



	void glGenTextures(uint, uint*);
	void glBindTexture(int, int);
	void glTexParameteri(uint, uint, int);
	void glTexImage2D(int, int, int, int, int, int, int, int, void*);


	void glTexCoord2f(float, float);
	void glVertex2i(int, int);
	void glBlendFunc (int, int);
	void glViewport(int, int, int, int);

	void glReadBuffer(uint);
	void glReadPixels(int, int, int, int, int, int, void*);


	const uint GL_FRONT = 0x0404;

	const uint GL_BLEND = 0x0be2;
	const uint GL_SRC_ALPHA = 0x0302;
	const uint GL_ONE_MINUS_SRC_ALPHA = 0x0303;


	const uint GL_UNSIGNED_BYTE = 0x1401;
	const uint GL_RGB = 0x1907;
	const uint GL_BGRA = 0x80e1;
	const uint GL_RGBA = 0x1908;
	const uint GL_TEXTURE_2D =   0x0DE1;
	const uint GL_TEXTURE_MIN_FILTER = 0x2801;
	const uint GL_NEAREST = 0x2600;
	const uint GL_LINEAR = 0x2601;
	const uint GL_TEXTURE_MAG_FILTER = 0x2800;

	const uint GL_NO_ERROR = 0;



	const int GL_VIEWPORT = 0x0BA2;
	const int GL_MODELVIEW = 0x1700;
	const int GL_TEXTURE = 0x1702;
	const int GL_PROJECTION = 0x1701;
	const int GL_DEPTH_TEST = 0x0B71;

	const int GL_COLOR_BUFFER_BIT = 0x00004000;
	const int GL_ACCUM_BUFFER_BIT = 0x00000200;
	const int GL_DEPTH_BUFFER_BIT = 0x00000100;

	const int GL_POINTS = 0x0000;
	const int GL_LINES =  0x0001;
	const int GL_LINE_LOOP = 0x0002;
	const int GL_LINE_STRIP = 0x0003;
	const int GL_TRIANGLES = 0x0004;
	const int GL_TRIANGLE_STRIP = 5;
	const int GL_TRIANGLE_FAN = 6;
	const int GL_QUADS = 7;
	const int GL_QUAD_STRIP = 8;
	const int GL_POLYGON = 9;

}

public struct Point{
	int x;
	int y;
	Point opAddAssign(Point p){
		x += p.x;
		y += p.y;
		version(D_Version2)
			return this;
		else
			return *this;
	}

	Point opAdd(Point p){
		Point a;
		a.x = x + p.x;
		a.y = y + p.y;
		return a;
	}

	Point opSub(Point p){
		Point a;
		a.x = x - p.x;
		a.y = y - p.y;
		return a;
	}
}

Point XY(int x, int y){
	Point p;
	p.x = x;
	p.y = y;
	return p;
}

Point XY(float x, float y){
	Point p;
	p.x = cast(int)x;
	p.y = cast(int)y;
	return p;
}

public struct Color{
	int r;
	int g;
	int b;
	int a;

	uint toInt(){
		return r << 24 | g << 16 | b << 8 | a;
	}

	void fromInt(uint i){
		r = i >> 24;
		g = (i >> 16) & 0x0ff;
		b = (i >> 8) & 0x0ff;
		a = i & 0x0ff;
	}
}

Color white = {255, 255, 255, 255};
Color black = {0, 0, 0, 255};

Color RGB(int r, int g, int b){
	Color c;
	c.r = r;
	c.g = g;
	c.b = b;
	c.a = 255;
	return c;
}

Color RGBA(int r, int g, int b, int a){
	Color c;
	c.r = r;
	c.g = g;
	c.b = b;
	c.a = a;
	return c;
}

Color XRGB(Color c, int alpha = -1){
	Color a;
	a.r = c.r ^ 255;
	a.g = c.g ^ 255;
	a.b = c.b ^ 255;
	if(alpha == -1)
		a.a = c.a;// ^ 255;
	else
		a.a = alpha;
	return a;
}

class FontEngine{
  public:

	static FontEngine instance;

	~this(){
		foreach(a; fonts)
			if(a != null)
				TTF_CloseFont(a);
		TTF_Quit();
	}

	void loadFont(in char[] font, int size = 12, int index = 0){
		if(fonts[index] != null)
			freeFont(index);
		TTF_Font* temp;
		temp = TTF_OpenFont(std.string.toStringz(font), size);
		if(temp == null)
			throw new Exception("load font");

		fonts[index] = temp;
	}

	void freeFont(int index = 0){
		if(fonts[index] != null){
			TTF_CloseFont(fonts[index]);
			fonts[index] = null;
		}
	}

	Image renderText(in char[] text, Color foreground = RGB(255,255,255), int font = 0){
		Image* a = immutableString(text) in cache[font];
		if(a !is null)
			return *a;
		SDL_Color f;
		f.r = cast(ubyte) foreground.r;
		f.g = cast(ubyte) foreground.g;
		f.b = cast(ubyte) foreground.b;
		f.unused = cast(ubyte) foreground.a;

		SDL_Surface* s = TTF_RenderText_Blended(fonts[font], std.string.toStringz(text), f);
		Image i = new Image(s);
		cache[font][text]/*[font]*/ = i;

		return i;
	}

	int textHeight(in char[] text=" ",int font = 0){
		int w, h;
		TTF_SizeText(FontEngine.instance.fonts[font], std.string.toStringz(text), &w, &h);
		return h;
	}

	void textSize(in char[] text, out int w, out int h, int font = 0){
		TTF_SizeText(fonts[font], std.string.toStringz(text), &w, &h);
	}
  private:
	static this() {
		instance = new FontEngine;
	}

	this(){
		if(TTF_Init() == -1)
			throw new Exception("TTF_Init");

	}

	TTF_Font*[8] fonts;
	Image[char[]][8] cache;
}

interface Drawable{
  public:
	void flip();
	int width();
	int height();
	int bpp();
	/*
	uint toGL();
	float texWidth();
	float texHeight();
	*/
  protected:
	SDL_Surface* surface();
}

int total = 0;


class Image : Drawable{
  public:
  	this(SDL_Surface* s){
		if(s == null)
			throw new Exception("Image");
		sur = s;
	}

	/// Loads an image with the filename checking to see if it has already been loaded into the cache
	/// loads it as read-only
//	static Image load(char[] filename){

//	}

	this(char[] filename){
		sur = IMG_Load(std.string.toStringz(filename));
		if(sur == null)
			throw new Exception(immutableString("Load " ~ filename));
		name = filename;
	}


	void replace(char[] filename){
		if(t){
			glDeleteTextures(1, &tex);
			total--;
			writef("[%s]OpenGL texture destroyed %d. %d remain\n", name, tex, total);
			t = 0;
		}
		if(sur){
			SDL_FreeSurface(sur);
			sur = null;
		}
		sur = IMG_Load(std.string.toStringz(filename));
		if(sur == null)
			throw new Exception(immutableString("Load " ~ filename));
		name = filename;
	}


	// loads a slice of an image
	this(char[] filename, int x, int y, int wid, int hei){
	/*
		Image i = new Image(filename);
		this(wid, hei);

		scope Painter p = new Painter(this);
		for(int a = 0; a < wid; a++)
		for(int b = 0; b < hei; b++)
			p.putpixel(XY(a, b), i.getPixel(XY(a + x, b + y)));
	*/

		SDL_Surface* s1;

		s1 = IMG_Load(std.string.toStringz(filename));
		if(s1 == null)
			throw new Exception(immutableString("Loading " ~ filename));
		scope(exit)
			SDL_FreeSurface(s1);


		sur = SDL_CreateRGBSurface(SDL_SWSURFACE, wid, hei, 32, 0xff0000, 0x00ff00, 0x0000ff, 0xff000000);
		if(sur == null)
			throw new Exception(immutableString("Create"));


		for(int b = 0; b < hei; b++){
			for(int a = 0; a < wid; a++){
			if(b+y >= s1.h || a+x >= s1.w){
				break;
			//	throw new Exception("eat my cum");
			}
				ubyte* wtf;
				if(s1.format.BitsPerPixel == 32){
					wtf = cast(ubyte*)(cast(ubyte*)s1.pixels + (b+y)*s1.pitch + (a+x) * 4);
				}
				else
				if(s1.format.BitsPerPixel == 24)
					wtf = cast(ubyte*)(cast(ubyte*)s1.pixels + (b+y)*s1.pitch + (a+x) * 3);
				else
					throw new Exception("fuck me in the ass");

				ubyte* good = cast(ubyte*)(cast(ubyte*)sur.pixels + b*sur.pitch + a * 4);

				good[0] = wtf[2];
				good[1] = wtf[1];
				good[2] = wtf[0];
				good[3] = wtf[3];

			}
		}


/*
		SDL_Rect r;
		r.x = x;
		r.y = y;
		r.w = wid;
		r.h = hei;

		SDL_Rect r2;
		r2.x = 0;
		r2.y = 0;
		r2.w = wid;
		r2.h = hei;
		if(SDL_BlitSurface(s1, &r, sur, &r2))
			throw new Exception("Blit");
*/
	}

	this(int wid, int hei){
		sur = SDL_CreateRGBSurface(SDL_SWSURFACE, wid, hei, 32, 0xff0000, 0x00ff00, 0x0000ff, 0xff000000);
		if(sur == null)
			throw new Exception("Create");
		t = false;
	}

	~this(){
		if(t){
			glDeleteTextures(1, &tex);
			total--;
			writef("[%s]OpenGL texture destroyed %d. %d remain\n", name, tex, total);
		}
		if(sur)
			SDL_FreeSurface(sur);
	}

	void flip(){

	}

	int width(){
		return surface.w;
	}

	int height(){
		return surface.h;
	}

	int bpp(){
		return sur.format.BitsPerPixel;
	}

	Color getPixel(Point p){
		ubyte* bufp;
		Color a;

		if(bpp == 32){
			bufp = cast(ubyte*)(cast(ubyte*)surface.pixels + p.y*surface.pitch + p.x * 4);
			a.a = bufp[3];
		        a.r = bufp[2];
		        a.g = bufp[1];
        		a.b = bufp[0];
		}else{
			bufp = cast(ubyte*)(cast(ubyte*)surface.pixels + p.y*surface.pitch + p.x * 3);
		        a.a = 255;
			a.r = bufp[2];
		       	a.g = bufp[1];
	        	a.b = bufp[0];
		}

		return a;
	}

	uint toGL(){
		if(t)
			return tex;
		else{
			float[4] f;
			tex = SDL_GL_LoadTexture(surface, f.ptr);
			t = true;
			total++;
			texWidth = f[2];
			texHeight = f[3];

//			total++;
//			writef("OpenGL texture created %d. %d exist\n", tex, total);

			return tex;
		}
	}
  protected:
	SDL_Surface* surface(){
		return sur;
	}
  private:
  	SDL_Surface* sur;
	uint tex;
	bool t;
	float texWidth;
	float texHeight;
	char[] name;
}

bool useGL;

class Screen : Drawable{
  public:
	this(int xres = 1024, int yres = 768, int bpp = 24, bool oGL = false, bool fullScreen = false){//true){
//	oGL = false;
	oGL = true;
		if(!oGL)
			screen = SDL_SetVideoMode(xres, yres, bpp/*32*/, SDL_SWSURFACE);
		else{
			SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 8 );
			SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 8 );
			SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 8 );
			SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 24 );
			SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
			if(fullScreen){
			screen = SDL_SetVideoMode(xres, yres, 24, SDL_OPENGL| SDL_FULLSCREEN);
			}
			else
			screen = SDL_SetVideoMode(xres, yres, 0, SDL_OPENGL);
			//screen = SDL_SetVideoMode(xres, yres, bpp, SDL_OPENGL);
			if(screen is null)
				throw new Exception("screen");

   			glMatrixMode(GL_PROJECTION);
   			glLoadIdentity();
   			//glOrtho(0, 1000, yres, 0, 0, 1);
   			glOrtho(0, xres, yres, 0, 0, 1);
   			glMatrixMode(GL_MODELVIEW);

			glDisable(GL_DEPTH_TEST);

			glEnable(GL_TEXTURE_2D);


			glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);


			glClearColor(0,0,0,0);

//			glViewport(0,0,1024,1024);
		}

		useGL = oGL;

		if(screen == null)
			throw new Exception("screen");

//		SDL_SetAlpha(screen, SDL_SRCALPHA | SDL_RLEACCEL, 128);

		xr = xres;
		yr = yres;
	}

	void switchSplitScreenMode(int player, int numberOfPlayers, bool horizontal){
			switch(numberOfPlayers){
				default: assert(0);
				case 1:
					return;
//					glViewport(0, 0, xr, yr);
				break;
				case 2:
					switch(player){
						default: assert(0);
						case 0:
							if(horizontal)
								glViewport(0, yr / 2, xr, yr / 2);
							else
								glViewport(0, 0, xr / 2, yr);
						break;
						case 1:
							if(horizontal)
								glViewport(0, 0, xr, yr / 2);
							else
								glViewport(xr / 2, 0, xr / 2, yr);
						break;
					}
				break;
				case 3:
				case 4:
					switch(player){
						default: assert(0);
					  case 0:
						glViewport(0, yr / 2, xr / 2, yr / 2);
					  break;
					  case 1:
						glViewport(xr / 2, yr / 2, xr / 2, yr / 2);
					  break;
					  case 2:
						glViewport(0, 0, xr / 2, yr / 2);
					  break;
					  case 3:
						glViewport(xr / 2, 0, xr / 2, yr / 2);
					  break;
					}

				break;
			}
			glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			glOrtho(0, xr, yr, 0, 0, 1);
	}


	~this(){
		delete FontEngine.instance;
	}

	Image screenshot(){
		if(!useGL)
			throw new Exception("Not yet implemented");
		Image image = new Image(xr, yr);
		glReadBuffer(GL_FRONT);
		glReadPixels(0, 0, xr, yr, GL_BGRA, GL_UNSIGNED_BYTE, image.sur.pixels);

		Image temp = new Image(xr, yr);


	// FIXME
	version(Windows)
		return image;

		// FIXME: this crashes on Windows
		for(int i = 0; i < yr; i++)
			stdcstring.memcpy(temp.sur.pixels + 4 * xr * i, image.sur.pixels + 4 * xr * (yr-1 - i), 4 * xr);
//        memcpy(image.sur.pixels, tem.psur.pixels, xres * yres * 4);

		return temp;
	}




	void flip(){
		if(useGL)
			SDL_GL_SwapBuffers();
		else
			SDL_Flip(screen);
	}

	int width(){
		return xr;
	}

	int height(){
		return yr;
	}

	int bpp(){
		return 124;
	}
	/*
	uint toGL(){
		throw new Error;
	}
	float texWidth(){
		return 1.0;
	}
	float texHeight(){
		return 1.0;
	}
*/
  protected:
	SDL_Surface* surface(){
		return screen;
	}

  private:
	SDL_Surface* screen;
	int xr;
	int yr;
}

scope class Painter{
  public:
	bool special;
	bool manualFlipped;
	Point translate;
	this(Painter p, Point t){
		s = p.s;
		special = true;
		translate = t;
	}

	this(Drawable d){
	/+
		in {
			assert(!(s is null));
		}
	+/
		s = d;
		if(s is null)
			throw new Exception("christ what were you thinking");

		if ( !(useGL
		&& s.bpp() == 124)
		&& SDL_MUSTLOCK(s.surface()) ) {
			if ( SDL_LockSurface(s.surface()) < 0 ) {
				throw new Exception("locking");
			}
			locked = true;
		}
	}

	~this(){
		if(!manualFlipped){
		if(glbegin)
			endDrawingShapes();
		if(!special){
		if (locked){
			SDL_UnlockSurface(s.surface);
		}
		s.flip();
		}
		}
	}

	void manualFlip(){
		if(glbegin)
			endDrawingShapes();
		if(!special){
		if (locked){
			SDL_UnlockSurface(s.surface);
		}
		s.flip();
		}
		manualFlipped = true;
	}

	void setGLColor(Color color){
		if(useGL && s.bpp == 124){
			glColor4f(cast(float)color.r/255.0, cast(float)color.g/255.0, cast(float)color.b/255.0, cast(float)color.a / 255.0);
			return;
		}
	}

	void putpixel(Point where, Color color){
		if(special) where += translate;
		if(glbegin)
			throw new Exception("Must end shape before doing anything else");

//		if(color.a == 255)
//			return;
		int x = where.x;
		int y = where.y;
		if(x < 0 || x >= s.width || y < 0 || y >= s.height)
			return;



		if(useGL && s.bpp == 124){
		//	y = 480 - y;
			glBegin(GL_POINTS);
			setGLColor(color);
			glVertex2f(cast(float)x, cast(float)y);
			glEnd();
			return;
		}


		ubyte *bufp;

		if(s.bpp == 32){
			bufp = cast(ubyte*)(cast(ubyte*)s.surface.pixels + y*s.surface.pitch + x * 4);
				bufp[3] = cast(ubyte) color.a;
			        bufp[2] = cast(ubyte) color.r;
			        bufp[1] = cast(ubyte) color.g;
        			bufp[0] = cast(ubyte) color.b;
		}else{
			bufp = cast(ubyte*)(cast(ubyte*)s.surface.pixels + y*s.surface.pitch + x * 3);
			if(color.a == 255){
			        bufp[2] = cast(ubyte) color.r;
		        	bufp[1] = cast(ubyte) color.g;
	        		bufp[0] = cast(ubyte) color.b;
			}
			else{
			        bufp[2] = cast(ubyte)(bufp[2] * (255-color.a) + (color.r * (color.a)) / 255);
			        bufp[1] = cast(ubyte)(bufp[1] * (255-color.a) + (color.g * (color.a)) / 255);
		        	bufp[0] = cast(ubyte)(bufp[0] * (255-color.a) + (color.b * (color.a)) / 255);
			}
		}
	}

	void beginDrawingLines(){
		if(glbegin)
			throw new Exception("Can only draw one kind at a time");
		glbegin = true;
		if(useGL && s.bpp == 124)
			glBegin(GL_LINES);
	}
	void beginDrawingConnectedLines(){
		if(glbegin)
			throw new Exception("Can only draw one kind at a time");
		glbegin = true;
		if(useGL && s.bpp == 124)
			glBegin(GL_LINE_STRIP);
	}
	void beginDrawingPolygon(){
		if(glbegin)
			throw new Exception("Can only draw one kind at a time");
		glbegin = true;
		if(useGL && s.bpp == 124)
			glBegin(GL_POLYGON);
	}
	void beginDrawingTriangles(){
		if(glbegin)
			throw new Exception("Can only draw one kind at a time");
		glbegin = true;
		if(useGL && s.bpp == 124)
			glBegin(GL_TRIANGLES);
	}
	void beginDrawingBoxes(){
		if(glbegin)
			throw new Exception("Can only draw one kind at a time");
		glbegin = true;
		if(useGL && s.bpp == 124)
			glBegin(GL_QUADS);
	}
	void beginDrawingPoints(){
		if(glbegin)
			throw new Exception("Can only draw one kind at a time");
		glbegin = true;
		if(useGL && s.bpp == 124)
			glBegin(GL_POINTS);
	}

	void endDrawingShapes(){
		if(!glbegin)
			return;
		glbegin = false;
		if(useGL && s.bpp == 124)
			glEnd();
	}
	void vertex(Point p){
		if(special) p += translate;
		if(!glbegin)
			throw new Exception("Can't use vertex without beginning first");
		if(useGL && s.bpp == 124)
			glVertex2i(p.x, p.y);
	}
	bool glbegin;

	void drawImageRotated(Point where, Image i, float a, Color color = RGB(255,255,255)){
		if(i is null)
			return;
		glPushMatrix();
	//	glRotatef(a, cast(float)(where.x + 32) / s.width, cast(float)(where.y + 32) / s.height, 1);
		glTranslatef(where.x, where.y, 0);
		glRotatef(a, 0,0, 1);
		drawImage(XY(-i.width/2,-i.height/2), i, i.width, i.height, color);
		glPopMatrix();
	}

	void drawImage(Point where, Image i, Color c){
		drawImage(where, i, 0, 0, c);
	}

	void drawImage(Point where, Image i, int W = 0, int H = 0, Color c = RGBA(255,255,255,255)){
		if(i is null)
			return;

		if(special) where += translate;
		if(glbegin)
			throw new Exception("Must end shape before doing anything else");
		if(useGL && s.bpp == 124){
			int x = where.x;
			int y = where.y;
			int w = W == 0 ? i.width : W;
			int h = H == 0 ? i.height : H;

//			glColor4f(.5,.5,.5,1);
			setGLColor(c);
			glBindTexture(GL_TEXTURE_2D, i.toGL);
			glBegin(GL_QUADS);
				glTexCoord2f(0, 0); 			glVertex2i(x, y);
				glTexCoord2f(i.texWidth, 0); 		glVertex2i(x+w, y);
				glTexCoord2f(i.texWidth, i.texHeight); 	glVertex2i(x+w, y+h);
				glTexCoord2f(0, i.texHeight); 		glVertex2i(x, y+h);
			glEnd();

			glBindTexture(GL_TEXTURE_2D, 0); // unbind the texture... I guess
				// I don't actually understand why that is needed
				// but without it, everything drawn after it is wrong (too light or dark)
			return;
		}

		if((W == 0 && H == 0) || (i.width == W && i.height == H)){
		SDL_Rect r;
		r.x = cast(short)( where.x);
		r.y = cast(short)( where.y);
		r.w = cast(short)( i.width);
		r.h = cast(short)( i.height);
		if(locked)
			SDL_UnlockSurface(s.surface);

		if(SDL_BlitSurface(i.surface, null, s.surface, &r) == -1)
			throw new Exception("blit");

		if ( SDL_MUSTLOCK(s.surface) ) {
			if ( SDL_LockSurface(s.surface) < 0 ) {
				throw new Exception("lock");
			}
			locked = true;
		}
		} else { // quick and dirty scaling needed
			float dx = cast(float)i.width / cast(float)W;
			float dy = cast(float)i.height / cast(float)H;
			int X = where.x, Y = where.y;

			for(float y = 0; y < i.height; y += dy){
				for(float x = 0; x < i.width; x += dx){
					putpixel(XY(X, Y), i.getPixel(XY(cast(int) x, cast(int) y)));
					X++;
				}
				X = where.x;
				Y++;
			}

		}

	}

	void drawText(Point where, in char[] text, Color foreground = RGB(255,255,255), int font = 0){
		if(glbegin)
			throw new Exception("Must end shape before doing anything else");

		if(useGL && s.bpp == 124){
			Image i = FontEngine.instance.renderText(text, RGB(255,255,255), font);
			drawImage(where, i, foreground);
		}else{
			Image i = FontEngine.instance.renderText(text, foreground, font);
			drawImage(where, i);
		}
	}

	version(D_Version2) {
		import std.format;
		void drawTextf(T...)(Point where, T args) {
			char[] t;
			t.length = 80;
			int a = 0;
			void putc(dchar c){
				if(a == t.length)
					t.length = t.length + 80;
				t[a] = cast(char) c;
				a++;
			}
			formattedWrite(&putc, args);
			t.length = a;

			drawText(where, t);
		}
	} else
	void drawTextf(Point where, ...){
		char[] t;
		t.length = 80;
		int a = 0;
		void putc(dchar c){
			if(a == t.length)
				t.length = t.length + 80;
			t[a] = cast(char) c;
			a++;
		}
		std.format.doFormat(&putc, _arguments, _argptr);
		t.length = a;

		drawText(where, t);
	}

	int wordLength(in char[] w, int font = 0){
		int a,b;
		FontEngine.instance.textSize(w, a, b, font);
		return a;
	}

	int drawTextBoxed(Point where, char[] text, int width, int height, Color foreground = RGB(255,255,255), int font = 0){
		if(glbegin)
			throw new Exception("Must end shape before doing anything else");


		int xc;
		int yc = TTF_FontLineSkip(FontEngine.instance.fonts[font]);

		int w = 0;
		int h = 0;

		int l;

char[] getWord(){
	int a = l;
	while(a < text.length && text[a] != ' ' && text[a] != '\n' && text[a] != '\t')
		a++;
	return text[l..a];
}

int wordLength(in char[] w){
	int a,b;
	FontEngine.instance.textSize(w, a, b, font);
	return a;
}

		Point ww = where;
		while(l < text.length){
			if(text[l] == '\n'){
				l++;
				goto newline;
			}
			if(wordLength(getWord()) + w > width){
				goto newline;
			}

			if(!(w == 0 && text[l] == ' ')){
				TTF_GlyphMetrics(FontEngine.instance.fonts[font], text[l], null,null,null,null,&xc);
				drawText(ww, text[l..(l+1)], foreground, font);
				w+=xc;
				ww.x += xc;
			}
			l++;
			if(w > (width - xc)){
			newline:
				w = 0;
				h += yc;
				ww.x = cast(short)(where.x);
				ww.y += cast(short)(yc);

				if(h > (height - yc))
					break;
			}
		}
		return l;
	}

	void drawTextCenteredHoriz(int top, char[] text, Color foreground, int font = 0){
		Point where;
		where.y = top;
		int w, h;
		TTF_SizeText(FontEngine.instance.fonts[font], std.string.toStringz(text), &w, &h);
		where.x = (s.width - w) / 2;
		drawText(where, text, foreground, font);
	}

	void line(Point start, Point end, Color color){
		if(special){ start += translate; end += translate; }
		if(glbegin)
			throw new Exception("Must end shape before doing anything else");

		if(useGL && s.bpp == 124){
			setGLColor(color);
			glBegin(GL_LINES);
			glVertex2i(start.x, start.y);
			glVertex2i(end.x, end.y);
			glEnd();
		}
	}

	void hline(Point start, int width, Color color){
	if(useGL && s.bpp == 124){
		line(start, XY(start.x + width, start.y), color);
		return;
	}
		Point point = start;
		for(int a = 0; a < width; a++){
			putpixel(point, color);
			point.x++;
		}
	}

	void vline(Point start, int height, Color color){
	if(useGL && s.bpp == 124){
		line(start, XY(start.x, start.y + height), color);
		return;
	}

		Point point = start;
		for(int a = 0; a < height; a++){
			putpixel(point, color);
			point.y++;
		}
	}


	void circle(Point center, int radius, Color color){
		if(special) center += translate;
		if(glbegin)
			throw new Exception("Must end shape before doing anything else");

	}

	void arc(Point center, int radius, float start, float end, Color color){
		if(special) center += translate;
		if(glbegin)
			throw new Exception("Must end shape before doing anything else");

//		for(float a = start; a <= end; a+= (3.14159265358 / 50.0))
//			putpixel((int)(cos(a) * (float)radius + center.x()),(int)( sin(a) * (float) radius + center.y()), color);
	}

	void box(Point upperLeft, Point lowerRight, Color color){
		if(special) { upperLeft += translate; lowerRight += translate; }
		if(glbegin)
			throw new Exception("Must end shape before doing anything else");

		if(useGL && s.bpp == 124){
			int x1 = upperLeft.x;
			int y1 = upperLeft.y;
			int x2 = lowerRight.x;
			int y2 = lowerRight.y;
			glBegin(GL_QUADS);
			//glColor3b(color.r, color.g, color.b);
			setGLColor(color);
			//glColor4f(1,1,1,1);
			glVertex2i(x1, y1);
			glVertex2i(x2, y1);
			glVertex2i(x2, y2);
			glVertex2i(x1, y2);
			glEnd();
			return;
		}
		SDL_Rect r;
		r.x = cast(short) upperLeft.x;
		r.y = cast(short) upperLeft.y;
		r.w = cast(short) (lowerRight.x - upperLeft.x);
		r.h = cast(short) (lowerRight.y - upperLeft.y);
		if(s.bpp == 32)
			SDL_FillRect(s.surface, &r, color.a << 24 | color.r << 16 | color.g << 8 | color.b);
		else
			SDL_FillRect(s.surface, &r, color.r << 16 | color.g << 8 | color.b);
	}

	void rect(Point upperLeft, Point lowerRight, Color color){
		if(special) { upperLeft += translate; lowerRight += translate; }
		if(glbegin)
			throw new Exception("Must end shape before doing anything else");

		if(useGL && s.bpp == 124){
			int x1 = upperLeft.x;
			int y1 = upperLeft.y;
			int x2 = lowerRight.x;
			int y2 = lowerRight.y;
			glBegin(GL_LINE_LOOP);
			//glColor3b(color.r, color.g, color.b);
			setGLColor(color);
			//glColor4f(1,1,1,1);
			glVertex2i(x1, y1);
			glVertex2i(x2+1, y1);
			glVertex2i(x2, y2);
			glVertex2i(x1, y2);
			glEnd();
			return;
		}
		/*
		SDL_Rect r;
		r.x = upperLeft.x;
		r.y = upperLeft.y;
		r.w = lowerRight.x - upperLeft.x;
		r.h = lowerRight.y - upperLeft.y;
		if(s.bpp == 32)
			SDL_FillRect(s.surface, &r, color.a << 24 | color.r << 16 | color.g << 8 | color.b);
		else
			SDL_FillRect(s.surface, &r, color.r << 16 | color.g << 8 | color.b);
		*/
	}

	void gbox(Point upperLeft, Point lowerRight, Color color1, Color color2, Color color3, Color color4){
		if(special) { upperLeft += translate; lowerRight += translate; }
		if(glbegin)
			throw new Exception("Must end shape before doing anything else");

		Color color = color1;
		if(useGL && s.bpp == 124){
			int x1 = upperLeft.x;
			int y1 = upperLeft.y;
			int x2 = lowerRight.x;
			int y2 = lowerRight.y;
			glBegin(GL_QUADS);
			setGLColor(color1);
			glVertex2i(x1, y1);
			setGLColor(color2);
			glVertex2i(x2, y1);
			setGLColor(color4);
			glVertex2i(x2, y2);
			setGLColor(color3);
			glVertex2i(x1, y2);
			glEnd();
		return;
		}
		SDL_Rect r;
		r.x = cast(short) upperLeft.x;
		r.y = cast(short) upperLeft.y;
		r.w = cast(short) (lowerRight.x - upperLeft.x);
		r.h = cast(short) (lowerRight.y - upperLeft.y);
		if(s.bpp == 32)
			SDL_FillRect(s.surface, &r, color.a << 24 | color.r << 16 | color.g << 8 | color.b);
		else
			SDL_FillRect(s.surface, &r, color.r << 16 | color.g << 8 | color.b);
	}


	void clear(){
		if(useGL && s.bpp == 124){
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_ACCUM_BUFFER_BIT);
			return;
		}
		box(XY(0,0), XY(s.width, s.height), RGB(0,0,0));
	}

	void fill(Color color){
		box(XY(0,0), XY(s.width, s.height), color);
	}

	void blend(Color color){
		if(useGL && s.bpp == 124){
			box(XY(0,0), XY(s.width, s.height), color);
			return;
		}

		ubyte *bufp;

		bufp = cast(ubyte*)s.surface.pixels;
		for(int y = 0; y < s.height; y++)
			for(int x = 0; x < s.width; x++){

				bufp[2] = cast(ubyte)((bufp[2] * (255-color.a) + color.r * color.a) / 255);
			        bufp[1] = cast(ubyte)((bufp[1] * (255-color.a) + color.g * color.a) / 255);
		        	bufp[0] = cast(ubyte)((bufp[0] * (255-color.a) + color.b * color.a) / 255);

				bufp += (s.bpp == 24 ? 3 : 4);
			}
	}

  private:
  	Drawable s;
	bool locked;
}







int SDL_BlitSurface
			(SDL_Surface *src, SDL_Rect *srcrect,
			 SDL_Surface *dst, SDL_Rect *dstrect)
{
	return SDL_UpperBlit(src, srcrect, dst, dstrect);
}

bit SDL_MUSTLOCK(SDL_Surface *surface)
{
	return surface.offset || ((surface.flags &
		(SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_RLEACCEL)) != 0);
}

/* Quick utility function for texture creation */
int power_of_two(int input)
{
    int value = 1;

    while ( value < input ) {
        value <<= 1;
    }
    return value;
}

uint SDL_GL_LoadTexture(SDL_Surface *surface, float *texcoord)
{
    uint texture;
    int w, h;
    SDL_Surface *image;
    SDL_Rect area;
    uint saved_flags;
    ubyte  saved_alpha;

    /* Use the surface width and height expanded to powers of 2 */
    w = power_of_two(surface.w);
    h = power_of_two(surface.h);
    texcoord[0] = 0.0f;         /* Min X */
    texcoord[1] = 0.0f;         /* Min Y */
    texcoord[2] = cast(float)surface.w / cast(float)w;  /* Max X */
    texcoord[3] = cast(float)surface.h / cast(float)h;  /* Max Y */

    image = SDL_CreateRGBSurface(
            SDL_SWSURFACE,
            w, h,
            32,
            0x000000FF,
            0x0000FF00,
            0x00FF0000,
            0xFF000000
               );
    if ( image == null) {
        throw new Exception("make image");
    }


    /* Save the alpha blending attributes */
    saved_flags = surface.flags&(SDL_SRCALPHA|SDL_RLEACCELOK);
    saved_alpha = surface.format.alpha;
    if ( (saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA ) {
        SDL_SetAlpha(surface, 0, 0);
    }

    /* Copy the surface into the GL texture image */
    area.x = 0;
    area.y = 0;
    area.w = cast(ushort) surface.w;
    area.h = cast(ushort) surface.h;
    SDL_BlitSurface(surface, &area, image, &area);

    /* Restore the alpha blending attributes */
    if ( (saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA ) {
        SDL_SetAlpha(surface, saved_flags, saved_alpha);
    }

    /* Create an OpenGL texture for the image */
    glGenTextures(1, &texture);

    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

    glTexImage2D(GL_TEXTURE_2D,
             0,
             GL_RGBA,
             w, h,
             0,
             GL_RGBA,
             GL_UNSIGNED_BYTE,
             image.pixels);
    SDL_FreeSurface(image); /* No longer needed */



    return texture;
}






		Color c1;
		Color c2;
		Color c3;
		Color c4;
static this(){
	c1 = RGBA(0,0,255,160);
	c2 = RGBA(0,0,255,160);
	c3 = RGBA(0,0,255,160);
	c4 = RGBA(0,0,0,160);
}

void drawHighlightBox(Painter p, Point where, int width, int height = 16){
	p.gbox(where, where + XY(width, height), XRGB(c1, 128), XRGB(c2, 128), XRGB(c3, 128), XRGB(c4, 128));
}

// Real size is width + 8, height + 8. Size given if of the client area
/*
Point drawShadedRect(Painter p, Point where, int width, int height){
	int x = where.x;
	int y = where.y;

	Color gray = RGB(128,128,128);

	p.box(XY(x + 2, y), XY( x + 2 + width + 2, y + 4), gray);
	p.box(XY(x + 2, y + height + 4), XY( x + 2 + width + 2, y + 4 + height + 4 ), gray);

	p.box(XY(x, y + 2), XY(x + 4, y + 2 + height + 2), gray);
	p.box(XY(x + 4 + width, y + 2), XY(x + 4 + width + 4, y + 2 + height + 2), gray);

//	p.putpixel(XY(x + 1, y + 1), gray);
//	p.putpixel(XY(x + 1, y + 4 + 3 + height), gray);
//	p.putpixel(XY(x + 4 + width + 3, y + 1), gray);
//	p.putpixel(XY(x + 4 + width + 3, y + 4 + 3 + height), gray);


	p.hline(XY(x + 4, y + 1),              width + 2, white);
	p.hline(XY(x + 4 - 2, y + 4 + height + 1), width + 2 + 1, white);

	p.vline(XY(x + 1 - 1, y + 3),             height + 2, white);
	p.vline(XY(x + 4 + width + 3, y + 3), height + 2, white);

	p.gbox(XY(x + 4, y + 4), XY(x + width + 4, y + height + 4), c1, c2, c3, c4);

	return XY(x + 4, y + 4);
}
*/

const int BORDER_WIDTH = 4;

Point drawShadedRect(Painter p, Point where, int width, int height){
	Color gray = RGB(128,128,128);

	Point w;

	// top section
	w = where + XY( BORDER_WIDTH, 0);
		p.box( 	w + XY(0, 				0 * BORDER_WIDTH / 4),
			w + XY(width, 				1 * BORDER_WIDTH / 4),
			gray);
		p.box( 	w + XY(-BORDER_WIDTH / 2, 		1 * BORDER_WIDTH / 4),
			w + XY(BORDER_WIDTH / 2 + width, 	3 * BORDER_WIDTH / 4),
			white);
		p.box( 	w + XY( -1 * BORDER_WIDTH / 4, 		3 * BORDER_WIDTH / 4),
			w + XY( 1 * BORDER_WIDTH / 4 + width , 		4 * BORDER_WIDTH / 4),
			black);
	// bottom section
	w = where + XY(BORDER_WIDTH, height + BORDER_WIDTH);
		p.box( 	w + XY(-1 * BORDER_WIDTH / 4,		0 * BORDER_WIDTH / 4),
			w + XY(1 * BORDER_WIDTH / 4 + width,			1 * BORDER_WIDTH / 4),
			black);
		p.box( 	w + XY(-BORDER_WIDTH / 2, 		1 * BORDER_WIDTH / 4),
			w + XY(BORDER_WIDTH / 2 + width, 	3 * BORDER_WIDTH / 4),
			white);
		p.box( 	w + XY(-1 *BORDER_WIDTH / 4,		3 * BORDER_WIDTH / 4),
			w + XY( 1 *BORDER_WIDTH / 4 + width, 	4 * BORDER_WIDTH / 4),
			gray);

	// left section
	w = where + XY( 0, BORDER_WIDTH);
		p.box( 	w + XY(0 * BORDER_WIDTH / 4, -1),
			w + XY(1 * BORDER_WIDTH / 4, height + 1),
			gray);
		p.box( 	w + XY(1 * BORDER_WIDTH / 4, -BORDER_WIDTH / 2),
			w + XY(3 * BORDER_WIDTH / 4, BORDER_WIDTH / 2 + height),
			white);
		p.box( 	w + XY(3 * BORDER_WIDTH / 4, 0),
			w + XY(4 * BORDER_WIDTH / 4, height),
			black);

	// right section
	w = where + XY( BORDER_WIDTH + width, BORDER_WIDTH);
		p.box( 	w + XY(0 * BORDER_WIDTH / 4, 0),
			w + XY(1 * BORDER_WIDTH / 4, height),
			black);
		p.box( 	w + XY(1 * BORDER_WIDTH / 4, -BORDER_WIDTH / 2),
			w + XY(3 * BORDER_WIDTH / 4, BORDER_WIDTH / 2 + height),
			white);
		p.box( 	w + XY(3 * BORDER_WIDTH / 4, -1),
			w + XY(4 * BORDER_WIDTH / 4, 1 + height),
			gray);
	w = where + XY(BORDER_WIDTH, BORDER_WIDTH);
	p.gbox(w, w + XY(width, height), c1, c2, c3, c4);
	return w;
}