#define PERSUADE_INTERNAL
#include <stdlib.h>
#include "persuade2.h"
#include "p2_sds_geo.h"
#include "p2_sds_table.h"
#include "p2_sds_obj.h"

#include <math.h>

extern void p_get_tri_tess_index(uint *index, uint base_tess);
extern void p_get_quad_tess_index(uint *index, uint base_tess);
extern boolean p_lod_displacement_update_test(PMesh *mesh);

static uint p_sds_force_level = 4;
static float p_sds_mesh_factor = 1;

void p_geo_set_sds_force_level(uint level)
{
	p_sds_force_level = level;
}

uint p_geo_get_sds_force_level(void)
{
	return p_sds_force_level;
}

void p_geo_set_sds_mesh_factor(float factor)
{
	p_sds_mesh_factor = factor;
}

float p_geo_get_sds_mesh_factor(void)
{
	return p_sds_mesh_factor;
}

PMesh *p_rm_create(float compexity, uint force_tess_level)
{
	PMesh *mesh;
	mesh = malloc(sizeof *mesh);
	mesh->stage = 0;
	mesh->sub_stages[0] = 0;
	mesh->sub_stages[1] = 0;
	mesh->sub_stages[2] = 0;
	mesh->sub_stages[3] = 0;
	mesh->tess.force = force_tess_level;
	mesh->tess.factor = compexity;
	mesh->tess.edge_tess_func = p_sds_edge_tesselation_global_func;
	mesh->temp = NULL;
	mesh->next = NULL;
	mesh->tess.tess = NULL;
	mesh->tess.order_node = NULL;
	mesh->tess.order_temp_mesh = NULL;
	mesh->tess.order_temp_mesh_rev = NULL;
	mesh->tess.order_temp_poly_start = NULL;
	mesh->render.vertex_array = NULL;
	mesh->render.normal_array = NULL;
	mesh->render.reference = NULL;
	mesh->render.mat = NULL;
	mesh->render.shadows = TRUE;
	mesh->depend.reference = NULL;
	mesh->depend.weight = NULL;
	mesh->depend.ref_count = NULL;
	mesh->normal.normal_ref = NULL;	
	mesh->normal.normals = NULL;
	mesh->normal.draw_normals = NULL;
	mesh->param.array = NULL;
	mesh->param.version = NULL;
	return mesh;
}

void p_rm_set_eay(PMesh *mesh, pgreal *eye)
{
	mesh->tess.eye[0] = eye[0];
	mesh->tess.eye[1] = eye[1];
	mesh->tess.eye[2] = eye[2];
	mesh->tess.edge_tess_func = p_sds_edge_tesselation_global_func;
}


void persuade_lod_destroy(PMesh *mesh)
{
	uint i;
	if(mesh->temp != NULL)
		free(mesh->temp);
	if(mesh->tess.tess != NULL)
		free(mesh->tess.tess);
	if(mesh->tess.order_node != NULL)
		free(mesh->tess.order_node);
	if(mesh->tess.order_temp_mesh != NULL)
		free(mesh->tess.order_temp_mesh);
	if(mesh->tess.order_temp_mesh_rev != NULL)
		free(mesh->tess.order_temp_mesh_rev);
	if(mesh->tess.order_temp_poly_start != NULL)
		free(mesh->tess.order_temp_poly_start);

	if(mesh->render.vertex_array != NULL)
		free(mesh->render.vertex_array);
	if(mesh->render.normal_array != NULL)
		free(mesh->render.normal_array);
	if(mesh->render.reference != NULL)
		free(mesh->render.reference);
	if(mesh->render.mat != NULL)
		free(mesh->render.mat);

	if(mesh->depend.reference != NULL)
		free(mesh->depend.reference);
	if(mesh->depend.weight != NULL)
		free(mesh->depend.weight);
	if(mesh->depend.ref_count != NULL)
		free(mesh->depend.ref_count);

	if(mesh->normal.normal_ref != NULL)
		free(mesh->normal.normal_ref);	
	if(mesh->normal.normals != NULL)
		free(mesh->normal.normals);
	if(mesh->normal.draw_normals != NULL)
		free(mesh->normal.draw_normals);

	if(mesh->param.version != NULL)
		free(mesh->param.version);
	free(mesh);
}

