#include <exec/types.h>
#include <exec/exec.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include "spawn.h"
#define fh_Interact fh_Port
#define fh_Process  fh_Type
#define ACTION_CLOSE 1007
#define ACTION_ALOHA 543210
#ifdef DEBUG
#undef STATIC
#define STATIC
#endif

/* Exec routines */
VOID Forbid();
struct Message *GetMsg(struct MsgPort *);
VOID PutMsg(struct MsgPort *, struct Message *);
struct Message *WaitPort(struct MsgPort *);
LONG Wait(LONG);
struct Task *FindTask(char *);
int AllocSignal(LONG);
VOID FreeSignal(int);
LONG SetSignal(LONG, LONG);
VOID Signal(struct Task *, LONG);

/* Exec support */
VOID NewList(struct List *);
struct MsgPort *CreatePort(char *, int);
VOID DeletePort(struct MsgPort *);

/* AmigaDOS routines */
BPTR Open(char *, LONG);
VOID Close(BPTR);
BPTR Input();
BPTR Output();
BOOL WaitForChar(BPTR, LONG);
VOID Delay(LONG);
BOOL Execute(char *, BPTR, BPTR);
struct MsgPort *CreateProc(char *, LONG, BPTR, LONG);
BPTR CurrentDir(BPTR);
BPTR DupLock(BPTR);
VOID UnLock(BPTR);

extern BPTR stdin, stdout, stderr;

char *mempos(s, n, c)
register char *s;
register int n;
register char c;
{
	while (--n >= 0)
	{
		if (*s == c) return(s);
		++s;
	}
	return(NULL);
}

/* free BCPL allocated storage */
STATIC VOID FreeBCPL(bptr)
BPTR bptr;
{
	LONG *lptr;

	if (!bptr) return;
	lptr = (LONG *)BADDR((bptr - 1));
	FreeMem(lptr, *lptr);
}

/* cleanup after spawn */
STATIC VOID CleanupSpawn(si)
register struct SPAWNINFO *si;
{
	FreeBCPL(si->fh1.fh_Buf);
#if 0
	FreeBCPL(si->fh2.fh_Buf);
#endif
	FreeSignal((LONG)si->port.mp_SigBit);
	FreeMem(si, (LONG)sizeof(*si));
}

/*
 * Return a packet
 */
STATIC VOID ReturnPkt(pkt, Res1, Res2)
register struct DosPacket *pkt;
LONG Res1, Res2;
{
	pkt->dp_Res1 = Res1;
	pkt->dp_Res2 = Res2;
	PutMsg(pkt->dp_Port, pkt->dp_Link);
}

/*
 * Abort a packet
 */
VOID AbortPkt(pkt, place)
register struct DosPacket *pkt;
char *place;
{
	printf("%s: Unexpected packet type %ld\n", place, pkt->dp_Type);
	ReturnPkt(pkt, 0L, ERROR_ACTION_NOT_KNOWN);
}

/*
 * CLI launching process
 */
STATIC VOID LaunchTask()
{
	register struct Process *proc, *parent;
	register struct SPAWNINFO *si;
	LONG retcode;
	APTR savedtask;
	BPTR saveddir;

	/* locate our task */
	proc = (struct Process *)FindTask((char *)NULL);

	/* get the startup message */
	(VOID)WaitPort(&proc->pr_MsgPort);
	si = (struct SPAWNINFO *)GetMsg(&proc->pr_MsgPort);

	/* make ourselves look like the parent CLI */
	parent = (struct Process *)si->port.mp_SigTask;
	saveddir = proc->pr_CurrentDir;
	proc->pr_CurrentDir = parent->pr_CurrentDir;
	savedtask = proc->pr_FileSystemTask;
	proc->pr_FileSystemTask = parent->pr_FileSystemTask;
	proc->pr_CLI = parent->pr_CLI;

	/* start up a CLI, wait for it to finish */
#if 0
	retcode = Execute("", ((LONG)&si->fh1) >> 2, ((LONG)&si->fh2) >> 2);
#else
	retcode = Execute("", ((LONG)&si->fh1) >> 2, NULL);
#endif

	/* restore old values */
	proc->pr_CurrentDir = saveddir;
	proc->pr_FileSystemTask = savedtask;
	proc->pr_CLI = NULL;

	/* Forbid so we can fade into sunset unmolested */
	Forbid();

	/* notify parent task */
	ReturnPkt(&si->dp, retcode, 0L);
}

#define BREAKSIGS (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D)

/*
 * Handle signals
 */
STATIC VOID SignalCLI(si, sigs)
register struct SPAWNINFO *si;
register LONG sigs;
{
	sigs |= SetSignal(0L, BREAKSIGS);
	if (sigs & SIGBREAKF_CTRL_C)
	{
		si->breaksig = 'C';
		if (si->proc) Signal(&si->proc->pr_Task, BREAKSIGS);
	}
	if ((sigs & SIGBREAKF_CTRL_D) && !si->breaksig)
		si->breaksig = 'D';
}

