/*
* UAE - The U*nix Amiga Emulator
*
* UAE Library v0.1
*
* (c) 1996 Tauno Taipaleenmaki <tataipal@raita.oulu.fi>
*
* Change UAE parameters and other stuff from inside the emulation.
*/

#include "sysconfig.h"
#include "sysdeps.h"

#include <assert.h>
#include <limits.h>
#include <string.h>

#include "options.h"
#include "uae.h"
#include "memory.h"
#include "newcpu.h"
#include "autoconf.h"
#include "traps.h"
#include "disk.h"
#include "debug.h"
#include "gensound.h"
#include "picasso96.h"
#include "filesys.h"
#ifdef AMIBERRY
#ifndef LIBRETRO
#include "mhi_host.h"
#endif
#include "target.h"
#include "parser.h" // for midi ports
#endif
/*
* Returns UAE Version
*/
static uae_u32 emulib_GetVersion (void)
{
	return version;
}

/*
* Resets your amiga
*/
static uae_u32 emulib_HardReset (void)
{
	uae_reset(1, 1);
	return 0;
}

static uae_u32 emulib_Reset (void)
{
	uae_reset(0, 0);
	return 0;
}

/*
* Enables SOUND
*/
static uae_u32 emulib_EnableSound (uae_u32 val)
{
	if (!sound_available || currprefs.produce_sound >= 2)
		return 0;

	currprefs.produce_sound = val;
	return 1;
}

/*
* Enables FAKE JOYSTICK
*/
static uae_u32 emulib_EnableJoystick (uae_u32 val)
{
	currprefs.jports[0].id = val & 255;
	currprefs.jports[1].id = (val >> 8) & 255;
	return 1;
}

/*
* Sets the framerate
*/
static uae_u32 emulib_SetFrameRate (uae_u32 val)
{
	if (val == 0)
		return 0;
	else if (val > 20)
		return 0;
	else {
		currprefs.gfx_framerate = val;
		return 1;
	}
}

/*
* Changes keyboard language settings
*/
static uae_u32 emulib_ChangeLanguage (uae_u32 which)
{
	if (which > 6)
		return 0;
	else {
		switch (which) {
		case 0:
			currprefs.keyboard_lang = KBD_LANG_US;
			break;
		case 1:
			currprefs.keyboard_lang = KBD_LANG_DK;
			break;
		case 2:
			currprefs.keyboard_lang = KBD_LANG_DE;
			break;
		case 3:
			currprefs.keyboard_lang = KBD_LANG_SE;
			break;
		case 4:
			currprefs.keyboard_lang = KBD_LANG_FR;
			break;
		case 5:
			currprefs.keyboard_lang = KBD_LANG_IT;
			break;
		case 6:
			currprefs.keyboard_lang = KBD_LANG_ES;
			break;
		default:
			break;
		}
		return 1;
	}
}

/* The following ones don't work as we never realloc the arrays... */
/*
* Changes chip memory size
*  (reboots)
*/
static uae_u32 REGPARAM2 emulib_ChgCMemSize(TrapContext *ctx, uae_u32 memsize)
{
	if (memsize != 0x80000 && memsize != 0x100000 &&
		memsize != 0x200000) {
			memsize = 0x200000;
			write_log (_T("Unsupported chipmem size!\n"));
	}
	trap_set_dreg(ctx, 0,  0);

	changed_prefs.chipmem.size = memsize;
	uae_reset(1, 1);
	return 1;
}

/*
* Changes slow memory size
*  (reboots)
*/
static uae_u32 REGPARAM2 emulib_ChgSMemSize(TrapContext *ctx, uae_u32 memsize)
{
	if (memsize != 0x80000 && memsize != 0x100000 &&
		memsize != 0x180000 && memsize != 0x1C0000) {
			memsize = 0;
			write_log (_T("Unsupported bogomem size!\n"));
	}

	trap_set_dreg(ctx, 0, 0);
	changed_prefs.bogomem.size = memsize;
	uae_reset (1, 1);
	return 1;
}

/*
* Changes fast memory size
*  (reboots)
*/
static uae_u32 REGPARAM2 emulib_ChgFMemSize(TrapContext *ctx, uae_u32 memsize)
{
	if (memsize != 0x100000 && memsize != 0x200000 &&
		memsize != 0x400000 && memsize != 0x800000) {
			memsize = 0;
			write_log (_T("Unsupported fastmem size!\n"));
	}
	trap_set_dreg(ctx, 0, 0);
	changed_prefs.fastmem[0].size = memsize;
	uae_reset (1, 1);
	return 0;
}

/*
* Inserts a disk
*/
static uae_u32 emulib_InsertDisk(TrapContext *ctx, uaecptr name, uae_u32 drive)
{
	char real_name[256];
	TCHAR *s;

	if (drive > 3)
		return 0;

	if (trap_get_string(ctx, real_name, name, sizeof real_name) >= sizeof real_name)
		return 0; /* ENAMETOOLONG */

	s = au (real_name);
	_tcscpy (changed_prefs.floppyslots[drive].df, s);
	xfree (s);

	return 1;
}

