#include "bbgraphics.hpp"
#include "bbinput.hpp"
#include <fstream>

#include <gxmovie.hpp>

gxGraphics* gx_graphics;
gxCanvas*   gx_canvas;

struct GfxMode {
	int w, h, d, caps;
};

class bbImage {
	public:
	bbImage(const std::vector<gxCanvas*>& f) : frames(f) {}
	~bbImage()
	{
		for (int k = 0; k < frames.size(); ++k) {
			gx_graphics->freeCanvas(frames[k]);
		}
	}
	const std::vector<gxCanvas*>& getFrames() const
	{
		return frames;
	}
	void replaceFrame(int n, gxCanvas* c)
	{
		gx_graphics->freeCanvas(frames[n]);
		frames[n] = c;
	}

	private:
	std::vector<gxCanvas*> frames;
};

//degrees to radians
static const float s_degreesToRadians = 0.0174532925199432957692369076848861f;

static int gx_driver; //current graphics driver

static bool          filter;
static bool          auto_dirty;
static bool          auto_midhandle;
static std::set<bbImage*> image_set;
static int           curs_x, curs_y;
static gxCanvas*     p_canvas;

static gxFont*  curr_font;
static unsigned curr_color;
static unsigned curr_clsColor;

static std::vector<GfxMode> gfx_modes;

#ifdef _DEBUG
static inline void debugImage(bbImage* i, int frame = 0)
{
	if (debug) {
		if (!image_set.count(i))
			ThrowRuntimeException("Image does not exist");
		if (frame >= i->getFrames().size())
			ThrowRuntimeException("Image frame out of range");
	}
}

static inline void debugFont(gxFont* f)
{
	if (debug) {
		if (!gx_graphics->verifyFont(f))
			ThrowRuntimeException("Font does not exist");
	}
}

static inline void debugCanvas(gxCanvas* c)
{
	if (debug) {
		if (!gx_graphics->verifyCanvas(c))
			ThrowRuntimeException("Buffer does not exist");
	}
}

static inline void debugDriver(int n)
{
	if (debug) {
		if (n < 1 || n > gx_runtime->enumerateGraphicsDrivers()) {
			ThrowRuntimeException("Illegal graphics driver index");
		}
	}
}

static inline void debugMode(int n)
{
	if (debug) {
		if (n < 1 || n > gfx_modes.size()) {
			ThrowRuntimeException("Illegal graphics mode index");
		}
	}
}
#else
#define debugImage
#define debugFont
#define debugCanvas
#define debugDriver
#define debugMode
#endif

void bbFreeImage(bbImage* i);

static void freeGraphics()
{
	extern void blitz3d_close();
	blitz3d_close();
	while (image_set.size())
		bbFreeImage(*image_set.begin());
	if (p_canvas) {
		gx_graphics->freeCanvas(p_canvas);
		p_canvas = 0;
	}
}

#define RED(_X_) (((_X_) >> 16) & 0xff)
#define GRN(_X_) (((_X_) >> 8) & 0xff)
#define BLU(_X_) ((_X_)&0xff)

static int getPixel(gxCanvas* c, float x, float y)
{
	debugCanvas(c);

	x -= .5f;
	y -= .5f;
	float fx = floor(x), fy = floor(y);
	int   ix = fx, iy = fy;
	fx = x - fx;
	fy = y - fy;

	int tl = c->getPixel(ix, iy);
	int tr = c->getPixel(ix + 1, iy);
	int br = c->getPixel(ix + 1, iy + 1);
	int bl = c->getPixel(ix, iy + 1);

	float w1 = (1 - fx) * (1 - fy), w2 = fx * (1 - fy), w3 = (1 - fx) * fy, w4 = fx * fy;

	float r = RED(tl) * w1 + RED(tr) * w2 + RED(bl) * w3 + RED(br) * w4;
	float g = GRN(tl) * w1 + GRN(tr) * w2 + GRN(bl) * w3 + GRN(br) * w4;
	float b = BLU(tl) * w1 + BLU(tr) * w2 + BLU(bl) * w3 + BLU(br) * w4;

	return (int(r + .5f) << 16) | (int(g + .5f) << 8) | int(b + .5f);
}

struct vec2 {
	float x, y;
};

static vec2 vrot(float m[2][2], const vec2& v)
{
	vec2 t;
	t.x = m[0][0] * v.x + m[0][1] * v.y;
	t.y = m[1][0] * v.x + m[1][1] * v.y;
	return t;
}

static float vmin(float a, float b, float c, float d)
{
	float t = a;
	if (b < t)
		t = b;
	if (c < t)
		t = c;
	if (d < t)
		t = d;
	return t;
}

static float vmax(float a, float b, float c, float d)
{
	float t = a;
	if (b > t)
		t = b;
	if (c > t)
		t = c;
	if (d > t)
		t = d;
	return t;
}

static gxCanvas* tformCanvas(gxCanvas* c, float m[2][2], int x_handle, int y_handle)
{
	vec2  v, v0, v1, v2, v3;
	float i[2][2];
	float dt = 1.0f / (m[0][0] * m[1][1] - m[1][0] * m[0][1]);
	i[0][0]  = dt * m[1][1];
	i[1][0]  = -dt * m[1][0];
	i[0][1]  = -dt * m[0][1];
	i[1][1]  = dt * m[0][0];

	float ox = x_handle, oy = y_handle;
	v0.x       = -ox;
	v0.y       = -oy; //tl
	v1.x       = c->getWidth() - ox;
	v1.y       = -oy; //tr
	v2.x       = c->getWidth() - ox;
	v2.y       = c->getHeight() - oy; //br
	v3.x       = -ox;
	v3.y       = c->getHeight() - oy; //bl
	v0         = vrot(m, v0);
	v1         = vrot(m, v1);
	v2         = vrot(m, v2);
	v3         = vrot(m, v3);
	float minx = floor(vmin(v0.x, v1.x, v2.x, v3.x));
	float miny = floor(vmin(v0.y, v1.y, v2.y, v3.y));
	float maxx = ceil(vmax(v0.x, v1.x, v2.x, v3.x));
	float maxy = ceil(vmax(v0.y, v1.y, v2.y, v3.y));
	int   iw = maxx - minx, ih = maxy - miny;

	gxCanvas* t = gx_graphics->createCanvas(iw, ih, 0);
	t->setHandle(-minx, -miny);
	t->setMask(c->getMask());

	c->lock();
	t->lock();

	v.y = miny + .5f;
	for (int y = 0; y < ih; ++v.y, ++y) {
		v.x = minx + .5f;
		for (int x = 0; x < iw; ++v.x, ++x) {
			vec2     q   = vrot(i, v);
			unsigned rgb = filter ? getPixel(c, q.x + ox, q.y + oy) : c->getPixel(floor(q.x + ox), floor(q.y + oy));
			t->setPixel(x, y, rgb);
		}
	}

	t->unlock();
	c->unlock();

	return t;
}

