/*
 * Schism Tracker - a cross-platform Impulse Tracker clone
 * copyright (c) 2003-2005 Storlek <storlek@rigelseven.com>
 * copyright (c) 2005-2008 Mrs. Brisby <mrs.brisby@nimh.org>
 * copyright (c) 2009 Storlek & Mrs. Brisby
 * copyright (c) 2010-2012 Storlek
 * URL: http://schismtracker.org/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "headers.h"
#include "osdefs.h"
#include "event.h"
#include "song.h"
#include "it.h" // need for kbd_get_alnum
#include "page.h" // need for struct key_event
#include "log.h"

#include <di/di.h>
#include <fat.h>
#include <ogc/machine/processor.h>
#include <ogc/system.h>
#include <ogc/es.h>
#include <ogc/ios.h>
#include <errno.h>
#include <sys/dir.h>
#include "isfs.h"
#define CACHE_PAGES 8

// cargopasta'd from libogc git __di_check_ahbprot
static u32 _check_ahbprot(void) {
	s32 res;
	u64 title_id;
	u32 tmd_size;
	STACK_ALIGN(u32, tmdbuf, 1024, 32);

	res = ES_GetTitleID(&title_id);
	if (res < 0) {
		log_appendf(4, "ES_GetTitleID() failed: %d", res);
		return res;
	}

	res = ES_GetStoredTMDSize(title_id, &tmd_size);
	if (res < 0) {
		log_appendf(4, "ES_GetStoredTMDSize() failed: %d", res);
		return res;
	}

	if (tmd_size > 4096) {
		log_appendf(4, "TMD too big: %d", tmd_size);
		return -EINVAL;
	}

	res = ES_GetStoredTMD(title_id, tmdbuf, tmd_size);
	if (res < 0) {
		log_appendf(4, "ES_GetStoredTMD() failed: %d", res);
		return -EINVAL;
	}

	if ((tmdbuf[0x76] & 3) == 3) {
		return 1;
	}

	return 0;
}

const char *osname = "wii";

void wii_sysinit(int *pargc, char ***pargv)
{
	DIR_ITER *dir;
	char *ptr = NULL;

	log_appendf(1, "[Wii] This is IOS%d v%X, and AHBPROT is %s",
		IOS_GetVersion(), IOS_GetRevision(), _check_ahbprot() > 0 ? "enabled" : "disabled");
	if (*pargc == 0 && *pargv == NULL) {
		// I don't know if any other loaders provide similarly broken environments
		log_appendf(1, "[Wii] Was I just bannerbombed? Prepare for crash at exit...");
	} else if (memcmp((void *) 0x80001804, "STUBHAXX", 8) == 0) {
		log_appendf(1, "[Wii] Hello, HBC user!");
	} else {
		log_appendf(1, "[Wii] Where am I?!");
	}

	ISFS_SU();
	if (ISFS_Initialize() == IPC_OK)
		ISFS_Mount();
	fatInit(CACHE_PAGES, 0);

	// Attempt to locate a suitable home directory.
	if (!*pargc || !*pargv) {
		// loader didn't bother setting these
		*pargc = 1;
		*pargv = malloc(sizeof(char **));
		*pargv[0] = str_dup("?");
	} else if (strchr(*pargv[0], '/') != NULL) {
		// presumably launched from hbc menu - put stuff in the boot dir
		// (does get_parent_directory do what I want here?)
		ptr = get_parent_directory(*pargv[0]);
	}
	if (!ptr) {
		// Make a guess anyway
		ptr = str_dup("sd:/apps/schismtracker");
	}
	if (chdir(ptr) != 0) {
		free(ptr);
		dir = diropen("sd:/");
		if (dir) {
			// Ok at least the sd card works, there's some other dysfunction
			dirclose(dir);
			ptr = str_dup("sd:/");
		} else {
			// Safe (but useless) default
			ptr = str_dup("isfs:/");
		}
		chdir(ptr); // Hope that worked, otherwise we're hosed
	}
	put_env_var("HOME", ptr);
	free(ptr);
}

void wii_sysexit(void)
{
	ISFS_Deinitialize();
}

void wii_sdlinit(void)
{
	int n, total;

	if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) != 0) {
		log_appendf(4, "joystick init failed: %s", SDL_GetError());
		return;
	}

	total = SDL_NumJoysticks();
	for (n = 0; n < total; n++) {
		SDL_Joystick *js = SDL_JoystickOpen(n);
		if (js == NULL) {
			log_appendf(4, "[%d] open fail", n);
			continue;
		}
	}
}


static int lasthatsym = 0;

static SDLKey hat_to_keysym(int value)
{
	// up/down take precedence over left/right
	switch (value) {
	case SDL_HAT_LEFTUP:
	case SDL_HAT_UP:
	case SDL_HAT_RIGHTUP:
		return SDLK_UP;
	case SDL_HAT_LEFTDOWN:
	case SDL_HAT_DOWN:
	case SDL_HAT_RIGHTDOWN:
		return SDLK_DOWN;
	case SDL_HAT_LEFT:
		return SDLK_LEFT;
	case SDL_HAT_RIGHT:
		return SDLK_RIGHT;
	default: // SDL_HAT_CENTERED
		return 0;
	}
}

// Huge event-rewriting hack to get at least a sort of useful interface with no keyboard.
// It's obviously impossible to provide any sort of editing functions in this manner,
// but it at least allows simple song playback.
int wii_sdlevent(SDL_Event *event)
{
	SDL_Event newev = {};
	SDLKey sym;

	switch (event->type) {
	case SDL_KEYDOWN:
	case SDL_KEYUP:
		{ // argh
			struct key_event k = {
				.mod = event->key.keysym.mod,
				.sym = event->key.keysym.sym,
			};
			event->key.keysym.unicode = kbd_get_alnum(&k);
		}
		return 1;

	case SDL_JOYHATMOTION:
		// TODO key repeat for these, somehow
		sym = hat_to_keysym(event->jhat.value);
		if (sym) {
			newev.type = SDL_KEYDOWN;
			newev.key.state = SDL_PRESSED;
			lasthatsym = sym;
		} else {
			newev.type = SDL_KEYUP;
			newev.key.state = SDL_RELEASED;
			sym = lasthatsym;
			lasthatsym = 0;
		}
		newev.key.which = event->jhat.which;
		newev.key.keysym.sym = sym;
		newev.key.type = newev.type; // is this a no-op?
		*event = newev;
		return 1;

	case SDL_JOYBUTTONDOWN:
	case SDL_JOYBUTTONUP:
		switch (event->jbutton.button) {
		case 0: // A
		case 1: // B
		default:
			return 0;
		case 2: // 1
			if (song_get_mode() == MODE_STOPPED) {
				// nothing playing? go to load screen
				sym = SDLK_F9;
			} else {
				sym = SDLK_F8;
			}
			break;
		case 3: // 2
			if (status.current_page == PAGE_LOAD_MODULE) {
				// if the cursor is on a song, load then play; otherwise handle as enter
				// (hmm. ctrl-enter?)
				sym = SDLK_RETURN;
			} else {
				// F5 key
				sym = SDLK_F5;
			}
			break;
		case 4: // -
			// dialog escape, or jump back a pattern
			if (status.dialog_type) {
				sym = SDLK_ESCAPE;
				break;
			} else if (event->type == SDL_JOYBUTTONDOWN && song_get_mode() == MODE_PLAYING) {
				song_set_current_order(song_get_current_order() - 1);
			}
			return 0;
		case 5: // +
			// dialog enter, or jump forward a pattern
			if (status.dialog_type) {
				sym = SDLK_RETURN;
				break;
			} else if (event->type == SDL_JOYBUTTONDOWN && song_get_mode() == MODE_PLAYING) {
				song_set_current_order(song_get_current_order() + 1);
			}
			return 0;
		case 6: // Home
			event->type = SDL_QUIT;
			return 1;
		}
		newev.key.which = event->jbutton.which;
		newev.key.keysym.sym = sym;
		if (event->type == SDL_JOYBUTTONDOWN) {
			newev.type = SDL_KEYDOWN;
			newev.key.state = SDL_PRESSED;
		} else {
			newev.type = SDL_KEYUP;
			newev.key.state = SDL_RELEASED;
		}
		newev.key.type = newev.type; // no-op?
		*event = newev;
		return 1;
	}
	return 1;
}