/*
* Exits the emulator
*/
static uae_u32 emulib_ExitEmu(void)
{
	uae_quit ();
	return 1;
}

/*
* Gets UAE Configuration
*/
static uae_u32 emulib_GetUaeConfig(TrapContext *ctx, uaecptr place)
{
	trap_put_long(ctx, place, version);
	trap_put_long(ctx, place + 4, chipmem_bank.allocated_size);
	trap_put_long(ctx, place + 8, bogomem_bank.allocated_size);
	trap_put_long(ctx, place + 12, fastmem_bank[0].allocated_size);
	trap_put_long(ctx, place + 16, currprefs.gfx_framerate);
	trap_put_long(ctx, place + 20, currprefs.produce_sound);
	trap_put_long(ctx, place + 24, currprefs.jports[0].id | (currprefs.jports[1].id << 8));
	trap_put_long(ctx, place + 28, currprefs.keyboard_lang);
	if (disk_empty (0))
		trap_put_byte(ctx, place + 32, 0);
	else
		trap_put_byte(ctx, place + 32, 1);
	if (disk_empty (1))
		trap_put_byte(ctx, place + 33, 0);
	else
		trap_put_byte(ctx, place + 33, 1);
	if (disk_empty(2))
		trap_put_byte(ctx, place + 34, 0);
	else
		trap_put_byte(ctx, place + 34, 1);
	if (disk_empty(3))
		trap_put_byte(ctx, place + 35, 0);
	else
		trap_put_byte(ctx, place + 35, 1);

	for (int i = 0; i < 4; i++) {
		char *s = ua (currprefs.floppyslots[i].df);
		trap_put_string(ctx, s, place + 36 + i * 256, 256);
		xfree (s);
	}
	return 1;
}

/*
* Sets UAE Configuration
*
* NOT IMPLEMENTED YET
*/
static uae_u32 emulib_SetUaeConfig(uaecptr place)
{
	return 1;
}

/*
* Gets the name of the disk in the given drive
*/
static uae_u32 emulib_GetDisk(TrapContext *ctx, uae_u32 drive, uaecptr name)
{
	if (drive > 3)
		return 0;

	char *n = ua(currprefs.floppyslots[drive].df);
	trap_put_string(ctx, (uae_u8*)n, name, 256);
	xfree(n);
	return 1;
}

/*
* Enter debugging state
*/
static uae_u32 emulib_Debug(void)
{
#ifdef DEBUGGER
	activate_debugger ();
	return 1;
#else
	return 0;
#endif
}


#define CREATE_NATIVE_FUNC_PTR uae_u32 (* native_func) (uae_u32, uae_u32, uae_u32, uae_u32, uae_u32, uae_u32, uae_u32, \
	uae_u32, uae_u32, uae_u32, uae_u32, uae_u32, uae_u32)
#define SET_NATIVE_FUNC(x) native_func = (uae_u32 (*)(uae_u32, uae_u32, uae_u32, uae_u32, uae_u32, uae_u32, uae_u32, uae_u32, uae_u32, uae_u32, uae_u32, uae_u32, uae_u32))(x)
#define CALL_NATIVE_FUNC( d1,d2,d3,d4,d5,d6,d7,a1,a2,a3,a4,a5,a6 ) if(native_func) native_func( d1,d2,d3,d4,d5,d6,d7,a1,a2,a3,a4,a5,a6 )
/* A0 - Contains a ptr to the native .obj data.  This ptr is Amiga-based. */
/*      We simply find the first function in this .obj data, and execute it. */
static uae_u32 REGPARAM2 emulib_ExecuteNativeCode (void)
{
#if 0
	uaecptr object_AAM = m68k_areg (regs, 0);
	uae_u32 d1 = m68k_dreg (regs, 1);
	uae_u32 d2 = m68k_dreg (regs, 2);
	uae_u32 d3 = m68k_dreg (regs, 3);
	uae_u32 d4 = m68k_dreg (regs, 4);
	uae_u32 d5 = m68k_dreg (regs, 5);
	uae_u32 d6 = m68k_dreg (regs, 6);
	uae_u32 d7 = m68k_dreg (regs, 7);
	uae_u32 a1 = m68k_areg (regs, 1);
	uae_u32 a2 = m68k_areg (regs, 2);
	uae_u32 a3 = m68k_areg (regs, 3);
	uae_u32 a4 = m68k_areg (regs, 4);
	uae_u32 a5 = m68k_areg (regs, 5);
	uae_u32 a6 = m68k_areg (regs, 6);

	uae_u8* object_UAM = NULL;
	CREATE_NATIVE_FUNC_PTR;

	if (get_mem_bank(object_AAM).check (object_AAM, 1))
		object_UAM = get_mem_bank (object_AAM).xlateaddr (object_AAM);

	if (object_UAM) {
		SET_NATIVE_FUNC (FindFunctionInObject (object_UAM));
		CALL_NATIVE_FUNC (d1, d2, d3, d4, d5, d6, d7, a1, a2, a3, a4, a5, a6);
	}
	return 1;
#endif
	return 0;
}