uint p_rm_get_param_count(PMesh *mesh)
{
	return mesh->param.array_count;
}


#define P_TRI_TESS_SELECT 10000
#define P_QUAD_TESS_SELECT 7500
#define P_TRI_TESS_REF 10000
#define P_QUAD_TESS_REF 7500

double *p_lod_get_view_pos(void);

void p_lod_set(PPolyStore *geometry, PMesh *mesh);

PMesh *persuade2_lod_create(PPolyStore *smesh, uint *ref, pgreal *vertex, float compexity, uint force_tess_level, float *eye)
{
	PMesh *mesh;
	PTessTableElement *table;
	uint i, j, k, l, material, polygon, poly_size = 4;
	mesh = p_rm_create(compexity, force_tess_level);
	for(i = 1 ; i < smesh->level; i++)
		poly_size *= 4;

	mesh->tess.tri_count = smesh->base_tri_count;
	mesh->tess.quad_count = smesh->base_quad_count;
	mesh->tess.tess = malloc((sizeof *mesh->tess.tess) * (mesh->tess.tri_count + mesh->tess.quad_count));
	for(i = 0; i < (mesh->tess.tri_count + mesh->tess.quad_count); i++)
		mesh->tess.tess[i] = NULL;
	mesh->tess.eye[0] = eye[0];
	mesh->tess.eye[1] = eye[1];
	mesh->tess.eye[2] = eye[2];
	mesh->temp = NULL;
	mesh->render.element_count = 0;
	mesh->render.vertex_count = 0;
	mesh->render.open_edges = smesh->open_edges != 0;
	mesh->render.shadows = smesh->open_edges == 0;
	mesh->depend.length = 0;
	mesh->sub_stages[0] = 0;
	mesh->sub_stages[1] = 0;
	mesh->sub_stages[2] = 0;
	mesh->sub_stages[3] = 0;
	mesh->stage++;
	
	p_lod_set(smesh, mesh);

	p_lod_select_tesselation(mesh, smesh, vertex);

	mesh->render.vertex_array = malloc((sizeof *mesh->render.vertex_array) * mesh->render.vertex_count * 3);
	mesh->render.normal_array = malloc((sizeof *mesh->render.normal_array) * mesh->render.vertex_count * 3);
	mesh->normal.normal_ref = malloc((sizeof *mesh->normal.normal_ref) * mesh->render.vertex_count * 4);
	mesh->render.reference = malloc((sizeof *mesh->render.reference) * mesh->render.element_count * 3);
	mesh->depend.reference = malloc((sizeof *mesh->depend.reference) * mesh->depend.length);
	mesh->depend.weight = malloc((sizeof *mesh->depend.weight) * mesh->depend.length);
	mesh->depend.length_temp = mesh->depend.length;
	mesh->depend.length_temp2 = mesh->render.vertex_count;
	mesh->depend.length_temp3 = mesh->render.element_count;
	mesh->depend.ref_count = malloc((sizeof *mesh->depend.ref_count) * mesh->render.vertex_count);
	mesh->render.element_count = 0;
	mesh->render.vertex_count = 0;
	mesh->depend.length = 0;

				
				
	/* building reference */
	for(i = k = l = 0; i < mesh->tess.tri_count + mesh->tess.quad_count; i++)
	{
		if(i == mesh->render.mat[l].tri_end)
		{
			mesh->render.mat[l].render_end = mesh->render.element_count;
			l++;
		}
		table = mesh->tess.tess[i];
		for(j = 0; j < table->element_count;)
		{
			mesh->render.reference[mesh->render.element_count++] = table->index[j++] + k;
			mesh->render.reference[mesh->render.element_count++] = table->index[j++] + k;
			mesh->render.reference[mesh->render.element_count++] = table->index[j++] + k;
		}
		k += table->vertex_count;
	}
				
	mesh->render.mat[l].render_end = mesh->render.element_count;

	/* building depend */
	{
//		uint poly, temp = 0;
		for(i = material = l = 0; i < mesh->tess.tri_count + mesh->tess.quad_count; i++)
		{
			table = mesh->tess.tess[i];

			if(i == mesh->render.mat[material].tri_end)
				material++;
			if(i < mesh->render.mat[material].quad_end)
				polygon = mesh->tess.order_temp_mesh[i] * smesh->poly_per_base * 4;
			else
				polygon = smesh->base_quad_count * smesh->poly_per_base * 4 + (mesh->tess.order_temp_mesh[i] - smesh->base_quad_count) * smesh->poly_per_base * 3;

			for(j = 0; j < table->vertex_count; j++)
			{
				PDepend *dep;
			//	temp++;
				dep = &smesh->vertex_dependency[smesh->ref[table->reference[j] + polygon]];
				mesh->depend.ref_count[mesh->render.vertex_count++] = dep->length;
				for(k = 0; k < dep->length; k++)
				{
					mesh->depend.reference[mesh->depend.length] = dep->element[k].vertex * 3;
					mesh->depend.weight[mesh->depend.length] = dep->element[k].value;
					mesh->depend.length++;
				}
			}
			mesh->tess.order_temp_poly_start[i] = l;
			l += table->vertex_count;
		}
	}

	p_lod_compute_vertex_normals(smesh, mesh);
	p_lod_create_normal_ref_and_shadow_skirts(smesh, mesh);

	/*			p_lod_create_layer_param(g_node, mesh);
				if(o_node != NULL)	
				{
					if(p_lod_displacement_update_test(mesh))
					{
						uint ii;
						mesh->displacement.displacement = malloc((sizeof *mesh->displacement.displacement) * mesh->render.vertex_count);
						p_lod_create_displacement_array(g_node, o_node, mesh, smesh->level);
					//	for(ii = 0; ii < mesh->render.vertex_count; ii++)
					//		mesh->displacement.displacement[ii] = 0;
					}
				}
					p_lod_anim_bones_update_test(mesh, o_node, g_node);
					p_lod_anim_scale_update_test(mesh, o_node);
					p_lod_anim_layer_update_test(mesh, o_node, g_node);
			case POS_ANIMATE :
		
				if(o_node != NULL)	
					p_lod_anim_vertex_array(mesh->anim.cvs, mesh->anim.cv_count, mesh, g_node);
				mesh->stage++;
				break;
			case POS_CREATE_VERTICES :
				j = 0;
				for(i = 0; i < mesh->render.vertex_count; i++)
					j += mesh->depend.ref_count[i];
				if(o_node != NULL)	
					p_lod_compute_vertex_array(mesh->render.vertex_array, mesh->render.vertex_count, mesh->depend.ref_count, mesh->depend.reference, mesh->depend.weight, mesh->anim.cvs);
				else
					p_lod_compute_vertex_array(mesh->render.vertex_array, mesh->render.vertex_count, mesh->depend.ref_count, mesh->depend.reference, mesh->depend.weight, vertex);
				p_lod_compute_normal_array(mesh->render.normal_array, mesh->render.vertex_count, mesh->normal.normal_ref, mesh->render.vertex_array);
			//	if(o_node != NULL)	
			//		p_lod_create_displacement_array(g_node, o_node, mesh, smesh->level);
				if(mesh->displacement.displacement != NULL)
				{
					p_lod_compute_displacement_array(mesh->render.vertex_array, mesh->render.vertex_count, mesh->render.normal_array, mesh->displacement.displacement);
					p_lod_compute_normal_array(mesh->render.normal_array, mesh->render.vertex_count, mesh->normal.normal_ref, mesh->render.vertex_array);
				}
				mesh->stage++;
				if(store != NULL)
					p_rm_destroy(store);
				store = NULL;
				break;
			case POS_DONE :
				if(o_node != NULL)
				{
					boolean update = FALSE;
					static double timer = 0;
					timer += 0.1;
					p_lod_update_shadow(g_node, mesh);
					p_lod_update_layer_param(g_node, mesh);
					if(p_lod_anim_bones_update_test(mesh, o_node, g_node))
						update = TRUE;
					if(p_lod_anim_scale_update_test(mesh, o_node))
						update = TRUE;
					if(p_lod_anim_layer_update_test(mesh, o_node, g_node))
						update = TRUE;
					if(p_lod_displacement_update_test(mesh))
					{
						uint ii;
						p_lod_update_displacement_array(g_node, o_node, mesh, smesh->level);
						update = TRUE;
					}
					if(update)
					{
						p_lod_anim_vertex_array(mesh->anim.cvs, mesh->anim.cv_count, mesh, g_node);
						p_lod_compute_vertex_array(mesh->render.vertex_array, mesh->render.vertex_count, mesh->depend.ref_count, mesh->depend.reference, mesh->depend.weight, mesh->anim.cvs);
						p_lod_compute_normal_array(mesh->render.normal_array, mesh->render.vertex_count, mesh->normal.normal_ref, mesh->render.vertex_array);
						if(mesh->displacement.displacement != NULL)
						{
							p_lod_compute_displacement_array(mesh->render.vertex_array, mesh->render.vertex_count, mesh->render.normal_array, mesh->displacement.displacement);
							p_lod_compute_normal_array(mesh->render.normal_array, mesh->render.vertex_count, mesh->normal.normal_ref, mesh->render.vertex_array);
						}
					}
				}
				return mesh;	
		}
	}
	if(store != NULL)
		return store;*/
	return mesh;
}


