#include "loadMQO.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>

#define BUFFER_SIZE 1024

int mqoReadScene(FILE *fp, struct mqoRoot *root);
int mqoReadMaterial(FILE *fp, struct mqoRoot *root);
int mqoReadObject(FILE *fp, struct mqoRoot *root);
int mqoReadVertex(FILE *fp, struct mqoObject *obj);
int mqoReadFace(FILE *fp, struct mqoObject *obj);

struct mqoRoot *mqoLoadFile(const char *filename)
{
	FILE *fp = fopen(filename, "r");
	if (!fp)
		goto error;

	char *fileBuffer = memalign(64, 256 * 1024);
	if (!fileBuffer)
		goto close_fp;

	setvbuf(fp, fileBuffer, _IOFBF, 256 * 1024);

	struct mqoRoot *root = malloc(sizeof(struct mqoRoot));
	if (!root)
		goto free_fileBuffer;

	root->scene = 0;
	root->numMaterial = 0;
	root->material = 0;
	root->numObject = 0;
	root->maxObject = 16;
	root->object = malloc(sizeof(struct mqoObject*) * root->maxObject);
	if (!root->object)
		goto free_root;

	int i;
	for(i = 0; i < root->maxObject; ++i)
		root->object[i] = 0;

	while(!feof(fp)) {
		char buf[BUFFER_SIZE];
		fscanf(fp, "%s", buf);
		if (!strcmp(buf, "Scene")) {
			if (mqoReadScene(fp, root) < 0)
				goto free_root;
		} else if (!strcmp(buf, "Material")) {
			if (mqoReadMaterial(fp, root) < 0)
				goto free_root;
		} else if (!strcmp(buf, "Object")) {
			if (mqoReadObject(fp, root) < 0)
				goto free_root;
		}
	}

	fclose(fp);
	free(fileBuffer);

	return root;

free_root:
	mqoClose(root);
free_fileBuffer:
	free(fileBuffer);
close_fp:
	fclose(fp);
error:
	return 0;
}

int mqoReadScene(FILE *fp, struct mqoRoot *root)
{
	struct mqoScene *scene = malloc(sizeof(struct mqoScene));
	if (!scene)
		return -1;

	scene->pos[0] = 0.0f;
	scene->pos[1] = 0.0f;
	scene->pos[2] = 0.0f;
	scene->lookat[0] = 0.0f;
	scene->lookat[1] = 0.0f;
	scene->lookat[2] = 0.0f;
	scene->head = 0.0f;
	scene->pich = 0.0f;
	scene->ortho = 0;
	scene->zoom2 = 0.0f;
	scene->amb[0] = 0.0f;
	scene->amb[1] = 0.0f;
	scene->amb[2] = 0.0f;

	root->scene = scene;

	while(!feof(fp)) {
		char buf[BUFFER_SIZE];
		fscanf(fp, "%s", buf);
		if (!strcmp(buf, "pos")) {
			fscanf(fp, "%f %f %f", &scene->pos[0], &scene->pos[1],
														&scene->pos[2]);
		} else if (!strcmp(buf, "lookat")) {
			fscanf(fp, "%f %f %f", &scene->lookat[0], &scene->lookat[1],
														&scene->lookat[2]);
		} else if (!strcmp(buf, "head")) {
			fscanf(fp, "%f", &scene->head);
		} else if (!strcmp(buf, "pich")) {
			fscanf(fp, "%f", &scene->pich);
		} else if (!strcmp(buf, "ortho")) {
			fscanf(fp, "%d", &scene->ortho);
		} else if (!strcmp(buf, "zoom2")) {
			fscanf(fp, "%f", &scene->zoom2);
		} else if (!strcmp(buf, "amb")) {
			fscanf(fp, "%f %f %f", &scene->amb[0], &scene->amb[1],
														&scene->amb[2]);
		} else if (!strcmp(buf, "}")) {
			break;
		}
	}
	return 0;
}

int mqoReadMaterial(FILE *fp, struct mqoRoot *root)
{
	int numMaterial;
	char buf[BUFFER_SIZE];
	fgets(buf, BUFFER_SIZE, fp);
	sscanf(buf, "%d", &numMaterial);

	struct mqoMaterial *material = malloc(sizeof(struct mqoMaterial) *
															numMaterial);
	if (!material)
		return -1;

	int i;
	for(i = 0; i < numMaterial; ++i) {
		material[i].color[0] = 0;
		material[i].color[1] = 0;
		material[i].color[2] = 0;
		material[i].color[3] = 0;
		material[i].dif = 0.0f;
		material[i].amb = 0.0f;
		material[i].emi = 0.0f;
		material[i].spc = 0.0f;
		material[i].power = 0.0f;
		material[i].tex[0] = '\0';

		fgets(buf, BUFFER_SIZE, fp);

		char *p;
		if ((p = strstr(buf, "col")) != 0)
			sscanf(p, "col(%f %f %f %f)",
					&material[i].color[0], &material[i].color[1],
					&material[i].color[2], &material[i].color[3]);

		if ((p = strstr(buf, "dif")) != 0)
			sscanf(p, "dif(%f)", &material[i].dif);

		if ((p = strstr(buf, "amb")) != 0)
			sscanf(p, "amb(%f)", &material[i].amb);

		if ((p = strstr(buf, "emi")) != 0)
			sscanf(p, "emi(%f)", &material[i].emi);

		if ((p = strstr(buf, "spc")) != 0)
			sscanf(p, "spc(%f)", &material[i].spc);

		if ((p = strstr(buf, "power")) != 0)
			sscanf(p, "power(%f)", &material[i].power);

		if ((p = strstr(buf, "tex")) != 0) {
			char *pEnd = strstr(p, "\")");
			int len = pEnd - (p + 5);
			strncpy(material[i].tex, p + 5, len);
			material[i].tex[len] = '\0';
		}
	}

	while(fgetc(fp) != '}')
		;

	root->numMaterial = numMaterial;
	root->material = material;

	return 0;
}

