/* 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.device_rev.h"

#define LIBNAME FLITENAME

struct ExecIFace *IExec;
struct DOSIFace *IDOS;

struct Library *DOSBase;

/* Version Tag */
CONST UBYTE USED verstag[] = VERSTAG;

int32 _start (void) {
    return RETURN_FAIL;
}

/* FOR RTF_AUTOINIT:
    This routine gets called after the device has been allocated.
    The device pointer is in D0.  The AmigaDOS segment list is in a0.
    If it returns the device pointer, then the device will be linked
    into the device list.  If it returns NULL, then the device
    will be unloaded.

   IMPORTANT:
    If you don't use the "RTF_AUTOINIT" feature, there is an additional
    caveat.  If you allocate memory in your Open function, remember that
    allocating memory can cause an Expunge... including an expunge of your
    device.  This must not be fatal.  The easy solution is don't add your
    device to the list until after it is ready for action.

   This call is single-threaded by exec; please read the description for
   "dev_open" below. */

/* The ROMTAG Init Function */
struct FliteBase *libInit (struct FliteBase *libBase, APTR seglist, struct ExecIFace *iexec) {
	struct FliteUnit *unit = NULL;
	BPTR infh = ZERO, outfh = ZERO, errfh = ZERO;
	struct Process *unit_proc = NULL;

	dbug(("Init()\n"));

	libBase->fb_LibNode.lib_Node.ln_Type = NT_DEVICE;
	libBase->fb_LibNode.lib_Node.ln_Pri  = 0;
	libBase->fb_LibNode.lib_Node.ln_Name = LIBNAME;
	libBase->fb_LibNode.lib_Flags        = LIBF_SUMUSED|LIBF_CHANGED;
	libBase->fb_LibNode.lib_Version      = VERSION;
	libBase->fb_LibNode.lib_Revision     = REVISION;
	libBase->fb_LibNode.lib_IdString     = VSTRING;

	IExec = iexec;
	
	/* Save pointer to our loaded code (the SegList) */
	libBase->fb_SegList = (BPTR)seglist;

	DOSBase = IExec->OpenLibrary("dos.library", 52);
	IDOS = (struct DOSIFace *)IExec->GetInterface(DOSBase, "main", 1, NULL);
	if (!IDOS) goto out;

	libBase->fb_SigSem = IExec->AllocSysObject(ASOT_SEMAPHORE, NULL);
	if (!libBase->fb_SigSem) goto out;

	libBase->fb_Port = IExec->AllocSysObjectTags(ASOT_PORT,
		ASOPORT_Signal,	SIGB_CHILD,
		ASOPORT_Action, PA_IGNORE,
		TAG_END);
	if (!libBase->fb_Port) goto out;
	
	unit = IExec->AllocVecTags(sizeof(*unit),
		AVT_Type,           MEMF_SHARED,
		AVT_Lock,           FALSE,
		AVT_ClearWithValue, 0,
		TAG_END);
	if (!unit) goto out;

	unit->fu_LibBase = libBase;

	unit->fu_SigSem = IExec->AllocSysObject(ASOT_SEMAPHORE, NULL);
	if (!unit->fu_SigSem) goto out;

	unit->fu_FliteMsg = IExec->AllocSysObjectTags(ASOT_MESSAGE,
		ASOMSG_Name,      "Flite Startup Message",
		ASOMSG_Size,      sizeof(struct FliteMessage),
		ASOMSG_ReplyPort, libBase->fb_Port,
		TAG_END);
	if (!unit->fu_FliteMsg) goto out;

	unit->fu_DeathMsg = IExec->AllocSysObjectTags(ASOT_MESSAGE,
		ASOMSG_Name,      "Flite Death Message",
		ASOMSG_Size,      sizeof(struct DeathMessage),
		ASOMSG_ReplyPort, libBase->fb_Port,
		TAG_END);
	if (!unit->fu_DeathMsg) goto out;