uint persuade2_lod_vertex_length_get(PMesh *mesh)
{
	return mesh->render.vertex_count;
}


void persuade2_lod_vertex_shape(PMesh *mesh, pgreal *render_vertex, uint output_stride, pgreal *cvs)
{
	p_lod_compute_vertex_array(render_vertex, output_stride, mesh->render.vertex_count, mesh->depend.ref_count, mesh->depend.reference, mesh->depend.weight, cvs);

}


void persuade2_lod_normal_shape(PMesh *mesh, pgreal *render_normal, uint normal_stride, pgreal *render_vertex, uint vertex_stride)
{
	p_lod_compute_normal_array(render_normal, normal_stride, mesh->render.vertex_count, mesh->normal.normal_ref, render_vertex, vertex_stride);
}


/*
void p_rm_compute(PMesh *mesh, pgreal *vertex, uint start, uint length)
{
	uint i, j, k = 0, l, count, *ref;
	pgreal f;
	ref = mesh->depend.reference;

	for(i = 0; i < mesh->render.vertex_count; i++)
	{
		mesh->render.vertex_array[i * 3] = 0;
		mesh->render.vertex_array[i * 3 + 1] = 0;
		mesh->render.vertex_array[i * 3 + 2] = 0;
		count = mesh->depend.ref_count[i];
		for(j = 0; j < count; j++)
		{
			l = mesh->depend.reference[k];
			f = mesh->depend.weight[k];
			mesh->render.vertex_array[i * 3] += vertex[l++] * f;
			mesh->render.vertex_array[i * 3 + 1] += vertex[l++] * f;
			mesh->render.vertex_array[i * 3 + 2] += vertex[l] * f;
			k++;
		}
	}
}
*/

