/*
 * ttymenu.c
 * 
 * Incorporates the browser, for rummaging around on disks, and the usual Emacs
 * editing command menu
 * 
 * Copyright (c) 1986, Mike Meyer Mic Kaczmarczik did a few things along the
 * way.
 * 
 * Permission is hereby granted to distribute this program, so long as this
 * source file is distributed with it, and this copyright notice is not
 * removed from the file.
 * 
 */

#include "do_menu.h"
#ifdef	DO_MENU

#include <exec/types.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <intuition/intuition.h>
#ifdef LATTICE
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/exec.h>
#else
#include <functions.h>
#endif

#undef	MANX
#undef LATTICE
#undef	AZTEC
#include "compiler.h"
#include "no_macro.h"
#include "no_dired.h"
#include "gosmacs.h"
#include "browser.h"
#include "change_font.h"
#include "menu.h"

#undef	TRUE
#undef	FALSE
#include "def.h"

#ifdef	BROWSER
#include "buffer.h"
#endif

#include "window.h"

#ifdef BROWSER
static int Add_Dir PROTO((char *dir, char *name));
static void Add_Devices PROTO((ULONG devtype));
#endif

extern struct Menu *AutoMenu;
extern struct Window *EmW;

#define MNUM(menu,item,sub) (SHIFTMENU(menu)|SHIFTITEM(item)|SHIFTSUB(sub))

#ifdef	BROWSER
#define LONGEST_NAME	80	/* Longest file name we can deal with	 */

# ifdef	ANSI
#include <string.h>
#include <stdlib.h>
# else
char           *index();	/* find first instance of c in s	 */
#define	strchr(s, c) index(s, c)
# endif

# ifdef	MENU
#define	FIRSTMENU	1
# else
#define	FIRSTMENU	0
# endif

#endif

#ifdef	MENU
/*
 * When ttgetc() sees a menu selection event, it stuffs the sequence KMENU
 * <menu><item><subitem> into the input buffer
 * 
 * The menu item names are chosen to be relatively close to the extended
 * function names, so a user can usually figure out the key binding of a menu
 * item by searching through the "display-bindings" buffer for something
 * that's close.
 */

/*
 * Commands for managing files and buffers
 */

extern int      filevisit();
extern int      poptofile();
extern int      fileinsert();
extern int      filesave();
extern int      filewrite();
#ifndef	NO_DIRED
extern int      dired();
#endif
extern int      usebuffer();
extern int      poptobuffer();
extern int      killbuffer();
extern int      listbuffers();
extern int      savebuffers();
extern int      quit();

static struct MenuBinding FileItems[] = {
	{"Find File         C-x C-f", filevisit},
	{"Pop To File       C-x 4 f", poptofile},
	{"Insert File       C-x i", fileinsert},
	{"Save File         C-x C-s", filesave},
	{"Write File        C-x C-w", filewrite},
#ifndef	NO_DIRED
	{"Dired	     C-x d", dired},
#endif
	{"Switch To Buffer  C-x b", usebuffer},
	{"Pop To Buffer     C-x 4 b", poptobuffer},
	{"Kill Buffer       C-x k", killbuffer},
	{"List Buffers      C-x C-b", listbuffers},
	{"Save Buffers      C-x s", savebuffers},
	{"Save And Exit     C-x C-c", quit}
};

/*
 * Commands for various editing functions
 */

extern int      yank();
extern int      openline();
extern int      killline();
extern int      deblank();
extern int      justone();
extern int      indent();
extern int      twiddle();
extern int      quote();

static struct MenuBinding EditItems[] = {
	{"Yank                 C-y", yank},
	{"Blank Line           C-o ", openline},
	{"Kill Line            C-k", killline},
	{"Delete Blank Lines   C-x C-o", deblank},
	{"Delete Blanks        M-SPC", justone},
	{"Newline And Indent   C-j", indent},
	{"Transpose Characters C-t", twiddle},
	{"Quoted Insert        C-q", quote}
};

/*
 * Movement commands
 */

