#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>


/********** UNCOMMENT FOLLOWING LINE IF USING A LINUX PLATFORM **********/
/* #define LINUX */
#define STR_BUF_SIZE 256


/* Type definitions */
typedef enum { FALSE=0, TRUE } boolean;
typedef struct {
  unsigned char r;
  unsigned char g;
  unsigned char b;
} colour_t;
typedef struct {
  char name[STR_BUF_SIZE];
  colour_t ambient;
  colour_t diffuse;
  colour_t specular;
  char texture[STR_BUF_SIZE];
} material_t;
typedef struct {
  char name[STR_BUF_SIZE];
} poly_t;
typedef struct {
  float x;
  float y;
  float z;
  unsigned short thing;
  int material;
  float index_x;
  float index_y;
} vertex_t;
typedef struct {
  unsigned short a;
  unsigned short b;
  unsigned short c;
  unsigned short thing;
  int material;
  unsigned char things1;
  unsigned char things2;
  unsigned char things3;
  unsigned char things4;
} triangle_t;


/* A little bit of help */
void help_screen( char *program )
{
  fprintf(stderr, "  Usage: %s [-c chunk] [-o object] [-h] initial_file final_file\n", program );
  fprintf(stderr, "    Use -o object to specify a part of the file to convert.\n");
  fprintf(stderr, "    Use -c chunk to dump the contents (ascii and binary) of a chunk.\n");

  return;
}


/* Parse inputs and open files */
int get_inputs( int argc, char *argv[],
		char initial_name[STR_BUF_SIZE],
		char final_name[STR_BUF_SIZE],
		unsigned short *dumpchunk,
		char dumpname[STR_BUF_SIZE] )
{
  int n=1;
  int ret_err = 0;
  boolean got_initial = FALSE;
  boolean got_final = FALSE;
  long tmp;

  /* Default settings */
  *dumpchunk = 0;
  dumpname[0] = '\0';

  /* Parse actual input arguments */
  while ( n < argc && !ret_err ) {
    if ( argv[n][0] == '-' ) switch (toupper(argv[n][1])) {
    case 'H': case '?':
      help_screen( argv[0] );
      exit(0);
      break;

    case 'C':
      n++;
      sscanf( argv[n], "%lx", &tmp);
	  *dumpchunk = (unsigned short)tmp;
      break;

    case 'O':
      n++;
      strcpy( dumpname, argv[n] );
      break;

    default :
      fprintf(stderr, "Unknown input parameter `%s'.\n", argv[n] );
      help_screen( argv[0] );
      ret_err = 2;
      break;
      
    } else if ( !got_initial ) {
      strcpy(initial_name, argv[n]);
      if ( initial_name[0] == '\0' ) ret_err = 1;
      got_initial = TRUE;
      
    } else if ( !got_final ) {
      strcpy(final_name, argv[n]);
      if ( final_name[0] == '\0' ) ret_err = 1;
      got_final = TRUE;
      
    } else {
      fprintf(stderr, "Unknown string input `%s'.\n", argv[n] );
      help_screen( argv[0] );
      ret_err = 2;
    }
    n++;
  }
  
  /* Check for any problems in parsing */
  if ( ret_err == 1 ) {
    fprintf(stderr, "Illegal argument `%s' for parameter `%s'.\n", argv[n-1], argv[n-2] );
    help_screen( argv[0] );
  } else if ( (!got_initial ) && ret_err != 2 ) {
    fprintf(stderr, "Initial name must be supplied.\n" );
    help_screen( argv[0] );
    ret_err = 1;
  }  

  return ret_err;
}


/* Exit routine */
void byebye( char *message )
{
  fprintf(stderr, "%s - aborting.\n", message);
  exit(1);
}


/* Read a char */
unsigned char get_char( FILE *file, long *count )
{
  unsigned char cdata;

  if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  *count -= 1;

  return cdata;
}


