#include "sprite.hpp"

#include <gxgraphics.hpp>
#include <gxruntime.hpp>

extern float stats3d[];

static float null[] = {0, 0, 0};

static float tex_coords0[2][2] = {{0, 0}, {0, 0}};
static float tex_coords1[2][2] = {{1, 0}, {1, 0}};
static float tex_coords2[2][2] = {{1, 1}, {1, 1}};
static float tex_coords3[2][2] = {{0, 1}, {0, 1}};

extern gxRuntime*  gx_runtime;
extern gxGraphics* gx_graphics;

static gxMesh*     mesh;
static int         mesh_size;
static std::vector<int> mesh_indices;

static int allocIndex()
{
	if (!mesh_indices.size()) {
		if (mesh_size)
			gx_graphics->freeMesh(mesh);
		for (int k = 0; k < 256; ++k) {
			mesh_indices.push_back(mesh_size++);
		}
		mesh = gx_graphics->createMesh(mesh_size * 4, mesh_size * 2, 0);
	}
	int n = mesh_indices.back();
	mesh_indices.pop_back();
	return n;
}

static void freeIndex(int n)
{
	mesh_indices.push_back(n);
	if (mesh_indices.size() != mesh_size)
		return;
	gx_graphics->freeMesh(mesh);
	mesh_indices.clear();
	mesh_size = 0;
}

Sprite::Sprite() : view_mode(VIEW_MODE_FREE), xhandle(0), yhandle(0), rot(0), xscale(1), yscale(1), captured(false)
{
	setRenderSpace(RENDER_SPACE_WORLD);
	mesh_index = allocIndex();
}

Sprite::Sprite(const Sprite& t)
	: Model(t), view_mode(t.view_mode), xhandle(t.xhandle), yhandle(t.yhandle), rot(t.rot), xscale(t.xscale),
	  yscale(t.yscale), captured(false)
{
	mesh_index = allocIndex();
}

Sprite::~Sprite()
{
	freeIndex(mesh_index);
}

void Sprite::setRotation(float angle)
{
	rot = angle;
}

void Sprite::setScale(float x, float y)
{
	xscale = x;
	yscale = y;
}

void Sprite::setHandle(float x, float y)
{
	xhandle = x;
	yhandle = y;
}

void Sprite::setViewmode(int mode)
{
	view_mode = mode;
}

void Sprite::capture()
{
	Model::capture();
	r_rot    = rot;
	r_xscale = xscale;
	r_yscale = yscale;
	captured = true;
}

bool Sprite::beginRender(float tween)
{
	Model::beginRender(tween);
	if (tween == 1 || !captured) {
		r_rot    = rot;
		r_xscale = xscale;
		r_yscale = yscale;
	} else {
		r_rot    = (rot - r_rot) * tween + r_rot;
		r_xscale = (xscale - r_xscale) * tween + r_xscale;
		r_yscale = (yscale - r_yscale) * tween + r_yscale;
	}
	return true;
}

bool Sprite::render(const RenderContext& rc)
{
	Transform t = getRenderTform();

	if (view_mode == VIEW_MODE_FREE) {
		t.m = rc.getCameraTform().m;
	} else if (view_mode == VIEW_MODE_UPRIGHT) {
		t.m.k = rc.getCameraTform().m.k;
		t.m.orthogonalize();
	} else if (view_mode == VIEW_MODE_UPRIGHT2) {
		t.m = yawMatrix(matrixYaw(rc.getCameraTform().m)) * t.m;
	}

	t.m = t.m * rollMatrix(r_rot) * scaleMatrix(r_xscale, r_yscale, 1);

	static Vector verts[4];
	verts[0] = t * Vector(-1 - xhandle, 1 - yhandle, 0);
	verts[1] = t * Vector(1 - xhandle, 1 - yhandle, 0);
	verts[2] = t * Vector(1 - xhandle, -1 - yhandle, 0);
	verts[3] = t * Vector(-1 - xhandle, -1 - yhandle, 0);

	if (!rc.getWorldFrustum().cull(verts, 4))
		return false;

	mesh->lock(false);
	int fv = mesh_index * 4, ft = mesh_index * 2;
	mesh->setVertex(fv + 0, &verts[0].x, null, tex_coords0);
	mesh->setVertex(fv + 1, &verts[1].x, null, tex_coords1);
	mesh->setVertex(fv + 2, &verts[2].x, null, tex_coords2);
	mesh->setVertex(fv + 3, &verts[3].x, null, tex_coords3);
	if (rc.isReflected()) {
		mesh->setTriangle(ft + 0, 0, 2, 1);
		mesh->setTriangle(ft + 1, 0, 3, 2);
	} else {
		mesh->setTriangle(ft + 0, 0, 1, 2);
		mesh->setTriangle(ft + 1, 0, 2, 3);
	}
	mesh->unlock();

	enqueue(mesh, fv, 4, ft, 2);
	return false;
}
