/*
Copyright (C) 1999-2006 Id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.

This file is part of GtkRadiant.

GtkRadiant is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

GtkRadiant is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "qdata.h"
#include "flex.h"

#define		MAXFILES	2048

typedef	struct
{
	int			x;
	int			y;
	int			w;
	int			h;
	int			cw;
	int			ch;
	int			rw;
	int			index;
	int			depth;
	int			col;
	int			baseline;
	char		name[128];
} Coords;

int				filenum;
int				valid;
Coords 			in[MAXFILES];
Coords			out;
char		  	outscript[256];
char		  	sourcedir[256];
char		  	outusage[256];
char		  	root[32];

int				destsize = 0;
byte		  	*pixels = NULL;				// Buffer to load image
long		  	*outpixels = NULL;			// Buffer to store combined textures
long		  	*usagemap = NULL;			// Buffer of usage map
void		  	*bmptemp = NULL;			// Buffer of usage map
byte		  	*map = NULL;

int				xcharsize;
int				ycharsize;
int				dosort = 0;
int				missed = 0;
int				overlap = 0;
int				nobaseline = 0;
int				percent;

//////////////////////////////////////////////////
// Setting the char based usage map				//
//////////////////////////////////////////////////

byte	TryPlace(Coords *coord)
{
	int		x, y;
	byte	entry = 0;
	byte	*mapitem;
	
	mapitem = map + (coord->x / xcharsize) + ((coord->y / ycharsize) * out.cw);

	for (y = 0; y < coord->ch; y++, mapitem += out.cw - coord->cw)
	{
		for (x = 0; x < coord->cw; x++)
		{
			if (entry |= *mapitem++ & 8)
			{
				return(entry);
			}
		}
	}
	return(entry);
}

void	SetMap(Coords *coord)
{
	int		x, y;
	byte	*mapitem;

	mapitem = map + (coord->x / xcharsize) + ((coord->y / ycharsize) * out.cw);

	for (y = 0; y < coord->ch; y++, mapitem += out.cw - coord->cw)
		for (x = 0; x < coord->cw; x++)
			*mapitem++ |= 8;
}

//////////////////////////////////////////////////
// Setting the pixel based usage map			//
//////////////////////////////////////////////////

void	CheckOverlap(Coords *coord)
{
	int			x;
	int			y;
	long		*dest;

	x = coord->x;
	y = coord->y;

	dest = (long *)(usagemap + x + (y * out.w));

	for (y = 0; y < coord->h; y++, dest += out.w - coord->w)
	{
		for (x = 0; x < coord->w; x++)
		{
			if (*dest++)
			{
				overlap++;
				return;
			}
		}
	}
}

void	SetUsageMap(Coords *coord)
{
	int			x;
	int			y;
	long		*dest;

	x = coord->x;
	y = coord->y;

	dest = (long *)(usagemap + x + (y * out.w));

	for (y = 0; y < coord->h; y++, dest += out.w - coord->w)
	{
		for (x = 0; x < coord->w; x++)
		{
			*dest++ = coord->col;
		}
	}
}

//////////////////////////////////////////////////
// Flips the BMP image to the correct way up	//
//////////////////////////////////////////////////

void	CopyLine(byte *dest, byte *src, int size)
{
	int		x;
	
	for (x = 0; x < size; x++)
		*dest++ = *src++;
}

/****************************************************/
/* Printing headers etc								*/
/****************************************************/

void RemoveLeading(char *name)
{
	int		i;
	char	temp[128];

	for(i = strlen(name) - 1; i > 0; i--)
	{
		if((name[i] == '\\') || (name[i] == '/'))
		{
			strcpy(temp, name + i + 1);
			strcpy(name, temp);
			return;
		}
	}
}

void RemoveExt(char *name)
{
	while ((*name != '.') && *name)
		name++;
	*name = 0;
}

/****************************************************/
/* Misc calcualtions								*/
/****************************************************/

int	TotalArea()
{
	int		i;
	int		total = 0;

	for (i = 0; i < (filenum + 2); i++)
		total += in[i].w * in[i].h;

	return(total);
}

/****************************************************/
/* Setup and checking of all info					*/
/****************************************************/