static uae_u32 emulib_Minimize (void)
{
	return 0; // OSDEP_minimize_uae();
}

static int native_dos_op(TrapContext *ctx, uae_u32 mode, uae_u32 p1, uae_u32 p2, uae_u32 p3)
{
	TCHAR tmp[MAX_DPATH];
	char *s;
	int v;

	if (mode)
		return -1;
	/* receive native path from lock
	* p1 = dos.library:Lock, p2 = buffer, p3 = max buffer size
	*/
	v = get_native_path(ctx, p1, tmp);
	if (v)
		return v;
	if (p3 == 0 || p3 > INT_MAX)
		return -1;
	s = ua (tmp);
	if (!s)
		return -1;
	v = trap_put_string(ctx, (uae_u8*)s, p2, static_cast<int>(p3));
	xfree (s);
	if (v >= static_cast<int>(p3))
		return -1;
	return 0;
}

#ifdef AMIBERRY
// Host command strings are not paths: MAX_DPATH is PATH_MAX, which is
// only 260 on Windows, far too small for generated shell commands.
#define HOST_SHELL_CMD_MAX 4096

// Execute a command on the Host OS
static uae_u32 emulib_execute_on_host(TrapContext* ctx, uaecptr name)
{
	char real_name[HOST_SHELL_CMD_MAX];
	if (trap_get_string(ctx, real_name, name, sizeof real_name) >= static_cast<int>(sizeof real_name))
		return 0; /* ENAMETOOLONG */

	return target_execute(real_name) ? 1 : 0;
}

enum midops {
	MIDI_OP_GET_NUM_OUT,
	MIDI_OP_GET_NUM_IN,
	MIDI_OP_GET_OUT_INDEX,
	MIDI_OP_GET_IN_INDEX,
	MIDI_OP_GET_OUT,
	MIDI_OP_GET_IN,	
	MIDI_OP_SET_OUT,
	MIDI_OP_SET_IN
};



// Structure to hold session details
struct ShellSession {
	int pid;
	int infd;  // Master PTY fd (read/write), or pipe CRT fds
	int outfd; // Master PTY fd (read/write), or pipe CRT fds
	FILE* pipe_in;
	FILE* pipe_out;
	bool exited;
	int exit_status;
#if defined(_WIN32)
	void* process; // HANDLE of the child process
#endif
};

#include <vector>
#include <map>
#include <string>
#include <unistd.h>
#ifndef _WIN32
#include <sys/wait.h>
#endif
#ifdef _WIN32
#include <windows.h>
#include <io.h>
#endif
#include <fcntl.h>
#include <signal.h>
#if !defined(_WIN32)                                                                                                       
  #if defined(__APPLE__)                                                                                                   
    #include <util.h>                                                                                                      
  #elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__)                      
    #include <libutil.h>                                                                                                   
  #else                                                                                                                    
    #include <pty.h>                                                                                                       
  #endif                                                                                                                   
#endif             

static std::map<uae_u32, ShellSession> shell_sessions;
static uae_u32 next_session_handle = 1;

static const uae_u32 HOST_SHELL_STATUS_INVALID = 0;
static const uae_u32 HOST_SHELL_STATUS_RUNNING = 1;
static const uae_u32 HOST_SHELL_STATUS_EXITED = 0x80000000;
static const uae_u32 HOST_SHELL_IO_MAX = 4096;

#if !defined(_WIN32)
static int host_shell_exit_code(int status)
{
	if (WIFEXITED(status))
		return WEXITSTATUS(status);
	if (WIFSIGNALED(status))
		return 128 + WTERMSIG(status);
	return 1;
}
#endif

static uae_u32 host_shell_pack_exit_status(int exit_status)
{
	return HOST_SHELL_STATUS_EXITED | (exit_status & 0xff);
}

