#include <windows.h>
#include <stdio.h>

#include "Types.h"
#include "File.h"
#include "List.h"


/********************************************************************/

class CFileN64 : public CFile
{
	public:

		void WriteLong( const u32 num );
};


class CAnim
{
	public:

		char
			Name[16];
		s32
			Size;
		u8
			*Data;

		void Load( CFile &file );
};


typedef struct
{
	CAnim
		*Anim;
	s32
		Offset,
		Flags;
	char
		Name[32];
} SLegDef;

typedef struct
{
	CAnim
		*Main,
		*Ball;
	s32
		Offset,
		Loop,
		Root,
		Fist;
	char
		Name[32];
} SMainDef;


/********************************************************************/

static s32
	MainAnims = 0,
	TotalAnims = 0,
	MaxLegSize = 0,
	MaxMainSize = 0;

static char
	Filename[32];

static CList<CAnim>
	Anims;

static CList<SLegDef>
	LegDefs;

static CList<SMainDef>
	MainDefs;

static const char
	*Names[] =
	{
		"hips",
		"chest",
		"head",
		"lshoulder",
		"lbicep",
		"lforarm",
		"lhand",
		"rshoulder",
		"rbicep",
		"rforarm",
		"rhand",
		"lthigh",
		"lshin",
		"lfoot",
		"rthigh",
		"rshin",
		"rfoot",
		"ball",
		""
	};


/********************************************************************/

static void Error( char *args, ... )
{
	char
		text[256];

	wvsprintf( text, args, (char *)(&args+1) );
	fprintf( stderr, "Error : %s\n", text );
	getchar();
}


static void PadFile( CFile &file, const s32 pad )
{
	const u8
		zero = 0;

	while (file.GetPos() % pad)
		file.Write( &zero, 1 );
}


static s32 FindNode( const char *name )
{
	s32
		i = 0;

	while (*Names[i])
	{
		if (!stricmp( Names[i], name ))
			return( i );

		i++;
	}

	return( -1 );
}


static void Swap32( void *mem )
{
	u8
		t,
		*ptr = (u8 *)mem;

	t = ptr[0];
	ptr[0] = ptr[3];
	ptr[3] = t;

	t = ptr[1];
	ptr[1] = ptr[2];
	ptr[2] = t;
}


/********************************************************************/

void CFileN64::WriteLong( u32 num )
{
	Swap32( &num );
	Write( &num, 4 );
}


/********************************************************************/

void CAnim::Load( CFile &file )
{
	char
		name[16];
	s32
		i,
		r,
		ref,
		refs,
		node,
		count;

	file.Skip( 4 );			// Skip ANM-
	file.Read( Name, 12 );
	Name[12] = 0;
	printf( "\t%s\n", Name );

	file.Read( &Size, 4 );

	file.Skip( 4 );			// Flags.

	file.Read( &refs, 4 );			// Num nodes.
	file.Skip( -4 );
	Swap32( &refs );
	Size -= 2*refs*4;			// Adjust tags size...
	Size += 20;

	Data = new u8[Size];

	file.Read( Data, 5*4 );
	file.Skip( 2*refs*4 );
	memset( &Data[5*4], 0, 20 );
	file.Read( &Data[(5*4)+20], Size - ((5*4)+20) );


	// tags...

	file.Read( &count, 4 );
	if (count != 0)
	{
		Error( "First tag count in %s is not 0!", Filename );

		for (i=0; i<count; i++)
		{
			file.Skip( 16 );
			file.Read( &refs, 4 );
			file.Skip( refs*4 );
		}
	}


	// gids...

	file.Read( &count, 4 );
	for (i=0; i<count; i++)
	{
		file.Read( name, 16 );
		node = FindNode( name );
		if (node < 0)
		{
			Error( "Unknown node %s in %s", name, Filename );
			node = 0;
		}
//		Swap32( &node );

		file.Read( &refs, 4 );
		for (r=0; r<refs; r++)
		{
			file.Read( &ref, 4 );
			ref -= (5*4);
			ref /= 4;
			ref += (5*4);
			Data[ref] = (u8)node;
		}
	}


	// tags...

	file.Read( &count, 4 );
	if (count != 0)
	{
		Error( "Second tag count in %s is not 0!", Filename );

		for (i=0; i<count; i++)
		{
			file.Skip( 16 );
			file.Read( &refs, 4 );
			file.Skip( refs*4 );
		}
	}
}


