/*
# _____     ___ ____     ___ ____
#  ____|   |    ____|   |        | |____|    https://github.com/pspdev
# |     ___|   |     ___|    ____| |    \    PSPDEV Open Source Project.
#-----------------------------------------------------------------------
# Review pspsdk README & LICENSE files for further details.
#
# New and improved mksfo
#
*/
#include <stdio.h>
#include <stdint.h>
#include <getopt.h>
#include <string.h>
#include <stdlib.h>
#include "types.h"

#define PSF_MAGIC    0x46535000
#define PSF_VERSION  0x00000101

struct SfoHeader 
{
	uint32_t magic;
	uint32_t version;
	uint32_t keyofs;
	uint32_t valofs;
	uint32_t count;
} __attribute__((packed));

struct SfoEntry
{
	uint16_t nameofs;
	uint8_t  alignment;
	uint8_t  type;
	uint32_t valsize;
	uint32_t totalsize;
	uint32_t dataofs;
} __attribute__((packed));

#define PSF_TYPE_BIN  0
#define PSF_TYPE_STR  2
#define PSF_TYPE_VAL  4

struct EntryContainer
{
	const char *name;
	int type;
	uint32_t value;
	const char *data;
};

struct EntryContainer g_defaults[] = {
	{ "BOOTABLE", PSF_TYPE_VAL, 1, NULL },
	{ "CATEGORY", PSF_TYPE_STR, 0, "MG" },
	{ "DISC_ID",  PSF_TYPE_STR, 0, "UCJS10041" },
	{ "DISC_VERSION", PSF_TYPE_STR, 0, "1.00" },
	{ "PARENTAL_LEVEL", PSF_TYPE_VAL, 1, NULL },
	{ "PSP_SYSTEM_VER", PSF_TYPE_STR, 0, "1.00" },
	{ "REGION", PSF_TYPE_VAL, 0x8000, NULL },
};

#define MAX_OPTIONS (256)

static const char *g_title = NULL;
static const char *g_filename = NULL;
static int g_empty = 0;
static struct EntryContainer g_vals[MAX_OPTIONS];

static struct option arg_opts[] = 
{
	{"dword", required_argument, NULL, 'd'},
	{"string", required_argument, NULL, 's'},
	{"empty", no_argument, NULL, 'e'},
	{ NULL, 0, NULL, 0 }
};

struct EntryContainer *find_free()
{
	int i;

	for(i = 0; i < MAX_OPTIONS; i++)
	{
		if(g_vals[i].name == NULL)
		{
			return &g_vals[i];
		}
	}

	return NULL;
}

struct EntryContainer *find_name(const char *name)
{
	int i;

	for(i = 0; i < MAX_OPTIONS; i++)
	{
		if((g_vals[i].name != NULL) && (strcmp(g_vals[i].name, name) == 0))
		{
			return &g_vals[i];
		}
	}

	return NULL;
}

int add_string(char *str)
{
	char *equals = NULL;
	struct EntryContainer *entry;

	equals = strchr(str, '=');
	if(equals == NULL)
	{
		fprintf(stderr, "Invalid option (no =)\n");
		return 0;
	}

	*equals++ = 0;

	entry = find_free();
	if(entry == NULL)
	{
		fprintf(stderr, "Maximum options reached\n");
		return 0;
	}

	memset(entry, 0, sizeof(struct EntryContainer));
	entry->name = str;
	entry->type = PSF_TYPE_STR;
	entry->data = equals;

	return 1;
}

int add_dword(char *str)
{
	char *equals = NULL;
	struct EntryContainer *entry;

	equals = strchr(str, '=');
	if(equals == NULL)
	{
		fprintf(stderr, "Invalid option (no =)\n");
		return 0;
	}

	*equals++ = 0;

	entry = find_free();
	if(entry == NULL)
	{
		fprintf(stderr, "Maximum options reached\n");
		return 0;
	}

	memset(entry, 0, sizeof(struct EntryContainer));
	entry->name = str;
	entry->type = PSF_TYPE_VAL;
	entry->value = strtoul(equals, NULL, 0);

	return 1;
}