STATIC struct DosPacket *GetPkt(si)
register struct SPAWNINFO *si;
{
	struct Message *msg;

	SignalCLI(si, 0L);
	while ((msg = GetMsg(&si->port)) == NULL)
		SignalCLI(si, Wait((1L << si->port.mp_SigBit) | BREAKSIGS) );
	return((struct DosPacket *)msg->mn_Node.ln_Name);
}

/*
 * Handle common requests, return unprocessed packet
 */
STATIC struct DosPacket *HandlePkt(si, pkt, echo)
register struct SPAWNINFO *si;
register struct DosPacket *pkt;
int echo;
{
	register struct FileHandle *fh;
	struct Process *opener;

	switch (pkt->dp_Type)
	{
	case MODE_NEWFILE:
	case MODE_OLDFILE:
		opener = (struct Process *)pkt->dp_Port->mp_SigTask;
		if (si->proc != opener)
		{	/* disallow opens from new processes */
			ReturnPkt(pkt, 0L, ERROR_OBJECT_NOT_FOUND);
			return(NULL);
		}
		fh = (struct FileHandle *)BADDR(pkt->dp_Arg1);
		fh->fh_Interact = si->fh1.fh_Interact;
		fh->fh_Process  = si->fh1.fh_Process;
		fh->fh_Func1 = si->fh1.fh_Func1;
		fh->fh_Func2 = si->fh1.fh_Func2;
		fh->fh_Func3 = si->fh1.fh_Func3;
		++si->opencount;
		ReturnPkt(pkt, -1L, 0L);
		break;
	case ACTION_CLOSE:
		--si->opencount;
		ReturnPkt(pkt, -1L, 0L);
		break;
	case ACTION_WRITE:
		if (echo)
		{
			register char *src;
			register LONG n;

			src = (char *)pkt->dp_Arg2;
			n = pkt->dp_Arg3;
			Write(stdout, src, n);
			/* Output with no command loaded is either a
			   prompt or a "command not found" message.
			   We assume it is a prompt unless it contains
			   a newline. */
			if (!si->CLI->cli_Module && mempos(src, n, '\n'))
				si->badoutput = 1;
		}
		ReturnPkt(pkt, pkt->dp_Arg3, 0L);
		break;
	case ACTION_WAIT_CHAR:
		/* behave as if char were immediately available */
		ReturnPkt(pkt, -1L, 0L);
		break;
	default:
		return(pkt);
	}
	return(NULL);
}

/*
 * Wait for first read or unusual packet
 */
STATIC struct DosPacket *WaitRead(si)
register struct SPAWNINFO *si;
{
	struct DosPacket *pkt;

	if ((pkt = si->queuedpkt) != NULL)
	{
		si->queuedpkt = NULL;
		return(pkt);
	}

	for (;;)
	{
		if ((pkt = HandlePkt(si, GetPkt(si), TRUE)) != NULL)
			return(pkt);
	}
}

/*
 * Spawn a CLI task
 */
struct SPAWNINFO *SpawnCLI()
{
	register struct SPAWNINFO *si;
	register struct FileHandle *fh;
	struct MsgPort *pid;
	int sigbit;
	register struct DosPacket *pkt;

	/* create spawninfo structure */
	si = (struct SPAWNINFO *)
	     AllocMem((LONG)sizeof(*si), MEMF_PUBLIC|MEMF_CLEAR);
	if (si == NULL) return(NULL);

	/* initialize message */
	si->msg.mn_Node.ln_Type = NT_MESSAGE;
	si->msg.mn_Node.ln_Name = (char *)&si->dp;
	si->msg.mn_Length = sizeof(*si) - sizeof(struct Message);

	/* initialize packet */
	si->dp.dp_Link = &si->msg;
	si->dp.dp_Port = &si->port;
	si->dp.dp_Type = ACTION_ALOHA;

	/* initialize segment */
	si->jmp = 0x4EF9;
	si->launcher = LaunchTask;

	/* initialize port */
	si->port.mp_Node.ln_Type = NT_MSGPORT;
	si->port.mp_Flags = PA_SIGNAL;
	si->port.mp_SigTask = FindTask((char *)NULL);
	if ((sigbit = AllocSignal(-1L)) < 0)
	{
		FreeMem(si, (LONG)sizeof(*si));
		return(NULL);
	}
	si->port.mp_SigBit = sigbit;
	NewList(&si->port.mp_MsgList);

	/* initialize file handles */
	fh = (struct FileHandle *)BADDR(stderr); /* Open("*") result */
	si->fh1.fh_Interact = fh->fh_Interact;	/*TRUE*/
	si->fh1.fh_Process  = &si->port;
	si->fh1.fh_Pos      = -1;
	si->fh1.fh_End      = -1;
	si->fh1.fh_Func1    = fh->fh_Func1;
	si->fh1.fh_Func2    = fh->fh_Func2;
	si->fh1.fh_Func3    = fh->fh_Func3;
#if 0
	si->fh2 = si->fh1;
#endif