static bool saveCanvas(gxCanvas* c, const std::string& f)
{
	std::ofstream out(f.c_str(), std::ios::binary);
	if (!out.good())
		return false;

	int tempsize = (c->getWidth() * 3 + 3) & ~3;

	BITMAPFILEHEADER bf;
	memset(&bf, 0, sizeof(bf));
	bf.bfType    = 'MB';
	bf.bfSize    = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + tempsize * c->getHeight();
	bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
	BITMAPINFOHEADER bi;
	memset(&bi, 0, sizeof(bi));
	bi.biSize     = sizeof(bi);
	bi.biWidth    = c->getWidth();
	bi.biHeight   = c->getHeight();
	bi.biPlanes   = 1;
	bi.biBitCount = 24;
	out.write((char*)&bf, sizeof(bf));
	out.write((char*)&bi, sizeof(bi));

	unsigned char* temp = new unsigned char[tempsize];
	memset(temp, 0, tempsize);

	c->lock();
	for (int y = c->getHeight() - 1; y >= 0; --y) {
		unsigned char* dest = temp;
		for (int x = 0; x < c->getWidth(); ++x) {
			unsigned rgb = c->getPixelFast(x, y);
			*dest++      = rgb & 0xff;
			*dest++      = (rgb >> 8) & 0xff;
			*dest++      = (rgb >> 16) & 0xff;
		}
		out.write((char*)temp, tempsize);
	}
	c->unlock();

	delete[] temp;

	return out.good();
}

int bbCountGfxDrivers()
{
	return gx_runtime->enumerateGraphicsDrivers();
}

BBStr* bbGfxDriverName(int n)
{
	debugDriver(n);
	std::string t;
	int    caps;
	gx_runtime->graphicsDriverInfo(n - 1, &t, &caps);
	return new BBStr(t);
}

void bbSetGfxDriver(int n)
{
	debugDriver(n);
	gfx_modes.clear();
	gx_driver = n - 1;
}

int bbCountGfxModes()
{
	gfx_modes.clear();
	int n = gx_runtime->countGraphicsModes(gx_driver);
	for (int k = 0; k < n; ++k) {
		GfxMode m;
		gx_runtime->graphicsModeInfo(gx_driver, k, &m.w, &m.h, &m.d, &m.caps);
		gfx_modes.push_back(m);
	}
	return gfx_modes.size();
}

int bbGfxModeWidth(int n)
{
	debugMode(n);
	return gfx_modes[n - 1].w;
}

int bbGfxModeHeight(int n)
{
	debugMode(n);
	return gfx_modes[n - 1].h;
}

int bbGfxModeDepth(int n)
{
	debugMode(n);
	return gfx_modes[n - 1].d;
}

static int modeExists(int w, int h, int d, bool bb3d)
{
	int cnt = gx_runtime->countGraphicsModes(gx_driver);
	for (int k = 0; k < cnt; ++k) {
		int tw, th, td, tc;
		gx_runtime->graphicsModeInfo(gx_driver, k, &tw, &th, &td, &tc);
		if (bb3d && !(tc & gxRuntime::GFXMODECAPS_3D))
			continue;
		if (w == tw && h == th && d == td)
			return 1;
	}
	return 0;
}

int bbGfxModeExists(int w, int h, int d)
{
	return modeExists(w, h, d, false);
}

int bbGfxDriver3D(int n)
{
	debugDriver(n);
	std::string t;
	int    caps;
	gx_runtime->graphicsDriverInfo(n - 1, &t, &caps);
	return (caps & gxRuntime::GFXMODECAPS_3D) ? 1 : 0;
}

int bbCountGfxModes3D()
{
	gfx_modes.clear();
	int n = gx_runtime->countGraphicsModes(gx_driver);
	for (int k = 0; k < n; ++k) {
		GfxMode m;
		gx_runtime->graphicsModeInfo(gx_driver, k, &m.w, &m.h, &m.d, &m.caps);
		if (m.caps & gxRuntime::GFXMODECAPS_3D)
			gfx_modes.push_back(m);
	}
	return gfx_modes.size();
}

int bbGfxMode3DExists(int w, int h, int d)
{
	return modeExists(w, h, d, true);
}

int bbGfxMode3D(int n)
{
	debugMode(n);
	return gfx_modes[n - 1].caps & gxRuntime::GFXMODECAPS_3D ? 1 : 0;
}

int bbWindowed3D()
{
	int tc;
	gx_runtime->windowedModeInfo(&tc);
	return (tc & gxRuntime::GFXMODECAPS_3D) ? 1 : 0;
}

int bbTotalVidMem()
{
	return gx_graphics->getTotalVidmem();
}

int bbAvailVidMem()
{
	return gx_graphics->getAvailVidmem();
}

void bbSetBuffer(gxCanvas* buff)
{
	debugCanvas(buff);
	gx_canvas = buff;
	curs_x = curs_y = 0;
	gx_canvas->setOrigin(0, 0);
	gx_canvas->setViewport(0, 0, gx_canvas->getWidth(), gx_canvas->getHeight());
	gx_canvas->setColor(curr_color);
	gx_canvas->setClsColor(curr_clsColor);
	gx_canvas->setFont(curr_font);

	// Switch Render Target
	//if (gx_graphics != nullptr) {
	//	if (gx_graphics->dir3dDev != nullptr) {
	//		LPDIRECTDRAWSURFACE7 pddsOldRenderTarget = NULL, pddsNewRenderTarget = gx_canvas->getSurface();
	//		gx_graphics->dir3dDev->GetRenderTarget(&pddsOldRenderTarget);

	//		if (pddsOldRenderTarget) {
	//			LPDIRECTDRAWSURFACE7 pddsZBuffer = NULL;
	//			DDSCAPS2 ddscaps = { DDSCAPS_ZBUFFER, 0, 0, 0 };
	//			pddsOldRenderTarget->GetAttachedSurface(&ddscaps, &pddsZBuffer);

	//			if (pddsZBuffer) {
	//				pddsOldRenderTarget->DeleteAttachedSurface(0, pddsZBuffer);
	//				pddsNewRenderTarget->AddAttachedSurface(pddsZBuffer);
	//				//pddsZBuffer->Release();
	//			}
	//			//pddsOldRenderTarget->Release();
	//		}

	//		gx_graphics->dir3dDev->SetRenderTarget(pddsNewRenderTarget, 0);
	//	}
	//}
}

gxCanvas* bbGraphicsBuffer()
{
	return gx_canvas;
}

