/* FUNCTION list 	---	---	---	---	---	---	---

*/

/* BASE headers	---	---	---	---	---	---	--- */
//#include "agi.h"
#include "../agi.h"

//~ RaDIaT1oN:
//~ fix const declaration in compare func
//~ cast in strtok_r
//~ lower string for linux
//~ unnamed union members


/* LIBRARY headers	---	---	---	---	---	---	--- */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#ifdef _WIN32
#include <Windows.h>
#include <direct.h>
#else
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#endif

/* OTHER headers	---	---	---	---	---	---	--- */
#include "../sys/ini_config.h"
#include "../config.h"
#include "../ui/string.h"
#include "../sys/agi_file.h"
#include "agi_crc.h"
#include "../sys/mem_wrap.h"
#include "../sys/vstring.h"
#include "../sys/sys_dir.h"
#include "../sys/drv_video.h"
#include "../sys/sdl_vid.h"
#include "../base.h"
#include "../ui/msg.h"
#include "../list.h"
#include "../ui/list_box.h"
#include "../ui/events.h"
#include "../ui/window.h"
#include "../ui/agi_text.h"
#include "../ui/printf.h"

#include "../sys/drv_video.h"
#include "../sys/gfx.h"

/* PROTOTYPES	---	---	---	---	---	---	--- */
//void test_function(void);


// two structs.. one for stuff i just need to help ident and
// the other to help load up/print menu
struct gameinfo_struct
{
	struct gameinfo_struct *next;
		
	char name[81];	// 80 + null
	VSTRING *dir;
	
	const char *standard;
	char file_id[ID_SIZE+1];
	u8 dir_type;
	u8 ver_type;	// used to pick defaults
};
typedef struct gameinfo_struct GAMEINFO;

struct agicrc_struct
{
	u32 object;
	u32 words;
	
	union
	{
		u32 dir_comb;
		
		struct
		{
			u32 log;
			u32 pic;
			u32 view;
			u32 snd;
		} dir;
	};

	u32 vol[16];
};
typedef struct agicrc_struct AGICRC;

/* VARIABLES	---	---	---	---	---	---	--- */


/* CODE	---	---	---	---	---	---	---	--- */


// ---------------------------------------- LIST INIT ----------------------------------------

// generate a crc from a file
static int file_crc_gen(const char *file_name, u32 *crc32)
{
	u8 *buf;
	
	assert(file_name != 0);
	assert(crc32 != 0);
	
	buf = file_to_buf(file_name);
	
	if (buf == 0)
	{
		*crc32 = 0;
		return 1;
	}
	else
	{
		// generate crc	
		*crc32 = crc_generate(buf, file_buf_size);
		
		a_free(buf);
		return 0;
	}
}


static char st_end_char;
static char *st_end = 0;

// hopefully a pointer to the standard you wanna sep
static void stand_sep(char *ptr)
{
	assert(ptr != 0);
	
	st_end = strstr(ptr, ",\0\n\r");
	if (st_end != 0)
	{
		st_end_char = *st_end;
		*st_end = 0;
	}
}

static void stand_rejoin(void)
{
	if (st_end != 0)
	{
		*st_end = st_end_char;
		st_end = 0;
	}
}