static uae_u32 host_shell_update_status(ShellSession& session)
{
#if defined(_WIN32)
	if (session.process == NULL)
		return HOST_SHELL_STATUS_INVALID;
	if (session.exited)
		return host_shell_pack_exit_status(session.exit_status);

	DWORD code = 0;
	if (!GetExitCodeProcess((HANDLE)session.process, &code))
		return HOST_SHELL_STATUS_INVALID;
	if (code == STILL_ACTIVE)
		return HOST_SHELL_STATUS_RUNNING;

	session.exited = true;
	session.exit_status = (int)(code & 0xff);
	if (code != 0 && session.exit_status == 0)
		session.exit_status = 1;
	return host_shell_pack_exit_status(session.exit_status);
#else
	if (session.exited)
		return host_shell_pack_exit_status(session.exit_status);

	int status;
	pid_t result = waitpid(session.pid, &status, WNOHANG);
	if (result == 0)
		return HOST_SHELL_STATUS_RUNNING;

	if (result == session.pid) {
		session.exited = true;
		session.exit_status = host_shell_exit_code(status);
		return host_shell_pack_exit_status(session.exit_status);
	}

	if (result < 0) {
		if (errno == EINTR)
			return HOST_SHELL_STATUS_RUNNING;
		if (errno == ECHILD) {
			session.exited = true;
			session.exit_status = 1;
			return host_shell_pack_exit_status(session.exit_status);
		}
	}

	return HOST_SHELL_STATUS_INVALID;
#endif
}

static uae_u32 uaelib_host_open(TrapContext* ctx, uaecptr command)
{
	char cmd[HOST_SHELL_CMD_MAX];
	if (trap_get_string(ctx, cmd, command, sizeof cmd) >= sizeof cmd)
		return 0;

#if defined(_WIN32)
	// Not supported on Windows yet
	return 0;
#else
	int master;
	pid_t pid = forkpty(&master, NULL, NULL, NULL);

	if (pid == 0) {
		// Child process
		setenv("TERM", "xterm", 1);

		const char* shell = getenv("SHELL");
		if (!shell) shell = "/bin/sh";
		const char* shell_name = strrchr(shell, '/');
		if (shell_name) shell_name++; else shell_name = shell;
		
		// Execute command
		if (strlen(cmd) > 0)
			execl(shell, shell_name, "-c", cmd, (char*)NULL);
		else
			execl(shell, shell_name, (char*)NULL); // Interactive shell

		perror("execl");
		_exit(127);
	} else if (pid > 0) {
		// Parent process
		// Set non-blocking read on master PTY
		int flags = fcntl(master, F_GETFL, 0);
		if (flags < 0 || fcntl(master, F_SETFL, flags | O_NONBLOCK) < 0) {
			close(master);
			kill(pid, SIGTERM);
			waitpid(pid, NULL, 0);
			return 0;
		}

		ShellSession session;
		session.pid = pid;
		session.infd = master;
		session.outfd = master;
		session.pipe_in = NULL;
		session.pipe_out = NULL;
		session.exited = false;
		session.exit_status = 0;
		
		uae_u32 handle = next_session_handle++;
		shell_sessions[handle] = session;
		return handle;
	}

	return 0;
#endif
}

static uae_u32 uaelib_host_open_pipe(TrapContext* ctx, uaecptr command)
{
	char cmd[HOST_SHELL_CMD_MAX];
	if (trap_get_string(ctx, cmd, command, sizeof cmd) >= sizeof cmd)
		return 0;
	if (cmd[0] == '\0')
		return 0;

#if defined(_WIN32)
	// Run the command through the command interpreter with anonymous
	// pipes; stderr is discarded. The pipe handles are mapped onto CRT
	// fds so the session bookkeeping matches the POSIX side.
	SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
	HANDLE in_read = NULL, in_write = NULL;
	HANDLE out_read = NULL, out_write = NULL;

	if (!CreatePipe(&in_read, &in_write, &sa, 0))
		return 0;
	if (!CreatePipe(&out_read, &out_write, &sa, 0)) {
		CloseHandle(in_read);
		CloseHandle(in_write);
		return 0;
	}
	SetHandleInformation(in_write, HANDLE_FLAG_INHERIT, 0);
	SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0);

	HANDLE nul = CreateFileA("NUL", GENERIC_WRITE, FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, NULL);

	const char* comspec = getenv("COMSPEC");
	if (!comspec)
		comspec = "cmd.exe";
	std::string cmdline = std::string("\"") + comspec + "\" /C " + cmd;

	STARTUPINFOA si = {};
	si.cb = sizeof si;
	si.dwFlags = STARTF_USESTDHANDLES;
	si.hStdInput = in_read;
	si.hStdOutput = out_write;
	si.hStdError = nul;

	PROCESS_INFORMATION pi = {};
	BOOL created = CreateProcessA(NULL, &cmdline[0], NULL, NULL, TRUE,
		CREATE_NO_WINDOW, NULL, NULL, &si, &pi);

	CloseHandle(in_read);
	CloseHandle(out_write);
	if (nul)
		CloseHandle(nul);

	if (!created) {
		CloseHandle(in_write);
		CloseHandle(out_read);
		return 0;
	}
	CloseHandle(pi.hThread);

	ShellSession session;
	session.pid = (int)pi.dwProcessId;
	session.infd = _open_osfhandle((intptr_t)in_write, 0);
	session.outfd = _open_osfhandle((intptr_t)out_read, _O_RDONLY);
	session.pipe_in = NULL;
	session.pipe_out = NULL;
	session.exited = false;
	session.exit_status = 0;
	session.process = (void*)pi.hProcess;

	if (session.infd < 0 || session.outfd < 0) {
		if (session.infd >= 0)
			_close(session.infd);
		else
			CloseHandle(in_write);
		if (session.outfd >= 0)
			_close(session.outfd);
		else
			CloseHandle(out_read);
		TerminateProcess(pi.hProcess, 1);
		CloseHandle(pi.hProcess);
		return 0;
	}

	uae_u32 handle = next_session_handle++;
	shell_sessions[handle] = session;
	return handle;