void	InitVars()
{
	filenum = 0;
	valid = 0;
   	dosort = 0;
   	missed = 0;
   	overlap = 0;
   	nobaseline = 0;
	
	memset(outscript, 0, sizeof(outscript));
	memset(outscript, 0, sizeof(sourcedir));
	memset(outscript, 0, sizeof(outusage));
	memset(outscript, 0, sizeof(root));

	memset(in, 0, sizeof(in));
	memset(&out, 0, sizeof(out));
}
void Cleanup()
{
	if (pixels)
		free(pixels);
	if (usagemap)
		free(usagemap);
	if (outpixels)
		free(outpixels);
	if (bmptemp)
		free(bmptemp);
	if (map)
		free(map);
}

typedef struct glxy_s
{
	float	xl, yt, xr, yb;
	int		w, h, baseline;
} glxy_t;

int	SaveScript(char *name)
{
	FILE		*fp;
	int			i, j;
	glxy_t		buff;
	
	if(fp = fopen(name, "wb"))
	{
		for (j = 0; j < filenum; j++)
		{
			for (i = 0; i < filenum; i++)
			{
				if (in[i].index == j)
				{
					if (in[i].depth)
					{
						buff.xl = (float)in[i].x / (float)out.w;
						buff.yt = (float)in[i].y / (float)out.h;
						buff.xr = ((float)in[i].w + (float)in[i].x) / (float)out.w;
						buff.yb = ((float)in[i].h + (float)in[i].y) / (float)out.h;
						buff.w = in[i].w;
						buff.h = in[i].h;
						buff.baseline = in[i].baseline;
					}
					else
					{
						memset(&buff, 0, sizeof(glxy_t));
					}
					fwrite(&buff, 1, sizeof(glxy_t), fp);
					i = filenum;
				}
			}
		}
		fclose(fp);
		return(true);
	}
	else
		return(false);
}

int		GetScriptInfo(char *name)
{
	FILE		*fp;
	char		buffer[256];
	char		tempbuff[256];
	char		delims[] = {" \t,\n"};

	printf("Opening script file %s.\n", name);

	if (fp = fopen(name, "r"))
	{
		while(fgets(buffer, 256, fp))
		{
			if (strncmp(buffer, "//", 2) && strncmp(buffer, "\n", 1))
			{
				strupr(buffer);
				strcpy(tempbuff, buffer);
				if (strcmp(strtok(tempbuff, delims), "OUTPUT") == 0)
				{
					strcpy(out.name, strtok(NULL, delims));
					strlwr(out.name);
				}

				strcpy(tempbuff, buffer);
				if (strcmp(strtok(tempbuff, delims), "SOURCEDIR") == 0)
				{
					strcpy(tempbuff, strtok(NULL, delims));
					strcpy(sourcedir, ExpandPathAndArchive(tempbuff));
				}

				strcpy(tempbuff, buffer);
				if (strcmp(strtok(tempbuff, delims), "DOSORT") == 0)
					dosort = 1;

				strcpy(tempbuff, buffer);
				if (strcmp(strtok(tempbuff, delims), "XCHARSIZE") == 0)
					xcharsize = strtol(strtok(NULL, delims), NULL, 0);

				strcpy(tempbuff, buffer);
				if (strcmp(strtok(tempbuff, delims), "YCHARSIZE") == 0)
					ycharsize = strtol(strtok(NULL, delims), NULL, 0);

				strcpy(tempbuff, buffer);
				if (strcmp(strtok(tempbuff, delims), "OUTSCRIPT") == 0)
				{
					strcpy(outscript, strtok(NULL, delims));
					strlwr(outscript);
				}

				strcpy(tempbuff, buffer);
				if (strcmp(strtok(tempbuff, delims), "OUTUSAGE") == 0)
					strcpy(outusage, strtok(NULL, delims));

				strcpy(tempbuff, buffer);
				if (strcmp(strtok(tempbuff, delims), "POS") == 0)
				{
					out.w = strtol(strtok(NULL, delims), NULL, 0);
			 		out.h = strtol(strtok(NULL, delims), NULL, 0);
				}

				strcpy(tempbuff, buffer);
				if (strcmp(strtok(tempbuff, delims), "FILE") == 0)
				{
					strcpy(in[filenum].name, strtok(NULL, delims));
					in[filenum].x = strtol(strtok(NULL, delims), NULL, 0);
					in[filenum].y = strtol(strtok(NULL, delims), NULL, 0);
					in[filenum].col = strtol(strtok(NULL, delims), NULL, 0);
					filenum++;
				}
			}
		}
		fclose(fp);
		return(true);
	}
	else
	{
		printf("ERROR : Could not open script file.\n");
		return(false);
	}
}