#define DOS_FILE_MAX (8 + 1 + 3)
// read the crc/game type/directory type and determine if the game is a proper agi game
// 0 = ok
// anything else is a failure
static int dir_get_info(AGICRC *agicrc, GAMEINFO *info)
{
	int i;
	char name[ID_SIZE + 20];
	
	assert(agicrc != 0);
	assert(info != 0);
	
	// search for object
	if (file_crc_gen("object", &agicrc->object) != 0)
		return 1;	// NO OBJECT
	
	// search for words.tok
	if (file_crc_gen("words.tok", &agicrc->words) != 0)
		return 2;	// NO WORDS.TOK
	
	// search for vol.0
	if (file_crc_gen("vol.0", &agicrc->vol[0]) == 0)
		info->file_id[0] = 0;	// if found then no file FILEID  
	// else search for FILEIDvol.0
	// if found set file_id (from func)
	else
	{
		char fname[DOS_FILE_MAX + 1];
		struct dir_list_struct *dir;
		char *tail;
		int ok = 0;
		
		dir = agi_open_cwd();
		if (dir != 0) 
		{
			for (;;) 
			{
				const char *fname_orig = agi_read_dir(dir);

				if (fname_orig == 0) { continue; }
				if (strlen(fname_orig) > DOS_FILE_MAX) { continue; }

				strcpy(fname, fname_orig);
				string_lower(fname);

				tail = strstr(fname, "vol.0");	// get id

				if (tail == 0) { continue; }
				if ((fname - tail) > ID_SIZE) { continue; }

				if (file_crc_gen(fname_orig, &agicrc->vol[0]) != 0) { continue; }
				
				tail[0] = 0;
				strcpy(info->file_id, fname);
				ok = 1;
				break;
			}

			agi_close_dir(dir);
			dir = 0;
		}

		if (!ok)
			return 3;	// NO *VOL.0
	}
	
	// search for logdir, snddir, viewdir, picdir
	if ( (file_crc_gen("logdir", &agicrc->dir.log) |
		file_crc_gen("snddir", &agicrc->dir.snd) |
		file_crc_gen("viewdir", &agicrc->dir.view) |
		file_crc_gen("picdir", &agicrc->dir.pic)) == 0) 
	{
		// if found then 4 dirs
			info->dir_type = DIR_SEP;
			info->ver_type = 2;
	}
	// else search for dirs
	else if (file_crc_gen("dirs", &agicrc->dir_comb) == 0)
	{
		info->dir_type = DIR_AMIGA; // if found then amiga v3 dir
		info->ver_type = 3;
	}
	else 
	{
		// else search for FILEIDdir
		sprintf(name, "%sdir", info->file_id);
		if (file_crc_gen(name, &agicrc->dir_comb) == 0)
		{
			info->ver_type = 3;
			info->dir_type = DIR_COMB;
		}
		else
			return 4;	// NO DIRs
	}
	
	// after that.. the rest of the volumes 1-15
	// not necessary to detect the game. but we need the crc's
	for (i=1; i<=15; i++)
	{
		sprintf(name, "%svol.%d", info->file_id, i);
		file_crc_gen(name, &(agicrc->vol[i]));
	}
	
	return 0; 	// everything ok.
}


static void crc_print(AGICRC *agicrc, GAMEINFO *info)
{
	char key_vol[20];	// twice as much needed
	int i;
	
	assert(agicrc != 0);
	assert(info != 0);
	
	// print object
	if (agicrc->object)
		printf("%s=0x%08X\n", "crc_object", (unsigned int)agicrc->object);
	
	// print words
	if (agicrc->words)
		printf("%s=0x%08X\n", "crc_words", (unsigned int)agicrc->words);
	
	// print vol[0] - vol[15]
	for (i=0; i<16; i++)
	{
		sprintf(key_vol, "crc_vol_%d", i);
		if (agicrc->vol[i])
			printf("%s=0x%08X\n", key_vol, (unsigned int)agicrc->vol[i]);
	}
	
	switch (info->dir_type)
	{
		case DIR_COMB:
		case DIR_AMIGA:
			// print dir_comb
			if (agicrc->dir_comb)
				printf("%s=0x%08X\n", "crc_dir", (unsigned int)agicrc->dir_comb);
			break;
		
		default:
		case DIR_SEP:
			// print dir.log
			if (agicrc->dir.log)
				printf("%s=0x%08X\n", "crc_dir_log", (unsigned int)agicrc->dir.log);
			// print dir.pic
			if (agicrc->dir.pic)
				printf("%s=0x%08X\n", "crc_dir_pic", (unsigned int)agicrc->dir.pic);
			// print dir.view
			if (agicrc->dir.view)
				printf("%s=0x%08X\n", "crc_dir_view", (unsigned int)agicrc->dir.view);
			// print dir.snd
			if (agicrc->dir.snd)
				printf("%s=0x%08X\n", "crc_dir_snd", (unsigned int)agicrc->dir.snd);
			break;
	}
}


#define CRC_FUDGE(x, y, z) key=ini_key(ini, (x) ); key_touched |= (key != 0); \
					if ( (key!= 0) \
					&& (agicrc->y != strtoul(key, 0, 16)) ) \
						return (z)


// compare each crc found for the game with the one's AVAILABLE in the standard.ini
// hopefully the ini file has been set to the appropriate section
// otherwise we'll crash 'n burn and destroy the whole world.
// return 0 if ok
static int crc_compare(AGICRC *agicrc, GAMEINFO *info, INI *ini)
{
	const char *key;
	char key_vol[20];	// twice as much needed
	int key_touched = 0;
	int i;
	
	assert(agicrc != 0);
	assert(info != 0);
	assert(ini != 0);
	
	// compare object
	CRC_FUDGE("crc_object", object, 1);
	
	// compare words
	CRC_FUDGE("crc_words", words, 2);
	
	// compare vol[0] - vol[15]
	for (i=0; i<16; i++)
	{
		sprintf(key_vol, "crc_vol_%d", i);
		CRC_FUDGE(key_vol, vol[i], i+3);
	}
	
	switch (info->dir_type)
	{
		case DIR_COMB:
		case DIR_AMIGA:
			// compare dir_comb
			CRC_FUDGE("crc_dir", dir_comb, 19);
			break;
		
		default:
		case DIR_SEP:
			// compare dir.log
			CRC_FUDGE("crc_dir_log", dir.log, 20);
			// compare dir.pic
			CRC_FUDGE("crc_dir_pic", dir.pic, 21);
			// compare dir.view
			CRC_FUDGE("crc_dir_view", dir.view, 22);
			// compare dir.snd
			CRC_FUDGE("crc_dir_snd", dir.snd, 23);
			break;
	}
	// no keys to match!
	if (key_touched == 0)
		return 24;
	return 0;
}


