/* Copyright (c) 2013 Fredrik Wikstrom. All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
**
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS'
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
** POSSIBILITY OF SUCH DAMAGE.
*/

#include "flite_device.h"

#include "flite/flite.h"
cst_voice *register_cmu_us_kal16();

static inline struct IOStdReq *GetIOMsg (struct FliteUnit *unit);

static int32 flite_write (struct FliteUnit *unit, struct IOStdReq *io);

struct Interface *INewlib;
struct Library *NewlibBase;

int32 UnitProcEntry (char *argstr, int32 arglen, struct ExecBase *sysbase) {
	struct MsgPort *proc_port;
	struct FliteMessage *fm;
	struct FliteUnit *unit;
	struct IOStdReq *io;
	int32 error;
	cst_voice *v;
	uint32 sigmask;
	int32 rc = RETURN_FAIL;

	dbug(("UnitProcEntry()\n"));

	proc_port = IDOS->GetProcMsgPort(NULL);
	IExec->WaitPort(proc_port);
	fm = (struct FliteMessage *)IExec->GetMsg(proc_port);
	unit = fm->fm_Unit;

	unit->fu_Port = IExec->AllocSysObject(ASOT_PORT, NULL);
	if (!unit->fu_Port) {
		goto out;
	}

	NewlibBase = IExec->OpenLibrary("newlib.library", 52);
	INewlib = IExec->GetInterface(NewlibBase, "main", 1, NULL);
	if (!INewlib) {
		goto out;
	}

	if (!freopen("NIL:", "w", stdout) ||
		!freopen("NIL:", "w", stderr))
	{
		goto out;
	}

	flite_init();
	v = register_cmu_us_kal16();

	unit->fu_Voice = v;
	unit->fu_Duration = 1.0; //feat_float(v->features, "duration_stretch");
	unit->fu_Pitch = 110.0; //feat_float(v->features, "int_f0_target_mean");

	dbug(("replying to start msg\n"));
	IExec->ReplyMsg(&fm->fm_Msg);

	dbug(("entering main loop\n"));
	sigmask = 1 << unit->fu_Port->mp_SigBit;
	rc = RETURN_OK;
	for (;;) {
		IExec->Wait(sigmask);

		while ((io = GetIOMsg(unit))) {
			if (&io->io_Message == &fm->fm_Msg) {
				goto out;
			}

			switch (io->io_Command) {
				case CMD_WRITE:
					dbug(("CMD_WRITE\n"));
					error = flite_write(unit, io);
					break;

				default:
					dbug(("Unsupported command: %ld\n", io->io_Command));
					error = IOERR_NOCMD;
					break;
			}

			if (io) {
				io->io_Error = error;
				IExec->ReplyMsg(&io->io_Message);
			}
		}
	}

out:
	if (INewlib) {
		IExec->DropInterface(INewlib);
		INewlib = NULL;
	}
	if (NewlibBase) {
		IExec->CloseLibrary(NewlibBase);
		NewlibBase = NULL;
	}
	IExec->FreeSysObject(ASOT_PORT, unit->fu_Port);

	return rc;
}

static inline struct IOStdReq *GetIOMsg (struct FliteUnit *unit) {
	struct IOStdReq *io;
	IExec->ObtainSemaphoreShared(unit->fu_SigSem);
	io = (struct IOStdReq *)IExec->GetMsg(unit->fu_Port);
	IExec->ReleaseSemaphore(unit->fu_SigSem);
	return io;
}

static int32 flite_write (struct FliteUnit *unit, struct IOStdReq *io) {
	int32 error = IOERR_SUCCESS;
	cst_voice *v = unit->fu_Voice;
	jmp_buf errjmp;

	if (io->io_Message.mn_Length >= sizeof(struct FliteRequest)) {
		struct FliteRequest *fr = (struct FliteRequest *)io;
		const char *outtype;
		BPTR olddir = ZERO;

		feat_set_float(v->features, "duration_stretch", fr->fr_Duration);
		feat_set_float(v->features, "int_f0_target_mean", fr->fr_Pitch);

		switch (fr->fr_Output) {
			case FLITE_OUTPUT_AHI:
				outtype = "play";
				break;
			case FLITE_OUTPUT_FILE:
				olddir = IDOS->CurrentDir(fr->fr_Lock);
				outtype = fr->fr_Filename;
				break;
			default:
				outtype = "none";
				break;
		}

		cst_errjmp = &errjmp;
		switch (fr->fr_Input) {
			case FLITE_INPUT_TEXT:
				switch (setjmp(errjmp)) {
					case 0:
						flite_text_to_speech(io->io_Data, v, outtype);
						break;
					default:
						error = FLERR_UNKNOWN;
						break;
				}
				break;
			case FLITE_INPUT_PHONES:
				switch (setjmp(errjmp)) {
					case 0:
						flite_phones_to_speech(io->io_Data, v, outtype);
						break;
					default:
						error = FLERR_UNKNOWN;
						break;
				}
				break;
		}
		cst_errjmp = NULL;

		if (fr->fr_Output == FLITE_OUTPUT_FILE) {
			IDOS->CurrentDir(olddir);
		}
	} else {
		cst_errjmp = &errjmp;
		switch (setjmp(errjmp)) {
			case 0:
				flite_text_to_speech(io->io_Data, v, "play");
				break;
			default:
				error = FLERR_UNKNOWN;
				break;
		}
		cst_errjmp = NULL;
	}
	io->io_Actual = (error == IOERR_SUCCESS) ? io->io_Length : 0;

	return error;
}