int bbLoadBuffer(gxCanvas* c, BBStr* str)
{
	debugCanvas(c);
	std::string s = *str;
	delete str;
	gxCanvas* t = gx_graphics->loadCanvas(s, 0);
	if (!t)
		return 0;
	float m[2][2];
	m[0][0] = (float)c->getWidth() / (float)t->getWidth();
	m[1][1] = (float)c->getHeight() / (float)t->getHeight();
	m[1][0] = m[0][1] = 0;
	gxCanvas* p       = tformCanvas(t, m, 0, 0);
	gx_graphics->freeCanvas(t);
	int ox, oy;
	c->getOrigin(&ox, &oy);
	c->setOrigin(0, 0);
	c->blit(0, 0, p, 0, 0, p->getWidth(), p->getHeight(), true);
	gx_graphics->freeCanvas(p);
	return 1;
}

int bbSaveBuffer(gxCanvas* c, BBStr* str)
{
	debugCanvas(c);
	std::string t = *str;
	delete str;
	return saveCanvas(c, t) ? 1 : 0;
}

void bbBufferDirty(gxCanvas* c)
{
	debugCanvas(c);
	c->backup();
}

static void graphics(int w, int h, int d, int flags)
{
	freeGraphics();
	gx_runtime->closeGraphics(gx_graphics);
	gx_graphics = gx_runtime->openGraphics(w, h, d, gx_driver, flags);
	if (!gx_runtime->idle())
		ThrowRuntimeException(0);
	if (!gx_graphics) {
		ThrowRuntimeException("Unable to set graphics mode");
	}
	curr_clsColor  = 0;
	curr_color     = 0xffffffff;
	curr_font      = gx_graphics->getDefaultFont();
	gxCanvas* buff = (flags & gxGraphics::GRAPHICS_3D) ? gx_graphics->getBackCanvas() : gx_graphics->getFrontCanvas();
	bbSetBuffer(buff);
}

void bbGraphics(int w, int h, int d, int mode)
{
	int flags = 0;
	switch (mode) {
	case 0:
		flags |= debug ? gxGraphics::GRAPHICS_WINDOWED : 0;
		break;
	case 1:
		break;
	case 2:
		flags |= gxGraphics::GRAPHICS_WINDOWED;
		break;
	case 3:
		flags |= gxGraphics::GRAPHICS_WINDOWED | gxGraphics::GRAPHICS_SCALED;
		break;
	case 6:
		flags |= gxGraphics::GRAPHICS_WINDOWED | gxGraphics::GRAPHICS_AUTOSUSPEND;
		break;
	case 7:
		flags |= gxGraphics::GRAPHICS_WINDOWED | gxGraphics::GRAPHICS_SCALED | gxGraphics::GRAPHICS_AUTOSUSPEND;
		break;
	default:
		ThrowRuntimeException("Illegal Graphics mode");
	}
	graphics(w, h, d, flags);
}

void bbGraphics3D(int w, int h, int d, int mode)
{
	int flags = gxGraphics::GRAPHICS_3D;
	switch (mode) {
	case 0:
		flags |= (debug && bbWindowed3D()) ? gxGraphics::GRAPHICS_WINDOWED : 0;
		break;
	case 1:
		break;
	case 2:
		flags |= gxGraphics::GRAPHICS_WINDOWED;
		break;
	case 3:
		flags |= gxGraphics::GRAPHICS_WINDOWED | gxGraphics::GRAPHICS_SCALED;
		break;
	case 6:
		flags |= gxGraphics::GRAPHICS_WINDOWED | gxGraphics::GRAPHICS_AUTOSUSPEND;
		break;
	case 7:
		flags |= gxGraphics::GRAPHICS_WINDOWED | gxGraphics::GRAPHICS_SCALED | gxGraphics::GRAPHICS_AUTOSUSPEND;
		break;
	default:
		ThrowRuntimeException("Illegal Graphics3D mode");
	}
	graphics(w, h, d, flags);
	extern void blitz3d_open();
	blitz3d_open();
}

void bbEndGraphics()
{
	freeGraphics();
	gx_runtime->closeGraphics(gx_graphics);
	gx_graphics = gx_runtime->openGraphics(400, 300, 0, 0, gxGraphics::GRAPHICS_WINDOWED);
	if (!gx_runtime->idle())
		ThrowRuntimeException(0);
	if (gx_graphics) {
		curr_clsColor = 0;
		curr_color    = 0xffffffff;
		curr_font     = gx_graphics->getDefaultFont();
		bbSetBuffer(gx_graphics->getFrontCanvas());
		return;
	}
	ThrowRuntimeException("Unable to set graphics mode");
}

int bbGraphicsLost()
{
	return gx_runtime->graphicsLost();
}

void bbSetGamma(int r, int g, int b, float dr, float dg, float db)
{
	if (dr < 0)
		dr = 0;
	else if (dr > 255.0f)
		dr = 255.0f;
	if (dg < 0)
		dg = 0;
	else if (dg > 255.0f)
		dg = 255.0f;
	if (db < 0)
		db = 0;
	else if (db > 255.0f)
		db = 255.0f;
	gx_graphics->setGamma(r, g, b, dr, dg, db);
}

void bbUpdateGamma(int calibrate)
{
	gx_graphics->updateGamma(!!calibrate);
}

float bbGammaRed(int n)
{
	float dr, dg, db;
	gx_graphics->getGamma(n, n, n, &dr, &dg, &db);
	return dr;
}

float bbGammaGreen(int n)
{
	float dr, dg, db;
	gx_graphics->getGamma(n, n, n, &dr, &dg, &db);
	return dg;
}

float bbGammaBlue(int n)
{
	float dr, dg, db;
	gx_graphics->getGamma(n, n, n, &dr, &dg, &db);
	return db;
}

gxCanvas* bbFrontBuffer()
{
	return gx_graphics->getFrontCanvas();
}

gxCanvas* bbBackBuffer()
{
	return gx_graphics->getBackCanvas();
}

void bbLockBuffer(gxCanvas* buff)
{
	if (buff)
		debugCanvas(buff);
	(buff ? buff : gx_canvas)->lock();
}

void bbUnlockBuffer(gxCanvas* buff)
{
	if (buff)
		debugCanvas(buff);
	(buff ? buff : gx_canvas)->unlock();
}

int bbReadPixel(int x, int y, gxCanvas* buff)
{
	if (buff)
		debugCanvas(buff);
	return (buff ? buff : gx_canvas)->getPixel(x, y);
}

void bbWritePixel(int x, int y, int argb, gxCanvas* buff)
{
	if (buff)
		debugCanvas(buff);
	(buff ? buff : gx_canvas)->setPixel(x, y, argb);
}

int bbReadPixelFast(int x, int y, gxCanvas* buff)
{
	return (buff ? buff : gx_canvas)->getPixelFast(x, y);
}

