#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <pspkernel.h>
#include <pspdisplay.h>
#include <pspctrl.h>
#include <pspgu.h>
#include <pspgum.h>
#include "vram.h"
#include "bitmap.h"
#include "loadMQO.h"
#include "quaternion.h"

#define VER 1
#define REV 1

PSP_MODULE_INFO("miku viewer", PSP_MODULE_USER, VER, REV);
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | PSP_THREAD_ATTR_VFPU);
PSP_HEAP_SIZE_KB(-1024);

#define BUF_WIDTH (512)
#define SCR_WIDTH (480)
#define SCR_HEIGHT (272)

static int g_running = 1;
static unsigned char list[262144] __attribute__((aligned(64)));
static unsigned char miku[262144] __attribute__((aligned(64)));

// Exit callback
int exit_callback(int arg1, int arg2, void *common)
{
	g_running = 0;
	return 0;
}

// Callback thread
int CallbackThread(SceSize args, void *argp)
{
	int cbid;
	cbid = sceKernelCreateCallback("Exit Callback", exit_callback, NULL);
	sceKernelRegisterExitCallback(cbid);
	sceKernelSleepThreadCB();
	return 0;
}

// Sets up the callback thread and returns its thread id
int SetupCallbacks(void)
{
	int thid = 0;
	thid = sceKernelCreateThread("update_thread", CallbackThread, 0x11, 0xFA0, 0, 0);
	if(thid >= 0)
		sceKernelStartThread(thid, 0, 0);

	return thid;
}

void swizzle_fast(u8* out, const u8* in, unsigned int width, unsigned int height)
{
	unsigned int blockx, blocky;
	unsigned int j;

	unsigned int width_blocks = (width / 16);
	unsigned int height_blocks = (height / 8);

	unsigned int src_pitch = (width-16)/4;
	unsigned int src_row = width * 8;

	const u8* ysrc = in;
	u32* dst = (u32*)out;

	for (blocky = 0; blocky < height_blocks; ++blocky) {
		const u8* xsrc = ysrc;
		for (blockx = 0; blockx < width_blocks; ++blockx) {
			const u32* src = (u32*)xsrc;
			for (j = 0; j < 8; ++j) {
				*(dst++) = *(src++);
				*(dst++) = *(src++);
				*(dst++) = *(src++);
				*(dst++) = *(src++);
				src += src_pitch;
			}
			xsrc += 16;
		}
		ysrc += src_row;
	}
}

void drawMqoObject(const struct mqoObject* obj, struct bitmap_t **tex, int *lastTex)
{
	struct VertexT16V32 {
		short u;
		short v;
		float x;
		float y;
		float z;
	};

	int n = obj->numFace;
	int i;
	for(i = 0; i < n; ++i) {
		struct VertexT16V32 *vertices = (struct VertexT16V32*)sceGuGetMemory(3 * sizeof(struct VertexT16V32));
		if (!vertices)
			return;

		if (obj->faces[i].material != *lastTex) {
			sceGuEnable(GU_TEXTURE_2D);
			sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB);
			sceGuTexFilter(GU_LINEAR, GU_LINEAR);
			sceGuTexScale(1.0f, 1.0f);
			sceGuTexOffset(0.0f, 0.0f);
			sceGuClutMode(GU_PSM_8888, 0, 0xff, 0);
			sceGuClutLoad(256 / 8, tex[obj->faces[i].material]->clut);
			sceGuTexMode(GU_PSM_T8, 0, 0, 1);
			sceGuTexImage(0, tex[obj->faces[i].material]->width, tex[obj->faces[i].material]->height, tex[obj->faces[i].material]->width, tex[obj->faces[i].material]->data);
			*lastTex = obj->faces[i].material;
		}
		int j;
		for(j = 0; j < 3; ++j) {
			vertices[j].u = obj->faces[i].uv[j * 2 + 0] * 32767;
			vertices[j].v = obj->faces[i].uv[j * 2 + 1] * 32767;
			vertices[j].x = obj->vertices[obj->faces[i].vIndex[j]].x;
			vertices[j].y = obj->vertices[obj->faces[i].vIndex[j]].y;
			vertices[j].z = obj->vertices[obj->faces[i].vIndex[j]].z;
		}
		sceGumDrawArray(GU_TRIANGLES, GU_TEXTURE_16BIT | GU_VERTEX_32BITF | GU_TRANSFORM_3D, 3, 0, vertices);
	}
}

void drawMqo(const struct mqoRoot* root, struct bitmap_t **tex)
{
	int lastTex = -1;
	int n = root->numObject;
	int i;
	for(i = 0; i < n; ++i)
		drawMqoObject(root->object[i], tex, &lastTex);
}