int	CheckVars()
{
	int		i;

	 if (out.name[0] == 0)
	 {
	 	printf("ERROR : No output name specified.\n");
		return(false);
	}
	if ((out.w <= 0) || (out.h <= 0))
	{
		printf("ERROR : Invalid VRAM coordinates.\n");
		return(false);
	}
	if (filenum == 0)
	{
		printf("ERROR : No input files specified.\n");
		return(false);
	}
	for (i = 0; i < filenum; i++)
		if (in[i].name[0] == 0)
		{
			printf("ERROR : Input filename invalid.\n");
			return(false);
		}
	return(true);
}

// Makes sure texture is totally within the output area

int	CheckCoords(Coords *coord)
{
	if ((coord->x + coord->w) > out.w)
		return(false);
	if ((coord->y + coord->h) > out.h)
		return(false);

	return(true);
}
// Gets the width, height, palette width and palette height of each BMP file

int		GetFileDimensions()
{
	int			i;
	int			width, height;
	char		name[128];

	for (i = 0; i < filenum; i++)
	{
		in[i].index = i;

		strcpy(name, sourcedir);
		strcat(name, in[i].name);
		printf("Getting file dimensions, file : %s        \r", in[i].name);
		if(FileExists(name))
		{
			LoadAnyImage(name, NULL, NULL, &width, &height);
			in[i].depth = 32;
			in[i].rw = width;
			in[i].w = width;					 	// makes it width in 
			in[i].h = height;
			in[i].cw = (in[i].w + (xcharsize - 1)) / xcharsize;
			in[i].ch = (in[i].h + (ycharsize - 1)) / ycharsize;

			if (!CheckCoords(&in[i]) && (in[i].x >= 0))
			{
			 	printf("Error : texture %s out of bounds.\n", in[i].name);
				return(false);
			}
			valid++;
		}
		else
		{
			in[i].depth = 0;
			in[i].x = -1;
			in[i].y = -1;
			in[i].w = 0;
			in[i].h = 0;
		}
	}
	printf("\n\n");
	return(true);
}

// Sorts files into order for optimal space finding
// Fixed position ones first, followed by the others in descending size
// The theory being that it is easier to find space for smaller textures.
// size = (width + height)
// For space finding it is easier to place a 32x32 than a 128x2

#define	WEIGHT	0x8000

void	Swap(Coords *a, Coords *b)
{
	Coords		c;

	c = *a;
	*a = *b;
	*b = c;
}

void	SortInNames()
{
	int		i, j;
	int		largest, largcount;
	int		size;

	printf("Sorting filenames by size.\n\n");

	for (j = 0; j < filenum; j++)
	{
		largest = -1;
		largcount = -1;

		for (i = j; i < filenum; i++)
		{
			if (in[i].depth)
			{
				size = in[i].w + in[i].h;
		
				if ((in[i].x < 0) && (size > largest))
				{
					largcount = i;
					largest = size;
				}
			}
		}
		if ((largcount >= 0) && (largcount != j))
			Swap(&in[j], &in[largcount]);
	}
}

int	SetVars(char *name)
{
	if (!GetScriptInfo(name))
		return(false);

	if (!CheckVars())
		return(false);

	destsize = out.w * out.h;

	out.cw = out.w / xcharsize;
	out.ch = out.h / ycharsize;

	if ((usagemap = (long *)SafeMalloc(destsize * 4, "")) == NULL)
		return(false);
	if ((outpixels = (long *)SafeMalloc(destsize * 4, "")) == NULL)
		return(false); 
	if ((bmptemp = (void *)SafeMalloc(destsize * 4, "")) == NULL)
		return(false);
	if ((map = (byte *)SafeMalloc(destsize / (xcharsize * ycharsize), "")) == NULL)
		return(false);

	if (GetFileDimensions() == false)
		return(false);

	if (dosort)
		SortInNames();

	return(true);
}
/****************************************************/
/* Actual copying routines							*/
/****************************************************/