extern int      forwpage();
extern int      backpage();
extern int      gotobol();
extern int      gotobob();
extern int      gotoeol();
extern int      gotoeob();
extern int      gotoline();
extern int      showcpos();

static struct MenuBinding MoveItems[] = {
	{"Scroll Up       C-v", forwpage},
	{"Scroll Down     M-v", backpage},
	{"Start Of Line   C-a", gotobol},
	{"Start Of Buffer M-<", gotobob},
	{"End Of Line     C-e", gotoeol},
	{"End Of Buffer   M->", gotoeob},
	{"Goto Line", gotoline},
	{"Show Cursor     C-x =", showcpos}
};

/*
 * Commands for searching and replacing
 */

extern int      forwisearch();
extern int      backisearch();
extern int      searchagain();
extern int      forwsearch();
extern int      backsearch();
extern int      queryrepl();

static struct MenuBinding SearchItems[] = {
	{"I-Search Forward  C-s", forwisearch},
	{"I-Search Backward C-r", backisearch},
	{"Search Again", searchagain},
	{"Search Forward    M-s", forwsearch},
	{"Search Backward   M-r", backsearch},
	{"Query Replace     M-%", queryrepl}
};

/*
 * Commands that manipulate words
 */
extern int      forwword();
extern int      backword();
extern int      delfword();
extern int      delbword();
extern int      capword();
extern int      lowerword();
extern int      upperword();

static struct MenuBinding WordItems[] = {
	{"Forward Word       M-f", forwword},
	{"Backward Word      M-b", backword},
	{"Kill Word          M-d", delfword},
	{"Backward Kill Word M-DEL", delbword},
	{"Capitalize Word    M-c", capword},
	{"Downcase Word      M-l", lowerword},
	{"Upcase Word        M-u", upperword}
};

/*
 * Commands relating to paragraphs
 */
extern int      gotoeop();
extern int      gotobop();
extern int      fillpara();
extern int      setfillcol();
extern int      killpara();
extern int      fillmode();

static struct MenuBinding ParaItems[] = {
	{"Forward Paragraph  M-]", gotoeop},
	{"Backward Paragraph M-[", gotobop},
	{"Fill Paragraph     M-q", fillpara},
	{"Set Fill Column    C-x f", setfillcol},
	{"Kill Paragraph", killpara},
	{"Auto Fill Mode", fillmode}
};

/*
 * Region stuff
 */
extern int      setmark();
extern int      swapmark();
extern int      killregion();
extern int      copyregion();
extern int      lowerregion();
extern int      upperregion();

static struct MenuBinding RegionItems[] = {
	{"Set Mark            C-@", setmark},
	{"Exch Point And Mark C-x C-x", swapmark},
	{"Kill Region         C-w", killregion},
	{"Copy Region As Kill M-w", copyregion},
	{"Downcase Region     C-x C-l", lowerregion},
	{"Upcase Region       C-x C-u", upperregion}
};

/*
 * Commands for manipulating windows
 */

extern int      splitwind();
extern int      delwind();
extern int      onlywind();
extern int      nextwind();
#ifdef	GOSMACS
extern int      prevwind();
#endif
extern int      enlargewind();
extern int      shrinkwind();
extern int      refresh();
extern int      reposition();
extern int      togglewindow();
#ifdef	CHANGE_FONT
extern int      setfont();
#endif

static struct MenuBinding WindowItems[] = {
	{"Split Window         C-x 2", splitwind},
	{"Delete Window        C-x 0", delwind},
	{"Delete Other Windows C-x 1", onlywind},
	{"Next Window          C-x o", nextwind},
#ifdef	GOSMACS
	{"Up Window", prevwind},
#endif
	{"Enlarge Window       C-x ^", enlargewind},
	{"Shrink Window", shrinkwind},
	{"Redraw Display", refresh},
	{"Recenter             C-l", reposition},
	{"Toggle Border", togglewindow},
#ifdef	CHANGE_FONT
	{"Set Font", setfont}
#endif
};

