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

#include "bmp2cgb.h"
#include "errors.h"
#include "rgbt.h"


void banner(void)
{
	printf("\nbmp2cgb v%.2f - bitmap converter for Game Boy Color\n", VERSION);
	printf("Programmed by: tmk, email: tmk@tuta.io\n");
	printf("Project page: https://github.com/gitendo/bmp2cgb/\n\n");
}



void usage(void)
{
	banner();

	printf("Syntax: bmp2cgb.exe [options] picture[.bmp]\n\n");
	printf("Options:\n");
	
	printf("\t-c    disable character optimization\n");
	printf("\t-x    disable horizontal flip optimization\n");
	printf("\t-y    disable vertical flip optimization\n");
	printf("\t-z    disable horizontal & vertical flip optimization\n");
	printf("\t-o    disable palette optimization\n");
	printf("\n");
	printf("\t-e#   expand map width to 32 blocks using character (0-255)\n");
	printf("\t-m#   map padding - starting character (1-511)\n");
	printf("\t-p#   palette padding - starting slot (1-7)\n");
	printf("\t-r    rebase character map to $8800-$97FF ($8000-$8FFF is default)\n");
	printf("\n");
	printf("\t-d    extended debug information without data output\n");
	printf("\t-s#   sprites output (transparent color RGB hex value ie. 4682b4)\n");
	printf("\t-t    RGBTuner ROM image output\n");

	exit(EXIT_FAILURE);
}



void error_handler(char status)
{
	char	*msg;

	switch(status)
	{
		case ERR_UNK_OPTION :	msg = "Invalid option!\n"; break;
		case ERR_NOT_FOUND	:	msg = "Input file not found or access denied!\n"; break;
		case ERR_MALLOC_HDR	:	msg = "Memory allocation for BITMAPFILEHEADER failed!\n"; break;
		case ERR_NOT_BMP	:	msg = "BMP signature missing!\n"; break;
		case ERR_MALLOC_NFO	:	msg = "Memory allocation for BITMAPINFOHEADER failed!\n"; break;
		case ERR_NO_NFOHDR	:	msg = "Unsupported bitmap type, BITMAPINFOHEADER not found!\n"; break;
		case ERR_NOT_ROUNDED:	msg = "Height and width must be a multiple of 8!\n"; break;
		case ERR_TOO_BIG	:	msg = "Image too big - character map exceeds bank size!\n"; break;
		case ERR_COMPRESSED	:	msg = "Compression method is not supported!\n"; break;
		case ERR_MALLOC_BMPD:	msg = "Memory allocation for BMP data failed!\n"; break;
		case ERR_NOT_SUPRTD	:	msg = "Unsupported / unknown bitmap type!\n"; break;
		case ERR_MAX_COLS1	:	msg = "No more than 32 unique colors are allowed!\n"; break;
		case ERR_BITFIELDS	:	msg = "Unsupported BITFIELD size, only 8 bit masks are valid!\n"; break;
		case ERR_TRNSP		:	msg = "Transparent color not found in bitmap palette!\n"; break;
		case ERR_PADDING	:	msg = "Padding allowed for maps below 32 * 32 blocks only!\n"; break;
		case ERR_MALLOC_TMPD:	msg = "Memory allocation for BMP data helper failed!\n"; break;
		case ERR_MALLOC_TMPC:	msg = "Memory allocation for palette helper failed!\n"; break;
		case ERR_MAX_COLS2	:	return; break;							
		case ERR_MALLOC_TMPI:	msg = "Memory allocation for palette index failed!\n"; break;
		case ERR_PASS		:	msg = "Maximum of 8 palettes reached!\n"; break;
		case ERR_PASS1		:	msg = "Pass 1 failed! Maximum of 8 palettes reached!\n"; break;
		case ERR_PASS2		:	msg = "Pass 2 failed! Maximum of 8 palettes reached!\n"; break;
		case ERR_PASS3		:	msg = "Pass 3 failed! Maximum of 8 palettes reached!\n"; break;
		case ERR_PASS4		:	msg = "Pass 4 failed! Maximum of 8 palettes reached!\n"; break;
		case ERR_TMPI_FAILED:	msg = "Building palette index helper failed!\n"; break;
		case ERR_WRONG_PAL	:	msg = "find_palette() supports only 1 or 2 color palettes!\n"; break;
		case ERR_MAX_TILES	:	msg = "Maximum characters limit exceeded!\n"; break;
		case ERR_MALLOC_CGBT:	msg = "Memory allocation for CGB tiles failed!\n"; break;
		default				:	msg = "Undefined error code!\n"; break;
	}

	printf("\nError: %s", msg);
	return;
}



