#include <windows.h>
#include <stdio.h>

#include "Types.h"
#include "Misc.h"
#include "Texture.h"


#define TEX_TOP			0
#define TEX_FRONT		1
#define TEX_BACK		2
#define TEX_SIDE		3
#define TEX_GOATIE	4


typedef struct
{
	u16 a : 1;
	u16 b : 5;
	u16 g : 5;
	u16 r : 5;
} RGBA16;


extern "C"
{
	s32 HeadTexToN64( const char *folder, FILE *fp );
}


extern u32
	BestPal_BestPalAndRemapNoTransp( u16 *pSrcImage16bits,
																	 u32 PixelNumber,
																	 u16 *pPLUT,
																	 s32 NbWantedColors,
																	 u16 *pDestImage16bits );


static CTexture
	TexList[5];

static BGRA32
	Big[64][128],
	Small[32][64];

static u16
	Shrunk[(8*8)+(64*32)];

static RGBA16
	Palette[116];


/*****************************************************************************/

static s32 LoadTextures( const char *folder )
{
	static const char
		*names[4] =
		{
			"TOP",
			"FRONT",
			"BACK",
			"SIDE"
		};
	char
		filename[MAX_PATH];
	s32
		i;

	for (i=0; i<4; i++)
	{
		strcpy( filename, folder );
		strcat( filename, "/" );
		strcat( filename, names[i] );
		strcat( filename, ".BMP" );

		if (!TexList[i].LoadBMP( filename ))
			return( FALSE );
	}


	// Load goatie if it exists...

	{
		FILE
			*fp;

		strcpy( filename, folder );
		strcat( filename, "/Goatie.BMP" );

		fp = fopen( filename, "rb" );
		if (fp)
		{
			fclose( fp );

			TexList[TEX_GOATIE].LoadBMP( filename );

			for (i=0; i<(16*64); i++)
			{
				if (TexList[TEX_GOATIE].Data[i] == TexList[TEX_SIDE].Data[i])
					TexList[TEX_GOATIE].Data[i] = 0;
			}
		}
	}


	return( TRUE );
}


static void PasteToBig( CTexture *tex, const s32 offset, s32 scale )
{
	s32
		s,
		d,
		x,
		y,
		dir;

	if (scale < 0)
	{
		dir = -1;
		scale = -scale;
	}
	else
	{
		dir = +1;
	}

	s = 0;

	for (y=0; y<tex->Height; y++)
	{
		d = offset;

		for (x=0; x<tex->Width; x++)
		{
			if (dir >= 0)
				s = (y * tex->Width) + x;
			else
				s = ((y + 1) * tex->Width) - x - 1;

			Big[y][d++] = tex->Palette[tex->Data[s]];

			if (scale == 2)
				Big[y][d++] = tex->Palette[tex->Data[s]];
		}
	}
}


static void AddTexel( const s32 x, const s32 y, u32 &r, u32 &g, u32 &b )
{
	r += Big[y][x].r;
	g += Big[y][x].g;
	b += Big[y][x].b;
}


static void Shrink( s32 scale )
{
	u32
		r,
		g,
		b;
	s32
		i,
		j,
		x,
		y;

	for (y=0; y<(64/scale); y++)
	{
		for (x=0; x<(128/scale); x++)
		{
			r = 0;
			g = 0;
			b = 0;

			for (j=0; j<scale; j++)
			{
				for (i=0; i<scale; i++)
					AddTexel( (x*scale)+i, (y*scale)+j, r, g, b );
			}

			r /= (scale * scale);
			g /= (scale * scale);
			b /= (scale * scale);

			Small[y][x].r = r & 0xf8;
			Small[y][x].g = g & 0xf8;
			Small[y][x].b = b & 0xf8;
		}
	}
}


static void Extract( const s32 w, const s32 h, u16 *dst )
{
	u16
		rgb;
	s32
		x,
		y;

	for (y=0; y<h; y++)
	{
		for (x=0; x<w; x++)
		{
			rgb = 0;
			rgb |= (u16)Small[y][x].r << 7;
			rgb |= (u16)Small[y][x].g << 2;
			rgb |= (u16)Small[y][x].b >> 3;

			*dst++ = rgb;
		}
	}
}