/*
 * Miscellaneous commands
 */

extern int      definemacro();
extern int      finishmacro();
extern int      executemacro();
extern int      extend();
extern int      bindtokey();
extern int      desckey();
extern int      wallchart();
extern int      showversion();
extern int      spawncli();

static struct MenuBinding MiscItems[] = {
#ifndef	NO_MACRO
	{"Start Kbd Macro   C-x (", definemacro},
	{"End Kbd Macro     C-x )", finishmacro},
	{"Call Kbd Macro    C-x e", executemacro},
#endif
	{"Execute Command   M-x", extend},
	{"Global Set Key", bindtokey},
	{"Describe Key      C-h c", desckey},
	{"Describe Bindings C-h b", wallchart},
	{"Emacs Version", showversion},
	{"New CLI           C-z", spawncli}
};

/*
 * The following table contains the titles, number of items, and pointers to,
 * the individual menus.
 */

static struct MenuInfo EMInfo[] = {
	{"File  ", NITEMS(FileItems), &FileItems[0]},
	{"Edit  ", NITEMS(EditItems), &EditItems[0]},
	{"Move  ", NITEMS(MoveItems), &MoveItems[0]},
	{"Search  ", NITEMS(SearchItems), &SearchItems[0]},
	{"Word  ", NITEMS(WordItems), &WordItems[0]},
	{"Paragraph  ", NITEMS(ParaItems), &ParaItems[0]},
	{"Region  ", NITEMS(RegionItems), &RegionItems[0]},
	{"Window  ", NITEMS(WindowItems), &WindowItems[0]},
	{"Miscellaneous  ", NITEMS(MiscItems), &MiscItems[0]}
};

/*
 * There are three cases to deal with; the menu alone, the Browser alone, and
 * both of them together.  We #define some things to make life a little
 * easier to deal with
 */
# ifdef	BROWSER
#  define Edit_Menu_Init() Menu_Add("Edit ", TRUE, FALSE)
#  define Edit_Menu_Add(n) Menu_Item_Add(n,(USHORT)ITEMENABLED,0L,(BYTE)0,FALSE)
#  define Edit_Item_Add(n) Menu_SubItem_Add(n,(USHORT)ITEMENABLED,0L,(BYTE)0,FALSE)
# else
#  define Edit_Menu_Init() cinf = NULL	/* harmless */
#  define Edit_Menu_Add(n) n[strlen(n)-1] = '\0'; Menu_Add(n, TRUE, FALSE)
#  define Edit_Item_Add(n) Menu_Item_Add(n,(USHORT)ITEMENABLED,0L,(BYTE)0,FALSE)
# endif				/* BROWSER */

#endif

/*
 * Initialize the Emacs menu
 */

struct Menu    *
InitEmacsMenu(EmW)
	struct Window  *EmW;
{
#ifdef	MENU
	register struct MenuInfo *cinf;
	register struct MenuBinding *lastbinding, *cb;
	struct MenuInfo *lastinfo;
#endif

	Menu_Init();		/* init the menu		 */

#ifdef	MENU
	Edit_Menu_Init();	/* Set up for editing menu	 */
	lastinfo = &EMInfo[NITEMS(EMInfo)];	/* loop sentinel	 */
	for (cinf = EMInfo; cinf < lastinfo; cinf++) {
		Edit_Menu_Add(cinf->Name);
		lastbinding = &cinf->Items[cinf->NumItems];
		for (cb = cinf->Items; cb < lastbinding; cb++)
			Edit_Item_Add(cb->Command);
	}
#endif

#ifdef	BROWSER
	Menu_Add("Disks ", TRUE, FALSE);	/* name is already there */
	Add_Devices(DLT_DEVICE);/* devices first */
	Add_Devices(DLT_VOLUME);/* mounted volume names next */
	Add_Devices(DLT_DIRECTORY);	/* assigned directories last */
#endif
	return AutoMenu;
}

/*
 * amigamenu() -- handles a menu pick.
 */