#else
	// Plain pipes instead of a pty: binary-safe output, no terminal
	// line-ending processing, clean EOF once the pipe drains.
	int inpipe[2];
	int outpipe[2];
	if (pipe(inpipe) < 0)
		return 0;
	if (pipe(outpipe) < 0) {
		close(inpipe[0]);
		close(inpipe[1]);
		return 0;
	}

	pid_t pid = fork();
	if (pid == 0) {
		// Child process
		dup2(inpipe[0], 0);
		dup2(outpipe[1], 1);
		int devnull = open("/dev/null", O_WRONLY);
		if (devnull >= 0) {
			dup2(devnull, 2);
			if (devnull > 2)
				close(devnull);
		}
		close(inpipe[0]);
		close(inpipe[1]);
		close(outpipe[0]);
		close(outpipe[1]);

		const char* shell = getenv("SHELL");
		if (!shell) shell = "/bin/sh";
		const char* shell_name = strrchr(shell, '/');
		if (shell_name) shell_name++; else shell_name = shell;

		execl(shell, shell_name, "-c", cmd, (char*)NULL);
		_exit(127);
	} else if (pid > 0) {
		// Parent process
		close(inpipe[0]);
		close(outpipe[1]);

		int flags = fcntl(outpipe[0], F_GETFL, 0);
		if (flags < 0 || fcntl(outpipe[0], F_SETFL, flags | O_NONBLOCK) < 0) {
			close(inpipe[1]);
			close(outpipe[0]);
			kill(pid, SIGTERM);
			waitpid(pid, NULL, 0);
			return 0;
		}

		ShellSession session;
		session.pid = pid;
		session.infd = inpipe[1];
		session.outfd = outpipe[0];
		session.pipe_in = NULL;
		session.pipe_out = NULL;
		session.exited = false;
		session.exit_status = 0;

		uae_u32 handle = next_session_handle++;
		shell_sessions[handle] = session;
		return handle;
	}

	close(inpipe[1]);
	close(outpipe[0]);
	return 0;
#endif
}

static uae_u32 uaelib_host_read(TrapContext* ctx, uae_u32 handle, uaecptr buffer, uae_u32 size)
{
#if defined(_WIN32)
	if (size == 0 || size > HOST_SHELL_IO_MAX)
		return -1;

	if (shell_sessions.find(handle) == shell_sessions.end())
		return -1;

	ShellSession& session = shell_sessions[handle];
	HANDLE h = (HANDLE)_get_osfhandle(session.outfd);
	if (h == INVALID_HANDLE_VALUE)
		return -1;

	DWORD avail = 0;
	if (!PeekNamedPipe(h, NULL, 0, NULL, &avail, NULL)) {
		// broken pipe: child exited and the buffer has drained
		return -1;
	}
	if (avail == 0) {
		if ((host_shell_update_status(session) & HOST_SHELL_STATUS_EXITED) != 0) {
			// drain race: data may have arrived between the peek and the wait
			if (!PeekNamedPipe(h, NULL, 0, NULL, &avail, NULL) || avail == 0)
				return -1;
		} else {
			return 0;
		}
	}

	DWORD want = avail < size ? avail : size;
	std::vector<char> buf(want);
	DWORD got = 0;
	if (!ReadFile(h, buf.data(), want, &got, NULL))
		return -1;
	if (got > 0) {
		trap_put_bytes(ctx, (uae_u8*)buf.data(), buffer, got);
		return got;
	}
	return 0;
#else
	if (size == 0 || size > HOST_SHELL_IO_MAX)
		return -1;

	if (shell_sessions.find(handle) == shell_sessions.end())
		return -1;

	ShellSession& session = shell_sessions[handle];
	std::vector<char> buf(size);

	ssize_t bytes_read = read(session.outfd, buf.data(), size);

	if (bytes_read > 0) {
		trap_put_bytes(ctx, (uae_u8*)buf.data(), buffer, bytes_read);
		return bytes_read;
	} else if (bytes_read == 0) {
		if ((host_shell_update_status(session) & HOST_SHELL_STATUS_EXITED) != 0)
			return -1; // Process exited
		return 0;
	} else {
		if (errno == EAGAIN || errno == EWOULDBLOCK) {
			if ((host_shell_update_status(session) & HOST_SHELL_STATUS_EXITED) != 0)
				return -1;
			return 0;
		}
		if ((host_shell_update_status(session) & HOST_SHELL_STATUS_EXITED) != 0)
			return -1;
		return -1;
	}
#endif
}

