/*
 * Name:	MicroEmacs
 *		Amiga terminal-dependent I/O (Intuition)
 *	 	Strategy and much code borrowed from the Lattice C
 *		example in the ROM Kernel manual.
 * Version:	31
 * Last edit:	21-Apr-86 ...!ihnp4!seismo!ut-sally!ut-ngp!mic
 * Created:	21-Apr-86 ...!ihnp4!seismo!ut-sally!ut-ngp!mic
 */
 
/*
 * Lots of includes.
 */

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/tasks.h>
#include <exec/ports.h>
#include <exec/io.h>
#include <devices/console.h>
#include <libraries/dos.h>
#include <graphics/clip.h>
#include <graphics/view.h>
#include <graphics/rastport.h>
#include <graphics/layers.h>
#include <graphics/text.h>
#include <intuition/intuition.h>
#undef	TRUE
#undef	FALSE
#include "def.h"	/* includes sysdef.h */

/*
 * External functions.  Declared explicitly
 * to avoid problems with different compilers.
 */

extern	char			*OpenLibrary();
extern	int			OpenConsole();
extern	struct	Window		*OpenWindow();
extern	struct	MsgPort		*CreatePort();
extern	struct	IOStdReq	*CreateStdIO();
extern	struct	Menu		*InitEmacsMenu();
extern	struct	IntuiMessage	*GetMsg();
extern	LONG			AbortIO();
extern	LONG			CloseDevice();
extern	LONG			ReplyMsg();
extern	LONG			SetMenuStrip();
extern	LONG			Wait();

/*
 * Terminal I/O global variables
 */

#define	NIBUF	128			/* Probably excessive.		*/
#define	NOBUF	512			/* Not too big for 750/730.	*/

unsigned char	obuf[NOBUF];		/* Output buffer		*/
int		nobuf;			/* # of bytes in above		*/
unsigned char	ibuf[NIBUF];		/* Input buffer			*/
int		ibufo, nibuf;		/* head, # of bytes in ibuf	*/
int		nrow;			/* Terminal size, rows.		*/
int		ncol;			/* Terminal size, columns.	*/
extern char	*version[];		/* Version information		*/
					/*   (I cheat and use it for	*/
					/*    the window title)		*/
/*
 * Intuition global variables
 */

#define WINDOWGADGETS (WINDOWSIZING | WINDOWDRAG | WINDOWDEPTH | WINDOWCLOSE)

struct NewWindow MicroEMACS = {
	0,	0,			/* start position       	*/
	640,	200,			/* width, height       		*/
	0,	1,	     		/* detail pen, block pen	*/
	MENUPICK | CLOSEWINDOW |	/* IDCMP flags			*/
	MOUSEBUTTONS | NEWSIZE,
	WINDOWGADGETS | ACTIVATE,	/* window flags			*/
	NULL,				/* pointer to first user gadget */
	NULL,				/* pointer to user checkmark	*/ 
	NULL,				/* title (filled in later)	*/
	NULL,				/* pointer to screen (none)	*/
	NULL,				/* pointer to superbitmap	*/
	360,99,639,199,			/* sizing limits min and max	*/
	WBENCHSCREEN			/* screen in which to open	*/ 
};

struct	IntuitionBase	*IntuitionBase;		/* library bases	*/
struct	GfxBase		*GfxBase;
struct	Window		*EmacsWindow;		/* Our window		*/
struct	Menu		*EmacsMenu;		/* Our menu		*/
struct	MsgPort		*consoleWritePort;	/* I/O ports 		*/
struct	MsgPort		*consoleReadPort;	
int			intuitionMsgBit,	/* Signal bits		*/
			consoleMsgBit;
struct	IOStdReq	*consoleWriteMsg;	/* I/O messages		*/
struct	IOStdReq	*consoleReadMsg;
unsigned char		letter;			/* Console input buffer	*/
USHORT			class,			/* Intuition event	*/
			code,			/*   information	*/
			qualifier;
APTR			address;
SHORT			x, y;

/*
 * Some definitions
 */

#define INTUITION_MESSAGE ((LONG) 1 << intuitionMsgBit)
#define CONSOLE_MESSAGE ((LONG) 1 << consoleMsgBit)

/*
 * Open up the virtual terminal MicroEMACS communicates with.
 * Set up the window, console, and menu strip.
 */

extern	int	Enable_Abort;		/* Do NOT allow abort!	*/