boolean p_rm_draw_ready(PMesh *mesh)
{
	return mesh->stage > 7;
}

#define NORMAL_ADD 0.001

void p_lod_compute_vertex_array(pgreal *vertex, uint output_stride, uint vertex_count, const uint *ref_count, const uint *reference,  const pgreal *weight, const pgreal *cvs)
{
	uint i, j, k = 0, count, r;
	pgreal f;
//	printf("vertex_count %u\n", vertex_count);
	for(i = 0; i < vertex_count; i++)
	{
		vertex[0] = 0;
		vertex[1] = 0;
		vertex[2] = 0;
		count = ref_count[i];
		for(j = 0; j < count; j++)
		{
			r = reference[k];
			f = weight[k++];
	//		printf("cvs %u %f %f %f\n", r / 3, cvs[r], cvs[r + 1], cvs[r + 2]);
			vertex[0] += cvs[r++] * f;
			vertex[1] += cvs[r++] * f;
			vertex[2] += cvs[r] * f;
		}
	//	printf("float %f %f %f\n", vertex[v + 0], vertex[v + 1], vertex[v + 2]);
		vertex = (pgreal *)(&((uint8 *)vertex)[output_stride]);
	}

}

void p_lod_compute_displacement_array(pgreal *vertex, uint vertex_count, const pgreal *normals, const pgreal *displacement)
{
	uint i;
	for(i = 0; i < vertex_count; i++)
	{
		*vertex++ += *normals++ * *displacement * 100;
		*vertex++ += *normals++ * *displacement * 100;
		*vertex++ += *normals++ * *displacement++ * 100;
	}
}