amigamenu(f, n)
{
	unsigned short  menunum, itemnum, subnum, Menu_Number;
	char           *name;
#ifdef	MENU
	register int     (*fp) PROTO((int, int));
#endif

#ifdef	BROWSER
	register unsigned short level, i, dirp;
	register char  *cp;
	int             stat;
	struct MenuItem *ItemAddress();

	/* State variables that describe the current directory */
	static char     Dir_Name[LONGEST_NAME];
	static unsigned short Menu_Level = 0;
#endif

	/* read the menu, item, and subitem codes from the input stream */
	menunum = getkey(DISSCR) - MN_OFFSET;
	itemnum = getkey(DISSCR) - MN_OFFSET;
	subnum = getkey(DISSCR) - MN_OFFSET;
	Menu_Number = (USHORT)
		(SHIFTMENU(menunum) | SHIFTITEM(itemnum) | SHIFTSUB(subnum));

#ifndef	BROWSER
# ifdef	MENU
	fp = EMInfo[menunum].Items[itemnum].Function;
	return (*(fp) (f, n));
# endif
#else				/* we're using the Browser */
# ifdef	MENU
	/* Handle commands from the Edit menu when using the Browser */
	if (0 == menunum) {
		fp = EMInfo[itemnum].Items[subnum].Function;
		return ((*fp) (f, n));
	}
# endif
	/* Here when a selection was made in a Browser menu */
	name = (char *) ((struct IntuiText *)
		     (ItemAddress(AutoMenu, (ULONG) Menu_Number)->ItemFill))
		->IText;
	level = MENUNUM(Menu_Number) - FIRSTMENU;

	/* Got what we want, so clear the menu to avoid confusing the user */
	ClearMenuStrip(EmW);

	/* set dirp to FALSE if the name is not a directory or disk */
	dirp = (strchr(name, '/') != NULL || strchr(name, ':') != NULL);

	/* First, set the directory name right */
	if (level > Menu_Level)	/* Not possible, die */
		panic("impossible menu_level in amigamenu");
	else if (level == 0)	/* picked a new disk */
		Dir_Name[0] = '\0';
	else if (level < Menu_Level) {	/* Throw away some levels */
		for (i = 1, cp = strchr(Dir_Name, ':'); i < level; i++) {
			if (cp == NULL)
				return FALSE;
			cp = strchr(cp, '/');
		}
		if (cp == NULL)
			panic("broken file name in amigamenu");
		*++cp = '\0';
	}
	/* else Menu_Level == level, chose a file a current level */

	/* Now, fix up the menu and it's state variable */
	while (Menu_Level > level) {
		Menu_Level--;
		Menu_Pop();
	}

	/*
	 * If we added a file, visit it, else add a new directory level to
	 * the menu.
	 */
	if (!dirp)
		stat = Display_File(Dir_Name, name);
	else {
		Menu_Level++;
		(void) strncat(Dir_Name, name,
			       LONGEST_NAME - strlen(Dir_Name) - 1);
		stat = Add_Dir(Dir_Name, name);
	}
	SetMenuStrip(EmW, AutoMenu);
	return stat;
#endif
}

#ifdef	BROWSER
/*
 * Display_File - Go fetch a the requested file into a window.
 */
Display_File(dir, file)
	char           *dir, *file;
{
	register struct buffer *bp, *findbuffer();
	char            File_Name[LONGEST_NAME], *fn, *adjustname();

	if (!dir || !file)
		return FALSE;
	(void) strcpy(File_Name, dir);
	(void) strncat(File_Name, file, LONGEST_NAME - strlen(File_Name) - 1);
	fn = adjustname(File_Name);
	if ((bp = findbuffer(fn)) == NULL)
		return FALSE;
	curbp = bp;
	if (showbuffer(bp, curwp, WFHARD) != TRUE)
		return FALSE;
	if (bp->b_fname[0] == 0)
		return (readin(fn));	/* Read it in.	 */
	return TRUE;
}

