#include <stdio.h>
#include <string.h>
#include <ogcsys.h>
#include <ogc/lwp_watchdog.h>

#include "config.h"
#include "patches.h"
#include "video.h"
#include "wpad.h"
#include "tools.h"

/* DOL header structure */
typedef struct {
	u32 textoff[7];
	u32 dataoff[11];

	u8 *textmem[7];
	u8 *datamem[11];

	u32 textsize[7];
	u32 datasize[11];

	u8 *bssmem;
	u32 bsssize;

	u32 entry;

	u32 unused[7];
} dolheader;

/* Entry pointer */
typedef void (*dolentry)(void);

/* Constants */
#define DOL_INDEX	1


void __Loader_SetLowMem(void)
{
	/* Setup low memory */
	*(vu32*)0x800000F8 = 0x0E7BE2C0;
	*(vu32*)0x800000FC = 0x2B73A840;

	/* Flush cache */
	DCFlushRange((void *)(0x800000F8), 0xFF);

	// Set the clock
	settime(secs_to_ticks(time(NULL) - 946684800)); 
	
	// Remove 002 error
	*(u16 *)0x80003140 = 0x0038;
	*(u16 *)0x80003142 = 0xffff;
	*(u16 *)0x80003188 = 0x0038;
	*(u16 *)0x8000318A = 0xffff;
	
	DCFlushRange((void*)0x80003140, 4);
	DCFlushRange((void*)0x80003188, 4);
}

void __Loader_SetVMode(u64 tid)
{
	GXRModeObj *vmode_ptr = NULL;
	u32         vmode_reg;

	u32 progressive, tvmode;

	/* Get video mode configuration */
	progressive = (CONF_GetProgressiveScan() > 0) && VIDEO_HaveComponentCable();
	tvmode      =  CONF_GetVideo();

	/* Select video mode */
	switch(tid & 0xFF) {
	/* PAL */
	case 'P':
		vmode_reg = (CONF_GetEuRGB60() > 0) ? 5 : 1;

		if (tvmode != CONF_VIDEO_PAL) {
			vmode_reg = 5;
			vmode_ptr = &TVNtsc480IntDf;
		}

		break;

	/* NTSC or unknown */
	case 'E':
	case 'J':
		vmode_reg = 0;

		if (tvmode != CONF_VIDEO_NTSC)
			vmode_ptr = &TVEurgb60Hz480IntDf;

		break;

	default:
		vmode_reg = 0;
	}

	/* Override video mode */
	if (loaderCfg.forcePal50) {
		vmode_reg = 1;
		vmode_ptr = &TVPal528IntDf;
	}

	if (loaderCfg.forcePal60) {
		vmode_reg = 5;
		vmode_ptr = &TVEurgb60Hz480IntDf;
	}

	if (loaderCfg.forceNtsc) {
		vmode_reg = 0;
		vmode_ptr = &TVNtsc480IntDf;
	}

	if (loaderCfg.forceHdtv) {
		vmode_reg = 0;
		vmode_ptr = &TVNtsc480Prog;
	}


	/* Set video mode register */
	*(vu32 *)0x800000CC = vmode_reg;

	/* Set video mode */
	if (vmode_ptr) {
		/* If progressive available, use it */
		if (progressive)
			vmode_ptr = &TVNtsc480Prog;

		Video_Configure(vmode_ptr);
		Video_Clear(COLOR_BLACK);
	}
	
	// Anti-green screen fix
    VIDEO_SetBlack(TRUE);
    VIDEO_Flush();
    VIDEO_WaitVSync();
}


void __Loader_PatchDol(u8 *buffer, u32 len)
{
	struct config *cfg = &loaderCfg;

	/* Video mode patching */
	if (cfg->patchVmode)
		Patch_VideoMode(buffer, len);
}

s32 __Loader_ReadDol(dolentry *p_entry)
{
	static dolheader dol ATTRIBUTE_ALIGN(32);

	u32 cnt;
	s32 cfd = -1, ret;

	/* Open DOL file */
	cfd = ES_OpenContent(DOL_INDEX);
	if (cfd < 0)
		return cfd;

	/* Read DOL header */
	ret = ES_ReadContent(cfd, (u8 *)&dol, sizeof(dol));
	if (ret < 0)
		goto out;

	/* Clear BSS */
	memset(dol.bssmem, 0, dol.bsssize);

	/* Read TEXT section */
	for (cnt = 0; cnt < 7; cnt++) {
		u32 offset = dol.textoff[cnt];

		if (offset >= sizeof(dol)) {
			u8 *buffer = (u8 *)dol.textmem[cnt];
			u32 len    = dol.textsize[cnt];

			/* Seek */
			ES_SeekContent(cfd, offset, 0);

			/* Read */
			ret = ES_ReadContent(cfd, buffer, len);
			if (ret < 0)
				goto out;

			/* Patch */
			__Loader_PatchDol(buffer, len);
		}
	}

	/* Read DATA section */
	for (cnt = 0; cnt < 11; cnt++) {
		u32 offset = dol.dataoff[cnt];

		if (offset >= sizeof(dol)) {
			u8 *buffer = (u8 *)dol.datamem[cnt];
			u32 len    = dol.datasize[cnt];

			/* Seek */
			ES_SeekContent(cfd, offset, 0);

			/* Read */
			ret = ES_ReadContent(cfd, buffer, len);
			if (ret < 0)
				goto out;

			/* Patch */
			__Loader_PatchDol(buffer, len);
		}
	}

	/* Set entry point */
	*p_entry = (dolentry)dol.entry;

out:
	/* Close DOL file */
	if (cfd >= 0)
		ES_CloseContent(cfd);

	return ret;
}

void __Loader_Shutdown(void)
{
	/* Deinitialize ISFS */
	ISFS_Deinitialize();

	/* Shutdown IOS subsystems */
	SYS_ResetSystem(SYS_SHUTDOWN, 0, 0);
}


s32 Loader_Execute(void)
{
	dolentry p_entry = NULL;

	u64 tid;
	s32 ret;

	/* Get title ID */
	ret = ES_GetTitleID(&tid);
	if (ret < 0)
		return ret;

	/* Load DOL */
	ret = __Loader_ReadDol(&p_entry);
	if (ret < 0)
		return ret;

	/* Set low memory */
	__Loader_SetLowMem();

	/* Set video mode */
	__Loader_SetVMode(tid);

	/* Shutdown subsystems */
	__Loader_Shutdown();

	/* Clear screen */
	Video_Clear(COLOR_BLACK);

	/* Jump to the entry point */
	p_entry();

	return 0;
}