ttopen()
{
	Enable_Abort = 0;		/* Disable ^C 		*/

	GfxBase = (struct GfxBase *)
		OpenLibrary("graphics.library", (LONG) 0);
	if (GfxBase  == NULL)				/* Graphics lib	*/
		cleanup(1);

	IntuitionBase = (struct IntuitionBase *)	/* Intuition	*/
		OpenLibrary("intuition.library", (LONG) 0);
	if (IntuitionBase == NULL)
		cleanup(2);

	/* Create our window */
	MicroEMACS.Title = (UBYTE *) version[0];
	EmacsWindow = OpenWindow(&MicroEMACS);
	if (EmacsWindow == NULL)
		cleanup(2);

	/* Ports for reading and writing */
	consoleWritePort = CreatePort("Emacs.con.write",(LONG) 0);
	if (consoleWritePort == NULL)
		cleanup(5);
	consoleWriteMsg = CreateStdIO(consoleWritePort);
	if (consoleWriteMsg == NULL)
		cleanup(6);

	consoleReadPort = CreatePort("Emacs.con.read",(LONG) 0);
	if (consoleReadPort == NULL)
		cleanup(7);
	consoleReadMsg = CreateStdIO(consoleReadPort);
	if (consoleReadMsg == NULL)
		cleanup(8);

	/* attach the console device to our window */
	if (OpenConsole(consoleWriteMsg,consoleReadMsg,EmacsWindow) != 0)
		cleanup(10);

	/* attach a menu strip to the window */
	EmacsMenu = InitEmacsMenu();
	SetMenuStrip(EmacsWindow, EmacsMenu);

	/* determine signal bit #'s	*/
	intuitionMsgBit = EmacsWindow->UserPort->mp_SigBit;
	consoleMsgBit = consoleReadPort->mp_SigBit;

	/* initialize console read and input buffer */
	QueueRead(consoleReadMsg,&letter);
	nibuf = ibufo = 0;

	/* Determine initial size of virtual terminal.	*/
	ttsize(&nrow,&ncol);
	return (0);
}

/*
 * Close the virtual terminal, de-allocating
 * everything we have allocated.
 */

ttclose()
{
	ttflush();
	AbortIO(consoleReadMsg);
	CloseDevice(consoleWriteMsg);
	cleanup(0);
	Enable_Abort = 1;
}

/*
 * Write a single character to the screen.
 * Buffered for extra speed, so ttflush()
 * does all the work.
 */

ttputc(c)
unsigned char c;
{
	obuf[nobuf++] = c;
	if (nobuf >= NOBUF)
		ttflush();
}

/*
 * Flush characters from the output buffer.
 * Just blast it out with a console write call.
 */
ttflush()
{
	if (nobuf > 0) {
		ConWrite(consoleWriteMsg, obuf, nobuf);
		nobuf = 0;
	}
}

/*
 * Input buffer management.
 *
 * The input buffer is a circular queue of characters
 * that is updated and manipulated by the macros and
 * functions below.   This allows multiple Intuition
 * events (menus, console input, etc.) to be buffered
 * up until Emacs asks for them.
 */

#define CharsBuffered()  (nibuf > 0 )
#define TypeInChar(c)  if (nibuf < (NIBUF - 1))\
				ibuf[(ibufo + nibuf++) % NIBUF] = c;

/*
 * Return the next character in the input buffer.
 */
static GetInputChar()
{
	unsigned char ch;

	if (nibuf <= 0)			/* this shouldn't happen.	*/
		return 0;
	ch = ibuf[ibufo++];
	ibufo %= NIBUF;
	nibuf--;
	return (int) ch;
}

/*
 * Get a character for Emacs, without echo or
 * translation.  Basically, handle Intuition
 * events until we get one that signifies
 * a character was typed in some way.
 */

ttgetc()
{
	register struct	IntuiMessage *message;		/* IDCMP message */
	register LONG	wakeupmask;
	register int	charfound;	/* have we got a character yet?	*/


	if (CharsBuffered())		/* check input buffer		*/
		return GetInputChar();	/* return immediately have one	*/

	charfound = FALSE;
	do {
		wakeupmask = Wait(INTUITION_MESSAGE|CONSOLE_MESSAGE);

		if (wakeupmask & CONSOLE_MESSAGE) {	/* Keyboard	*/
			GetMsg(consoleReadPort);	/* free message	*/
			TypeInChar(letter);		/* do this FIRST */
			QueueRead(consoleReadMsg, &letter);
			charfound = TRUE;
		}

		if (wakeupmask & INTUITION_MESSAGE) {	/* Intuition	*/
			while(message =	GetMsg(EmacsWindow->UserPort)) {
				class =	message->Class;
				code = message->Code;
				qualifier = message->Qualifier;
				address = message->IAddress;
				x = message->MouseX;
				y = message->MouseY;

				ReplyMsg(message);
				/* Need ||= here because next event may */
				/* not result in a character... */
				charfound = charfound || HandleEvent();
			} /* while (GetMsg()) */
		} /* if Intuition event */
	} while (charfound == FALSE);

	return GetInputChar();		/* finally got a character.	*/
}

/*
 * Handle the events we handle...
 * The result indicates if we've
 * put a character in the input buffer.
 * All the event information is global,
 * because there's a lot of it...
 */

extern	int	quit();				/* Defined by "main.c"	*/