/********************************************************************/

static void LoadAnims( const char *filename )
{
	CFile
		file;
	CAnim
		*anim;

	strcpy( Filename, filename );
	puts( filename );

	file.Open( filename );

	while (file.GetPos() < file.GetSize())
	{
		anim = new CAnim;
		anim->Load( file );
		Anims.Add( anim );
	}

	file.Close();
}


static CAnim *FindAnim( const char *name )
{
	CAnim
		*anim;
	s32
		i;

	for (i=0; i<Anims.Count; i++)
	{
		anim = Anims[i];
		if (!stricmp( name, anim->Name ))
			return( anim );
	}

	return( NULL );
}


static void ProcessLegAnims( const char *filename, CFile &file )
{
	FILE
		*fp;
	SLegDef
		*def;
	CAnim
		*anim;
	char
		name[16],
		text[128],
		descrip[32];
	s32
		n,
		flags,
		line = 0;

	fp = fopen( filename, "r" );
	if (!fp)
	{
		Error( "Cannot open: '%s'", filename );
		return;
	}

	while (!feof( fp ))
	{
		text[0] = 0;
		fgets( text, 128, fp );
		line++;

		while (text[0] && text[0] <= ' ')
			strcpy( &text[0], &text[1] );

		if (text[0] && text[0] != '#')
		{
			descrip[0] = 0;
			n = sscanf( text, "%s %d %s", name, &flags, descrip );
			if (n < 2 || n > 3)
				Error( "%d params at line %d in %s; should be 2 or 3.", n, line, filename );
			else
			{
				anim = FindAnim( name );
				if (!anim)
					Error( "Cannot find anim %s at line %d in %s.", name, line, filename );
				else
				{
					PadFile( file, 4 );

					def = new SLegDef;
					def->Anim = anim;
					def->Offset = file.GetPos();
					def->Flags = flags;
					strcpy( def->Name, descrip );
					LegDefs.Add( def );

					file.Write( anim->Data, anim->Size );

					TotalAnims++;

					if (anim->Size > MaxLegSize)
						MaxLegSize = anim->Size;

					if (anim->Size & 1)
						Error( "Anim size is not multiple of 2!" );
				}
			}
		}
	}

	fclose( fp );

	printf( "%d leg anims.\n", LegDefs.Count );
}


static void SaveLegAnims( const char *filename )
{
	CFileN64
		file;
	SLegDef
		*def;
	u8
		*data;
	s32
		i,
		len,
		size;

	printf( "-> %s\n", filename );


	file.Open( "Anims.TMP" );
	len = file.GetSize();
	data = new u8[len];
	file.Read( data, len );
	file.Close();


	if (!file.Create( filename ))
	{
		Error( "Cannot create: '%s'", filename );
		return;
	}

	file.Write( "ANMl", 4 );
	file.Write( "BB2!", 4 );
	file.Write( "N64!", 4 );
	file.WriteLong( 1 );			// Version.

	file.WriteLong( LegDefs.Count );

	size = file.GetSize();
	size += LegDefs.Count * (2*4);

	for (i=0; i<LegDefs.Count; i++)
	{
		def = LegDefs[i];

		file.WriteLong( def->Offset + size );
		file.WriteLong( def->Anim->Size );
	}

	file.Write( data, len );

	file.Close();


	delete [] data;
}


static void SaveLegDefs( const char *filename )
{
	CFileN64
		file;
	SLegDef
		*def;
	s32
		i,
		bits = 0,
		used = 0;

	printf( "-> %s\n", filename );


	file.Create( filename );
	if (!file.Create( filename ))
	{
		Error( "Cannot create: '%s'", filename );
		return;
	}

	file.Write( "DEFl", 4 );
	file.Write( "BB2!", 4 );
	file.Write( "N64!", 4 );
	file.WriteLong( 1 );			// Version.

	file.WriteLong( LegDefs.Count );

	for (i=0; i<LegDefs.Count; i++)
	{
		def = LegDefs[i];

		if (def->Flags)
			bits |= 1 << used;

		used++;
		if (used == 32)
		{
			file.WriteLong( bits );
			bits = 0;
			used = 0;
		}
	}

	if (used)
		file.WriteLong( bits );

	file.Close();
}