void p_lod_compute_normal_array(pgreal *normals, uint normal_stride, uint vertex_count, const uint *normal_ref, const pgreal *vertex, uint vertex_stride)
{
	uint i = 0, j = 0, k = 0;
	pgreal x, y, z, f, vec0[3], vec1[3], vec2[3], vec3[3];

	
	normal_stride /= sizeof(pgreal);
	vertex_stride /= sizeof(pgreal);
	vertex_count *= normal_stride;
	if(TRUE)
	{
		while(i < vertex_count)
		{
			x = vertex[k];
			y = vertex[k + 1];
			z = vertex[k + 2];
			k += vertex_stride;
			vec0[0] = vertex[normal_ref[j] * vertex_stride] - x;
			vec0[1] = vertex[normal_ref[j] * vertex_stride + 1] - y;
			vec0[2] = vertex[normal_ref[j] * vertex_stride + 2] - z;
			j++;
			vec1[0] = vertex[normal_ref[j] * vertex_stride] - x;
			vec1[1] = vertex[normal_ref[j] * vertex_stride + 1] - y;
			vec1[2] = vertex[normal_ref[j] * vertex_stride + 2] - z;
			j++;
			vec2[0] = vertex[normal_ref[j] * vertex_stride] - x;
			vec2[1] = vertex[normal_ref[j] * vertex_stride + 1] - y;
			vec2[2] = vertex[normal_ref[j] * vertex_stride + 2] - z;
			j++;
			vec3[0] = vertex[normal_ref[j] * vertex_stride] - x;
			vec3[1] = vertex[normal_ref[j] * vertex_stride + 1] - y;
			vec3[2] = vertex[normal_ref[j] * vertex_stride + 2] - z;
			j++;

			x = (vec0[1] * vec1[2] - vec0[2] * vec1[1]) + (vec2[1] * vec3[2] - vec2[2] * vec3[1]);
			y = (vec0[2] * vec1[0] - vec0[0] * vec1[2]) + (vec2[2] * vec3[0] - vec2[0] * vec3[2]);
			z = (vec0[0] * vec1[1] - vec0[1] * vec1[0]) + (vec2[0] * vec3[1] - vec2[1] * vec3[0]);

			f = sqrt(x * x + y * y + z * z);
			normals[i] = x / f;
			normals[i + 1] = y / f;
			normals[i + 2] = z / f;
			i += normal_stride;
		}
	}else
	{
		while(i < vertex_count)
		{
			x = vertex[k];
			y = vertex[k + 1];
			z = vertex[k + 2];
			k += vertex_stride;
			vec0[0] = vertex[normal_ref[j] * vertex_stride] - x;
			vec0[1] = vertex[normal_ref[j] * vertex_stride + 1] - y;
			vec0[2] = vertex[normal_ref[j] * vertex_stride + 2] - z;
			f = sqrt(vec0[0] * vec0[0] + vec0[1] * vec0[1] + vec0[2] * vec0[2]);
			vec0[0] /= f;
			vec0[1] /= f;
			vec0[2] /= f;
			j++;
			vec1[0] = vertex[normal_ref[j] * vertex_stride] - x;
			vec1[1] = vertex[normal_ref[j] * vertex_stride + 1] - y;
			vec1[2] = vertex[normal_ref[j] * vertex_stride + 2] - z;
			f = sqrt(vec1[0] * vec1[0] + vec1[1] * vec1[1] + vec1[2] * vec1[2]);
			vec1[0] /= f;
			vec1[1] /= f;
			vec1[2] /= f;
			j++;
			vec2[0] = vertex[normal_ref[j] * vertex_stride] - x;
			vec2[1] = vertex[normal_ref[j] * vertex_stride + 1] - y;
			vec2[2] = vertex[normal_ref[j] * vertex_stride + 2] - z;
			f = sqrt(vec2[0] * vec2[0] + vec2[1] * vec2[1] + vec2[2] * vec2[2]);
			vec2[0] /= f;
			vec2[1] /= f;
			vec2[2] /= f;
			j++;
			vec3[0] = vertex[normal_ref[j] * vertex_stride] - x;
			vec3[1] = vertex[normal_ref[j] * vertex_stride + 1] - y;
			vec3[2] = vertex[normal_ref[j] * vertex_stride + 2] - z;
			f = sqrt(vec3[0] * vec3[0] + vec3[1] * vec3[1] + vec3[2] * vec3[2]);
			vec3[0] /= f;
			vec3[1] /= f;
			vec3[2] /= f;
			j++;

			x = (vec0[1] * vec1[2] - vec0[2] * vec1[1]) + (vec2[1] * vec3[2] - vec2[2] * vec3[1]);
			y = (vec0[2] * vec1[0] - vec0[0] * vec1[2]) + (vec2[2] * vec3[0] - vec2[0] * vec3[2]);
			z = (vec0[0] * vec1[1] - vec0[1] * vec1[0]) + (vec2[0] * vec3[1] - vec2[1] * vec3[0]);

			f = sqrt(x * x + y * y + z * z);
			normals[i] = x / f;
			normals[i + 1] = y / f;
			normals[i + 2] = z / f;
			i += normal_stride;
		}
	}
}


pgreal *p_rm_get_vertex(PMesh *mesh)
{
	return mesh->render.vertex_array;
}

pgreal *p_rm_get_normal(PMesh *mesh)
{
	return mesh->render.normal_array;
}

uint p_rm_get_vertex_length(PMesh *mesh)
{
	return mesh->render.vertex_count;
}

uint *p_rm_get_reference(PMesh *mesh)
{
	return mesh->render.reference;
}

uint p_rm_get_ref_length(PMesh *mesh)
{
	return mesh->render.element_count;
}

uint p_rm_get_mat_count(PMesh *mesh)
{
	return mesh->render.mat_count;
}

uint p_rm_get_material_range(PMesh *mesh, uint mat)
{
	return mesh->render.mat[mat].render_end;
}

uint p_rm_get_material(PMesh *mesh, uint mat)
{
	return mesh->render.mat[mat].material;
}

boolean p_rm_get_shadow(PMesh *mesh)
{
	return mesh->render.shadows;
}