/* (C) Copyright 2009 WiiGator */
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <gctypes.h>
#include <ogc/cache.h>
#include <ogcsys.h>
#include <gccore.h>
#include <string.h>
#include <ogc/lwp_watchdog.h>
#include <ogc/cache.h>

#include "dvd.h"
#include "gameloader.h"
#include "debugprintf.h"
#include "loadapp.h"
#include "loadmios.h"
#include "screen.h"
#include "dvdreadpatch.h"
#include "systemconfig.h"
#include "video.h"
#include "Toy.h"
#include "patchcode.h"

unsigned long second_disc_idx;

static char dvdid[6];
static unsigned int gameOffsetTable[MAX_GAMES];
static dvdinfo_t gcm_disk __attribute((aligned(32)));
static int driveUnlocked = 0;

extern u32 hookselect;
extern u32 hook2select;
extern u32 ocarinaselect;
extern u32 debuggerselect;

void check_game(int activeGame, int count)
{
	char tempStr[40];
	int rv;

	rv = dvd_read(&gcm_disk, sizeof(gcm_disk), 0);
	DVD_CHECK();

	clearScreen();
	memset(tempStr, 0, sizeof(tempStr));
	strncpy(tempStr, gcm_disk.GameName, sizeof(tempStr) - 2);
	debug_printf("Game ID: %c%c%c%c%c%c %s\n",
		gcm_disk.ConsoleID,
		gcm_disk.GamecodeA,
		gcm_disk.GamecodeB,
		gcm_disk.CountryCode,
		gcm_disk.MakerCodeA,
		gcm_disk.MakerCodeB,
		tempStr);

	if (count > 1) {
		printf("[%d] %s\n\n", activeGame, tempStr);
	} else {
		printf("%s\n\n", tempStr);
	}
	printf("Press: X for PAL   L for PAL60\n");
	printf("    Y for NTSC  R for 480p\n");
	printf("    A for Auto Detect\n");
	if (!driveUnlocked) {
		printf("    B to use patched MIOS for start\n");
	}
#ifdef RELOAD_SUPPORT
	printf("    START for Reloader (slow)\n");
#endif
	if (count > 1) {
		//if (second_disk_selected) {
			printf("    Left/Right to switch game  Z for second disc [%lu]\n", second_disc_idx);
		//} else {
		//	printf("    Left/Right to switch game  Z for second disc [none]\n");
		//}
	}
}

void game_selector(int count)
{
	volatile int activeGame = 0;

	if (count > 1) {
		activeGame = 1;
	}
	base_offset = gameOffsetTable[activeGame];
	check_game(activeGame, count);

	second_disc_offset = base_offset;
	second_disc_idx = activeGame;

	while (1) {
		unsigned int cvstatus;

		cvstatus = get_cover_status();
		if ((cvstatus & 0x01) == 0x01) {
			debug_printf("Disc ejected.\n");
			if (!driveUnlocked) {
				debug_printf("Unlocking drive (patched MIOS will not work).\n");
				wii_dvd_reset_unlock();
				driveUnlocked = -1;
			}

			/* Disc changed. Need to retry. */
			return;
		}

		PAD_ScanPads();
		int buttonsDown = PAD_ButtonsDown(0);

		if (buttonsDown & PAD_BUTTON_Y) {
			if (!driveUnlocked) {
				debug_printf("Unlocking drive.\n");
				wii_dvd_reset_unlock();
				driveUnlocked = -1;
			}
			setup_video(VIDEO_MODE_NTSC);
			load_app(0);
			break;
		}
		if (buttonsDown & PAD_BUTTON_X) {
			if (!driveUnlocked) {
				debug_printf("Unlocking drive.\n");
				wii_dvd_reset_unlock();
				driveUnlocked = -1;
			}
			setup_video(VIDEO_MODE_PAL);
			load_app(0);
			break;
		}
		if (buttonsDown & PAD_TRIGGER_L) {
			if (!driveUnlocked) {
				debug_printf("Unlocking drive.\n");
				wii_dvd_reset_unlock();
				driveUnlocked = -1;
			}
			setup_video(VIDEO_MODE_PAL60);
			load_app(0);
			break;
		}
		if (buttonsDown & PAD_TRIGGER_R) {
			if (!driveUnlocked) {
				debug_printf("Unlocking drive.\n");
				wii_dvd_reset_unlock();
				driveUnlocked = -1;
			}
			setup_video(VIDEO_MODE_NTSC480P);
			load_app(0);
			break;
		}
		if (buttonsDown & PAD_BUTTON_A) {
			if (!driveUnlocked) {
				debug_printf("Unlocking drive.\n");
				wii_dvd_reset_unlock();
				driveUnlocked = -1;
			}
			setup_video(gcm_disk.CountryCode);
			load_app(0);
			break;
		}
#ifdef RELOAD_SUPPORT
		if (buttonsDown & PAD_BUTTON_START) {
			if (!driveUnlocked) {
				debug_printf("Unlocking drive.\n");
				wii_dvd_reset_unlock();
				driveUnlocked = -1;
			}
			setup_video(gcm_disk.CountryCode);
			load_app(1);
			break;
		}
#endif
		if (!driveUnlocked && (buttonsDown & PAD_BUTTON_B)) {
			/* Start using MIOS with backup support. */
			load_mios();
			break;
		}
		if (count > 1) {
			if (buttonsDown & PAD_BUTTON_LEFT) {
				activeGame--;
				if (activeGame < 0) {
					activeGame++;
				}
				base_offset = gameOffsetTable[activeGame];
				check_game(activeGame, count);
			}
			if (buttonsDown & PAD_BUTTON_RIGHT) {
				activeGame++;
				if (activeGame >= count) {
					activeGame--;
				}
				base_offset = gameOffsetTable[activeGame];
				check_game(activeGame, count);
			}
			if (buttonsDown & PAD_TRIGGER_Z) {
				second_disc_offset = base_offset;
				second_disc_idx = activeGame;

				/* Update graphic. */
				check_game(activeGame, count);
			}
		}
	};
}