static u8 FindClosest( s32 r, s32 g, s32 b, u16 *pal, s32 count )
{
	s32
		i,
		d,
		diff,
		which = 0,
		prev = 0x7fffffff;

	for (i=0; i<count; i++)
	{
		diff = 0;

		d = (pal[i] >> 10) & 31;
		d -= r;
		diff += d * d;

		d = (pal[i] >> 5) & 31;
		d -= g;
		diff += d * d;

		d = (pal[i] >> 0) & 31;
		d -= b;
		diff += d * d;

		if (diff < prev)
		{
			prev = diff;
			which = i;
		}
	}

	return( which );
}


static void Quantize()
{
	u16
		*work,
		pal[256];
	s32
		i,
		used;

	work = new u16[(8*8)+(64*32)];

	used = BestPal_BestPalAndRemapNoTransp( Shrunk, (8*8)+(64*32), pal, 115, work );

	delete [] work;


	for (i=0; i<used; i++)
	{
		Palette[i].r = (pal[i] >> 10) & 31;
		Palette[i].g = (pal[i] >>  5) & 31;
		Palette[i].b = (pal[i] >>  0) & 31;
		Palette[i].a = 0;
	}

	while (i < 116)
	{
		Palette[i].r = 0;
		Palette[i].g = 0;
		Palette[i].b = 0;
		Palette[i].a = 0;

		i++;
	}


//	printf( "%d colors used.\n", used );
}


static s32 FolderExists( const char *folder )
{
	DWORD
		attrib;

	attrib = GetFileAttributes( folder );
	if (attrib == 0xffffffff)
		return( FALSE );

	if (attrib & FILE_ATTRIBUTE_DIRECTORY)
		return( TRUE );

	return( FALSE );
}


s32 HeadTexToN64( const char *folder, FILE *fp )
{
	u16
		word;
	RGBA16
		rgba;
	s32
		i;

	if (!FolderExists( folder ))
		return( FALSE );


	if (!LoadTextures( folder ))
		return( FALSE );


	PasteToBig( &TexList[TEX_TOP], 0, 1 );

	Shrink( 4 );
	Extract( 8, 8, Shrunk );


	PasteToBig( &TexList[TEX_FRONT], 48, 1 );

	PasteToBig( &TexList[TEX_SIDE], 32, 1 );
	PasteToBig( &TexList[TEX_SIDE], 80, -1 );

	PasteToBig( &TexList[TEX_BACK], 0, 2 );
	PasteToBig( &TexList[TEX_BACK], 96, -2 );

	Shrink( 2 );
	Extract( 64, 32, &Shrunk[8*8] );


	Quantize();


	for (i=0; i<115; i++)
	{
		word = *(u16 *)&Palette[i];
		fputc( word >> 8, fp );
		fputc( word & 255, fp );
	}


	if (TexList[TEX_GOATIE].Data)
	{
		fputc( -1, fp );
		fputc( -1, fp );
	}
	else
	{
		TexList[TEX_GOATIE].Width = 16;
		TexList[TEX_GOATIE].Height = 64;
		TexList[TEX_GOATIE].Data = new u8[16*64];
		ZeroMemory( TexList[TEX_GOATIE].Data, 16*64 );

		fputc( 0, fp );
		fputc( 0, fp );
	}


	for (i=0; i<256; i++)
	{
		rgba.r = (TexList[0].Palette[i].r >> 3) & 31;
		rgba.g = (TexList[0].Palette[i].g >> 3) & 31;
		rgba.b = (TexList[0].Palette[i].b >> 3) & 31;
		rgba.a = 0;

		word = *(u16 *)&rgba;
		fputc( word >> 8, fp );
		fputc( word & 255, fp );
	}


	for (i=0; i<5; i++)
	{
		fwrite( TexList[i].Data, TexList[i].Width * TexList[i].Height, 1, fp );
	}


	delete [] TexList[TEX_GOATIE].Data;
	TexList[TEX_GOATIE].Data = NULL;


	return( TRUE );
}