static uae_u32 uaelib_host_write(TrapContext* ctx, uae_u32 handle, uaecptr buffer, uae_u32 size)
{
#if defined(_WIN32)
	if (size == 0 || size > HOST_SHELL_IO_MAX)
		return -1;

	if (shell_sessions.find(handle) == shell_sessions.end())
		return -1;

	ShellSession& session = shell_sessions[handle];
	if ((host_shell_update_status(session) & HOST_SHELL_STATUS_EXITED) != 0)
		return -1;

	std::vector<char> buf(size);
	trap_get_bytes(ctx, (uae_u8*)buf.data(), buffer, size);

	int written = _write(session.infd, buf.data(), size);
	return written;
#else
	if (size == 0 || size > HOST_SHELL_IO_MAX)
		return -1;

	if (shell_sessions.find(handle) == shell_sessions.end())
		return -1;

	ShellSession& session = shell_sessions[handle];
	if ((host_shell_update_status(session) & HOST_SHELL_STATUS_EXITED) != 0)
		return -1;

	std::vector<char> buf(size);
	trap_get_bytes(ctx, (uae_u8*)buf.data(), buffer, size);

	ssize_t bytes_written = write(session.infd, buf.data(), size);
	return bytes_written;
#endif
}

static uae_u32 uaelib_host_close(TrapContext* ctx, uae_u32 handle)
{
#if defined(_WIN32)
	if (shell_sessions.find(handle) == shell_sessions.end())
		return 0;

	ShellSession& session = shell_sessions[handle];
	int closed_fd = session.infd;
	if (session.infd >= 0) {
		_close(session.infd);
		session.infd = -1;
	}
	if (session.outfd >= 0 && session.outfd != closed_fd) {
		_close(session.outfd);
		session.outfd = -1;
	}
	if ((host_shell_update_status(session) & HOST_SHELL_STATUS_EXITED) == 0) {
		TerminateProcess((HANDLE)session.process, 1);
		WaitForSingleObject((HANDLE)session.process, 2000);
	}
	if (session.process != NULL)
		CloseHandle((HANDLE)session.process);

	shell_sessions.erase(handle);
	return 1;
#else
	if (shell_sessions.find(handle) == shell_sessions.end())
		return 0;

	ShellSession& session = shell_sessions[handle];
	int closed_fd = session.infd;
	if (session.infd >= 0) {
		close(session.infd);
		session.infd = -1;
	}
	if (session.outfd >= 0 && session.outfd != closed_fd) {
		close(session.outfd);
		session.outfd = -1;
	}
	if ((host_shell_update_status(session) & HOST_SHELL_STATUS_EXITED) == 0) {
		kill(session.pid, SIGTERM);
		waitpid(session.pid, NULL, 0);
	}

	shell_sessions.erase(handle);
	return 1;
#endif
}

// Host platform ids reported by trap 96; 0 (unknown trap on older
// builds) means the guest should assume a POSIX host.
static uae_u32 uaelib_host_get_platform(void)
{
#if defined(_WIN32)
	return 3;
#elif defined(__APPLE__)
	return 2;
#else
	return 1;
#endif
}

static uae_u32 uaelib_host_status(TrapContext* ctx, uae_u32 handle)
{
	(void)ctx;
#if defined(_WIN32)
	return HOST_SHELL_STATUS_INVALID;
#else
	if (shell_sessions.find(handle) == shell_sessions.end())
		return HOST_SHELL_STATUS_INVALID;

	ShellSession& session = shell_sessions[handle];
	return host_shell_update_status(session);
#endif
}

static std::string quote_path(const char* path) {
	std::string res = "'";
	for (const char* p = path; *p; ++p) {
		if (*p == '\'') res += "'\\''";
		else res += *p;
	}
	res += "'";
	return res;
}

static uae_u32 uaelib_host_view(TrapContext* ctx, uaecptr filename) {
	char file[HOST_SHELL_CMD_MAX];
	if (trap_get_string(ctx, file, filename, sizeof file) >= sizeof file)
		return 0;

#if defined(_WIN32)
	std::string cmd = "start \"\" ";
#elif defined(__APPLE__)
	std::string cmd = "open ";
#else
	std::string cmd = "xdg-open ";
#endif
	cmd += quote_path(file);

	return target_execute(cmd.c_str()) ? 1 : 0;
}