static void ProcessMainAnims( const char *filename, CFile &file )
{
	FILE
		*fp;
	SMainDef
		*def;
	CAnim
		*mainanim,
		*ballanim;
	char
		text[128],
		descrip[32],
		mainname[16],
		ballname[16];
	s32
		n,
		loop,
		root,
		left,
		right,
		bcnt = 0,
		line = 0;

	fp = fopen( filename, "r" );
	if (!fp)
	{
		Error( "Cannot open: '%s'", filename );
		return;
	}

	while (!feof( fp ))
	{
		text[0] = 0;
		fgets( text, 128, fp );
		line++;

		while (text[0] && text[0] <= ' ')
			strcpy( &text[0], &text[1] );

		if (text[0] && text[0] != '#')
		{
			descrip[0] = 0;
			n = sscanf( text, "%s %s %d %d %d %d %s", mainname, ballname, &loop, &root, &left, &right, descrip );
			if (n < 6 || n > 7)
				Error( "%d params at line %d in %s; should be 6 or 7.", n, line, filename );
			else
			{
				mainanim = FindAnim( mainname );
				if (!mainanim)
					Error( "Cannot find anim %s at line %d in %s.", mainname, line, filename );

				if (stricmp( ballname, "NULL" ))
				{
					ballanim = FindAnim( ballname );
					if (!ballanim)
						Error( "Cannot find anim %s at line %d in %s.", ballname, line, filename );
				}
				else
					ballanim = NULL;

				if (mainanim)
				{
					def = new SMainDef;
					def->Main = mainanim;
					def->Ball = ballanim;
					def->Loop = loop;
					def->Root = root;
					def->Fist = (left << 1) | right;
					strcpy( def->Name, descrip );
					MainDefs.Add( def );

					PadFile( file, 4 );
					def->Offset = file.GetPos();
					file.Write( mainanim->Data, mainanim->Size );

					TotalAnims++;

					if (ballanim)
					{
						bcnt++;
						PadFile( file, 4 );
						file.Write( ballanim->Data, ballanim->Size );

						TotalAnims++;
					}
				}
			}
		}
	}

	fclose( fp );

	printf( "%d main anims, %d ball anims.\n", MainDefs.Count, bcnt );

	MainAnims = MainDefs.Count + bcnt;
}


static void SaveMainAnims( const char *filename )
{
	CFileN64
		file;
	SMainDef
		*def;
	u8
		*data;
	s32
		i,
		len,
		size,
		padded;

	printf( "-> %s\n", filename );


	file.Open( "Anims.TMP" );
	len = file.GetSize();
	data = new u8[len];
	file.Read( data, len );
	file.Close();


	file.Create( filename );
	if (!file.Create( filename ))
	{
		Error( "Cannot create: '%s'", filename );
		return;
	}

	file.Write( "ANMm", 4 );
	file.Write( "BB2!", 4 );
	file.Write( "N64!", 4 );
	file.WriteLong( 1 );			// Version.

	file.WriteLong( MainDefs.Count );
	file.WriteLong( MainAnims );

	size = file.GetSize();
	size += MainDefs.Count * (3*4);

	for (i=0; i<MainDefs.Count; i++)
	{
		def = MainDefs[i];

		file.WriteLong( def->Offset + size );

		if (def->Ball)
		{
			padded = def->Main->Size;
			padded = (padded + 3) & ~3;

			file.WriteLong( padded + def->Ball->Size );
			file.WriteLong( padded );

			if ((padded + def->Ball->Size) > MaxMainSize)
				MaxMainSize = (padded + def->Ball->Size);
		}
		else
		{
			file.WriteLong( def->Main->Size );
			file.WriteLong( 0 );

			if (def->Main->Size > MaxMainSize)
				MaxMainSize = def->Main->Size;
		}
	}

	file.Write( data, len );

	file.Close();


	delete [] data;
}


static void SaveMainDefs( const char *filename )
{
	CFileN64
		file;
	SMainDef
		*def;
	s32
		i,
		bits = 0,
		used = 0;

	printf( "-> %s\n", filename );


	file.Create( filename );
	if (!file.Create( filename ))
	{
		Error( "Cannot create: '%s'", filename );
		return;
	}

	file.Write( "DEFm", 4 );
	file.Write( "BB2!", 4 );
	file.Write( "N64!", 4 );
	file.WriteLong( 1 );			// Version.

	file.WriteLong( MainDefs.Count );

	for (i=0; i<MainDefs.Count; i++)
	{
		def = MainDefs[i];

		if (def->Loop)
			bits |= 1 << used;
		used++;

		if (def->Root)
			bits |= 1 << used;
		used++;

		bits |= def->Fist << used;
		used += 2;

		if (used == 32)
		{
			file.WriteLong( bits );
			bits = 0;
			used = 0;
		}
	}

	if (used)
		file.WriteLong( bits );

	file.Close();
}