void bbWritePixelFast(int x, int y, int argb, gxCanvas* buff)
{
	(buff ? buff : gx_canvas)->setPixelFast(x, y, argb);
}

void bbCopyPixel(int src_x, int src_y, gxCanvas* src, int dest_x, int dest_y, gxCanvas* buff)
{
	(buff ? buff : gx_canvas)->copyPixel(dest_x, dest_y, src ? src : gx_canvas, src_x, src_y);
}

void bbCopyPixelFast(int src_x, int src_y, gxCanvas* src, int dest_x, int dest_y, gxCanvas* buff)
{
	(buff ? buff : gx_canvas)->copyPixelFast(dest_x, dest_y, src ? src : gx_canvas, src_x, src_y);
}

int bbScanLine()
{
	return gx_graphics->getScanLine();
}

void bbVWait(int n)
{
	gx_graphics->vwait();
	if (!gx_runtime->idle())
		ThrowRuntimeException(0);
}

void bbFlip(int vwait)
{
	gx_graphics->flip(vwait ? true : false);
	if (!gx_runtime->idle())
		ThrowRuntimeException(0);
}

int bbGraphicsWidth()
{
	return gx_graphics->getWidth();
}

int bbGraphicsHeight()
{
	return gx_graphics->getHeight();
}

int bbGraphicsDepth()
{
	return gx_graphics->getDepth();
}

void bbOrigin(int x, int y)
{
	gx_canvas->setOrigin(x, y);
}

void bbViewport(int x, int y, int w, int h)
{
	gx_canvas->setViewport(x, y, w, h);
}

void bbColor(int r, int g, int b)
{
	gx_canvas->setColor(curr_color = (r << 16) | (g << 8) | b);
}

void bbGetColor(int x, int y)
{
	gx_canvas->setColor(curr_color = gx_canvas->getPixel(x, y));
}

int bbColorRed()
{
	return (gx_canvas->getColor() >> 16) & 0xff;
}

int bbColorGreen()
{
	return (gx_canvas->getColor() >> 8) & 0xff;
}

int bbColorBlue()
{
	return gx_canvas->getColor() & 0xff;
}

void bbClsColor(int r, int g, int b)
{
	gx_canvas->setClsColor(curr_clsColor = (r << 16) | (g << 8) | b);
}

void bbSetFont(gxFont* f)
{
	debugFont(f);
	gx_canvas->setFont(curr_font = f);
}

void bbCls()
{
	gx_canvas->cls();
}

void bbPlot(int x, int y)
{
	gx_canvas->plot(x, y);
}

void bbLine(int x1, int y1, int x2, int y2)
{
	gx_canvas->line(x1, y1, x2, y2);
}

void bbRect(int x, int y, int w, int h, int solid)
{
	gx_canvas->rect(x, y, w, h, solid ? true : false);
}

void bbOval(int x, int y, int w, int h, int solid)
{
	gx_canvas->oval(x, y, w, h, solid ? true : false);
}

void bbText(int x, int y, BBStr* str, int centre_x, int centre_y)
{
	if (centre_x)
		x -= curr_font->getWidth(*str) / 2;
	if (centre_y)
		y -= curr_font->getHeight() / 2;
	gx_canvas->text(x, y, *str);
	delete str;
}

void bbCopyRect(int sx, int sy, int w, int h, int dx, int dy, gxCanvas* src, gxCanvas* dest)
{
	if (src)
		debugCanvas(src);
	else
		src = gx_canvas;
	if (dest)
		debugCanvas(dest);
	else
		dest = gx_canvas;
	dest->blit(dx, dy, src, sx, sy, w, h, true);
}

gxFont* bbLoadFont(BBStr* name, int height, int bold, int italic, int underline)
{
	int flags =
		(bold ? gxFont::FONT_BOLD : 0) | (italic ? gxFont::FONT_ITALIC : 0) | (underline ? gxFont::FONT_UNDERLINE : 0);
	gxFont* font = gx_graphics->loadFont(*name, height, flags);
	delete name;
	return font;
}

void bbFreeFont(gxFont* f)
{
	debugFont(f);
	if (f == curr_font)
		bbSetFont(gx_graphics->getDefaultFont());
	gx_graphics->freeFont(f);
}

int bbFontWidth()
{
	return curr_font->getWidth();
}

int bbFontHeight()
{
	return curr_font->getHeight();
}

int bbStringWidth(BBStr* str)
{
	std::string t = *str;
	delete str;
	return curr_font->getWidth(t);
}

int bbStringHeight(BBStr* str)
{
	delete str;
	return curr_font->getHeight();
}

gxMovie* bbOpenMovie(BBStr* s)
{
	gxMovie* movie = gx_graphics->openMovie(*s, 0);
	delete s;
	return movie;
}

int bbDrawMovie(gxMovie* movie, int x, int y, int w, int h)
{
	if (w < 0)
		w = movie->getWidth();
	if (h < 0)
		h = movie->getHeight();
	int playing = movie->draw(gx_canvas, x, y, w, h);
	if (!gx_runtime->idle())
		ThrowRuntimeException(0);
	return playing;
}

int bbMovieWidth(gxMovie* movie)
{
	return movie->getWidth();
}

int bbMovieHeight(gxMovie* movie)
{
	return movie->getHeight();
}

int bbMoviePlaying(gxMovie* movie)
{
	return movie->isPlaying();
}

void bbCloseMovie(gxMovie* movie)
{
	gx_graphics->closeMovie(movie);
}

bbImage* bbLoadImage(BBStr* s)
{
	std::string t = *s;
	delete s;
	gxCanvas* c = gx_graphics->loadCanvas(t, 0);
	if (!c)
		return 0;
	if (auto_dirty)
		c->backup();
	if (auto_midhandle)
		c->setHandle(c->getWidth() / 2, c->getHeight() / 2);
	std::vector<gxCanvas*> frames;
	frames.push_back(c);
	bbImage* i = new bbImage(frames);
	image_set.insert(i);
	return i;
}