void save(char *fname, char *ext, void *src, int size)
{
	char 	fn[FILENAME_MAX];
	FILE 	*fp = NULL;

	strcpy(fn, fname);													
	strcat(fn, ext);													

	fp = fopen(fn, "wb");												
	fwrite(src, size, 1, fp);
	fclose(fp);
}



void make_rgbt(unsigned char *rgbtuner, unsigned char *cgb_chr, unsigned char *cgb_map, unsigned char *cgb_atr, CGBQUAD *cgb_pal, unsigned short map_height, unsigned short map_width, unsigned short used_tiles)
{
	unsigned char	*dst, *src, size;
	unsigned short	crc = 0;
	size_t			i;

	memcpy(&rgbtuner[PAL_DATA], cgb_pal, sizeof(CGBQUAD) * MAX_SLOTS);	

	if(map_width > 32)													
		size = 32;
	else
		size = (unsigned char) map_width;

	if(map_height > 32)
		map_height = 32;
	
	for(map_height--; map_height != 0xffff; map_height--)								
	{
		src = &cgb_map[map_height * map_width];
		dst = &rgbtuner[MAP_DATA + (map_height * 32)];
		memcpy(dst, src, size);

		src = &cgb_atr[map_height * map_width];
		dst = &rgbtuner[ATR_DATA + (map_height * 32)];
		memcpy(dst, src, size);
	}

	memcpy(&rgbtuner[CHR_DATA], cgb_chr, used_tiles * CGB_TILE_SIZE);	

	if(used_tiles > 240)												
	{
		*(unsigned short *) &rgbtuner[CHR_SIZE_VBK0] = 240 * CGB_TILE_SIZE;
		used_tiles -= 240;
		*(unsigned short *) &rgbtuner[CHR_SIZE_VBK1] = used_tiles * CGB_TILE_SIZE;
	}
	else																
	{
		*(unsigned short *) &rgbtuner[CHR_SIZE_VBK0] = used_tiles * CGB_TILE_SIZE;
		*(unsigned short *) &rgbtuner[CHR_SIZE_VBK1] = 0;
	}

	for(i = 0; i < RGBT_ROM_SIZE; i++)									
		crc += rgbtuner[i];

	*(unsigned short *) &rgbtuner[CHECKSUM] = ((crc << 8) & 0xff00) | ((crc >> 8) & 0xff);
}


void release(unsigned char **bmp_data, CGBQUAD **tmp_pal, unsigned char	**tmp_idx, unsigned char **cgb_chr)
{
	if(*bmp_data != NULL)
		free(*bmp_data);
	if(*tmp_pal != NULL)
		free(*tmp_pal);
	if(*tmp_idx != NULL)
		free(*tmp_idx);
	if(*cgb_chr != NULL)
		free(*cgb_chr);
}