void multiboot(void)
{
	int rv;
	int i;
	int count;
	uint32_t type;

	rv = dvd_read(&gameOffsetTable[1], sizeof(gameOffsetTable) - sizeof(gameOffsetTable[0]), 64);
	DVD_CHECK();

	dvd_read(&type, sizeof(type), 4);

	count = 1;
	for (i = 1; i < MAX_GAMES; i++) {
		if (gameOffsetTable[i] != 0) {
			if (type != 0x44564439) // 'DVD9'
			{
				/* Not DVD9 offsets need to convert the offset. */
				gameOffsetTable[i] = gameOffsetTable[i] >> 2;
			}
			count++;
		} else {
			break;
		}
	}

	game_selector(count);
}

void start_backup_loader(void)
{
	unsigned char checkBuffer[32];
	unsigned int printed;
	int rv;
	int firstCall = -1;

	clearScreen();
	printed = 0;
	while (1) {
		unsigned int cvstatus;

		cvstatus = get_cover_status();
		if (!firstCall && (cvstatus & 0x04)) {
			/* Reset, because of disc change. */
			sleep(2);

			/* Disc inserted, need to reset. */
			ack_cover_interrupt();
			dvd_reset();

			/* Wait until reset is finished. */
			do {
				cvstatus = get_cover_status();
			} while ((cvstatus & 0x01) != 0);
		} else {
			/* Don't need to reset after startup (only on disc change). */
			firstCall = 0;
		}

		if ((cvstatus & 0x01) == 0) {
			if (printed) {
				clearScreen();
			}
			base_offset = 0;
	
			rv = dvd_read(dvdid, 6, 0);
			DVD_CHECK();
	
			rv = dvd_read(checkBuffer, 32, 0x8001);
			DVD_CHECK();
			if (strncmp((const char *) dvdid, "COBRAM", 6) == 0) {
				multiboot();
	
			} else if ((strncmp((const char *) dvdid, "GGCOSD", 6) == 0)
				|| (strncmp((const char *) dvdid, "GCOPDV", 6) == 0)
				|| (strncmp((const char *) dvdid, "RGCOSD", 6) == 0)) {
				multiboot();
			} else {
				game_selector(1);
			}
			clearScreen();
			printed = 0;
		} else {
			if (!printed) {
				printf("Please insert disc.\n");
				printed = -1;
			}
		}
	}
}

void start_from_NeoGamma(void)
{
	base_offset = 0;
	int rv;
	u8 reloader;
	
	//rv = dvd_read(checkBuffer, 32, 0x8001);
	//DVD_CHECK();

	base_offset = *(u32 *)0x807FFF20;
	second_disc_offset = *(u32 *)0x807FFF24;
	set_patchmode(*(u8 *)0x807FFF30);
	reloader = *(u8 *)0x807FFF34;

	set_codes_settings(*(u8 *)0x807FFF31, *(u8 *)0x807FFF32, *(u8 *)0x807FFF33, *(u8 *)0x807FFF35);

#ifdef DONTTELL
	if ( (hookselect != 0 || hook2select != 0) && (ocarinaselect != 0 || debuggerselect != 0))
#else
	if (!backuploading && hookselect != 0 && (ocarinaselect != 0 || debuggerselect != 0))
#endif
	{
		if (ocarinaselect != 0)
		{
			// Copy codes now before the memory is cleared
			memcpy((void *)0x800027B0, (void *)0x807FF6B0, 0x0850);
		} else
		{
			// Clear codes area for debugger
			memset((void *)0x800027B0, 0, 0x0850);
		}
	}

	rv = dvd_read(&gcm_disk, sizeof(gcm_disk), 0);
	DVD_CHECK();

	u32 mode;
	
	switch (*(u32 *)0x807FFF28)	// video mode
	{
		case 0:	// Video Mode Wii
			if (*(u32 *)0x807FFF2C == 0)	// region: NTSC
			{
				mode = VIDEO_MODE_NTSC;
			} else							// region: PAL
			{
				if (gcm_disk.CountryCode == 'E' || gcm_disk.CountryCode == 'J')
				{
					mode = VIDEO_MODE_PAL60;
				} else
				{
					mode = VIDEO_MODE_PAL;		// PAL games can't be booted with PAL60...
				}
			}
		break;
		
		case 2:	// NTSC480i
			mode = VIDEO_MODE_NTSC;
		break;
		
		case 3:	// NTSC480p
			mode = VIDEO_MODE_NTSC480P;
		break;
		
		case 4:	// PAL(auto)
			if (gcm_disk.CountryCode == 'E' || gcm_disk.CountryCode == 'J')
			{
				mode = VIDEO_MODE_PAL60;
			} else
			{
				mode = VIDEO_MODE_PAL;		// PAL games can't be booted with PAL60...
			}
		break;	
		
		case 5:	// PAL576i
			mode = VIDEO_MODE_PAL;
		break;
		
		case 6:	// PAL480i
			mode = VIDEO_MODE_PAL60;
		break;
		
		case 7:	// PAL480p
			mode = VIDEO_MODE_PAL480P;
		break;

		case 1:	// video Mode Disc
		default:
			mode = gcm_disk.CountryCode;
		break;
	}
	
	wii_dvd_reset_unlock();
	
	setup_video(mode);	// Also clears memory
	load_app(reloader);
}