static void SaveNames( const char *filename )
{
	FILE
		*fp;
	SMainDef
		*main;
	SLegDef
		*leg;
	s32
		i;

	printf( "-> %s\n", filename );


	fp = fopen( filename, "w" );
	if (!fp)
	{
		Error( "Cannot create: '%s'", filename );
		return;
	}


	fprintf( fp, "char *a_names[] =\n{\n" );

	for (i=0; i<MainDefs.Count; i++)
	{
		main = MainDefs[i];

		if (main->Name[0])
			fprintf( fp, "\t\"%s\",\n", main->Name );
		else
			fprintf( fp, "\t\"%s\",\n", main->Main->Name );
	}

	fprintf( fp, "};\n" );


	fprintf( fp, "\nchar *l_names[] =\n{\n" );

	for (i=0; i<LegDefs.Count; i++)
	{
		leg = LegDefs[i];

		if (leg->Name[0])
			fprintf( fp, "\t\"%s\",\n", leg->Name );
		else
			fprintf( fp, "\t\"%s\",\n", leg->Anim->Name );
	}

	fprintf( fp, "};\n" );


	fclose( fp );
}


static void SaveEnums( const char *filename )
{
	FILE
		*fp;
	SMainDef
		*main;
	SLegDef
		*leg;
	s32
		i;

	printf( "-> %s\n", filename );


	fp = fopen( filename, "w" );
	if (!fp)
	{
		Error( "Cannot create: '%s'", filename );
		return;
	}


	fprintf( fp, "enum ANIM_TOP\n{\n" );

	for (i=0; i<MainDefs.Count; i++)
	{
		main = MainDefs[i];

		if (main->Name[0])
			fprintf( fp, "\t%s,\n", main->Name );
		else
			fprintf( fp, "\t%s,\n", main->Main->Name );
	}

	fprintf( fp, "};\n" );


	fprintf( fp, "\nenum ANIM_LEG\n{\n" );

	for (i=0; i<LegDefs.Count; i++)
	{
		leg = LegDefs[i];

		if (leg->Name[0])
			fprintf( fp, "\t%s,\n", leg->Name );
		else
			fprintf( fp, "\t%s,\n", leg->Anim->Name );
	}

	fprintf( fp, "};\n" );


	fclose( fp );
}


int main()
{
	CFile
		file;
	HANDLE
		handle;
	WIN32_FIND_DATA
		fd;
	BOOL
		found = TRUE;
	s32
		count = 0;

	handle = FindFirstFile( "*.BIN", &fd );
	if (handle != INVALID_HANDLE_VALUE)
	{
		while (found)
		{
			count++;
			LoadAnims( fd.cFileName );
			putchar( '\n' );

			found = FindNextFile( handle, &fd );
		}

		FindClose( handle );
	}

	printf( "%d files processed.\n", count );
	printf( "%d anims loaded.\n", Anims.Count );


	putchar( '\n' );
	file.Create( "Anims.TMP" );
	ProcessLegAnims( "Legs.TXT", file );
	file.Close();

	SaveLegDefs( "LegsAnim.DEF" );
	SaveLegAnims( "LegsAnim.DAT" );
	printf( "Max anim size = %d\n", MaxLegSize );


	putchar( '\n' );
	file.Create( "Anims.TMP" );
	ProcessMainAnims( "Anim.TXT", file );
	file.Close();

	SaveMainDefs( "MainAnim.DEF" );
	SaveMainAnims( "MainAnim.DAT" );
	printf( "Max anim size = %d\n", MaxMainSize );


	putchar( '\n' );
	SaveNames( "ANames.C" );
	SaveEnums( "AnimEnum.H" );


	DeleteFile( "Anims.TMP" );


	putchar( '\n' );
	printf( "Total anims = %d\n", TotalAnims );


#ifdef _DEBUG
	getchar();
#endif

	return( 0 );
}