int main (int argc, char *argv[])
{
	BITMAPFILEHEADER	*header = NULL;
	BITMAPINFOHEADER	*info = NULL;
	RGBQUAD				bmp_pal[256];
	unsigned char		*bmp_data = NULL;

	CGBQUAD				*tmp_pal = NULL;
	unsigned char		*tmp_idx = NULL;

	unsigned char		*cgb_chr = NULL;
	unsigned char		cgb_atr[MAX_MAP_SIZE];
	unsigned char		cgb_map[MAX_MAP_SIZE];
	CGBQUAD 			cgb_pal[MAX_SLOTS] = {{0, 0, 0, 0}};

	char				 match, status;
	unsigned char		arg, chr = 0, fname = 0, slot = 0, used_slots;
	unsigned short		i, width, height, columns, rows, options = 0, padding = 0, used_tiles = 0;
	unsigned int		rgbhex = 0;


	if(argc < 2)
		usage();

	for(arg = 1; arg < argc; arg++)
	{
		if((argv[arg][0] == '-') || (argv[arg][0] == '/'))
		{
			switch(tolower(argv[arg][1]))
			{
				case 'c': options |= FLAG_DUMP; break;
				case 'd': options |= FLAG_DEBUG; break;
				case 'e': options |= FLAG_EXPAND; chr = (unsigned char) atoi(&argv[arg][2]); break;
				case 'm': options |= FLAG_MPAD; padding = (unsigned short) ((atoi(&argv[arg][2])) & 0x1ff); break;
				case 'o': options |= FLAG_UNOPT; break;
				case 'p': options |= FLAG_PPAD; slot = (unsigned char) (atoi(&argv[arg][2]) & 7); break;
				case 'r': options |= FLAG_REBASE; break;
				case 's': options |= FLAG_OBJ; rgbhex = (strtol(&argv[arg][2], NULL, 16) & 0xffffff) | 1UL << 24; break;
				case 't': options |= FLAG_TUNER | FLAG_MPAD; padding = 0x10; break;	
				case 'x': options |= FLAG_FLIPX; break;
				case 'y': options |= FLAG_FLIPY; break;
				case 'z': options |= FLAG_FLIPXY; break;

				default: error_handler(ERR_UNK_OPTION); break;
			}
		}
		else
			fname = arg;
	}




	status = process_bmp(argv[fname], &header, &info, bmp_pal, &bmp_data, &width, &height);

	if(header != NULL)													
		free(header);
	if(info != NULL)
		free(info);

	rows = (unsigned short) (height / TILE_WIDTH);						
	columns = (unsigned short) (width / TILE_HEIGHT);

	if(options & FLAG_OBJ)												
	{
		match = 0;
		unsigned char *rgb = (unsigned char *) &rgbhex;
		for(i = 0; i < 256; i++)
		{
			if(bmp_pal[i].red == rgb[0] && bmp_pal[i].green == rgb[1] && bmp_pal[i].blue == rgb[2])
			{
				match++;
				break;
			}
		}

		if(match == 0)
			status = ERR_TRNSP;
	}

	if(options & FLAG_EXPAND)
	{
		if(columns >= 32)
			status = ERR_PADDING;
	}

	status = create_tiles(bmp_data, width, height, status);
	status = optimize(bmp_data, bmp_pal, &tmp_pal, rows, columns, rgbhex, options, status);

	used_slots = slot;

	status = create_palettes(cgb_pal, &tmp_idx, tmp_pal, rows * columns, &used_slots, options, status);
	remap_colors(bmp_data, cgb_pal, tmp_idx, tmp_pal, rows * columns, status);
	status = convert(bmp_data, &cgb_chr, cgb_map, cgb_atr, tmp_idx, rows * columns, padding, &used_tiles, options, status);

	if(options & FLAG_EXPAND)
	{
		expand_maps(cgb_map, cgb_atr, columns, rows, chr, status);		
		columns = 32;
	}

	if(status)
	{
		error_handler(status);
		release(&bmp_data, &tmp_pal, &tmp_idx, &cgb_chr);
		exit(EXIT_FAILURE);
	}

	if(!(options & FLAG_DEBUG))
	{
		char *ext = strstr(argv[fname], ".bmp");
		if(ext)
			ext[0] = 0;													

		save(argv[fname], EXT_CHR, cgb_chr, used_tiles * CGB_TILE_SIZE);

		if(options & FLAG_OBJ)
		{
			save_oam(argv[fname], EXT_TXT, cgb_atr, cgb_map, rows * columns);
		}
		else
		{
			save(argv[fname], EXT_ATR, cgb_atr, rows * columns);

			if(options & FLAG_REBASE)
				rebase(cgb_map, rows * columns);

			save(argv[fname], EXT_MAP, cgb_map, rows * columns);
		}

		if(options & FLAG_TUNER)
		{
			make_rgbt(rgbt, cgb_chr, cgb_map, cgb_atr, cgb_pal, rows, columns, used_tiles);
			save(argv[fname], EXT_GBC, rgbt, sizeof(rgbt));
		}
		else
			save(argv[fname], EXT_PAL, &cgb_pal[slot], sizeof(CGBQUAD) * (used_slots - slot));
	}

	release(&bmp_data, &tmp_pal, &tmp_idx, &cgb_chr);

	exit(EXIT_SUCCESS);
}