/* Read a short, backwards */
unsigned short reverse_short( FILE *file, long *count )
{
  unsigned char cdata;
  unsigned short sdata;

  if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  sdata = (unsigned short)cdata;
  if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  sdata |= (unsigned short)cdata << 8;
  *count -= 2;

  return sdata;
}


/* Read a short, backwards */
signed short reverse_sshort( FILE *file, long *count )
{
  unsigned char cdata;
  short sdata;
  
  if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  sdata = (signed short)cdata;
  if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  sdata |= (signed short)cdata << 8;
  *count -= 2;

  return sdata;
}


/* Read an int, backwards */
unsigned int reverse_int( FILE *file, long *count )
{
  unsigned char cdata;
  unsigned int idata;
  
  if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  idata = (unsigned int)cdata;
  if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  idata |= (unsigned int)cdata << 8;
  if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  idata |= (unsigned int)cdata << 16;
  if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  idata |= (unsigned int)cdata << 24;
  *count -= 4;

  return idata;
}


/* Read a float, backwards */
float reverse_float( FILE *file, long *count )
{
  unsigned char cdata;
  unsigned char *cptr;
  float fdata;
  
#ifndef LINUX
  cptr = (unsigned char *)(&fdata) + 3;
  if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  *cptr-- = (unsigned int)cdata;
  if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  *cptr-- = (unsigned int)cdata;
  if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  *cptr-- = (unsigned int)cdata;
  if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  *cptr = (unsigned int)cdata;
#else
  if ( fread( &fdata, sizeof(float), 1, file) != 1 ) byebye( "Couldnt read float" );
#endif
  *count -= 4;
  
  return fdata;
}