int main(int argc, char *argv[])
{
	SetupCallbacks();
	atexit(sceKernelExitGame);

	const char *mikufile = "miku_pose_exp01.mqo";
	struct mqoRoot *mqo = mqoLoadFile(mikufile);
	if (!mqo)
		return 0;

	struct bitmap_t **mikuTexture = malloc(sizeof(struct bitmap_t*) * mqo->numMaterial);
	if (!mikuTexture)
		return 0;

	int i;
	for(i = 0; i < mqo->numMaterial; ++i) {
		if (mqo->material[i].tex[0] == '\0') {
			mikuTexture[i] = 0;
			continue;
		}

		mikuTexture[i] = bitmapOpen(mqo->material[i].tex);
		if (!mikuTexture[i])
			return 0;

		unsigned char *tmp = memalign(64, mikuTexture[i]->pitch * mikuTexture[i]->height);
		if (!tmp)
			return 0;

		swizzle_fast(tmp, mikuTexture[i]->data, mikuTexture[i]->pitch, mikuTexture[i]->height);
		free(mikuTexture[i]->data);
		mikuTexture[i]->data = tmp;
	}

	sceKernelDcacheWritebackAll();

	void *fbp0 = getStaticVramBuffer(BUF_WIDTH, SCR_HEIGHT, GU_PSM_8888);
	void *fbp1 = getStaticVramBuffer(BUF_WIDTH, SCR_HEIGHT, GU_PSM_8888);
	void *zbp = getStaticVramBuffer(BUF_WIDTH, SCR_HEIGHT, GU_PSM_4444);

	sceGuInit();
	sceGuStart(GU_DIRECT, list);
	sceGuDrawBuffer(GU_PSM_8888, fbp0, BUF_WIDTH);
	sceGuDispBuffer(SCR_WIDTH, SCR_HEIGHT, fbp1, BUF_WIDTH);
	sceGuDepthBuffer(zbp, BUF_WIDTH);
	sceGuOffset(2048 - (SCR_WIDTH / 2), 2048 - (SCR_HEIGHT / 2));
	sceGuViewport(2048, 2048, SCR_WIDTH, SCR_HEIGHT);
	sceGuDepthRange(0xffff,0x0000);
	sceGuDepthFunc(GU_GEQUAL);
	sceGuEnable(GU_DEPTH_TEST);
	sceGuScissor(0, 0, SCR_WIDTH, SCR_HEIGHT);
	sceGuEnable(GU_SCISSOR_TEST);
	sceGuShadeModel(GU_SMOOTH);
	sceGuFrontFace(GU_CW);
	sceGuEnable(GU_CULL_FACE);
	sceGuEnable(GU_CLIP_PLANES);
	sceGuClear(GU_COLOR_BUFFER_BIT | GU_DEPTH_BUFFER_BIT);
	sceGuFinish();
	sceGuSync(0, 0);

	// setup display list
	sceGuStart(GU_CALL, miku);
	drawMqo(mqo, mikuTexture);
	sceGuFinish();

	sceDisplayWaitVblankStart();
	sceGuDisplay(1);

	// Setup Pad
	SceCtrlData pad;
	sceCtrlSetSamplingCycle(0);
	sceCtrlSetSamplingMode(PSP_CTRL_MODE_ANALOG);

	ScePspFVector3 mikupos = {-50.0f, -220.0f, -550.0f};
	ScePspFQuaternion mikuquat = {-0.154322f, 0.043484f, -0.034942, 0.986445};
	while(g_running) {
		sceCtrlPeekBufferPositive(&pad, 1);
		ScePspFVector3 mikurot = {0.0f, 0.0f, 0.0f};
		if (pad.Ly <= 96 || 160 <= pad.Ly)
			mikurot.x = (pad.Ly - 128) * 0.0004f;

		if (pad.Lx <= 96 || 160 <= pad.Lx)
			mikurot.y = (pad.Lx - 128) * 0.0004f;

		if (pad.Buttons & PSP_CTRL_LTRIGGER)
			mikurot.z = 0.04f;

		if (pad.Buttons & PSP_CTRL_RTRIGGER)
			mikurot.z = -0.04f;

		if (pad.Buttons & PSP_CTRL_LEFT)
			mikupos.x += 10.0f;

		if (pad.Buttons & PSP_CTRL_RIGHT)
			mikupos.x -= 10.0f;

		if (pad.Buttons & PSP_CTRL_UP)
			mikupos.y -= 10.0f;

		if (pad.Buttons & PSP_CTRL_DOWN)
			mikupos.y += 10.0f;

		if (pad.Buttons & PSP_CTRL_TRIANGLE)
			mikupos.z -= 10.0f;

		if (pad.Buttons & PSP_CTRL_CROSS)
			mikupos.z += 10.0f;

		sceGuStart(GU_DIRECT, list);
		sceGuClearColor(0xff554433);
		sceGuClearDepth(0);
		sceGuClear(GU_COLOR_BUFFER_BIT | GU_DEPTH_BUFFER_BIT);

		sceGumMatrixMode(GU_PROJECTION);
		sceGumLoadIdentity();
		sceGumPerspective(45.0f, 16.0f / 9.0f, 10.0f, 10000.0f);

		sceGumMatrixMode(GU_VIEW);
		sceGumLoadIdentity();
		{
			ScePspFVector3 eye = {0.0f, 0.0f, 0.0f};
			ScePspFVector3 target = {0.0f, 0.0f, -1.0f};
			ScePspFVector3 up = {0.0f, 1.0f, 0.0f};
			sceGumLookAt(&eye, &target, &up);
		}

		sceGumMatrixMode(GU_MODEL);
		sceGumLoadIdentity();

		sceGumTranslate(&mikupos);
		ScePspFQuaternion tQuat;
		quaternionFromEulerAngle(&tQuat, &mikurot);
		mikuquat = quaternionMultiply(&tQuat, &mikuquat);
		ScePspFMatrix4 mikuMatrix;
		quaternionGetMatrix(&mikuquat, &mikuMatrix);
		sceGumMultMatrix(&mikuMatrix);
		sceGumUpdateMatrix();

		sceGuCallList(miku);

		sceGuFinish();
		sceGuSync(0, 0);

		sceDisplayWaitVblankStart();
		sceGuSwapBuffers();
	}

	sceGuTerm();

	for(i = 0; i < mqo->numMaterial; ++i) {
		if (mikuTexture[i])
			bitmapClose(mikuTexture[i]);
	}
	
	free(mikuTexture);
	mqoClose(mqo);

	return 0;
}

