/*****************************************
* This code is from Mighty Channel 11
* which is based on the TriiForce source.
* Modifications by Dimok.
*****************************************/
#include <stdio.h>
#include <ogcsys.h>
#include <malloc.h>
#include <string.h>

#include "system/IosLoader.h"
#include "utils/tools.h"
#include "gecko.h"
#include "NandEmu.h"

/* 'NAND Device' structure */
typedef struct {
	/* Device name */
	const char *name;

	/* Mode value */
	u32 mode;

	/* Un/mount command */
	u32 mountCmd;
	u32 umountCmd;
} nandDevice;

static nandDevice ndevList[] =
{
	{ "Disable",						0,	0x00,	0x00 },
	{ "SD/SDHC Card",					1,	0xF0,	0xF1 },
	{ "USB 2.0 Mass Storage Device",	2,	0xF2,	0xF3 },
};

/* Buffer */
static const char fs[] ATTRIBUTE_ALIGN(32) = "/dev/fs";
static const char fat[] ATTRIBUTE_ALIGN(32) = "fat";
static u32 partition ATTRIBUTE_ALIGN(32) = 0;
static u32 mode ATTRIBUTE_ALIGN(32) = 0;
static char path[32] ATTRIBUTE_ALIGN(32) = "\0";
static int fullmode = 0;
static int mounted = 0;

static s32 Nand_Mount(nandDevice *dev)
{
	s32 fd, ret;
	ioctlv *vector = NULL;

	/* Open FAT module */
	fd = IOS_Open(fat, 0);
	if (fd < 0)
		return fd;

	// NOTE:
	// The official cIOSX rev21 by Waninkoko ignores the partition argument
	// and the nand is always expected to be on the 1st partition.
	// However this way earlier d2x betas having revision 21 take in
	// consideration the partition argument.

	/* Allocate memory */
	vector = (ioctlv *) memalign(32, sizeof(ioctlv));
	if(vector == NULL)
	{
		/* Close FAT module */
		IOS_Close(fd);
		return -2;
	}

	vector[0].data = &partition;
	vector[0].len = sizeof(u32);

	/* Mount device */
	ret = IOS_Ioctlv(fd, dev->mountCmd, 1, 0, vector);

	/* Close FAT module */
	IOS_Close(fd);

	/* Free memory */
	free(vector);

	return ret;
}

static s32 Nand_Unmount(nandDevice *dev)
{
	s32 fd, ret;

	// Open FAT module
	fd = IOS_Open(fat, 0);
	if (fd < 0)
		return fd;

	// Unmount device
	ret = IOS_Ioctlv(fd, dev->umountCmd, 0, 0, NULL);

	// Close FAT module
	IOS_Close(fd);

	return ret;
}

static s32 Nand_Enable(nandDevice *dev)
{
	s32 fd, ret;
	ioctlv *vector = NULL;

	// Open /dev/fs
	fd = IOS_Open(fs, 0);
	if (fd < 0)
		return fd;

	//FULL NAND emulation since rev18
	//needed for reading images on triiforce mrc folder using ISFS commands
	mode = dev->mode | fullmode;

	// NOTE:
	// The official cIOSX rev21 by Waninkoko provides an undocumented feature
	// to set nand path when mounting the device.
	// This feature has been discovered during d2x development.

	/* Allocate memory */
	vector = (ioctlv *)memalign(32, sizeof(ioctlv)*2);
	if(vector == NULL)
	{
		/* Close FAT module */
		IOS_Close(fd);
		return -2;
	}

	vector[0].data = &mode;
	vector[0].len = sizeof(u32);
	vector[1].data = path;
	vector[1].len = sizeof(path);

	ret = IOS_Ioctlv(fd, 100, 2, 0, vector);

	/* Free memory */
	free(vector);

	// Close /dev/fs
	IOS_Close(fd);

	return ret;
}

static s32 Nand_Disable(void)
{
	s32 fd, ret;

	// Open /dev/fs
	fd = IOS_Open(fs, 0);
	if (fd < 0)
		return fd;

	// Set input buffer
	mode = 0;

	// Disable NAND emulator
	ret = IOS_Ioctl(fd, 100, &mode, sizeof(mode), NULL, 0);

	// Close /dev/fs
	IOS_Close(fd);

	return ret;
}

s32 Enable_Emu(int selection)
{
	if(!IosLoader::IsD2X())
		return -1;

	if(mounted != 0)
		return -1;

	s32 ret;
	nandDevice *ndev = NULL;
	ndev = &ndevList[selection];

	ret = Nand_Mount(ndev);
	if (ret < 0)
	{
		gprintf(" ERROR Mount! (ret = %d)\n", ret);
		return ret;

	}

	ret = Nand_Enable(ndev);
	if (ret < 0)
	{
		gprintf(" ERROR Enable! (ret = %d)\n", ret);
		return ret;
	}
	mounted = selection;
	return 0;
}

s32 Disable_Emu()
{
	if(!IosLoader::IsD2X())
		return -1;

	if(mounted==0)
		return 0;

	nandDevice *ndev = NULL;
	ndev = &ndevList[mounted];

	Nand_Disable();
	Nand_Unmount(ndev);

	mounted = 0;

	return 0;
}

void Set_Partition(int p)
{
	partition = p;
}

void Set_Path(const char* p)
{
	int i = 0;

	while(p[i] != '\0' && i < 31)
	{
		path[i] = p[i];
		i++;
	}
	while(path[i-1] == '/')
	{
		path[i-1] = '\0';
		--i;
	}
	path[i] = '\0';
}

void Set_FullMode(int fm)
{
	fullmode = fm ? 0x100 : 0;
}

const char* Get_Path(void)
{
	return path;
}