struct name_node {
	char           *name;
	struct name_node *next;
};
static void free_name_list PROTO((struct name_node * name_list));

static void 
free_name_list(name_list)
	register struct name_node *name_list;
{
	register struct name_node *tmp;

	while (name_list) {
		tmp = name_list;
		name_list = name_list->next;
		free(tmp->name);
		free(tmp);
	}
}

/*
 * Add_Dir - given a dir and a name, add the menu name with the files in dir
 * as entries.  Use AllocMem() in order to make sure the file info block is
 * on a longword boundary.
 */
static
Add_Dir(dir, name)
	char           *dir, *name;
{
	register char  *last_char;
	BPTR            my_lock;
	unsigned short  count;
	int             stat = FALSE;
	static char     Name_Buf[LONGEST_NAME];
	struct FileInfoBlock *File_Info;
	struct name_node *name_list, *tmp_name;
	char           *cp, **name_table;
	int             i;

	if ((File_Info = (struct FileInfoBlock *)
	     AllocMem((LONG) sizeof(struct FileInfoBlock), 0L)) == NULL)
		return (FALSE);

	/* Fix up the trailing / if it needs it */
	last_char = &dir[strlen(dir) - 1];
	if (*last_char == '/')
		*last_char = '\0';

	/* Now, start on the directory */
	if ((my_lock = Lock(dir, ACCESS_READ)) == NULL)
		goto out;

	if (!Examine(my_lock, File_Info))
		goto out;
	if (File_Info->fib_DirEntryType < 0L)
		goto out;

	if (Menu_Add(name, TRUE, TRUE) == 0)
		goto out;
	name_list = NULL;
	for (count = 0; ExNext(my_lock, File_Info)
	     || IoErr() != ERROR_NO_MORE_ENTRIES; count++) {
		(void) strcpy(Name_Buf, File_Info->fib_FileName);
		if (File_Info->fib_DirEntryType >= 0L) {
			(void) strcat(Name_Buf, "/");
		}
		if (!(cp = malloc(strlen(Name_Buf) + 1))) {
			free_name_list(name_list);
			return (FALSE);
		}
		strcpy(cp, Name_Buf);
		if (!(tmp_name = (struct name_node *)
		      malloc(sizeof(struct name_node)))) {
			free_name_list(name_list);
			return (FALSE);
		}
		tmp_name->name = cp;
		tmp_name->next = name_list;
		name_list = tmp_name;
	}
	if (count > 0) {
		if (!(name_table = (char **) malloc(count * sizeof(char *)))) {
			free_name_list(name_list);
			return (FALSE);
		}
		i = count;
		for (tmp_name = name_list; tmp_name;
		     tmp_name = tmp_name->next) {
			name_table[--i] = tmp_name->name;
		}
		tqsort(name_table, count);
		for (i = 0; i < count; i++) {
			if (Menu_Item_Add(name_table[i],
				   (USHORT) ITEMENABLED, 0L, (BYTE) 0, TRUE)
			    == MNUM(NOMENU, NOITEM, NOSUB))
				break;
		}
		free_name_list(name_list);
		free(name_table);
	} else
		Menu_Item_Add("EMPTY", (USHORT) 0, 0L, (BYTE) 0, FALSE);

	/* Put everything back */
	if (*last_char == '\0')
		*last_char = '/';
	stat = TRUE;
out:
	UnLock(my_lock);
	FreeMem(File_Info, (LONG) sizeof(struct FileInfoBlock));
	return stat;
}

/*
 * Add all the devices currently known by the system to the current menu,
 * based on the type of device list entry desired.  Disable multitasking
 * while we look inside the device list, so we don't fly off into space while
 * traversing it.
 */
struct DosLibrary *DosBase;