	infh = IDOS->Open("NIL:", MODE_OLDFILE);
	outfh = IDOS->Open("NIL:", MODE_NEWFILE);
	errfh = IDOS->Open("NIL:", MODE_NEWFILE);
	if (infh && outfh && errfh) {
		unit_proc = IDOS->CreateNewProcTags(
			NP_Name,       "Flite Unit Process",
			NP_StackSize,  81920,
			NP_Input,      infh,
			NP_Output,     outfh,
            NP_Error,      errfh,
			NP_CloseError, TRUE,
			NP_CurrentDir, ZERO,
			NP_Entry,      UnitProcEntry,
            NP_Priority,   1,
			NP_NotifyOnDeathMessage, unit->fu_DeathMsg,
			TAG_END);
	}

	if (unit_proc) {
		struct MsgPort *replyport = libBase->fb_Port;
		struct FliteMessage *fm = unit->fu_FliteMsg;
		struct MsgPort *port;

		replyport->mp_SigTask = IExec->FindTask(NULL);
		replyport->mp_Flags = PA_SIGNAL;
		fm->fm_Unit = unit;

		port = IDOS->GetProcMsgPort(unit_proc);
		IExec->PutMsg(port, &fm->fm_Msg);
		IExec->WaitPort(replyport);

		replyport->mp_Flags = PA_IGNORE;
		replyport->mp_SigTask = NULL;

		if (IExec->GetMsg(replyport) == &fm->fm_Msg) {
			libBase->fb_Unit = unit;
			return libBase;
		}
	} else {
		IDOS->Close(infh);
		IDOS->Close(outfh);
		IDOS->Close(errfh);
	}

out:
	if (unit) {
		IExec->FreeSysObject(ASOT_MESSAGE, unit->fu_DeathMsg);
		IExec->FreeSysObject(ASOT_MESSAGE, unit->fu_FliteMsg);
		IExec->FreeSysObject(ASOT_SEMAPHORE, unit->fu_SigSem);
		IExec->FreeVec(unit);
	}
	IExec->FreeSysObject(ASOT_PORT, libBase->fb_Port);
	IExec->FreeSysObject(ASOT_SEMAPHORE, libBase->fb_SigSem);
	IExec->DropInterface((struct Interface *)IDOS);
	IExec->CloseLibrary(DOSBase);
	IExec->DeleteLibrary((struct Library *)libBase);

	return NULL;
}

/***********************************************************************/

/* Here begins the system interface commands.  When the user calls
   OpenDevice/CloseDevice/RemDevice, this eventually gets translated
   into a call to the following routines (Open/Close/Expunge).
   Exec has already put our device pointer in a6 for us.

   IMPORTANT:
     These calls are guaranteed to be single-threaded; only one task
     will execute your Open/Close/Expunge at a time.

     For Kickstart V33/34, the single-threading method involves "Forbid".
     There is a good chance this will change.  Anything inside your
     Open/Close/Expunge that causes a direct or indirect Wait() will break
     the Forbid().  If the Forbid() is broken, some other task might
     manage to enter your Open/Close/Expunge code at the same time.
     Take care!

   Since exec has turned off task switching while in these routines
   (via Forbid/Permit), we should not take too long in them. */

/***********************************************************************/

/* dev_open() sets the io_Error field on an error. If it was successful,
   we should also set up the io_Unit and ln_Type fields.
   Exec takes care of setting up io_Device. */

/* Open the library */
LONG libOpen (struct DeviceManagerInterface *Self,
	struct IORequest *io, ULONG unit_number, ULONG flags)
{
	struct FliteBase *libBase = (struct FliteBase *)Self->Data.LibBase;
	struct FliteUnit *unit;

	dbug(("Open()\n"));

	/* Subtle point: any AllocMem() call can cause a call to this device's
	   expunge vector.  If lib_OpenCnt is zero, the device might get expunged. */

	IExec->ObtainSemaphore(libBase->fb_SigSem);
	   
	libBase->fb_LibNode.lib_OpenCnt++; /* Fake an opener for duration of call */
	io->io_Error = IOERR_SUCCESS;
	