	/* call the launcher process */
	/* from this point on there is no easy way to clean up */
	/* since the subprocess is active and will be hung */
	pid = CreateProc("Launcher", 0L, ((LONG)&si->nextseg) >> 2, 2000L);
	if (pid == NULL)
	{	CleanupSpawn(si); return(NULL); }
	PutMsg(pid, &si->msg);

#if 1
	/* wait for the first open */
	while ((pkt = GetPkt(si))->dp_Type != MODE_NEWFILE)
	{
		if (pkt->dp_Type == ACTION_ALOHA)
		{	/* premature death */
			CleanupSpawn(si);
			return(NULL);
		}
		AbortPkt(pkt, "SpawnCLI(open):");
	}

	/* get process info */
	si->proc = (struct Process *)pkt->dp_Port->mp_SigTask;

	/* handle open */
	HandlePkt(si, pkt, FALSE);
#endif

	while ((pkt = GetPkt(si))->dp_Type != ACTION_WRITE)
	{
		if (pkt->dp_Type == ACTION_ALOHA)
		{	/* premature death */
			CleanupSpawn(si);
			return(NULL);
		}
		AbortPkt(pkt, "SpawnCLI(write):");
	}

	/* get process info */
	si->proc = (struct Process *)pkt->dp_Port->mp_SigTask;
	si->CLI = (struct CommandLineInterface *)BADDR(si->proc->pr_CLI);

	/* change the prompt to an empty string */
	*(char *)BADDR(si->CLI->cli_Prompt) = 0;

	/* skip the current packet (write of the first prompt) */
	HandlePkt(si, pkt, FALSE);

	/* wait for the first command */
	si->queuedpkt = WaitRead(si);

	/* return the spawn info */
	return(si);
}

/*
 * perform a read operation, return NULL normally or unprocessed packet
 * a newline is automatically appended to the source
 */
STATIC struct DosPacket *HandleRead(si, src, srclen, addnl)
register struct SPAWNINFO *si;
register char *src;
register int srclen;
int addnl;
{
	register struct DosPacket *pkt;
	register char *dest;
	register LONG n;

	if (srclen < 0) srclen = strlen(src);

	if (srclen == 1 && *src == '\x1C')
	   {
           addnl = FALSE;
           srclen = 0;
           }

	while (srclen >= 0)
	{
		pkt = WaitRead(si);
		if (pkt->dp_Type != ACTION_READ) return(pkt);
		dest = (char *)pkt->dp_Arg2;
		for (n = 0; srclen > 0 && n < pkt->dp_Arg3; --srclen, ++n)
			*dest++ = *src++;
		if (srclen == 0 && (!addnl || n < pkt->dp_Arg3))
		{
			if (addnl)
			{
				*dest = '\n';
				++n;
			}
			srclen = -1;
		}
		ReturnPkt(pkt, n, 0);
	}
	return(NULL);
}

/*
 * execute command in spawned CLI
 */
int ExecCLI(si, cmd)
register struct SPAWNINFO *si;
char *cmd;
{
	register struct DosPacket *pkt;

	/* reset bad output indicator */
	si->badoutput = 0;

	/* send the command to the CLI */
	while ((pkt = HandleRead(si, cmd, -1, TRUE)) != NULL)
		AbortPkt(pkt, "ExecCLI(read)");

	/* wait for command to complete */
	si->queuedpkt = WaitRead(si);

	/* return -1 if command didn't load,
	          0 if the command is still executing
		  the return code otherwise */
	return((si->badoutput)	     ? -1 :
	       (si->CLI->cli_Module) ? 0
				     : (int)si->CLI->cli_ReturnCode);
}

/*
 * terminate the spawned CLI
 */
VOID KillCLI(si)
register struct SPAWNINFO *si;
{
	register struct DosPacket *pkt;

	/* send a break if a command is still in progress */
	while (si->CLI->cli_Module)
	{
		Signal(&si->proc->pr_Task, BREAKSIGS);

		/* send eof */
		if ((pkt = HandleRead(si, "", 0, FALSE)) != NULL)
			AbortPkt(pkt, "KillCLI(eof)");

		/* wait for next prompt */
		si->queuedpkt = WaitRead(si);
	}

	/* send an endcli command */
	while ((pkt = HandleRead(si, "EndCLI;\x85", -1, TRUE)) != NULL)
		AbortPkt(pkt, "KillCLI(EndCLI)");

	/* wait for ALOHA */
	for (;;)
	{
		while ((pkt = HandlePkt(si, GetPkt(si), FALSE)) == NULL)
			;

		if (pkt->dp_Type == ACTION_ALOHA)
			break;

		AbortPkt(pkt, "KillCLI(Aloha)");
	}

	/* terminate */
	CleanupSpawn(si);
}

/*
 * return current status of CLI break signals
 */
int BreakCLI(si)
struct SPAWNINFO *si;
{
	return(si->breaksig);
}