bbImage* bbLoadAnimImage(BBStr* s, int w, int h, int first, int cnt)
{
	std::string t = *s;
	delete s;

	if (cnt < 1)
		ThrowRuntimeException("Illegal frame count");
	if (first < 0)
		ThrowRuntimeException("Illegal first frame");

	gxCanvas* pic = gx_graphics->loadCanvas(t, gxCanvas::CANVAS_NONDISPLAY);
	if (!pic)
		return 0;

	//frames per row, per picture
	int fpr = pic->getWidth() / w;
	int fpp = pic->getHeight() / h * fpr;
	if (first + cnt > fpp) {
		gx_graphics->freeCanvas(pic);
		ThrowRuntimeException("Not enough frames in bitmap");
	}

	//x,y of first frame...
	std::vector<gxCanvas*> frames;
	int               src_x = first % fpr * w, src_y = first / fpr * h;

	for (int k = 0; k < cnt; ++k) {
		gxCanvas* c = gx_graphics->createCanvas(w, h, 0);
		if (!c) {
			for (--k; k >= 0; --k)
				gx_graphics->freeCanvas(frames[k]);
			gx_graphics->freeCanvas(pic);
			return 0;
		}
		c->blit(0, 0, pic, src_x, src_y, w, h, true);
		if (auto_dirty)
			c->backup();
		if (auto_midhandle)
			c->setHandle(c->getWidth() / 2, c->getHeight() / 2);
		frames.push_back(c);
		src_x += w;
		if (src_x + w > pic->getWidth()) {
			src_x = 0;
			src_y += h;
		}
	}
	gx_graphics->freeCanvas(pic);
	bbImage* i = new bbImage(frames);
	image_set.insert(i);
	return i;
}

bbImage* bbCopyImage(bbImage* i)
{
	debugImage(i);
	std::vector<gxCanvas*>   frames;
	const std::vector<gxCanvas*>& f = i->getFrames();
	for (int k = 0; k < f.size(); ++k) {
		gxCanvas* t = f[k];
		gxCanvas* c = gx_graphics->createCanvas(t->getWidth(), t->getHeight(), 0);
		if (!c) {
			for (--k; k >= 0; --k)
				gx_graphics->freeCanvas(frames[k]);
			return 0;
		}
		int x, y;
		t->getHandle(&x, &y);
		t->setHandle(0, 0);
		c->blit(0, 0, t, 0, 0, t->getWidth(), t->getHeight(), true);
		if (auto_dirty)
			c->backup();
		t->setHandle(x, y);
		c->setHandle(x, y);
		c->setMask(t->getMask());
		frames.push_back(c);
	}
	bbImage* t = new bbImage(frames);
	image_set.insert(t);
	return t;
}

bbImage* bbCreateImage(int w, int h, int n)
{
	std::vector<gxCanvas*> frames;
	for (int k = 0; k < n; ++k) {
		gxCanvas* c = gx_graphics->createCanvas(w, h, 0);
		if (!c) {
			for (--k; k >= 0; --k)
				gx_graphics->freeCanvas(frames[k]);
			return 0;
		}
		if (auto_dirty)
			c->backup();
		if (auto_midhandle)
			c->setHandle(c->getWidth() / 2, c->getHeight() / 2);
		frames.push_back(c);
	}
	bbImage* i = new bbImage(frames);
	image_set.insert(i);
	return i;
}

void bbFreeImage(bbImage* i)
{
	if (!image_set.erase(i))
		return;
	const std::vector<gxCanvas*>& f = i->getFrames();
	for (int k = 0; k < f.size(); ++k) {
		if (f[k] == gx_canvas) {
			bbSetBuffer(gx_graphics->getFrontCanvas());
			break;
		}
	}
	delete i;
}

int bbSaveImage(bbImage* i, BBStr* str, int n)
{
	debugImage(i, n);
	std::string t = *str;
	delete str;
	gxCanvas* c = i->getFrames()[n];
	return saveCanvas(c, t) ? 1 : 0;
}

void bbGrabImage(bbImage* i, int x, int y, int n)
{
	debugImage(i, n);
	gxCanvas* c = i->getFrames()[n];
	int       src_ox, src_oy, dst_hx, dst_hy;
	gx_canvas->getOrigin(&src_ox, &src_oy);
	c->getHandle(&dst_hx, &dst_hy);
	x += src_ox - dst_hx;
	y += src_oy - dst_hy;
	c->setViewport(0, 0, c->getWidth(), c->getHeight());
	c->blit(0, 0, gx_canvas, x, y, c->getWidth(), c->getHeight(), true);
	if (auto_dirty)
		c->backup();
}

gxCanvas* bbImageBuffer(bbImage* i, int n)
{
	debugImage(i, n);
	return i->getFrames()[n];
}

void bbDrawImage(bbImage* i, int x, int y, int frame)
{
	debugImage(i, frame);
	gxCanvas* c = i->getFrames()[frame];
	gx_canvas->blit(x, y, c, 0, 0, c->getWidth(), c->getHeight(), false);
}

void bbDrawBlock(bbImage* i, int x, int y, int frame)
{
	debugImage(i, frame);
	gxCanvas* c = i->getFrames()[frame];
	gx_canvas->blit(x, y, c, 0, 0, c->getWidth(), c->getHeight(), true);
}

static void tile(bbImage* i, int x, int y, int frame, bool solid)
{
	gxCanvas* c = i->getFrames()[frame];

	int hx, hy;
	c->getHandle(&hx, &hy);
	int w = c->getWidth(), h = c->getHeight();

	int ox, oy, vp_x, vp_y, vp_w, vp_h;
	gx_canvas->getOrigin(&ox, &oy);
	gx_canvas->getViewport(&vp_x, &vp_y, &vp_w, &vp_h);
	int dx = vp_x - ox + hx;
	int dy = vp_y - oy + hy;
	x -= dx;
	y -= dy;
	dx += (x >= 0 ? x % w : w - (-x % w));
	dy += (y >= 0 ? y % h : h - (-y % h));

	for (y = -h; y < vp_h; y += h) {
		for (x = -w; x < vp_w; x += w) {
			gx_canvas->blit(x + dx, y + dy, c, 0, 0, w, h, solid);
		}
	}
}

void bbTileImage(bbImage* i, int x, int y, int frame)
{
	debugImage(i, frame);
	tile(i, x, y, frame, false);
}

void bbTileBlock(bbImage* i, int x, int y, int frame)
{
	debugImage(i, frame);
	tile(i, x, y, frame, true);
}

void bbDrawImageRect(bbImage* i, int x, int y, int r_x, int r_y, int r_w, int r_h, int frame)
{
	debugImage(i, frame);
	gxCanvas* c = i->getFrames()[frame];
	gx_canvas->blit(x, y, c, r_x, r_y, r_w, r_h, false);
}

void bbDrawBlockRect(bbImage* i, int x, int y, int r_x, int r_y, int r_w, int r_h, int frame)
{
	debugImage(i, frame);
	gxCanvas* c = i->getFrames()[frame];
	gx_canvas->blit(x, y, c, r_x, r_y, r_w, r_h, true);
}

void bbMaskImage(bbImage* i, int r, int g, int b)
{
	debugImage(i);
	unsigned                 argb = (r << 16) | (g << 8) | b;
	const std::vector<gxCanvas*>& f    = i->getFrames();
	for (int k = 0; k < f.size(); ++k)
		f[k]->setMask(argb);
}