static uae_u32 uaelib_midi(TrapContext *ctx, uae_u32 op, uae_u32 index, uaecptr name)
{
	uae_u32 result = -1;
	switch (op)
	{
		case MIDI_OP_GET_NUM_OUT:
		{
			result = midi_out_ports.size();
			break;
		}
		case MIDI_OP_GET_NUM_IN:
		{
			result = midi_in_ports.size();
			break;
		}
		case MIDI_OP_GET_OUT_INDEX:
		{
			if(index < midi_out_ports.size())
			{
				char *n = ua(midi_out_ports[index].c_str());
				if(n)
				{
					trap_put_string(ctx, (uae_u8*)n, name, 256);
					result = strlen(n);
					xfree(n);
				}
				
			}
			break;
		}
		case MIDI_OP_GET_IN_INDEX:
		{
			if(index < midi_in_ports.size())
			{
				char *n = ua(midi_in_ports[index].c_str());
				if(n)
				{
					trap_put_string(ctx, (uae_u8*)n, name, 256);
					result = strlen(n);
					xfree(n);
				}
				
			}
			break;
		}
		case MIDI_OP_GET_OUT:
		{
			char *n = ua(currprefs.midioutdev);
			if(n)
			{
				trap_put_string(ctx, (uae_u8*)n, name, 256);
				result = strlen(n);
				xfree(n);
			}
			break;
		}
		case MIDI_OP_GET_IN:
		{
			char *n = ua(currprefs.midiindev);
			if(n)
			{
				trap_put_string(ctx, (uae_u8*)n, name, 256);
				result = strlen(n);
				xfree(n);
			}
			break;
		}
		case MIDI_OP_SET_OUT:
		{
			char midi_name[256];
			
			if (trap_get_string(ctx, midi_name, name, sizeof(midi_name)) < sizeof(midi_name))
			{
				TCHAR *s;
				if((s = au (midi_name)))
				{
					_sntprintf(changed_prefs.midioutdev, 256, "%s", s);
					xfree(s);
					result = 0;
					set_config_changed();
				}
			}
			break;
		}
		case MIDI_OP_SET_IN:
		{
			char midi_name[256];
			
			if (trap_get_string(ctx, midi_name, name, sizeof(midi_name)) < sizeof(midi_name))
			{
				TCHAR *s;
				if((s = au (midi_name)))
				{
					_sntprintf(changed_prefs.midiindev, 256, "%s", s);
					xfree(s);
					result = 0;
					set_config_changed();
				}
			}
			break;
		}
		default:
			break;
	}
	
	return result;
}

#endif //AMIBERRY

static uae_u32 uaelib_demux_common(TrapContext *ctx, uae_u32 ARG0, uae_u32 ARG1, uae_u32 ARG2, uae_u32 ARG3, uae_u32 ARG4, uae_u32 ARG5)
{
	write_log("%ld\n",ARG0);
	
	switch (ARG0) {
		case 0: return emulib_GetVersion();
		case 1: return emulib_GetUaeConfig(ctx, ARG1);
		case 2: return emulib_SetUaeConfig(ARG1);
		case 3: return emulib_HardReset();
		case 4: return emulib_Reset();
		case 5: return emulib_InsertDisk(ctx, ARG1, ARG2);
		case 6: return emulib_EnableSound(ARG1);
		case 7: return emulib_EnableJoystick(ARG1);
		case 8: return emulib_SetFrameRate(ARG1);
		case 9: return emulib_ChgCMemSize(ctx, ARG1);
		case 10: return emulib_ChgSMemSize(ctx, ARG1);
		case 11: return emulib_ChgFMemSize(ctx, ARG1);
		case 12: return emulib_ChangeLanguage(ARG1);
			/* The next call brings bad luck */
		case 13: return emulib_ExitEmu();
		case 14: return emulib_GetDisk(ctx, ARG1, ARG2);
		case 15: return emulib_Debug();

		case 68: return emulib_Minimize();
		case 69: return emulib_ExecuteNativeCode();

		case 70: return 0; /* RESERVED. Something uses this.. */

		case 80:
		if (!currprefs.maprom)
			return 0xffffffff;
		/* Disable possible ROM protection */
		unprotect_maprom();
		return currprefs.maprom;
		case 81: return cfgfile_uaelib(ctx, ARG1, ARG2, ARG3, ARG4);
		case 82: return cfgfile_uaelib_modify(ctx, ARG1, ARG2, ARG3, ARG4, ARG5);
		case 83: currprefs.mmkeyboard = ARG1 ? 1 : 0; return currprefs.mmkeyboard;
#ifdef DEBUGGER
		case 84: return mmu_init(ARG1, ARG2, ARG3);
#endif
		case 85: return native_dos_op(ctx, ARG1, ARG2, ARG3, ARG4);
		case 86:
		if (valid_address(ARG1, 1)) {
			uae_char tmp[MAX_DPATH];
			trap_get_string(ctx, tmp, ARG1, sizeof tmp);
			TCHAR *s = au(tmp);
			write_log(_T("DBG: %s\n"), s);
			xfree(s);
			return 1;
		}
		return 0;
		case 87:
		{
			uae_u32 d0, d1;
			d0 = emulib_target_getcpurate(ARG1, &d1);
			trap_set_dreg(ctx, 1, d1);
			return d0;
		}
#ifdef AMIBERRY
		case 88:
		if (currprefs.native_code)
			return emulib_execute_on_host(ctx, ARG1);
		return 0;
		case 89: return uaelib_host_view(ctx, ARG1);
		
		case 90: return uaelib_host_open(ctx, ARG1);
		case 91: return uaelib_host_read(ctx, ARG1, ARG2, ARG3);
		case 92: return uaelib_host_write(ctx, ARG1, ARG2, ARG3);
		case 93: return uaelib_host_close(ctx, ARG1);
		case 94: return uaelib_host_status(ctx, ARG1);
		case 95: return uaelib_host_open_pipe(ctx, ARG1);
		case 96: return uaelib_host_get_platform();

		case 100:
		{
			return uaelib_midi(ctx, ARG1, ARG2, ARG3);
		}
#ifndef LIBRETRO
		case UAE_MHI_TRAP_ALLOC:
			return mhi_host_alloc(ctx, ARG1, ARG2);
		case UAE_MHI_TRAP_FREE:
			return mhi_host_free(ctx, ARG1);
		case UAE_MHI_TRAP_QUEUE:
			return mhi_host_queue(ctx, ARG1, ARG2, ARG3, ARG4);
		case UAE_MHI_TRAP_GET_EMPTY:
			return mhi_host_get_empty(ctx, ARG1);
		case UAE_MHI_TRAP_STATUS:
			return mhi_host_status(ctx, ARG1);
		case UAE_MHI_TRAP_PLAY:
			return mhi_host_play(ctx, ARG1);
		case UAE_MHI_TRAP_STOP:
			return mhi_host_stop(ctx, ARG1);
		case UAE_MHI_TRAP_PAUSE:
			return mhi_host_pause(ctx, ARG1);
		case UAE_MHI_TRAP_SET_PARAM:
			return mhi_host_set_param(ctx, ARG1, ARG2, ARG3);
		case UAE_MHI_TRAP_QUERY:
			return mhi_host_query(ctx, ARG1);
#endif
#endif
	}
	return 0;
}