#undef CRC_FUDGE

// open up each section on the list and compare available crc's with one's calculated before
// return pointer to section in section list if a match is found.
static const char *crc_search(AGICRC *agicrc, GAMEINFO *info, INI *ini)
{
	char *crc_list; 
	char *token, *running;
	
	assert(agicrc != 0);
	assert(info != 0);
	assert(ini != 0);
	
	crc_list = strdupa(c_standard_crc_list);
	
	token = strtok_r(crc_list, ",", (char**)&running);
	while (token != 0)
	{
		if (ini_section(ini, token) == 0)
		{
			if (crc_compare(agicrc, info, ini)== 0)
				return c_standard_crc_list + (token - crc_list);
		}
		token = strtok_r(0, ",", (char**)&running);
	}

	return 0;	
}

// from the amiga gr vol.0
// replicated comp and uncomp.. lzw comp!!
// 12 34 00 2F 00 2F 00

// generate a name for the game info from standard.ini or just from other available resrouces
static void gameinfo_namegen(GAMEINFO *info, INI *ini, const char *dir_sub, const char *dir)
{
	char *name = 0;

	assert(info != 0);
	assert(dir != 0);
	assert(dir_sub != 0);
	
	// standard separate
	if ( (info->standard != 0) && (ini != 0) )
	{
		char *standard_dupe = strdupa(info->standard);
		stand_sep(standard_dupe);
		// go to section
		ini_section(ini, standard_dupe);
		// read name
		name = strdupa(ini_key(ini, "name"));
	}
	
	if (name == 0)
	{
		int str_size;
		
		if (info->file_id[0] == 0)
		{
			// "v3_kq4/games - AGI v2"
			str_size = strlen(dir_sub) + strlen(dir) + 10 + strlen("/ - AGI v");
			name = alloca(str_size);
			sprintf(name, "%s/%s - AGI v%d", dir_sub, dir, info->ver_type);
		}
		else
		{
			// "KQ4 @ v3_kq4/games - AGI v2"
			str_size = strlen(info->file_id) + strlen(dir_sub) + strlen(dir) + 10 + strlen(" @ / - AGI v");
			name = alloca(str_size);
			sprintf(name, "%s @ %s/%s - AGI v%d", info->file_id, dir_sub, dir, info->ver_type);
		}
	}
	
	assert(name != 0);
	
	strncpy(info->name, name, 80);
	if (strlen(name) >= 80)
		info->name[80] = 0;
	else
		info->name[strlen(name)] = 0;

	stand_rejoin();
}

static int game_count = 0;


// create a new gameinfo struct for a given directory if a game exists in it.. 
// called on for each dir
static void gameinfo_add(LIST *list, INI *ini, const char *dir_sub, const char *dir)
{
	AGICRC agicrc;
	GAMEINFO info_new;
	GAMEINFO *info;
	char *msg = alloca(strlen("Games found: XXXXXXXXXXXX"));
	
	assert(list != 0);
	
	memset(&info_new, 0, sizeof(GAMEINFO) );
	
	if (dir_get_info(&agicrc, &info_new) == 0)
	{
		if (ini != 0)
			info_new.standard = crc_search(&agicrc, &info_new, ini);
		
		//if (standard == 0)
		// don't worry... we'll just read the conf values.. don't have to dup strings all the time	
		
		// get directory
		info_new.dir = vstring_new(0, 100);
		vstring_getcwd(info_new.dir);
		
		// get name
		gameinfo_namegen(&info_new, ini, dir_sub, dir);

		if (c_nagi_crc_print)
		{
			printf("%s\n-----------------------------------------\n", info_new.name);
			crc_print(&agicrc, &info_new);
			printf("\n");
		}
		
		// if everything ok
		// ADD TO LIST
		info = list_add(list);
		game_count++;
		sprintf(msg, "Games found: %d", game_count);
		message_box_draw(msg, 0, 0, 0);
		memcpy( info, &info_new, sizeof(GAMEINFO) );
	}
}