static HandleEvent()
{
	switch(class) {
	case MENUPICK:				/* fake the menu key	*/
		if (code != MENUNULL)
			return (DoMenu());
		else
			return (FALSE);		/* No character found	*/
		break;
	case MOUSEBUTTONS:			/* fake the mouse key */
		return (DoMouse());
		break;
        case CLOSEWINDOW:			/* Call quit() directly	*/
		quit(FALSE, 1, KRANDOM);	/* This loses if in a 	*/
		return (FALSE);			/*   dialogue...	*/
                break;
	}
	return(FALSE);				/* No char found	*/
}

/*
 * Handle a menu selection by hand-crafting
 * an escape sequence that looks like a function
 * key to the terminal driver.  Save the
 * menu number and item number until the
 * menu execution function can ask for it.
 */

int	LastMenuNum;		/* Menu number for KMENU `key'		*/
int	LastItemNum;		/* Menu item for KMENU `key'		*/
int	LastSubItem;		/* Subitem number (for completeness)	*/

#define	CSI	0x9b		/* Amiga command sequence introducer	*/

static DoMenu()
{
	struct	MenuItem *item, *ItemAddress();

	while (code != MENUNULL) {
		item = ItemAddress(EmacsMenu,(LONG) code);
		LastMenuNum = MENUNUM(code);	/* number of menu	*/
		LastItemNum = ITEMNUM(code);	/* item number		*/
		LastSubItem = SUBNUM(code);	/* subitem code		*/
		code = item->NextSelect;
	}
	TypeInChar(CSI);			/* fake the MENU key	*/
	TypeInChar('M');
	TypeInChar('~');
	return (TRUE);				/* found a character!	*/
}

/*
 * Return the last menu selection numbers to
 * the caller.  Used by "ttymenu.c".
 */

ttmenu(menu,item,subitem)
int *menu, *item, *subitem;
{
	*menu = LastMenuNum;
	*item = LastItemNum;
	*subitem = LastSubItem;
	LastMenuNum = (USHORT)-1;	/* Forget the values		*/
	LastItemNum = (USHORT)-1;	/* so they don't get re-used	*/
	LastSubItem = (USHORT)-1;
}


/*
 * Handle a mouse selection by inserting
 * a "MOUSE key" (teer) sequence into the
 * input buffer and saving the x, y and
 * qualifier values for later.
 */

SHORT	LastMouseX, LastMouseY;	/* Position of mouse			*/
USHORT	LastQualifier;		/* Qualifier (shift key?)		*/

static DoMouse()
{
	/* Save last mouse position */
	if (code != SELECTDOWN)
		return (FALSE);
	LastMouseX = x - EmacsWindow->BorderLeft;
	LastMouseY = y - EmacsWindow->BorderTop;
	LastQualifier = qualifier;

	TypeInChar(CSI);			/* fake the MOUSE key	*/
	TypeInChar('P');			/* P for Pointer	*/
	TypeInChar('~');
	return (TRUE);				/* found a character!	*/
}

/*
 * Return the last mouse click values to
 * the caller.   X and Y are translated
 * so that (0,0) is at the edge of the
 * top and left borders. Used by "ttymouse.c".
 */

ttmouse(x,y,qualifier)
SHORT *x, *y;
USHORT *qualifier;
{
	*x = LastMouseX;
	*y = LastMouseY;
	*qualifier = LastQualifier;
	LastMouseX = (SHORT)-1;
	LastMouseY = (SHORT)-1;
	LastQualifier = (USHORT)-1;
}

/*
 * Return the current size of the virtual
 * terminal to the caller.  Placed in
 * ttyio.c because it uses information
 * that is only available to the virtual
 * terminal handler.
 *
 * Assumes the WorkBench screen default
 * font is TOPAZ_EIGHTY (8 wide by 8 high).
 */

ttsize(rows,cols)
int *rows, *cols;
{
	*rows = (EmacsWindow->Height - 		/* have to take borders	*/
		 EmacsWindow->BorderTop -	/* into account.	*/
		 EmacsWindow->BorderBottom) / 8;
	*cols = (EmacsWindow->Width -
		 EmacsWindow->BorderLeft -
		 EmacsWindow->BorderRight) / 8;
}

/*
 * Clean up.
 * Fall through all the possible cases (0 means
 * get rid of everything and start with the case
 * that fits the error situation.
 */

extern	LONG	ClearMenuStrip();
extern	LONG	DeleteStdIO();
extern	LONG	DeletePort();
extern	LONG	CloseWindow();
extern	LONG	CloseLibrary();

static cleanup(prob)
{
	
	switch (prob) {
	case 0:
		ClearMenuStrip(EmacsWindow);
		DisposeMenus(EmacsMenu);
	case 10:
	case 8:
		DeleteStdIO(consoleReadMsg);
	case 7:
		DeletePort(consoleReadPort);
	case 6:
		DeleteStdIO(consoleWriteMsg);
	case 5:
		DeletePort(consoleWritePort);
	case 4:
		CloseWindow(EmacsWindow);
	case 2:
		if (GfxBase != NULL) CloseLibrary(GfxBase);
	case 1:
		if (IntuitionBase != NULL) CloseLibrary(IntuitionBase);
		break;
	}
        return(0);
}