void bbHandleImage(bbImage* i, int x, int y)
{
	debugImage(i);
	const std::vector<gxCanvas*>& f = i->getFrames();
	for (int k = 0; k < f.size(); ++k)
		f[k]->setHandle(x, y);
}

void bbMidHandle(bbImage* i)
{
	debugImage(i);
	const std::vector<gxCanvas*>& f = i->getFrames();
	for (int k = 0; k < f.size(); ++k)
		f[k]->setHandle(f[k]->getWidth() / 2, f[k]->getHeight() / 2);
}

void bbAutoMidHandle(int enable)
{
	auto_midhandle = enable ? true : false;
}

int bbImageWidth(bbImage* i)
{
	debugImage(i);
	return i->getFrames()[0]->getWidth();
}

int bbImageHeight(bbImage* i)
{
	debugImage(i);
	return i->getFrames()[0]->getHeight();
}

int bbImageXHandle(bbImage* i)
{
	debugImage(i);
	int x, y;
	i->getFrames()[0]->getHandle(&x, &y);
	return x;
}

int bbImageYHandle(bbImage* i)
{
	debugImage(i);
	int x, y;
	i->getFrames()[0]->getHandle(&x, &y);
	return y;
}

int bbImagesOverlap(bbImage* i1, int x1, int y1, bbImage* i2, int x2, int y2)
{
	debugImage(i1);
	debugImage(i2);
	gxCanvas* c1 = i1->getFrames()[0];
	gxCanvas* c2 = i2->getFrames()[0];
	return c1->collide(x1, y1, c2, x2, y2, true);
}

int bbImagesCollide(bbImage* i1, int x1, int y1, int f1, bbImage* i2, int x2, int y2, int f2)
{
	debugImage(i1, f1);
	debugImage(i2, f2);
	gxCanvas* c1 = i1->getFrames()[f1];
	gxCanvas* c2 = i2->getFrames()[f2];
	return c1->collide(x1, y1, c2, x2, y2, false);
}

int bbRectsOverlap(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2)
{
	if (x1 + w1 <= x2 || x1 >= x2 + w2 || y1 + h1 <= y2 || y1 >= y2 + h2)
		return 0;
	return 1;
}

int bbImageRectOverlap(bbImage* i, int x, int y, int x2, int y2, int w2, int h2)
{
	debugImage(i);
	gxCanvas* c = i->getFrames()[0];
	return c->rect_collide(x, y, x2, y2, w2, h2, true);
}

int bbImageRectCollide(bbImage* i, int x, int y, int f, int x2, int y2, int w2, int h2)
{
	debugImage(i, f);
	gxCanvas* c = i->getFrames()[f];
	return c->rect_collide(x, y, x2, y2, w2, h2, false);
}

void bbTFormImage(bbImage* i, float a, float b, float c, float d)
{
	debugImage(i);
	const std::vector<gxCanvas*>& f = i->getFrames();
	int                      k;
	for (k = 0; k < f.size(); ++k) {
		if (f[k] == gx_canvas) {
			bbSetBuffer(gx_graphics->getFrontCanvas());
			break;
		}
	}
	float m[2][2];
	m[0][0] = a;
	m[1][0] = b;
	m[0][1] = c;
	m[1][1] = d;
	for (k = 0; k < f.size(); ++k) {
		gxCanvas* c = f[k];
		int       hx, hy;
		c->getHandle(&hx, &hy);
		gxCanvas* t = tformCanvas(c, m, hx, hy);
		i->replaceFrame(k, t);
		t->backup();
	}
}

void bbScaleImage(bbImage* i, float w, float h)
{
	debugImage(i);
	bbTFormImage(i, w, 0, 0, h);
}

void bbResizeImage(bbImage* i, float w, float h)
{
	debugImage(i);
	gxCanvas* c = i->getFrames()[0];
	bbTFormImage(i, w / (float)c->getWidth(), 0, 0, h / (float)c->getHeight());
}

void bbRotateImage(bbImage* i, float d)
{
	debugImage(i);
	d *= -s_degreesToRadians;
	bbTFormImage(i, cos(d), -sin(d), sin(d), cos(d));
}

void bbTFormFilter(int enable)
{
	filter = enable ? true : false;
}

static int p_ox, p_oy, p_hx, p_hy, p_vpx, p_vpy, p_vpw, p_vph;

static gxCanvas* startPrinting()
{
	gxCanvas* c = gx_graphics->getFrontCanvas();

	c->lock();
	c->unlock();

	c->getOrigin(&p_ox, &p_oy);
	c->getHandle(&p_hx, &p_hy);
	c->getViewport(&p_vpx, &p_vpy, &p_vpw, &p_vph);

	c->setOrigin(0, 0);
	c->setHandle(0, 0);
	c->setViewport(0, 0, c->getWidth(), c->getHeight());
	if (c != gx_canvas) {
		c->setFont(curr_font);
		c->setColor(curr_color);
	}

	int dy = curs_y + curr_font->getHeight() - c->getHeight();
	if (dy > 0) {
		curs_y = c->getHeight() - curr_font->getHeight();
		c->blit(0, 0, c, 0, dy, c->getWidth(), c->getHeight() - dy, true);
		c->setColor(curr_clsColor);
		c->rect(0, c->getHeight() - dy, c->getWidth(), dy, true);
		c->setColor(curr_color);
	}
	return c;
}

static void endPrinting(gxCanvas* c)
{
	c->setViewport(p_vpx, p_vpy, p_vpw, p_vph);
	c->setHandle(p_hx, p_hy);
	c->setOrigin(p_ox, p_oy);
	if (c == gx_canvas)
		c->setColor(curr_color);
	if (!gx_runtime->idle())
		ThrowRuntimeException(0);
}

void bbWrite(BBStr* str)
{
	gxCanvas* c = startPrinting();
	c->text(curs_x, curs_y, *str);
	curs_x += curr_font->getWidth(*str);
	endPrinting(c);
	delete str;
}

void bbPrint(BBStr* str)
{
	gxCanvas* c = startPrinting();
	c->text(curs_x, curs_y, *str);
	curs_x = 0;
	curs_y += curr_font->getHeight();
	endPrinting(c);
	delete str;
}