uae_u32 uaeboard_demux(uae_u32 *board)
{
	uae_u32 arg0, arg1, arg2, arg3, arg4, arg5;

	arg0 = do_get_mem_word((uae_u16*)&board[0]);
	arg1 = do_get_mem_long(&board[2]);
	arg2 = do_get_mem_long(&board[3]);
	arg3 = do_get_mem_long(&board[4]);
	arg4 = do_get_mem_long(&board[5]);
	arg5 = do_get_mem_long(&board[6]);
	return uaelib_demux_common(NULL, arg0, arg1, arg2, arg3, arg4, arg5);
}

static uae_u32 REGPARAM2 uaelib_demux2 (TrapContext *ctx)
{
#define ARG0 (trap_get_long(ctx, trap_get_areg(ctx, 7) + 4))
#define ARG1 (trap_get_long(ctx, trap_get_areg(ctx, 7) + 8))
#define ARG2 (trap_get_long(ctx, trap_get_areg(ctx, 7) + 12))
#define ARG3 (trap_get_long(ctx, trap_get_areg(ctx, 7) + 16))
#define ARG4 (trap_get_long(ctx, trap_get_areg(ctx, 7) + 20))
#define ARG5 (trap_get_long(ctx, trap_get_areg(ctx, 7) + 24))

#ifdef PICASSO96
	if (ARG0 >= 16 && ARG0 <= 39)
		return picasso_demux(ARG0, ctx);
#endif
	return uaelib_demux_common(ctx, ARG0, ARG1, ARG2, ARG3, ARG4, ARG5);
}

extern int uaelib_debug;
static uae_u32 REGPARAM2 uaelib_demux (TrapContext *ctx)
{
	uae_u32 v;
	struct regstruct *r = &regs;

	if (uaelib_debug)
		write_log (_T("%d: %08x %08x %08x %08x %08x %08x %08x %08x, %08x %08x %08x %08x %08x %08x %08x %08x\n"),
		ARG0,
		r->regs[0],r->regs[1],r->regs[2],r->regs[3],r->regs[4],r->regs[5],r->regs[6],r->regs[7],
		r->regs[8],r->regs[9],r->regs[10],r->regs[11],r->regs[12],r->regs[13],r->regs[14],r->regs[15]);
	v = uaelib_demux2 (ctx);
	if (uaelib_debug)
		write_log (_T("=%08x\n"), v);
	return v;
}

/*
* Installs the UAE LIBRARY
*/
void emulib_install (void)
{
	uaecptr a;
	if (!uae_boot_rom_type && !currprefs.uaeboard)
		return;
	a = here ();
	currprefs.mmkeyboard = false;
	org (rtarea_base + 0xFF60);
	calltrap (deftrapres (uaelib_demux, 0, _T("uaelib_demux")));
	dw (RTS);
	org (a);
}