static          VOID
Add_Devices(devtype)
	ULONG           devtype;
{
	register struct DeviceList *devlist;
	struct RootNode *rootnode;
	struct DosInfo *dosinfo;
	UBYTE           buffer[80];
	int             ramflag = 0;
	struct name_node *name_list, *tmp_name;
	char           *cp, **name_table;
	int             count, i;

	/* if you've gotten this far, none of these will be null. */
	DosBase = (struct DosLibrary *) OpenLibrary(DOSNAME, 0L);

	Forbid();		/* let's be careful out there... */
	rootnode = (struct RootNode *) DosBase->dl_Root;
	dosinfo = (struct DosInfo *) BADDR(rootnode->rn_Info);
	devlist = (struct DeviceList *) BADDR(dosinfo->di_DevInfo);

	name_list = NULL;
	count = 0;
	while (devlist) {
		/* select by specified device type */
		if (devlist->dl_Type != devtype) {
			devlist = (struct DeviceList *) BADDR(devlist->dl_Next);
			continue;
		}
		/* convert device's name into AmigaDOS name and concat a ":" */
		btocstr((BPTR) devlist->dl_Name, buffer, sizeof(buffer));
		strcat(buffer, ":");

		/*
		 * Always add volumes and assigned directories. However,
		 * disks should be the only devices added to the list. Magic
		 * disk test courtesy of Phillip Lindsay, Commodore-Amiga
		 * Inc.
		 */
		if (devtype != DLT_DEVICE || devlist->dl_Task) {
			count++;
			if (!(cp = malloc(strlen(buffer) + 1))) {
				free_name_list(name_list);
				Permit();
				CloseLibrary((struct Library *) DosBase);
				return;
			}
			strcpy(cp, buffer);
			if (!(tmp_name = (struct name_node *)
			      malloc(sizeof(struct name_node)))) {
				free_name_list(name_list);
				Permit();
				CloseLibrary((struct Library *) DosBase);
				return;
			}
			tmp_name->name = cp;
			tmp_name->next = name_list;
			name_list = tmp_name;
			if (devtype == DLT_DEVICE &&
			    !strcmp(buffer, "RAM:"))
				ramflag = 1;
		}
		devlist = (struct DeviceList *) BADDR(devlist->dl_Next);
	}
	Permit();
	CloseLibrary((struct Library *) DosBase);
	if (count > 0 || (devtype == DLT_DEVICE && !ramflag)) {
		if (!(name_table = (char **)
		      malloc((count + 1) * sizeof(char *)))) {
			free_name_list(name_list);
			return;
		}
		name_table[count] = "RAM:";
		i = count;
		for (tmp_name = name_list; tmp_name;
		     tmp_name = tmp_name->next) {
			name_table[--i] = tmp_name->name;
		}
		if (devtype == DLT_DEVICE && !ramflag)
			count++;
		tqsort(name_table, count);
		for (i = 0; i < count; i++) {
			if (Menu_Item_Add(name_table[i],
				   (USHORT) ITEMENABLED, 0L, (BYTE) 0, TRUE)
			    == MNUM(NOMENU, NOITEM, NOSUB))
				break;
		}
		free_name_list(name_list);
		free(name_table);
	}
}

btocstr(bp, buf, bufsiz)
	BPTR            bp;
	char           *buf;
	int             bufsiz;
{
	register UBYTE *cp;
	register int    len, i;

	cp = (UBYTE *) BADDR(bp);
	len = (int) *(cp++);
	len = (len > bufsiz) ? bufsiz : len;	/* truncate if necessary */
	for (i = 0; i < len; i++)
		buf[i] = *(cp++);
	buf[i] = '\0';
	return (len < bufsiz);	/* return FALSE if truncated */
}

#ifndef LATTICE
tqsort(v, n)
	register char **v;
	int             n;
{
	register char  *temp;
	register int    gap, j, i;

	for (gap = n / 2; gap > 0; gap /= 2)
		for (i = gap; i < n; i++)
			for (j = i - gap; j >= 0; j -= gap)
				if (strcmp(v[j], v[j + gap]) > 0) {
					/* exchange them */
					temp = v[j];
					v[j] = v[j + gap];
					v[j + gap] = temp;
				}
}
#endif
#endif

#else				/* DO_MENU */
#include "nullfile.h"
#endif