BBStr* bbInput(BBStr* prompt)
{
	gxCanvas* c = startPrinting();
	std::string t = *prompt;
	delete prompt;

	//get temp canvas
	if (!p_canvas || p_canvas->getWidth() < c->getWidth() || p_canvas->getHeight() < curr_font->getHeight() * 2) {
		if (p_canvas)
			gx_graphics->freeCanvas(p_canvas);
		p_canvas = gx_graphics->createCanvas(c->getWidth(), curr_font->getHeight() * 2, 0);
		if (!p_canvas) {
			endPrinting(c);
			return new BBStr();
		}
	}
	//draw prompt
	c->text(curs_x, curs_y, t);
	curs_x += curr_font->getWidth(t);

	p_canvas->setFont(curr_font);
	p_canvas->setColor(curr_color);
	p_canvas->blit(0, 0, c, 0, curs_y, c->getWidth(), curr_font->getHeight(), true);

	std::string str;
	bool   go   = true;
	int    curs = 0, last_key = 0, last_time, rep_delay;

	while (go) {
		//render all text
		//calc curs x and width
		int cx = curs_x + curr_font->getWidth(str.substr(0, curs));
		int cw = curr_font->getWidth(curs < str.size() ? str.substr(curs, 1) : "X");

		//wait for a key
		int key = 0, st = gx_runtime->getMilliSecs(), tc = -1;

		while (gx_runtime->idle()) {
			int t = gx_runtime->getMilliSecs();
			int n = (t - st) / 320;
			if (n != tc) {
				tc = n;
				if (!(tc & 1)) { //cursor ON
					c->setColor(curr_clsColor ^ 0xffffff);
					c->rect(cx, curs_y, cw, curr_font->getHeight(), true);
					c->setColor(curr_clsColor);
				} else { //cursor OFF
					c->blit(cx, curs_y, p_canvas, cx, 0, cw, curr_font->getHeight(), true);
					c->setColor(curr_color);
				}
				c->text(cx, curs_y, str.substr(curs, 1));
			}
			if (key = gx_keyboard->getKey()) {
				if (int asc = gx_input->toAscii(key)) {
					rep_delay = 280;
					last_key  = key;
					last_time = t;
					key       = asc;
					break;
				}
			}
			if (last_key && gx_keyboard->keyDown(last_key)) {
				if (t - last_time > rep_delay) {
					if (key = gx_input->toAscii(last_key)) {
						last_time += rep_delay;
						rep_delay = 40;
						break;
					}
				}
			} else
				last_key = 0;
			gx_runtime->delay(20);
		}

		//check the key
		switch (key) {
		case 0:
			go  = false;
			str = "";
			break;
		case 8:
			if (curs) {
				str = str.substr(0, curs - 1) + str.substr(curs);
				--curs;
			}
			break;
		case 27:
			curs = 0;
			str  = "";
			break;
		case gxInput::ASC_DELETE:
			if (curs < str.size())
				str = str.substr(0, curs) + str.substr(curs + 1);
			break;
		case gxInput::ASC_HOME:
			curs = 0;
			break;
		case gxInput::ASC_END:
			curs = str.size();
			break;
		case gxInput::ASC_LEFT:
			if (curs)
				--curs;
			break;
		case gxInput::ASC_RIGHT:
			if (curs < str.size())
				++curs;
			break;
		case '\r':
			go = false;
			break;
		default:
			if (curr_font->isPrintable(key)) {
				str = str.substr(0, curs) + char(key) + str.substr(curs);
				++curs;
			}
		}

		//render text
		p_canvas->blit(0, curr_font->getHeight(), p_canvas, 0, 0, c->getWidth(), curr_font->getHeight(), true);
		p_canvas->text(curs_x, curr_font->getHeight(), str);
		c->blit(0, curs_y, p_canvas, 0, curr_font->getHeight(), c->getWidth(), curr_font->getHeight(), true);
	}

	curs_x = 0;
	curs_y += curr_font->getHeight();
	endPrinting(c);
	return new BBStr(str);
}

void bbLocate(int x, int y)
{
	gxCanvas* c = gx_graphics->getFrontCanvas();
	curs_x      = x < 0 ? 0 : (x > c->getWidth() ? c->getWidth() : x);
	curs_y      = y < 0 ? 0 : (y > c->getHeight() ? c->getHeight() : y);
}

void bbShowPointer()
{
	gx_runtime->setPointerVisible(true);
}

void bbHidePointer()
{
	gx_runtime->setPointerVisible(false);
}

bool graphics_create()
{
	p_canvas  = 0;
	filter    = true;
	gx_driver = 0;
	freeGraphics();
	auto_dirty     = true;
	auto_midhandle = false;
	gx_graphics    = gx_runtime->openGraphics(400, 300, 0, 0, gxGraphics::GRAPHICS_WINDOWED);
	if (gx_graphics) {
		curr_clsColor = 0;
		curr_color    = 0xffffffff;
		curr_font     = gx_graphics->getDefaultFont();
		bbSetBuffer(bbFrontBuffer());
		return true;
	}
	return false;
}

bool graphics_destroy()
{
	freeGraphics();
	gfx_modes.clear();
	if (gx_graphics) {
		gx_runtime->closeGraphics(gx_graphics);
		gx_graphics = 0;
	}
	return true;
}