// create a list of game infos starting from gameinfo_head from the dirlist in standard.ini
// search one level into it too if possible
static void gi_list_init(LIST *list, INI *ini)
{
	char *dir_list;
	char *token, *running;
	struct dir_list_struct *dir; 
	
	assert(list != 0);

	game_count = list_length(list);
	
	// for each dir
	
	dir_list = strdupa(c_nagi_dir_list);
	token = strtok_r(dir_list, ";", (char**)&running);
	while (token != 0)
	{
		dir_preset_change(DIR_PRESET_ORIG);
		
		dir = agi_open_cwd();
		if (dir != 0)
		{
			for(;;)
			{
				const char *filename = agi_read_dir(dir);
				if (filename == 0) { break; }

				if (strcmp(filename, "..") == 0) { continue; }

				dir_preset_change(DIR_PRESET_ORIG);

				if (chdir(token) != 0) { continue; }

				if (!dir_exists(filename)) { continue; }
				if (chdir(filename) != 0) { continue; }

				//printf("trying %s\\%s...\n", token, ep->d_name);
				gameinfo_add(list, ini, filename, token);
			} 
			agi_close_dir(dir);
		}
		
		token = strtok_r(0, ";", (char**)&running);
	}
	
	dir_preset_change(DIR_PRESET_ORIG);
}














// ---------------------------------------- MENU ----------------------------------------

// display nice menu of games and allow user to select game to play
// returns 0 on error
static GAMEINFO *gi_list_menu(LIST *list)
{
	int list_size, selection;
	const char **str_list, **str_cur;
	char *msg;
	char newline_orig;	// d0d orig
	GAMEINFO *info;
	
	assert(list != 0);

	// get the size of the list
	list_size = list_length(list);

	// check for errors
	if (list_size == 0)
		return 0;
	if (list_size == 1)
		return list_element_head(list);
	
	// allocate list
	str_list = alloca(sizeof(char *) * (list_size+1) );
	// set the first item to instruction string
	str_list[0] = "Use the arrow keys to select the game which you wish to play.\nPress ENTER to play the game, ESC to not play a game.";
	
	// get the rest of the strings
	info = list_element_head(list);
	str_cur = str_list;
	while(info)
	{
		str_cur++;
		*str_cur = info->name;
		info = node_next(info);
	}
	
	// use list_box
	selection = 0;
top:
	selection = list_box(str_list, list_size, selection);
	cmd_close_window(0);
	
	if (selection == -1)
		return 0;
	
	// convert number to required info struct
	info = list_element_at(list, selection);
	
	assert(info != 0);

	// check user response.
	// uses malloc so I can free it again after.
	// i free again after in case the user decides to pick and cancel a whole bunch of games
	// i didn't want to fill up the stack
	msg = a_malloc(200 + strlen(info->name) + strlen(info->dir->data));
	newline_orig = msgstate.newline_char;
	msgstate.newline_char = '@';
	sprintf(msg, "About to execute the game\ndescribed as:\n\n%s\n\nfrom dir:\n %s\n\n%s",
		info->name, info->dir->data, "Press ENTER to continue.\nPress ESC to cancel.");
	message_box_draw(msg, 0, 0x23, 0);
	a_free(msg);
	msg = 0;
	msgstate.newline_char = newline_orig;
	if (user_bolean_poll() == 0)
		goto top;

	return info;
}


// ---------------------------------------- ITEM INIT ----------------------------------------

