#include <iostream>
#include <fstream>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include "m3dme.h"
#include "mmap.h"
#include <math.h>
#include <stdlib.h>

using namespace std;
using namespace M3D;
float texture_clamp;
int convert( uint16_t *input, int x_s, int y_s, M3DME_Vertex_Data *vertex_data, M3DME_Index_Data *index_data )
{
	//Найдем наибольшую высоту - она будет равна 0.5 остальные высоты получаем относительно нее
	uint16_t max_height = 0;
	std::cout << "Analizing height map...";
	for ( int y = 0; y < y_s; ++y )
		for ( int x = 0;x < x_s;++x )
		{
			max_height = ( max_height > *( input + y * x_s + x ) ) ? max_height : *( input + y * x_s + x );
		}
	std::cout << "done." << std::endl << "Max height: " << max_height << std::endl;
	if(!max_height)max_height=1;

	std::cout << "Converting to mesh...";
	int vertex_count = 0;
	for ( int y = 0; y < y_s; ++y )
		for ( int x = 0;x < x_s;++x )
		{
			vertex_data[ y * x_s + x ].x = ( float ) x / ( float ) (x_s-1) - 0.5;
			vertex_data[ y * x_s + x ].z = ( float ) y / ( float ) (y_s-1) - 0.5;
			vertex_data[ y * x_s + x ].y = ( float ) ( *( input + y * x_s + x ) ) / ( float ) max_height;
			vertex_data[ y * x_s + x ].u = (texture_clamp*( float ) x )/ ( float ) (x_s-1);
			vertex_data[ y * x_s + x ].v = (texture_clamp*( float ) y )/ ( float ) (y_s-1);
			vertex_count++;
		}
	std::cout << "done." << std::endl;
	std::cout << "Vertex count: "<<vertex_count<<std::endl;

	std::cout << "Generating index...";
	int i_count = 0;
	//генерация индексов идет построчно. -1 - индекс рестарта
	//В одной строке (x_s -1 )*2 треугольников.
	//В каждом столбце (y_s -1 ) *2 треугольников.
	//Каждый треугольник требует 3 индекса

	for ( int y = 0; y <y_s - 1; ++y ) //по всем строкам
	{
		for ( int tri_c = 1; tri_c <= ( x_s - 1 ) * 2; ++tri_c )
		{
			if ( tri_c % 2 == 0 )  //Для четных треугольников
			{
				index_data[ i_count ].index = ( y * x_s + tri_c / 2 ) - 1;
				index_data[ i_count+1 ].index = ( y * x_s + tri_c / 2 + 1 ) - 1;
				index_data[ i_count+2 ].index = ( ( y + 1 ) * x_s + tri_c / 2 + 1 ) - 1;
			}
			else  //Для нечетных треугольников
			{
				index_data[ i_count ].index = ( y * x_s + ( tri_c - 1 ) / 2 );
				index_data[ i_count+1 ].index = ( ( y + 1 ) * x_s + ( tri_c - 1 ) / 2 );
				index_data[ i_count+2 ].index = ( ( y + 1 ) * x_s + ( tri_c - 1 ) / 2 + 1 );
			}
			i_count+=3;
		}
	}
	std::cout << "done." << std::endl;
	std::cout << "Index count:" << i_count << std::endl;
	std::cout << "Calculating normals...";
	for(int i_c = 0; i_c < i_count; i_c+=3)
	{
		float v1_x, v1_y, v1_z;
		float v2_x, v2_y, v2_z;
		v1_x = vertex_data[index_data[i_c].index].x - vertex_data[index_data[i_c+1].index].x; 
		v1_y = vertex_data[index_data[i_c].index].y - vertex_data[index_data[i_c+1].index].y; 
		v1_z = vertex_data[index_data[i_c].index].z - vertex_data[index_data[i_c+1].index].z; 
		
		v2_x = vertex_data[index_data[i_c].index].x - vertex_data[index_data[i_c+2].index].x; 
		v2_y = vertex_data[index_data[i_c].index].y - vertex_data[index_data[i_c+2].index].y; 
		v2_z = vertex_data[index_data[i_c].index].z - vertex_data[index_data[i_c+2].index].z; 

		float n_x, n_y, n_z;
		n_x = v1_y*v2_z - v1_x*v2_y;
		n_y = v1_z*v2_x - v1_x*v2_z;
		n_z = v1_x*v2_y - v1_y*v2_x;
		
		//нормализуем
		float len = sqrt(n_x*n_x + n_y*n_y+n_z*n_z);
		n_x/=len;
		n_y/=len;
		n_z/=len;
		for (int a=0;a<3;a++)
		{
			vertex_data[index_data[i_c+a].index].n_x = n_x;
			vertex_data[index_data[i_c+a].index].n_y = n_y;
			vertex_data[index_data[i_c+a].index].n_z = n_z;
		}
	}
	std::cout << "done." << std::endl;


	return i_count;
}

int main( int argc, char *argv[] )
{
	if ( argc < 6 )
	{
		cerr << "Usage: " << argv[ 0 ] << " -o output_file -i input_file -x x_size -y y_size -t texture_clamp_size" << endl;
		return 1;
	}
	int opt;
	int x, y;
	std::string ofile;
	std::string ifile;
	while ( ( opt = getopt ( argc, argv, "o:i:x:y:t:" ) ) != -1 )
	{
		switch ( opt )
		{
				case 'o':
				ofile = optarg;
				break;
				case 'i':
				ifile = optarg;
				break;
				case 'x':
				x = atoi ( optarg );
				break;
				case 'y':
				y = atoi ( optarg );
				break;
				case 't':
				texture_clamp = atof ( optarg );
				break;
				default:  /* '?' */
				cerr << "Usage: " << argv[ 0 ] << " -o output_file -i input_file -x x_size -y y_size -t texture_clamp_size" << endl;
				return 1;
		}
	}
	std::cout << "Converting heightmap \"" << ifile << "\" to \"" << ofile << "\". Mesh size:" << x << ":" << y << std::endl;

	MMAP input( ifile );
	uint16_t *inputfile = ( uint16_t* ) input.get_pointer();
	M3DME_Vertex_Data *vertex_data = new M3DME_Vertex_Data[ x * y ];
	M3DME_Index_Data *index_data = new M3DME_Index_Data[ x * y * 6 ];
	int i_count = convert( inputfile, x, y, vertex_data, index_data );
	
	M3DME mesh;
	M3DME_Header header;
	header.vertex_data_size = x*y*sizeof(M3DME_Vertex_Data);
	header.index_data_size = i_count*sizeof(M3DME_Index_Data);
	header.primitive_type = TRIANGLES;
	mesh.set_header( &header );
	mesh.set_vertex_data( vertex_data );
	mesh.set_index_data( index_data );
	mesh.save( ofile );
	return 0;
}