/* Get name of object */
void get_name( FILE *file,
			   char *name,
			   long *count )
{
  int n=0;
  
  if ( fread( &(name[n]), sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  (*count)--;
  while ( name[n] != '\0' ) {
    n++;
    (*count)--;
    if ( fread( &(name[n]), sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
  }
  fprintf(stdout, " %s", name );
  
  return;
}


/* Get chunk and pointer to next chunk */
void get_chunk( FILE *file,
				unsigned short *id, 
				unsigned int *next,
				long *count )
{
  *id = reverse_short( file, count );
  *next = reverse_int( file, count );
  
  return;
}


/* Get triangles */
void get_triangles( FILE *file,
					unsigned short *n,
					triangle_t **list,
					long *count )
{
  unsigned short tmp;
  
  /* Make sure we have the memory allocated */
  if ( *n != 0 ) byebye( "Triangles already allocated" );
  *n = reverse_short( file, count );
  *list = malloc( *n * sizeof(triangle_t) );
  if ( *list == NULL ) byebye( "Couldnt malloc triangle list" );
  
  /* Get all the vertices */
  for ( tmp=0; tmp<(*n); tmp++ ) {
    (*list)[tmp].a = reverse_short( file, count );
    (*list)[tmp].b = reverse_short( file, count );
    (*list)[tmp].c = reverse_short( file, count );
    (*list)[tmp].thing = reverse_short( file, count );
    (*list)[tmp].material = -1;
  }
  
  return;
}


/* Get vertices */
void get_vertices( FILE *file,
				   unsigned short *n,
				   vertex_t **list,
				   long *count )
{
  unsigned short tmp;

  /* Make sure we have the memory allocated */
  if ( *n != 0 ) byebye( "Vertices already allocated" );
  *n = reverse_short( file, count );
  *list = malloc( *n * sizeof(vertex_t) );
  if ( *list == NULL ) byebye( "Couldnt malloc vertex list" );

  /* Get all the vertices */
  for ( tmp=0; tmp<(*n); tmp++ ) {
    (*list)[tmp].x = reverse_float( file, count );
    (*list)[tmp].y = reverse_float( file, count );
    (*list)[tmp].z = reverse_float( file, count );
    (*list)[tmp].material = -1;
    (*list)[tmp].index_x = 0.0;
    (*list)[tmp].index_y = 0.0;
  }

  return;
}


/* Get vertex - something */
void get_vertex_thing( FILE *file,
					   unsigned short n, 
					   vertex_t *list,
					   long *count )
{
  unsigned short tmp;

  /* Make sure we have the memory allocated */
  tmp = reverse_short( file, count );
  if ( tmp != n ) byebye( "Number of vertex things not the same as vertices" );
  
  /* Get all the vertices */
  for ( tmp=0; tmp<(n); tmp++ ) {
    list[tmp].thing = reverse_short( file, count );
  }

  return;
}


/* Get vertex - something else */
void get_vertex_index( FILE *file,
					   unsigned short n,
					   vertex_t *list,
					   long *count )
{
  unsigned short tmp;
  
  /* Make sure we have the memory allocated */
  tmp = reverse_short( file, count );
  if ( tmp != n ) byebye( "Number of vertex indexes not the same as vertices" );
  
  /* Get all the vertices */
  for ( tmp=0; tmp<(n); tmp++ ) {
    list[tmp].index_x = reverse_float( file, count );
    list[tmp].index_y = reverse_float( file, count );
  }
  
  return;
}


/* Get material name */
void get_material( FILE *file,
				   unsigned short *materials,
				   material_t **list,
				   long *count )
{

  /* Allocate another material structure */
  (*list) = realloc( (*list), (*materials+1) * sizeof(material_t) );
  if ( *list == NULL ) byebye( "Couldn't allocate memory for material" );

  /* Read name into material */
  get_name( file, (*list)[*materials].name, count );
  (*list)[*materials].texture[0] = '\0';
  (*materials)++;

  return;
}


/* Get material colour */
void get_colour( FILE *file,
				 colour_t *col,
				 long *count )
{
  /* Don't know what the first six bytes are !! */
  reverse_float( file, count );
  reverse_short( file, count );
  col->r = get_char( file, count );
  col->g = get_char( file, count );
  col->b = get_char( file, count );

  return;
}


/* Attribute material to triangle */
void set_triangle_material( FILE *file,
							unsigned short triangles,
							triangle_t *triangle,
							unsigned short materials,
							material_t *material,
							long *count )
{
  char name[STR_BUF_SIZE];
  int n, i, t, total;
  
  /* Get the name of the material, and find it */
  get_name( file, name, count );
  for (n=0; n<materials; n++) {
    if ( !strcmp( name, material[n].name ) ) break;
  }
  
  /* Set triangle properties */
  total = reverse_short( file, count );
  if ( n == materials ) {
    for (i=0; i<total; i++) reverse_short( file, count );
  } else {
    for (i=0; i<total; i++) {
      t = reverse_short( file, count );
      if ( t<triangles ) triangle[t].material = n;
    }
  }

  return;
}


/* Attribute things to triangle */
void set_triangle_things( FILE *file,
						  unsigned short triangles,
						  triangle_t *triangle,
						  long *count )
{
  int i;
  
  /* Set triangle properties */
  for (i=0; i<triangles; i++) {
    triangle[i].things1 = get_char( file, count );
    triangle[i].things2 = get_char( file, count );
    triangle[i].things3 = get_char( file, count );
    triangle[i].things4 = get_char( file, count );
  }

  return;
}


/* Output the chunk in ASCII, then hex */
void dump_chunk( FILE *file, unsigned short id, unsigned int next )
{
  unsigned int n;
  unsigned char cdata;

  /* Header */
  fprintf(stdout, "\nChunk: %x, Size: %hi\n", id, next-6);

  /* ASCII dump */
  for (n=0; n<(next-6); n++) {
    if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
    if ( cdata > 31 && cdata < 127 ) {
      fprintf(stdout, " %c", cdata);
    } else {
      fprintf(stdout, "..");
    }
  }
  fprintf(stdout, "\n");
  fseek(file, 6-next, SEEK_CUR);
  
  /* Hex dump */
  for (n=0; n<((long)next-6); n++) {
    if ( fread( &cdata, sizeof(char), 1, file) != 1 ) byebye( "Couldnt read character" );
    fprintf(stdout, "%02x", cdata);
  }
  fprintf(stdout, "\n");
  fseek(file, 6-next, SEEK_CUR);

  return;
}


/* Main */
int main (int argc, char *argv[])
{
  /* Local variables */
  FILE *infile;
  FILE *outfile;
  char inname[STR_BUF_SIZE];
  char outname[STR_BUF_SIZE];
  char dumpname[STR_BUF_SIZE];
  unsigned short chunk, subchunk, subsubchunk, subsubsubchunk, polychunk, dumpchunk;
  long in_subchunk, in_subsubchunk, in_subsubsubchunk, in_polychunk;
  unsigned int next, n;
  unsigned short vertices, triangles, materials=0;
  vertex_t *vertex = NULL;
  triangle_t *triangle = NULL;
  material_t *material = NULL;
  poly_t poly;
  colour_t col;

  /* Open input and output files */
  if ( get_inputs( argc, argv, inname, outname, &dumpchunk, dumpname ) ) {
    byebye( "Couldn't parse inputs" );
  }
  infile = fopen( inname, "r" );
  outfile = fopen( outname, "w" );
  if ( infile == NULL ) byebye( "Couldnt open input file" );
  if ( outfile == NULL ) byebye( "Couldnt open output file" );
  fprintf(outfile, "LIST\n");

  /* Loop through other chunks */
  get_chunk( infile, &chunk, &next, &in_subchunk );
  switch ( chunk ) {
	
  case 0x4d4d:
    in_subchunk = (long)next-6;
    while ( in_subchunk ) {
      get_chunk( infile, &subchunk, &next, &in_subchunk );
      switch ( subchunk ) {
		
      case 0x3d3d:
		in_subsubchunk = (long)next - 6;
		in_subchunk -= in_subsubchunk;
		fprintf(stdout, "[");
		while ( in_subsubchunk ) {
		  get_chunk( infile, &subsubchunk, &next, &in_subsubchunk );
		  
		  /* Might want to output chunk details */
		  if ( dumpname[0] == '\0' ) {
			if ( subsubchunk == dumpchunk ) {
			  dump_chunk( infile, subsubchunk, next );
			} else if ( dumpchunk == 0xFFFF ) {
			  fprintf(stdout, " %x(%li)", subsubchunk, (long)next-6);
			}
		  }
		  
		  switch ( subsubchunk ) {
			
		  case 0x4000:
			fprintf(stdout, "\n[");
			in_subsubsubchunk = (long)next - 6;
			in_subsubchunk -= in_subsubsubchunk;
			get_name( infile, poly.name, &in_subsubsubchunk );
			while( in_subsubsubchunk ) {
			  get_chunk( infile, &subsubsubchunk, &next, &in_subsubsubchunk );
			  
			  /* Might want to output chunk details */
			  if ( dumpname[0] == '\0' ) {
				if ( subsubsubchunk == dumpchunk ) {
				  dump_chunk( infile, subsubsubchunk, next );
				} else if ( dumpchunk == 0xFFFF ) {
				  fprintf(stdout, " %x(%li)", subsubsubchunk, (long)next-6);
				}
			  }
			  
			  switch ( subsubsubchunk ) {
				
			  case 0x4100:
				fprintf(stdout, " [");
				in_polychunk = (long)next - 6;
				in_subsubsubchunk -= in_polychunk;
				vertices = 0;
				if ( vertex != NULL ) free( vertex );
				triangles = 0;
				if ( triangle != NULL ) free( triangle );
				while( in_polychunk ) {
				  get_chunk( infile, &polychunk, &next, &in_polychunk );
				  
				  /* Might want to output chunk details */
				  if ( polychunk == dumpchunk ) {
					if ( dumpname[0] == '\0' || !strcmp( poly.name, dumpname ) ) {
					  dump_chunk( infile, polychunk, next );
					}
				  } else if ( dumpchunk == 0xFFFF ) {
					if ( dumpname[0] == '\0' || !strcmp( poly.name, dumpname ) ) {
					  fprintf(stdout, " %x(%li)", polychunk, (long)next-6);
					}
				  }
				  
				  switch ( polychunk ) {
					
				  case 0x4110:
					get_vertices( infile, &vertices, &vertex, &in_polychunk );
					break;
					
				  case 0x4111:
					get_vertex_thing( infile, vertices, vertex, &in_polychunk );
					break;
					
				  case 0x4120:
					get_triangles( infile, &triangles, &triangle, &in_polychunk );
					break;
					
				  case 0x4130:
					set_triangle_material( infile, triangles, triangle, 
										   materials, material, &in_polychunk );
					break;
					
				  case 0x4140:
					get_vertex_index( infile, vertices, vertex, &in_polychunk );
					break;
					
				  case 0x4150:
					set_triangle_things( infile, triangles, triangle, &in_polychunk );
					break;
					
				  default:
					fseek( infile, (long)next-6, SEEK_CUR );
					in_polychunk -= (long)next - 6;
					break;
				  }
				}
				fprintf(stdout, " ]");
				
				/* Here we write what we've got to the output */
				if ( dumpname[0] == '\0' || strstr( dumpname, poly.name ) != NULL ) {
				  fprintf(outfile, "# %s\n{\nOFF %hi %hi 0\n", 
						  poly.name, vertices, triangles);
				  for (n=0; n<vertices; n++) {
					fprintf(outfile, "%f %f %f # %f %f\n", 
							vertex[n].x, vertex[n].y, vertex[n].z,
							vertex[n].index_x, vertex[n].index_y );
				  }
				  for (n=0; n<triangles; n++) {
					if ( triangle[n].material != -1 ) {
					  col = material[triangle[n].material].diffuse;
					} else {
					  col.r = 255;
					  col.g = 255;
					  col.b = 255;
					}
					fprintf(outfile, "3 %hi %hi %hi %i %i %i 1\n", 
							triangle[n].a, triangle[n].b, triangle[n].c,
							(int)col.r, (int)col.g, (int)col.b);
				  }
				  fprintf(outfile, "}\n");
				}
				break;
				
			  default:
				fseek( infile, (long)next-6, SEEK_CUR );
				in_subsubsubchunk -= (long)next - 6;
				break;
			  }
			}
			fprintf(stdout, " ]");
			break;
			
		  case 0xAFFF:
			fprintf(stdout, "\n[");
			in_subsubsubchunk = (long)next - 6;
			in_subsubchunk -= in_subsubsubchunk;
			while( in_subsubsubchunk ) {
			  get_chunk( infile, &subsubsubchunk, &next, &in_subsubsubchunk );
			  
			  /* Might want to output chunk details */
			  if ( dumpname[0] == '\0' ) {
				if ( subsubsubchunk == dumpchunk ) {
				  dump_chunk( infile, subsubsubchunk, next );
				} else if ( dumpchunk == 0xFFFF ) {
				  fprintf(stdout, " %x(%li)", subsubsubchunk, (long)next-6);
				}
			  }
			  
			  switch ( subsubsubchunk ) {
				
			  case 0xA000:
				get_material( infile, &materials, &material, &in_subsubsubchunk );
				break;
				
			  case 0xA010:
				if ( materials > 0 ) {
				  if ( next == 18 ) {
					get_colour( infile, &(material[materials-1].ambient), &in_subsubsubchunk );
					get_colour( infile, &col, &in_subsubsubchunk );
				  } else {
					get_colour( infile, &(material[materials-1].ambient), &in_subsubsubchunk );
				  }
				} else {
				  fseek( infile, (long)next-6, SEEK_CUR );
				  in_subsubsubchunk -= (long)next - 6;
				}
				break;
				
			  case 0xA020:
				if ( materials > 0 ) {
				  if ( next == 18 ) {
					get_colour( infile, &(material[materials-1].diffuse), &in_subsubsubchunk );
					get_colour( infile, &col, &in_subsubsubchunk );
				  } else {
					get_colour( infile, &(material[materials-1].diffuse), &in_subsubsubchunk );
				  }
				} else {
				  fseek( infile, (long)next-6, SEEK_CUR );
				  in_subsubsubchunk -= (long)next - 6;
				}
				break;
				
			  case 0xA030:
				if ( materials > 0 ) {
				  if ( next == 18 ) {
					get_colour( infile, &(material[materials-1].specular), &in_subsubsubchunk );
					get_colour( infile, &col, &in_subsubsubchunk );
				  } else {
					get_colour( infile, &(material[materials-1].specular), &in_subsubsubchunk );
				  }
				} else {
				  fseek( infile, (long)next-6, SEEK_CUR );
				  in_subsubsubchunk -= (long)next - 6;
				}
				break;
				
			  case 0xA200:
				fprintf(stdout, " [");
				in_polychunk = (long)next - 6;
				in_subsubsubchunk -= in_polychunk;
				while( in_polychunk ) {
				  get_chunk( infile, &polychunk, &next, &in_polychunk );
				  
				  /* Might want to output chunk details */
				  if ( dumpname[0] == '\0' ) {
					if ( polychunk == dumpchunk ) {
					  dump_chunk( infile, polychunk, next );
					} else if ( dumpchunk == 0xFFFF ) {
					  fprintf(stdout, " %x(%li)", polychunk, (long)next-6);
					}
				  }
				  
				  switch ( polychunk ) {
					
				  case 0xA300:
					get_name( infile, material[materials-1].texture, &in_polychunk );
					break;
					
				  default:
					fseek( infile, (long)next-6, SEEK_CUR );
					in_polychunk -= (long)next - 6;
					break;
				  }
				}
				fprintf(stdout, " ]");
				break;
				
			  case 0xA210:
				fprintf(stdout, " [");
				in_polychunk = (long)next - 6;
				in_subsubsubchunk -= in_polychunk;
				while( in_polychunk ) {
				  get_chunk( infile, &polychunk, &next, &in_polychunk );
				  
				  /* Might want to output chunk details */
				  if ( dumpname[0] == '\0' ) {
					if ( polychunk == dumpchunk ) {
					  dump_chunk( infile, polychunk, next );
					} else if ( dumpchunk == 0xFFFF ) {
					  fprintf(stdout, " %x(%li)", polychunk, (long)next-6);
					}
				  }
				  
				  switch ( polychunk ) {
					
				  case 0xA300:
					get_name( infile, material[materials-1].texture, &in_polychunk );
					break;
					
				  default:
					fseek( infile, (long)next-6, SEEK_CUR );
					in_polychunk -= (long)next - 6;
					break;
				  }
				}
				fprintf(stdout, " ]");
				break;
				
			  default:
				fseek( infile, (long)next-6, SEEK_CUR );
				in_subsubsubchunk -= (long)next - 6;
				break;
			  }
			}
			fprintf(stdout, " ]");
			break;
			
		  default:
			fseek( infile, (long)next-6, SEEK_CUR );
			in_subsubchunk -= (long)next - 6;
			break;
		  }
		}
		fprintf(stdout, " ]\n");
		break;
		
      default:
		fseek( infile, (long)next-6, SEEK_CUR );
		in_subchunk -= (long)next - 6;
		break;
      }
    }
    break;
    
  default:
    byebye( "Not a 3DS file" );
  }
  
  return 0;
}