static void standard_init_ng(GAMEINFO *game, INI *ini)
{
	assert(game != 0);
	
	// set the defaults

	// obj_packed
	if (game->dir_type == DIR_AMIGA)
	{
		config_game[5].b.def = 1;
	}
	else
		config_game[5].b.def = 0;
	
	// dir_type
	config_game[7].i.def = game->dir_type;
	
	switch (game->ver_type)
	{
		case 3:
			// version info
			config_game[0].s.def = "v3.002.149";
			// loop update
			config_game[2].i.def = L_FLAG;
			// compression
			config_game[6].b.def = 1;
			break;
		case 2:
		default:
			// version info
			config_game[0].s.def = "v2.917";
			// loop update
			config_game[2].i.def = L_FOUR;
			// compression
			config_game[6].b.def = 0;
			break;
	}
	
	// change to appropriate section
	// use the v2 or v3 section defaults if available
	
	if (ini != 0)
	{
		if (game->standard != 0)
		{
			char *standard_dupe = strdupa(game->standard);
			stand_sep(standard_dupe);
			ini_section(ini, standard_dupe);
			stand_rejoin();
		}
		else if (game->dir_type == DIR_AMIGA)
			// I know it won't read amiga_v2_Default.. but it's a start
			switch(game->ver_type)
			{
				case 3:
					ini_section(ini, c_standard_amiga_v3_default);
					break;
				case 2:
				default:
					ini_section(ini, c_standard_amiga_v2_default);
					break;
			}
		else
			switch(game->ver_type)
			{
				case 3:
					ini_section(ini, c_standard_v3_default);
					break;
				case 2:
				default:
					ini_section(ini, c_standard_v2_default);
					break;
			}
	}
	
	// read in file_id
	strcpy(c_game_file_id, game->file_id);
	
	// set up location
	//c_game_location = vstring_new(game->dir->data, 10);
	dir_preset_set(DIR_PRESET_GAME, game->dir->data);
	
	// need to setup game.id if you want to load up savegames before agi is init'd
	// standard.ini, or the file_id, or the default ""
	config_game[3].s.def = c_game_file_id;
	
	// use config_Read
	config_load(config_game, ini);
	
	// dont' store name.. use it for window only
	// init window name
	SDL_Window *window = vid_get_main_window();
	if (window != 0)
	{
		if ( (game->standard != 0) && (strlen(game->name) > 0) )
		{
			int title_len = snprintf(NULL, 0, "%s - NAGI", game->name);
			char *title = alloca(title_len + 1);
			snprintf(title, title_len + 1, "%s - NAGI", game->name);
			SDL_SetWindowTitle(window, title);
		}
		else
			SDL_SetWindowTitle(window, "NAGI");
	}
}


// ---------------------------------------- MAIN ----------------------------------------


static int gameinfo_compare(const void *a, const void *b)
{
	const GAMEINFO *info_a = *((const GAMEINFO * const *)a);
	const GAMEINFO *info_b = *((const GAMEINFO * const *)b);
	
	//~ info_a = *((GAMEINFO **)a);
	//~ info_b = *((GAMEINFO **)b);
	
	return strcasecmp(info_a->name, info_b->name);
}

// destory list
static void gi_list_free(LIST *list)
{
	GAMEINFO *info;
	
	assert(list);
	
	info = list_element_head(list);
	while (info)
	{
		vstring_free(info->dir);
		info = node_next(info);
	}
}

static void text_init(void)
{
	state.window_row_min = 2;
	state.window_row_max = 23;
	state.status_line_row = 0;
	
	push_row_col();
	text_attrib_push();

	window_line_clear(0, 0xFF);
	text_colour(0, 0x0F);
	goto_row_col(0, 5);
	agi_printf("New Adventure Game Interpreter");

	text_attrib_pop();
	pop_row_col();
}

static void text_shutdown(void)
{
	// clear window
	gfx_clear();
	
	state.window_row_min = 0;
	state.window_row_max = 0;
	state.status_line_row = 0x15;
}


void standard_select_ng(void)
{
	INI *ini_standard;
	LIST *list_game;
	GAMEINFO *game_selected;
	
	dir_preset_change(DIR_PRESET_NAGI);
	ini_standard = ini_open("standard.ini");
	text_init();

	// read in standard parameters
	config_load(config_standard, ini_standard);
	
	list_game = list_new(sizeof(GAMEINFO));
	gi_list_init(list_game, ini_standard);
	
	if ( msgstate.active != 0)
		cmd_close_window(0);
	
	if (list_length(list_game) == 0)
	{
		printf("no games detected\n");
		agi_exit();
	}
	
	// SORT THE LIST
	list_sort(list_game, gameinfo_compare);
	
	// MENU
	game_selected = gi_list_menu(list_game);
	if (game_selected == 0)
	{
		printf("user exits.\n");
		agi_exit();
	}

	// init game
	standard_init_ng(game_selected, ini_standard);

	// close ini file
	dir_preset_change(DIR_PRESET_NAGI);
	ini_close(ini_standard);

	gi_list_free(list_game);	// free up any alloc'd data
	list_free(list_game);
	
	text_shutdown();
}

// TODO:
// menu WHOOOOO DONE
// standard_separation DONE
// what happens if a dir doesn't exist on list? WORKS
// what happens if no game is set NOT CHECKED
// what happens if there's no crc's on the list WORKS
// if standard.ini doesn't exist.. where's dir_list come from? FROM THE CONFIG DEFAULT
// what if ini_key is called without claling ini_section? WORK
// instead of c_game_location.. use the dir_preset idea?  (nagi, user, game)  DONE BABY!!!

