#include "TN_UADE.player_rev.h"
#include "uade_tnplug.h"
#include <string.h>
#include <stdio.h>
#include <netinet/in.h>
#include "uadeconfig.h"
#include "uadeconf.h"
#include "uadestate.h"
#include "uadecontrol.h"
#include "uadeconstants.h"
#include "songdb.h"

const char USED verstag[] = VERSTAG;

struct ExecIFace *IExec;
struct DOSIFace *IDOS;
struct UtilityIFace *IUtility;
struct Interface *INewlib;

static BOOL OpenLibs (void);
static void CloseLibs (void);
static void play_loop (struct TNDecHandle *handle, struct uade_state *state);
static void play_audio (struct TNDecHandle *handle, int16_t *pcm,
	uint32_t pcm_size, uint32_t num_frames);

static volatile BOOL player_is_done = FALSE;

extern const char *uadecore_stdout_name;

int32 _start (STRPTR argstr, int32 argstrlen, struct ExecBase *SysBase) {
	int32 rc = RETURN_FAIL;
	struct MsgPort *proc_port;
	struct StartMessage *start_msg;
	struct TNDecHandle *handle;
	static struct uade_state state;
	int uadeconf_loaded;
	static char uadeconfname[PATH_MAX] = "";
    static char configname[PATH_MAX] = "";
    static char scorename[PATH_MAX] = "";
	static char uadename[PATH_MAX] = "";
	static char modulename[PATH_MAX] = "";
	static char playername[PATH_MAX] = "";
	static char songname[PATH_MAX] = "";
	FILE *playerfile;
	int ret;

	IExec = (struct ExecIFace *)SysBase->MainInterface;

	proc_port = &((struct Process *)IExec->FindTask(NULL))->pr_MsgPort;
	dbug(("Player: Waiting for Start Message.\n"));
	IExec->WaitPort(proc_port);
	start_msg = (struct StartMessage *)IExec->GetMsg(proc_port);
	handle = start_msg->sm_Handle;

	if (!OpenLibs()) {
		return rc;
	}

	state.ipc.input = state.ipc.output = (void *)-1;

	dbug(("Player: uade_load_initial_config.\n"));
	uadeconf_loaded = uade_load_initial_config(uadeconfname, sizeof(uadeconfname),
		&state.config, NULL);
	dbug(("uadeconf_loaded: %ld\n", uadeconf_loaded));
	dbug(("uadeconfname: %s\n", uadeconfname));

	IUtility->Strlcpy(configname, state.config.basedir.name, sizeof(configname));
	IDOS->AddPart(configname, "uaerc", sizeof(configname));
	dbug(("configname: %s\n", configname));

	IUtility->Strlcpy(scorename, state.config.basedir.name, sizeof(scorename));
	IDOS->AddPart(scorename, "score", sizeof(scorename));
	dbug(("scorename: %s\n", scorename));

	IUtility->Strlcpy(uadename, UADE_CONFIG_UADE_CORE, sizeof(uadename));
	dbug(("uadename: %s\n", uadename));

	uadecore_stdout_name = "NIL:";
	uade_spawn(&state, uadename, configname);

	IUtility->Strlcpy(modulename, handle->filename, sizeof(modulename));
	dbug(("modulename: %s\n", modulename));
	if (!uade_is_our_file(modulename, 0, &state)) {
		goto error;
	}
	if (!strcmp(state.ep->playername, "custom")) {
		IUtility->Strlcpy(playername, modulename, sizeof(playername));
		modulename[0] = 0;
	} else {
		IUtility->Strlcpy(playername, state.config.basedir.name, sizeof(playername));
		IDOS->AddPart(playername, "players", sizeof(playername));
		IDOS->AddPart(playername, state.ep->playername, sizeof(playername));
	}
	if (!playername[0]) {
		goto error;
	}
	dbug(("playername: %s\n", playername));
	IUtility->Strlcpy(songname, modulename[0] ? modulename : playername, sizeof(songname));
	dbug(("songname: %s\n", songname));
	if (!uade_alloc_song(&state, songname)) {
		goto error;
	}

	if (state.ep) uade_set_ep_attributes(&state);

	uade_set_effects(&state);

	playerfile = fopen(playername, "r");
	if (!playerfile) {
		uade_unalloc_song(&state);
		goto error;
	}
	fclose(playerfile);

	ret = uade_song_initialization(scorename, playername, modulename, &state);
	if (ret) {
		switch (ret) {
			case UADECORE_INIT_ERROR:
				dbug(("Player: Uadecore init error.\n"));
				break;
			case UADECORE_CANT_PLAY:
				dbug(("Player: Uadecore refuses to play the song.\n"));
				break;
			default:
				dbug(("Player: Unknown error from uade_song_initialization().\n"));
				break;
		}
		uade_unalloc_song(&state);
		goto error;
	}

	handle->data_sig = 1 << IExec->AllocSignal(-1);
	handle->end_sig = 1 << IExec->AllocSignal(-1);
	dbug(("Player: Replying to Start Message.\n"));
	IExec->ReplyMsg((struct Message *)start_msg);

	dbug(("Player: Going into play_loop.\n"));
	play_loop(handle, &state);
	dbug(("Player: Returned from play_loop.\n"));

	dbug(("Player: uade_unalloc_song.\n"));
	uade_unalloc_song(&state);
	if ((int)state.ipc.input != -1) close((int)state.ipc.input);
	if ((int)state.ipc.output != -1) close((int)state.ipc.output);
	state.ipc.input = state.ipc.output = (void *)-1;

	if (!player_is_done) dbug(("Player: Waiting for exit signal.\n"));
	while (!player_is_done) play_audio(handle, NULL, 0, 0);
	dbug(("Player: Exit signal received.\n"));

	rc = RETURN_OK;

error:
	dbug(("Player: Leaving.\n"));
	if ((int)state.ipc.input != -1) close((int)state.ipc.input);
	if ((int)state.ipc.output != -1) close((int)state.ipc.output);
	CloseLibs();
	return rc;
}