	if (unit_number == 0) {
		unit = libBase->fb_Unit;
		unit->fu_OpenCnt++;
		io->io_Unit = (struct Unit *)unit;

		if (io->io_Message.mn_Length >= sizeof(struct FliteRequest)) {
			struct FliteRequest *fr = (struct FliteRequest *)io;
			if (VERSION < fr->fr_Version || (VERSION == fr->fr_Version &&
				REVISION < fr->fr_Revision)) goto out;

			fr->fr_Input = fr->fr_Output = 0;

			fr->fr_Voice = NULL;
			fr->fr_Duration = unit->fu_Duration;
			fr->fr_Pitch = unit->fu_Pitch;
		}

		io->io_Message.mn_Node.ln_Type = NT_REPLYMSG;

		libBase->fb_LibNode.lib_Flags &= ~LIBF_DELEXP;
		IExec->ReleaseSemaphore(libBase->fb_SigSem);
		return IOERR_SUCCESS;
	}

out:
	/* IMPORTANT: Mark IORequest as "complete" */
	io->io_Message.mn_Node.ln_Type = NT_REPLYMSG;

	/* IMPORTANT: trash io_Device on open failure */
	io->io_Device = NULL;
	
	if (io->io_Error == IOERR_SUCCESS) io->io_Error = IOERR_OPENFAIL;

	libBase->fb_LibNode.lib_OpenCnt--; /* End of expunge protection */

	IExec->ReleaseSemaphore(libBase->fb_SigSem);

	return io->io_Error;
}

/***********************************************************************/

/* There are two different things that might be returned from the dev_close()
   routine.  If the device wishes to be unloaded, then dev_close() must return
   the segment list (as given to dev_init()).  Otherwise dev_close() MUST
   return NULL. */

BPTR libExpunge (struct DeviceManagerInterface *Self);

/* Close the library */
BPTR libClose (struct DeviceManagerInterface *Self, struct IORequest *io) {
	struct FliteBase *libBase = (struct FliteBase *)Self->Data.LibBase;
	struct FliteUnit *unit = (struct FliteUnit *)io->io_Unit;
	BPTR result = ZERO;

	dbug(("Close()\n"));

	IExec->ObtainSemaphore(libBase->fb_SigSem);
	
	/* IMPORTANT: make sure the IORequest is not used again
	   with a -1 in io_Device, any BeginIO() attempt will
	   immediatly halt (which is better than a subtle corruption
	   that will lead to hard-to-trace crashes!!! */
	io->io_Unit = (struct Unit *)-1;
	io->io_Device = (struct Device *)-1;

	if (unit) {
		unit->fu_OpenCnt--;
	}

	/* mark us as having one fewer openers */
	libBase->fb_LibNode.lib_OpenCnt--;

	/* see if there is anyone left with us open */
	if(libBase->fb_LibNode.lib_OpenCnt == 0)
	{
		/* see if we have a delayed expunge pending */
		if(libBase->fb_LibNode.lib_Flags & LIBF_DELEXP)
		{
			/* do the expunge */
			result = libExpunge(Self);
		}
	}

	IExec->ReleaseSemaphore(libBase->fb_SigSem);
		
	/* MUST return either zero or the SegList!!! */
	return result;
}

/***********************************************************************/

/* dev_expunge() is called by the memory allocator when the system is low on
   memory.

   There are two different things that might be returned from the dev_expunge()
   routine.  If the device is no longer open then dev_expunge() may return the
   segment list (as given to dev_init()).  Otherwise dev_expunge() may set the
   delayed expunge flag and return NULL.
 
   One other important note: because dev_expunge() is called from the memory
   allocator, it may NEVER Wait() or otherwise take long time to complete. */