/* Process the arguments */
int process_args(int argc, char **argv)
{
	int ch;

	g_title = NULL;
	g_filename = NULL;
	g_empty = 0;

	ch = getopt_long(argc, argv, "ed:s:", arg_opts, NULL);
	while(ch != -1)
	{
		switch(ch)
		{
			case 'd' : if(!add_dword(optarg))
					   {
						   return 0;
					   }
				break;
			case 's' : if(!add_string(optarg))
					   {
					   }
				break;
			default  : break;
		};

		ch = getopt_long(argc, argv, "ed:s:", arg_opts, NULL);
	}

	argc -= optind;
	argv += optind;

	if(argc < 1)
	{
		return 0;
	}

	if(!g_empty)
	{
		g_title = argv[0];
		argc--;
		argv++;
	}

	if(argc < 1)
	{
		return 0;
	}

	g_filename = argv[0];

	return 1;
}

int main(int argc, char **argv)
{
	FILE *fp;
	int i;
	char head[8192];
	char keys[8192];
	char data[8192];
	struct SfoHeader *h;
	struct SfoEntry  *e;
	char *k;
	char *d;
	unsigned int align;
	unsigned int keyofs;
	unsigned int count;

	if(!process_args(argc, argv)) 
	{
		fprintf(stderr, "Usage: mksfoex [options] TITLE output.sfo\n");
		fprintf(stderr, "Options:\n");
		fprintf(stderr, "-d NAME=VALUE - Add a new DWORD value\n");
		fprintf(stderr, "-s NAME=STR   - Add a new string value\n");

		return 1;
	}

	if(!g_empty)
	{
		struct EntryContainer *entry;

		for(i = 0; i < (sizeof(g_defaults) / sizeof(struct EntryContainer)); i++)
		{
			if(!find_name(g_defaults[i].name))
			{
				entry = find_free();
				if(entry == NULL)
				{
					fprintf(stderr, "Maximum options reached\n");
					return 0;
				}
				*entry = g_defaults[i];
			}
		}

		if(!find_name("TITLE"))
		{
			entry = find_free();
			entry->name = "TITLE";
			entry->type = PSF_TYPE_STR;
			entry->value = 0;
			entry->data = g_title;
		}
	}

	memset(head, 0, sizeof(head));
	memset(keys, 0, sizeof(keys));
	memset(data, 0, sizeof(data));
	h = (struct SfoHeader*) head;
	e = (struct SfoEntry*)  (head+sizeof(struct SfoHeader));
	k = keys;
	d = data;
	SW(&h->magic, PSF_MAGIC);
	SW(&h->version, PSF_VERSION);
	count = 0;

	for(i = 0; g_vals[i].name; i++)
	{
		SW(&h->count, ++count);
		SW(&e->nameofs, k-keys);
		SW(&e->dataofs, d-data);
		SW(&e->alignment, 4);
		SW(&e->type, g_vals[i].type);

		strcpy(k, g_vals[i].name);
		k += strlen(k)+1;
		if(e->type == PSF_TYPE_VAL)
		{
			SW(&e->valsize, 4);
			SW(&e->totalsize, 4);
			SW((uint32_t*) d, g_vals[i].value);
			d += 4;
		}
		else
		{
			int totalsize;
			int valsize;

			valsize = strlen(g_vals[i].data)+1;
			totalsize = (valsize + 3) & ~3;
			SW(&e->valsize, valsize);
			SW(&e->totalsize, totalsize);
			memset(d, 0, totalsize);
			memcpy(d, g_vals[i].data, valsize);
			d += totalsize;
		}
		e++;
	}


	keyofs = (char*)e - head;
	SW(&h->keyofs, keyofs);
	align = 3 - ((unsigned int) (k-keys) & 3);
	while(align < 3)
	{
		k++;
		align--;
	}
	
	SW(&h->valofs, keyofs + (k-keys));

	fp = fopen(g_filename, "wb");
	if(fp == NULL)
	{
		fprintf(stderr, "Cannot open filename %s\n", g_filename);
		return 0;
	}

	fwrite(head, 1, (char*)e-head, fp);
	fwrite(keys, 1, k-keys, fp);
	fwrite(data, 1, d-data, fp);
	fclose(fp);

	return 0;
}