int FindCoords(Coords *coord)
{
	int		tx, ty;

	if (coord->x >= 0)
	{	
		SetMap(coord);
		return(true);
	}
	else
	{
		for (ty = 0; ty < out.ch; ty++)
		{
			for (tx = 0; tx < out.cw; tx++)
			{
				coord->x = (tx * xcharsize);
				coord->y = (ty * ycharsize);
	
				if (CheckCoords(coord) && !TryPlace(coord))
				{
					SetMap(coord);
					return(true);
				}
			}
		}
	}
	coord->x = -1;
	coord->y = -1;

	return(false);
}

void CheckBaseline(int i)
{
	int		y;
	long	*pix;

	in[i].baseline = -1;
	pix = (long *)pixels;

	for(y = 0; y < in[i].h; y++, pix += in[i].w)
	{
		if((*pix & 0x00ffffff) == 0x00ff00ff)
		{
			in[i].baseline = y;
			break;
		}
	}
	pix = (long *)pixels;
	for(y = 0; y < in[i].w * in[i].h; y++, pix++)
	{
		if((*pix & 0x00ffffff) == 0x00ff00ff)
		{
			*pix = 0;
		}
	}

	if(in[i].baseline == -1)
	{
		printf("\nERROR : %s has no baseline\n", in[i].name);
		nobaseline++;
	}
}

void	CopyToMain32(Coords *coord)
{
	int			x;
	int			y;
	long		*source;
	long		*dest;

	x = coord->x;
	y = coord->y;

	source = (long *)pixels;
	dest = (long *)(outpixels + x + (y * out.w));

	for (y = 0; y < coord->h; y++, dest += out.w - coord->w)
	{
		for (x = 0; x < coord->w; x++)
		{
			*dest++ = *source++;
		}
	}
}

void CreateMain()
{
	int			i, count;
	int			width, height;
	char		name[128];
	
	for (i = 0, count = 0; i < filenum; i++)
	{
		if (in[i].depth)
		{
			printf("\rProcessing %d of %d (%d missed, %d overlapping, %d nobase)\r", count + 1, valid, missed, overlap, nobaseline);
			count++;
			if (!FindCoords(&in[i]))
				missed++;
			else
			{
				strcpy(name, sourcedir);
				strcat(name, in[i].name);
				LoadAnyImage(name, &pixels, NULL, &width, &height);
				CheckBaseline(i);
				CheckOverlap(&in[i]);
				CopyToMain32(&in[i]);
				SetUsageMap(&in[i]);
			}
		}
	}
}

void Cmd_TextureMix()
{
	miptex32_t		*qtex32;
	char			filename[1024];
	int				size;

	InitVars();

	GetScriptToken (false);

	strcpy(root, token);
	RemoveExt(root);
	RemoveLeading(root);

	strcpy(filename, ExpandPathAndArchive(token));
	if (SetVars(filename))
	{
		// Create combined texture
		percent = ((TotalArea() * 100) / (out.w * out.h));
		printf("Total area consumed : %d%%\n", percent);
		printf("Texture resolution  : %dx%d pixels.\n", xcharsize, ycharsize);
		CreateMain();

		// Save image as m32
		sprintf (filename, "%spics/misc/%s.m32", gamedir, out.name);
		qtex32 = CreateMip32((unsigned *)outpixels, out.w, out.h, &size, false);

		qtex32->contents = 0;
		qtex32->value = 0;
		qtex32->scale_x = 1.0;
		qtex32->scale_y = 1.0;
		sprintf (qtex32->name, "misc/%s", out.name);

		printf ("\n\nwriting %s\n", filename);
		SaveFile (filename, (byte *)qtex32, size);
		free (qtex32);

		// Save out script file
		sprintf (filename, "%spics/misc/%s.fnt", gamedir, outscript);
		printf("Writing %s as script file\n", filename);
		if (!SaveScript(filename))
		{
			printf("Unable to save output script.\n");
		}
	}
	printf("Everythings groovy.\n");
	Cleanup();
}

// end