/* Expunge the library */
BPTR libExpunge (struct DeviceManagerInterface *Self) {
	struct FliteBase *libBase = (struct FliteBase *)Self->Data.LibBase;
	BPTR result = ZERO;

	dbug(("Expunge()\n"));

	/* see if anyone has us open */
	if (libBase->fb_LibNode.lib_OpenCnt > 0) {
		/* it is still open.  set the delayed expunge flag */
		libBase->fb_LibNode.lib_Flags |= LIBF_DELEXP;
	} else {
		struct FliteUnit *unit = libBase->fb_Unit;
		struct MsgPort *replyport = libBase->fb_Port;
		struct FliteMessage *fm = unit->fu_FliteMsg;
		struct MsgPort *port = unit->fu_Port;

		/* go ahead and get rid of us. */
		result = libBase->fb_SegList;

		/* unlink from device list */
		IExec->Remove((struct Node *)libBase); /* Remove first (before FreeMem) */

		/* ...device specific closings here... */

		replyport->mp_SigTask = IExec->FindTask(NULL);
		replyport->mp_Flags = PA_SIGNAL;
		fm->fm_Unit = NULL;

		IExec->PutMsg(port, &fm->fm_Msg);
		IExec->WaitPort(replyport);
		IExec->GetMsg(replyport);

		replyport->mp_Flags = PA_IGNORE;
		replyport->mp_SigTask = NULL;

		IExec->FreeSysObject(ASOT_MESSAGE, unit->fu_DeathMsg);
		IExec->FreeSysObject(ASOT_MESSAGE, unit->fu_FliteMsg);
		IExec->FreeSysObject(ASOT_SEMAPHORE, unit->fu_SigSem);
		IExec->FreeSysObject(ASOT_MESSAGE, unit);

		IExec->FreeSysObject(ASOT_PORT, libBase->fb_Port);
		IExec->FreeSysObject(ASOT_SEMAPHORE, libBase->fb_SigSem);
		IExec->DropInterface((struct Interface *)IDOS);
		IExec->CloseLibrary(DOSBase);

		/* free our memory */
		IExec->DeleteLibrary((struct Library *)libBase);
	}

	return result;
}

/* ------------------- Manager Interface ------------------------ */
/* These are generic. Replace if you need more fancy stuff */
static LONG _generic_Obtain (struct DeviceManagerInterface *Self) {
    return ++Self->Data.RefCount;
}

static ULONG _generic_Release (struct DeviceManagerInterface *Self) {
    return --Self->Data.RefCount;
}

/* Manager interface vectors */
STATIC CONST APTR lib_manager_vectors[] = {
    (APTR)_generic_Obtain,
    (APTR)_generic_Release,
    NULL,
    NULL,
    (APTR)libOpen,
    (APTR)libClose,
    (APTR)libExpunge,
    NULL,
    (APTR)libBeginIO,
    (APTR)libAbortIO,
    (APTR)-1
};

/* "__library" interface tag list */
STATIC CONST struct TagItem lib_managerTags[] = {
    { MIT_Name,        (Tag)"__device"       	},
    { MIT_VectorTable, (Tag)lib_manager_vectors },
    { MIT_Version,     1                        },
    { TAG_DONE,        0                        }
};

/* ------------------- Library Interface(s) ------------------------ */

/* Uncomment this line (and see below) if your library has a 68k jump table */
/* extern APTR VecTable68K[]; */

STATIC CONST CONST_APTR libInterfaces[] = {
    lib_managerTags,
    NULL
};

STATIC CONST struct TagItem libCreateTags[] = {
    { CLT_DataSize,    sizeof(struct FliteBase) },
    { CLT_InitFunc,    (Tag)libInit },
    { CLT_Interfaces,  (Tag)libInterfaces},
    /* Uncomment the following line if you have a 68k jump table */
    /* { CLT_Vector68K, (Tag)VecTable68K }, */
    {TAG_DONE,         0 }
};


/* ------------------- ROM Tag ------------------------ */
CONST struct Resident USED lib_res = {
    RTC_MATCHWORD,
    (struct Resident *)&lib_res,
    (APTR)(&lib_res + 1),
    RTF_NATIVE|RTF_AUTOINIT, /* Add RTF_COLDSTART if you want to be resident */
    VERSION,
    NT_DEVICE, /* Make this NT_DEVICE if needed */
    0, /* PRI, usually not needed unless you're resident */
    LIBNAME,
    VSTRING,
    (APTR)libCreateTags
};