int mqoReadObject(FILE *fp, struct mqoRoot *root)
{
	if (root->numObject == root->maxObject) {
		root->maxObject *= 2;
		root->object = realloc(root->object, sizeof(struct mqoObject*) *
														root->maxObject);
		if (!root->object)
			return -1;
	}

	struct mqoObject *obj = malloc(sizeof(struct mqoObject));
	if (!obj)
		return -1;
	
	char buf[BUFFER_SIZE];
	{
		fgets(buf, BUFFER_SIZE, fp);
		char *start = strstr(buf, "\"");
		++start;
		char *end = strstr(start, "\"");
		int len = end - start;
		strncpy(obj->name, start, len);
		obj->name[len] = '\0';
	}
	obj->visible = 0;
	obj->shading = 0;
	obj->facet = 0.0f;
	obj->color[0] = 0.0f;
	obj->color[1] = 0.0f;
	obj->color[2] = 0.0f;
	obj->color_type = 0;
	obj->numVertex = 0;
	obj->vertices = 0;
	obj->numFace = 0;
	obj->faces = 0;

	while(!feof(fp)) {
		fscanf(fp, "%s", buf);
		if (!strcmp(buf, "visible")) {
			fscanf(fp, "%d", &obj->visible);
		} else if (!strcmp(buf, "shading")) {
			fscanf(fp, "%d", &obj->shading);
		} else if (!strcmp(buf, "facet")) {
			fscanf(fp, "%f", &obj->facet);
		} else if (!strcmp(buf, "color")) {
			fscanf(fp, "%f %f %f", &obj->color[0], &obj->color[1],
														&obj->color[2]);
		} else if (!strcmp(buf, "color_type")) {
			fscanf(fp, "%d", &obj->color_type);
		} else if (!strcmp(buf, "vertex")) {
			if (mqoReadVertex(fp, obj) < 0)
				goto free_obj;
		} else if (!strcmp(buf, "face")) {
			if (mqoReadFace(fp, obj) < 0)
				goto free_obj;
		} else if (!strcmp(buf, "}")) {
			break;
		}
	}

	root->object[root->numObject] = obj;
	root->numObject++;
	return 0;

free_obj:
	if (obj->vertices)
		free(obj->vertices);

	if (obj->faces)
		free(obj->faces);

	free(obj);
	return -1;
}

int mqoReadVertex(FILE *fp, struct mqoObject *obj)
{
	char buf[BUFFER_SIZE];
	int n;
	fgets(buf, BUFFER_SIZE, fp);
	sscanf(buf, "%d", &n);

	struct mqoVertex *vertices = malloc(sizeof(struct mqoVertex) * n);
	if (!vertices)
		return -1;

	int i;
	for(i = 0; i < n; ++i)
		fscanf(fp, "%f %f %f", &vertices[i].x, &vertices[i].y,
													&vertices[i].z);

	while(getc(fp) != '}')
		;

	obj->numVertex = n;
	obj->vertices = vertices;

	return 0;
}

int mqoReadFace(FILE *fp, struct mqoObject *obj)
{
	char buf[BUFFER_SIZE];
	int n;
	fgets(buf, BUFFER_SIZE, fp);
	sscanf(buf, "%d", &n);

	struct mqoFace *face = malloc(sizeof(struct mqoFace) * n);
	if (!face)
		return -1;

	int i;
	for(i = 0; i < n; ++i) {
		fgets(buf, BUFFER_SIZE, fp);
		sscanf(buf, "%d", &face[i].numVertex);

		char *p;
		if ((p = strstr(buf, "V(")) != 0) {
			switch(face[i].numVertex) {
			case 3:
				sscanf(p, "V(%d %d %d)", &face[i].vIndex[0],
						&face[i].vIndex[1], &face[i].vIndex[2]);
				break;
			case 4:
				sscanf(p, "V(%d %d %d %d)",
						&face[i].vIndex[0], &face[i].vIndex[1],
						&face[i].vIndex[2], &face[i].vIndex[3]);
				break;
			default:
				goto error;
			}
		}

		face[i].material = 0;
		if ((p = strstr(buf, "M(")) != 0)
			sscanf(p, "M(%d)", &face[i].material);

		if ((p = strstr(buf, "UV(")) != 0) {
			switch(face[i].numVertex) {
			case 3:
				sscanf(p, "UV(%f %f %f %f %f %f)",
						&face[i].uv[0], &face[i].uv[1],
						&face[i].uv[2], &face[i].uv[3],
						&face[i].uv[4], &face[i].uv[5]);
				break;
			case 4:
				sscanf(p, "UV(%f %f %f %f %f %f %f %f)",
						&face[i].uv[0], &face[i].uv[1],
						&face[i].uv[2], &face[i].uv[3],
						&face[i].uv[4], &face[i].uv[5],
						&face[i].uv[6], &face[i].uv[7]);
				break;
			default:
				goto error;
			}
		}
	}

	while(fgetc(fp) != '}')
		;

	obj->numFace = n;
	obj->faces = face;

	return 0;

error:
	free(obj->faces);
	return -1;
}

void mqoClose(struct mqoRoot *mqo)
{
	int i;
	for(i = 0; i < mqo->numObject; ++i) {
		free(mqo->object[i]->vertices);
		free(mqo->object[i]->faces);
		free(mqo->object[i]);
	}

	free(mqo->object);
	free(mqo->scene);
	free(mqo->material);
	free(mqo);
}