void graphics_link(void (*rtSym)(const char* sym, void* pc))
{
	//gfx driver info
	rtSym("%CountGfxDrivers", bbCountGfxDrivers);
	rtSym("$GfxDriverName%driver", bbGfxDriverName);
	rtSym("SetGfxDriver%driver", bbSetGfxDriver);

	//gfx mode info
	rtSym("%CountGfxModes", bbCountGfxModes);
	rtSym("%GfxModeExists%width%height%depth", bbGfxModeExists);

	rtSym("%GfxModeWidth%mode", bbGfxModeWidth);
	rtSym("%GfxModeHeight%mode", bbGfxModeHeight);
	rtSym("%GfxModeDepth%mode", bbGfxModeDepth);
	rtSym("%AvailVidMem", bbAvailVidMem);
	rtSym("%TotalVidMem", bbTotalVidMem);

	rtSym("%GfxDriver3D%driver", bbGfxDriver3D);
	rtSym("%CountGfxModes3D", bbCountGfxModes3D);
	rtSym("%GfxMode3DExists%width%height%depth", bbGfxMode3DExists);
	rtSym("%GfxMode3D%mode", bbGfxMode3D);
	rtSym("%Windowed3D", bbWindowed3D);

	//display mode
	//	rtSym( "Graphics%width%height%depth=0%mode=0",bbGraphics );
	rtSym("Graphics3D%width%height%depth=0%mode=0", bbGraphics3D);
	rtSym("EndGraphics", bbEndGraphics);
	rtSym("%GraphicsLost", bbGraphicsLost);

	rtSym("SetGamma%src_red%src_green%src_blue#dest_red#dest_green#dest_blue", bbSetGamma);
	rtSym("UpdateGamma%calibrate=0", bbUpdateGamma);
	rtSym("#GammaRed%red", bbGammaRed);
	rtSym("#GammaGreen%green", bbGammaGreen);
	rtSym("#GammaBlue%blue", bbGammaBlue);

	rtSym("%FrontBuffer", bbFrontBuffer);
	rtSym("%BackBuffer", bbBackBuffer);
	rtSym("%ScanLine", bbScanLine);
	rtSym("VWait%frames=1", bbVWait);
	rtSym("Flip%vwait=1", bbFlip);
	rtSym("%GraphicsWidth", bbGraphicsWidth);
	rtSym("%GraphicsHeight", bbGraphicsHeight);
	rtSym("%GraphicsDepth", bbGraphicsDepth);

	//buffer management
	rtSym("SetBuffer%buffer", bbSetBuffer);
	rtSym("%GraphicsBuffer", bbGraphicsBuffer);
	rtSym("%LoadBuffer%buffer$bmpfile", bbLoadBuffer);
	rtSym("%SaveBuffer%buffer$bmpfile", bbSaveBuffer);
	rtSym("BufferDirty%buffer", bbBufferDirty);

	//fast pixel reads/write
	rtSym("LockBuffer%buffer=0", bbLockBuffer);
	rtSym("UnlockBuffer%buffer=0", bbUnlockBuffer);
	rtSym("%ReadPixel%x%y%buffer=0", bbReadPixel);
	rtSym("WritePixel%x%y%argb%buffer=0", bbWritePixel);
	rtSym("%ReadPixelFast%x%y%buffer=0", bbReadPixelFast);
	rtSym("WritePixelFast%x%y%argb%buffer=0", bbWritePixelFast);
	rtSym("CopyPixel%src_x%src_y%src_buffer%dest_x%dest_y%dest_buffer=0", bbCopyPixel);
	rtSym("CopyPixelFast%src_x%src_y%src_buffer%dest_x%dest_y%dest_buffer=0", bbCopyPixelFast);

	//rendering
	rtSym("Origin%x%y", bbOrigin);
	rtSym("Viewport%x%y%width%height", bbViewport);
	rtSym("Color%red%green%blue", bbColor);
	rtSym("GetColor%x%y", bbGetColor);
	rtSym("%ColorRed", bbColorRed);
	rtSym("%ColorGreen", bbColorGreen);
	rtSym("%ColorBlue", bbColorBlue);
	rtSym("ClsColor%red%green%blue", bbClsColor);
	rtSym("SetFont%font", bbSetFont);
	rtSym("Cls", bbCls);
	rtSym("Plot%x%y", bbPlot);
	rtSym("Rect%x%y%width%height%solid=1", bbRect);
	rtSym("Oval%x%y%width%height%solid=1", bbOval);
	rtSym("Line%x1%y1%x2%y2", bbLine);
	rtSym("Text%x%y$text%centre_x=0%centre_y=0", bbText);
	rtSym("CopyRect%source_x%source_y%width%height%dest_x%dest_y%src_buffer=0%dest_buffer=0", bbCopyRect);

	//fonts
	rtSym("%LoadFont$fontname%height=12%bold=0%italic=0%underline=0", bbLoadFont);
	rtSym("FreeFont%font", bbFreeFont);
	rtSym("%FontWidth", bbFontWidth);
	rtSym("%FontHeight", bbFontHeight);
	rtSym("%StringWidth$string", bbStringWidth);
	rtSym("%StringHeight$string", bbStringHeight);

	//movies
	rtSym("%OpenMovie$file", bbOpenMovie);
	rtSym("%DrawMovie%movie%x=0%y=0%w=-1%h=-1", bbDrawMovie);
	rtSym("%MovieWidth%movie", bbMovieWidth);
	rtSym("%MovieHeight%movie", bbMovieHeight);
	rtSym("%MoviePlaying%movie", bbMoviePlaying);
	rtSym("CloseMovie%movie", bbCloseMovie);

	rtSym("%LoadImage$bmpfile", bbLoadImage);
	rtSym("%LoadAnimImage$bmpfile%cellwidth%cellheight%first%count", bbLoadAnimImage);
	rtSym("%CopyImage%image", bbCopyImage);
	rtSym("%CreateImage%width%height%frames=1", bbCreateImage);
	rtSym("FreeImage%image", bbFreeImage);
	rtSym("%SaveImage%image$bmpfile%frame=0", bbSaveImage);

	rtSym("GrabImage%image%x%y%frame=0", bbGrabImage);
	rtSym("%ImageBuffer%image%frame=0", bbImageBuffer);
	rtSym("DrawImage%image%x%y%frame=0", bbDrawImage);
	rtSym("DrawBlock%image%x%y%frame=0", bbDrawBlock);
	rtSym("TileImage%image%x=0%y=0%frame=0", bbTileImage);
	rtSym("TileBlock%image%x=0%y=0%frame=0", bbTileBlock);
	rtSym("DrawImageRect%image%x%y%rect_x%rect_y%rect_width%rect_height%frame=0", bbDrawImageRect);
	rtSym("DrawBlockRect%image%x%y%rect_x%rect_y%rect_width%rect_height%frame=0", bbDrawBlockRect);
	rtSym("MaskImage%image%red%green%blue", bbMaskImage);
	rtSym("HandleImage%image%x%y", bbHandleImage);
	rtSym("MidHandle%image", bbMidHandle);
	rtSym("AutoMidHandle%enable", bbAutoMidHandle);
	rtSym("%ImageWidth%image", bbImageWidth);
	rtSym("%ImageHeight%image", bbImageHeight);
	rtSym("%ImageXHandle%image", bbImageXHandle);
	rtSym("%ImageYHandle%image", bbImageYHandle);

	rtSym("ScaleImage%image#xscale#yscale", bbScaleImage);
	rtSym("ResizeImage%image#width#height", bbResizeImage);
	rtSym("RotateImage%image#angle", bbRotateImage);
	rtSym("TFormImage%image#a#b#c#d", bbTFormImage);
	rtSym("TFormFilter%enable", bbTFormFilter);

	rtSym("%ImagesOverlap%image1%x1%y1%image2%x2%y2", bbImagesOverlap);
	rtSym("%ImagesCollide%image1%x1%y1%frame1%image2%x2%y2%frame2", bbImagesCollide);
	rtSym("%RectsOverlap%x1%y1%width1%height1%x2%y2%width2%height2", bbRectsOverlap);
	rtSym("%ImageRectOverlap%image%x%y%rect_x%rect_y%rect_width%rect_height", bbImageRectOverlap);
	rtSym("%ImageRectCollide%image%x%y%frame%rect_x%rect_y%rect_width%rect_height", bbImageRectCollide);

	rtSym("Write$string", bbWrite);
	rtSym("Print$string=\"\"", bbPrint);
	rtSym("$Input$prompt=\"\"", bbInput);
	rtSym("Locate%x%y", bbLocate);

	rtSym("ShowPointer", bbShowPointer);
	rtSym("HidePointer", bbHidePointer);
}

// NVidia Optimus
extern "C" {
_declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
}