static APTR OpenInterface (CONST_STRPTR lib_name, int32 lib_vers,
	CONST_STRPTR int_name, int32 int_vers)
{
	struct Library *library;
	struct Interface *interface;
	library = IExec->OpenLibrary(lib_name, lib_vers);
	if (library) {
		interface = IExec->GetInterface(library, int_name, int_vers, NULL);
		if (interface) {
			return interface;
		}
		IExec->CloseLibrary(library);
	}
	return NULL;
}

static void CloseInterface (APTR interface) {
	if (interface) {
		struct Library *library = ((struct Interface *)interface)->Data.LibBase;
		IExec->DropInterface(interface);
		IExec->CloseLibrary(library);
	}
}

static BOOL OpenLibs (void) {
	if ((IDOS = OpenInterface("dos.library", 52, "main", 1)) &&
		(IUtility = OpenInterface("utility.library", 52, "main", 1)) &&
		(INewlib = OpenInterface("newlib.library", 52, "main", 1)))
	{
		return TRUE;
	}
	CloseLibs();
	return FALSE;
}

static void CloseLibs (void) {
	CloseInterface(INewlib);
	CloseInterface(IUtility);
	CloseInterface(IDOS);
}

static void play_loop (struct TNDecHandle *handle, struct uade_state *state) {
	int uade_song_end_trigger = 0;

	int i;
	uint32_t *u32ptr;

	uint8_t space[UADE_MAX_MESSAGE_SIZE];
	struct uade_msg *um = (struct uade_msg *) space;

	uint8_t sampledata[UADE_MAX_MESSAGE_SIZE];
	int left = 0;
	int what_was_left = 0;

	int subsong_end = 0;
	int next_song = 0;
	int ret;
	int tailbytes = 0;
	int playbytes;
	char *reason;

	int64_t subsong_bytes = 0;

	const int framesize = UADE_BYTES_PER_SAMPLE * UADE_CHANNELS;
	const int bytes_per_second = UADE_BYTES_PER_FRAME * state->config.frequency;

	enum uade_control_state controlstate = UADE_S_STATE;

	struct uade_ipc *ipc = &state->ipc;
	struct uade_song *us = state->song;
	struct uade_effect *ue = &state->effects;
	struct uade_config *uc = &state->config;

	dbug(("UADE_BYTES_PER_SAMPLE: %ld\n", UADE_BYTES_PER_SAMPLE));
	dbug(("UADE_CHANNELS: %ld\n", UADE_CHANNELS));
	dbug(("UADE_BYTES_PER_FRAME: %ld\n", UADE_BYTES_PER_FRAME));
	dbug(("frequency: %ld\n", state->config.frequency));

	uade_effect_reset_internals();

	while (next_song == 0) {
		if (controlstate == UADE_S_STATE) {
			/* send state */
			if (subsong_end && uade_song_end_trigger == 0) {
				if (uc->one_subsong == 0 && us->cur_subsong != -1 && us->max_subsong != -1) {
					us->cur_subsong++;
					if (us->cur_subsong > us->max_subsong) {
						uade_song_end_trigger = 1;
					} else {
						subsong_end = 0;
						subsong_bytes = 0;

						uade_change_subsong(state);

						dbug(("Changing to subsong %ld from range [%ld, %ld]\n",
							us->cur_subsong, us->min_subsong, us->max_subsong));
					}
				} else {
					uade_song_end_trigger = 1;
				}
			}
			if (uade_song_end_trigger) {
				next_song = 1;
				if (uade_send_short_message(UADE_COMMAND_REBOOT, ipc)) {
					dbug(("Player: Can not send reboot.\n"));
					return;
				}
			} else {
				left = uade_read_request(ipc);
			}
			if (uade_send_short_message(UADE_COMMAND_TOKEN, ipc)) {
				dbug(("Player: Can not send token.\n"));
				return;
			}
			controlstate = UADE_R_STATE;
			if (what_was_left) {
				if (subsong_end) {
					playbytes = tailbytes;
					tailbytes = 0;
				} else {
					playbytes = what_was_left;
				}
				us->out_bytes += playbytes;
				subsong_bytes += playbytes;
				uade_effect_run(ue, (int16_t *)sampledata, playbytes / framesize);
				play_audio(handle, (int16_t *)sampledata, playbytes, playbytes / framesize);
				if (player_is_done) {
					goto out;
				}
				if (uc->timeout != -1 && uc->use_timeouts) {
					if (uade_song_end_trigger == 0) {
						if (us->out_bytes / bytes_per_second >= uc->timeout) {
							uade_song_end_trigger = 1;
						}
					}
				}
				if (uc->subsong_timeout != -1 && uc->use_timeouts) {
					if (subsong_end == 0 && uade_song_end_trigger == 0) {
						if (subsong_bytes / bytes_per_second >= uc->subsong_timeout) {
							subsong_end = 1;
						}
					}
				}
			}
		} else {
			/* receive state */
			if (uade_receive_message(um, sizeof(space), ipc) <= 0) {
				dbug(("Player: Can not receive events from UADE.\n"));
				return;
			}
			switch (um->msgtype) {
				case UADE_COMMAND_TOKEN:
					controlstate = UADE_S_STATE;
					break;
				case UADE_REPLY_DATA:
					memcpy(sampledata, um->data, um->size);
					what_was_left = left;
					left = 0;
					break;
				case UADE_REPLY_FORMATNAME:
					uade_check_fix_string(um, 128);
					dbug(("Format name: %s\n", um->data));
					break;
				case UADE_REPLY_MODULENAME:
					uade_check_fix_string(um, 128);
					dbug(("Module name: %s\n", um->data));
					break;
				case UADE_REPLY_MSG:
					uade_check_fix_string(um, 128);
					dbug(("Message: %s\n", um->data));
					break;
				case UADE_REPLY_PLAYERNAME:
					uade_check_fix_string(um, 128);
					dbug(("Player name: %s\n", um->data));
					break;
				case UADE_REPLY_SONG_END:
					if (um->size < 9) {
						dbug(("Player: Invalid song end reply.\n"));
						return;
					}
					tailbytes = ntohl(((uint32_t *)um->data)[0]);
					if (((uint32_t *)um->data)[1] == 0) {
						subsong_end = 1;
					} else {
						uade_song_end_trigger = 1;
					}
					i = 0;
					reason = (char *)&um->data[8];
					while (reason[i] && i < (um->size - 8)) i++;
					if (reason[i] != 0 || (i != (um->size - 9))) {
						dbug(("Player: Broken reason string with song end notice.\n"));
						return;
					}
					dbug(("Player: Song end (%s).\n", reason));
					break;
				case UADE_REPLY_SUBSONG_INFO:
					if (um->size != 12) {
						dbug(("Player: Invalid subsong message.\n"));
						return;
					}
					u32ptr = (uint32_t *)um->data;
					us->min_subsong = ntohl(u32ptr[0]);
					us->max_subsong = ntohl(u32ptr[1]);
					us->cur_subsong = ntohl(u32ptr[2]);
					if (!(-1 <= us->min_subsong &&
						us->min_subsong <= us->cur_subsong &&
						us->cur_subsong <= us->max_subsong))
					{
						int tempmin = us->min_subsong, tempmax = us->max_subsong;
						dbug(("Player: The player is broken. Subsong info does not match.\n"));
						us->min_subsong = tempmin <= tempmax ? tempmin : tempmax;
						us->max_subsong = tempmax >= tempmin ? tempmax : tempmin;
						if (us->cur_subsong > us->max_subsong)
							us->max_subsong = us->cur_subsong;
						else if (us->cur_subsong < us->min_subsong)
							us->min_subsong = us->cur_subsong;
					}
					if ((us->max_subsong - us->min_subsong) != 0) {
						dbug(("There are %d subsongs in range [%d, %d].\n",
							1 + us->max_subsong - us->min_subsong,
							us->min_subsong, us->max_subsong));
					}
					break;
				default:
					dbug(("Player: Unknown message %lu\n", um->msgtype));
					return;
			}
		}
	}

out:
	do {
		ret = uade_receive_message(um, sizeof(space), ipc);
		if (ret < 0) {
			dbug(("Player: Can not receive events (TOKEN) from UADE.\n"));
			return;
		}
		if (ret == 0) {
			dbug(("Player: End of input after reboot.\n"));
			return;
		}
	} while (um->msgtype != UADE_COMMAND_TOKEN);
}

static void play_audio (struct TNDecHandle *handle, int16_t *pcm,
	uint32_t pcm_size, uint32_t num_frames)
{
	uint32_t sigs;
	dbug2(("Player: Waiting for signals: %lx\n", handle->data_sig | handle->end_sig));
	sigs = IExec->Wait(handle->data_sig | handle->end_sig);
	if (sigs & handle->end_sig) {
		dbug2(("Player: Received Exit Signal.\n"));
		player_is_done = TRUE;
	} else {
		dbug2(("Player: Sending PCM Data.\n"));
		dbug2(("PCM ptr: %08lx PCM size: %ld PCM frames: %ld\n", pcm, pcm_size, num_frames));
		if (pcm && pcm_size) memcpy(handle->pcm, pcm, pcm_size);
		handle->adata_msg->adm_NumPCMFrames = num_frames;
		IExec->PutMsg(handle->main_port, (struct Message *)handle->adata_msg);
	}
}

