#include "uade_player.h"
#include <string.h>
#include <stdio.h>
#include <netinet/in.h>
#include "uadeconfig.h"
#include "uadeconf.h"
#include "uadecontrol.h"
#include "uadeconstants.h"
#include "songdb.h"

extern const char *uadecore_stdout_name;

BOOL InitPlayer (struct PlayerState *ps) {
	BOOL res = FALSE;
	struct TNDecHandle *handle = ps->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;
	static uint8_t space[UADE_MAX_MESSAGE_SIZE];
	static uint8_t sampledata[UADE_MAX_MESSAGE_SIZE];

	IUtility->ClearMem(ps, sizeof(struct PlayerState));
	ps->handle = handle;
	ps->state = &state;

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

	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;
	}

	ps->space = space;
	ps->sampledata = sampledata;
	ps->controlstate = UADE_S_STATE;

	handle->frequency = state.config.frequency;
	handle->cur_subsong = 1;
	handle->num_subsongs = 1;
	handle->jump_subsong = -1;

	uade_effect_reset_internals();

	res = TRUE;

error:
	if (!res) {
		if ((int)state.ipc.input != -1) close((int)state.ipc.input);
		if ((int)state.ipc.output != -1) close((int)state.ipc.output);
	}

	return res;
}

void ExitPlayer (struct PlayerState *ps) {
	struct uade_state *state = ps->state;
	uade_unalloc_song(state);
	close((int)state->ipc.input);
	close((int)state->ipc.output);
}

BOOL PlayerFunc (struct PlayerState *ps) {
	struct TNDecHandle *handle = ps->handle;
	struct uade_state *state = ps->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;
	uint8_t *sampledata = ps->sampledata;
	struct uade_msg *um = (struct uade_msg *)ps->space;
	const int framesize = UADE_BYTES_PER_SAMPLE * UADE_CHANNELS;
	const int bytes_per_second = UADE_BYTES_PER_FRAME * state->config.frequency;
	char *reason;
	uint32_t *u32ptr;
	int i;
	while (!ps->next_song) {
		if (ps->controlstate == UADE_S_STATE) {
			if (ps->playbytes != 0) {
				return TRUE;
			}
			if (ps->subsong_end && ps->song_end_trigger == 0) {
				if (ps->jump_sub) {
					ps->jump_sub = 0;
					us->cur_subsong++;
					if (us->cur_subsong > us->max_subsong) {
						ps->song_end_trigger = 1;
					} else {
						ps->subsong_bytes = 0;
						ps->subsong_end = 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 {
					ps->song_end_trigger = 1;
				}
			}
			if (ps->song_end_trigger) {
				ps->next_song = 1;
			} else {
				ps->left = uade_read_request(ipc);
				if (uade_send_short_message(UADE_COMMAND_TOKEN, ipc)) {
					dbug(("Player: Can not send token.\n"));
					return FALSE;
				}
				ps->controlstate = UADE_R_STATE;
			}
			if (ps->what_was_left) {
				if (ps->subsong_end) {
					ps->playbytes = ps->tailbytes;
					ps->tailbytes = 0;
				} else {
					ps->playbytes = ps->what_was_left;
				}
				us->out_bytes += ps->playbytes;
				ps->subsong_bytes += ps->playbytes;
				uade_effect_run(ue, (int16_t *)sampledata, ps->playbytes / framesize);
				memcpy(handle->pcm, sampledata, ps->playbytes);
				if (uc->timeout != -1 && uc->use_timeouts) {
					if (ps->song_end_trigger == 0) {
						if (us->out_bytes / bytes_per_second >= uc->timeout) {
							ps->song_end_trigger = 1;
						}
					}
				}
				if (uc->subsong_timeout != -1 && uc->use_timeouts) {
					if (ps->subsong_end == 0 && ps->song_end_trigger == 0) {
						if (ps->subsong_bytes / bytes_per_second >= uc->subsong_timeout) {
							ps->subsong_end = 1;
						}
					}
				}
			}
		} else {
			/* receive state */
			if (uade_receive_message(um, UADE_MAX_MESSAGE_SIZE, ipc) <= 0) {
				dbug(("Player: Can not receive events from UADE.\n"));
				return FALSE;
			}
			switch (um->msgtype) {
				case UADE_COMMAND_TOKEN:
					ps->controlstate = UADE_S_STATE;
					break;
				case UADE_REPLY_DATA:
					memcpy(sampledata, um->data, um->size);
					ps->what_was_left = ps->left;
					ps->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 FALSE;
					}
					ps->tailbytes = ntohl(((uint32_t *)um->data)[0]);
					if (((uint32_t *)um->data)[1] == 0) {
						ps->subsong_end = 1;
					} else {
						ps->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 FALSE;
					}
					dbug(("Player: Song end (%s).\n", reason));
					break;
				case UADE_REPLY_SUBSONG_INFO:
					if (um->size != 12) {
						dbug(("Player: Invalid subsong message.\n"));
						return FALSE;
					}
					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));
					}
					handle->num_subsongs = 1 + us->max_subsong - us->min_subsong;
					break;
				default:
					dbug(("Player: Unknown message %lu\n", um->msgtype));
					return FALSE;
			}
		}
	}
	return TRUE;
}

