/** simpmenu.c
 *
 *   Simple Menu routines.
 *
**/
#include <exec/types.h>
#include <exec/exec.h>
#include <intuition/intuition.h>
#include <graphics/gfx.h>
#include <functions.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <simpmenu.h>
#include <simpreq.h>

static struct MenuItem *SItemY( struct Menu *menu, UBYTE *s, BYTE c, long excl, int dy );
static struct MenuItem *SSItemY( struct Menu *menu, UBYTE *s, BYTE c, long excl, int dy );
static struct Menu *LastMenu( struct Menu *menu );
static struct MenuItem *LastItem( struct MenuItem *item );
static void   FixupItemLength( struct MenuItem *item, int leftedge );
static void   FixupSubItemPos( struct MenuItem *item );
static struct MenuItem *MakeMenuItem( WORD x, WORD y, UBYTE *s, BYTE c, long excl );
static struct MenuItem *FreeItem( struct MenuItem *item );

#define MENUSIZE ((long) sizeof(struct Menu))
#define ITEMSIZE ((long) sizeof(struct XMenuItem))
#define COMMANDSIZE   45
#define LINESPACING   10
#define VSLOP          1
#define CHARWIDTH      8
#define MENUOFFSET     5
#define MENUSPACING   10
#define SUBMENUINDENT 10
	
	/**
	 *
	 *
	 *    SMenu(1.0)    ARP Programmers Manual   SMenu(1.0)
	 *
	 *
	 *    NAME
	 *   SMenu      -- Allocate and initialize a new Menu structure.
	 *
	 *    SYNOPSIS
	 *   struct Menu *menu = SMenu(menutree, menuname);
	 *
	 *    FUNCTION
	 *   This function is used to create and initialize a new Menu structure.
	 *         and link it to a previously created menu tree.
	 *
	 *    INPUTS
	 *         struct Menu *menutree Pointer to a menu tree to be extended
	 *                               or NULL to create a first Menu structure.
	 *         char *menuname        Name for new menu to be added
	 *
	 *    RESULT
	 *   Pointer to the newly created Menu structure, NULL on failure.
	 *
	 *    ADDITIONAL CONSIDERATIONS
	 *   This routine assumes the necessary libraries to be open.
	 *         Typically, this routine is called the first time with a NULL
	 *         first argument. This will create the first Menu structure.
	 *         Subsequent calls to any of the simple menu functions should all
	 *         use that first pointer as a first argument, and the menu will
	 *         be built in sequence.
	 *
	 *    BUGS
	 *   None reported sofar.
	 *
	 *    AUTHOR
	 *   W.G.J. Langeveld (WGL)
	 *
	**/
struct Menu *SMenu( struct Menu *menu, UBYTE *s)
{
	struct Menu *m, *lm = NULL;
	int    l = 0;
	ULONG *foo;
	ULONG  bar;
	
	lm = LastMenu(menu);
	
	l = MENUOFFSET;
	if (lm)
		l = strlen(lm->MenuName) * CHARWIDTH + lm->LeftEdge + MENUSPACING;
	
	m = AllocMem(MENUSIZE, MEMF_CLEAR);
	if (m == NULL)
		goto cleanup;
	
	m->LeftEdge = l;
	m->Width    = strlen(s) * CHARWIDTH + MENUSPACING;
	m->Flags    = MENUENABLED;
	bar = strlen( s ) + 5;
	foo = (ULONG *) AllocMem( bar, MEMF_CLEAR );
	*foo++ = bar;
//	m->MenuName = (BYTE *) malloc(strlen(s) + 1);
	m->MenuName = (BYTE *) foo;
	if (m->MenuName)
		strcpy(m->MenuName, s);
	else
		goto cleanup;
	
	if (lm)
		lm->NextMenu = m;
	
	return(m);
	
	cleanup:
	if (m)
		FreeSMenu(m);
	
	return(NULL);
}


/**
 *
 *
 *    SItem(1.0)    ARP Programmers Manual   SItem(1.0)
 *
 *
 *    NAME
 *   SItem      -- Allocate and initialize a new MenuItem structure.
 *
 *    SYNOPSIS
 *   struct MenuItem *item = SItem(menutree, itemname, command, exclude);
 *
 *    FUNCTION
 *   This function is used to create and initialize a new MenuItem structure
 *         and link it to a menutree.
 *
 *    INPUTS
 *         struct Menu *menutree Pointer to a menu tree to be extended
 *         char *itemname        Name of new item to be added
 *         int command           Character to be used in Right-Amiga combination.
 *         long exclude          Mutual Exclude mask (see Intuition manual);
 *                               0L means no check marks, -1L means check mark
 *                               but no mutual exclusion.
 *
 *    RESULT
 *   Pointer to the newly created item, NULL on failure.
 *
 *    ADDITIONAL CONSIDERATIONS
 *   This routine assumes the necessary libraries to be open.
 *         When called, this routine will automatically create the item as a
 *         new line in the menu below the previous item. See SItemB().
 *
 *    BUGS
 *   None reported sofar.
 *
 *    AUTHOR
 *   W.G.J. Langeveld (WGL)
 *
 *
**/
struct MenuItem *SItem( struct Menu *menu, UBYTE *s, BYTE c, long excl )
{
	return(SItemY(menu, s, c, excl, LINESPACING));
}

/**
 *
 *
 *    SItemB(1.0)    ARP Programmers Manual   SItemB(1.0)
 *
 *
 *    NAME
 *   SItemB      -- Allocate and initialize a new MenuItem structure.
 *
 *    SYNOPSIS
 *   struct MenuItem *item = SItemB(menutree,itemname,command,exclude);
 *
 *    FUNCTION
 *   This function is used to create and initialize a new MenuItem structure
 *         and link it to a menutree.
 *
 *    INPUTS
 *         struct Menu *menutree Pointer to a menu tree to be extended
 *         char *itemname        Name of new item to be added
 *         BYTE command           Character to be used in Right-Amiga combination.
 *         long exclude          Mutual Exclude mask (see Intuition manual);
 *                               0L means no check marks, -1L means check mark
 *                               but no mutual exclusion.
 *
 *    RESULT
 *   Pointer to the newly created item, NULL on failure.
 *
 *    ADDITIONAL CONSIDERATIONS
 *   This routine assumes the necessary libraries to be open.
 *         When called, this routine will automatically create the item on the
 *         same line in the menu to the right of the previous item. See SItem().
 *
 *    BUGS
 *   None reported sofar.
 *
 *    AUTHOR
 *   W.G.J. Langeveld (WGL)
 *
 *
**/
struct MenuItem *SItemB( struct Menu *menu, UBYTE *s, BYTE c, long excl )
{
	return(SItemY(menu, s, c, excl, 0));
}

/**
 *
 *
 *    SSItem(1.0)    ARP Programmers Manual   SSItem(1.0)
 *
 *
 *    NAME
 *   SSItem      -- Allocate and initialize a new MenuItem structure.
 *
 *    SYNOPSIS
 *   struct MenuItem *item = SSItem(menutree,itemname,command,exclude);
 *
 *    FUNCTION
 *   This function is used to create and initialize a new MenuItem structure
 *         and link it to a menutree.
 *
 *    INPUTS
 *         struct Menu *menutree Pointer to a menu tree to be extended
 *         UBYTE *itemname       Name of new subitem to be added
 *         BYTE command          Character to be used in Right-Amiga combination.
 *         long exclude          Mutual Exclude mask (see Intuition manual);
 *                               0L means no check marks, -1L means check mark
 *                               but no mutual exclusion.
 *
 *    RESULT
 *   Pointer to the newly created subitem, NULL on failure.
 *
 *    ADDITIONAL CONSIDERATIONS
 *   This routine assumes the necessary libraries to be open.
 *         When called, this routine will automatically create the subitem as a
 *         new line in the submenu below the previous subitem.
 *         See SSItemB().
 *
 *    BUGS
 *   None reported sofar.
 *
 *    AUTHOR
 *   W.G.J. Langeveld (WGL)
 *
 *
**/
struct MenuItem *SSItem( struct Menu *menu, UBYTE *s, BYTE c, long excl )
{
	return(SSItemY(menu, s, c, excl, LINESPACING));
}

/**
 *
 *
 *    SSItemB(1.0)    ARP Programmers Manual   SSItemB(1.0)
 *
 *
 *    NAME
 *   SSItemB  -- Allocate and initialize a new MenuItem structure.
 *
 *    SYNOPSIS
 *   struct MenuItem *item=SSItemB(menutree,itemname,command,exclude);
 *
 *    FUNCTION
 *   This function is used to create and initialize a new MenuItem structure
 *         and link it to a menutree.
 *
 *    INPUTS
 *         struct Menu *menutree Pointer to a menu tree to be extended
 *         char *itemname        Name of new subitem to be added
 *         BYTE command          Character to be used in Right-Amiga combination.
 *         long exclude          Mutual Exclude mask (see Intuition manual);
 *                               0L means no check marks, -1L means check mark
 *                               but no mutual exclusion.
 *
 *    RESULT
 *   Pointer to the newly created subitem, NULL on failure.
 *
 *    ADDITIONAL CONSIDERATIONS
 *   This routine assumes the necessary libraries to be open.
 *         When called, this routine will automatically create the subitem on the
 *         same line in the submenu to the right of the previous subitem.
 *         See SSItem().
 *
 *    BUGS
 *   None reported sofar.
 *
 *    AUTHOR
 *   W.G.J. Langeveld (WGL)
 *
 *
**/
struct MenuItem *SSItemB( struct Menu *menu, UBYTE *s, BYTE c, long excl )
{
	return(SSItemY(menu, s, c, excl, 0));
}

/**
 *
 *
 *    FreeSMenu(1.0)    ARP Programmers Manual   FreeSMenu(1.0)
 *
 *
 *    NAME
 *   FreeSMenu  -- Allocate and initialize a new MenuItem structure.
 *
 *    SYNOPSIS
 *   struct Menu *menu = FreeSMenu(menutree);
 *
 *    FUNCTION
 *   This function is used to free all resources allocated for a particular
 *         menutree.
 *
 *    INPUTS
 *         struct Menu *menutree Pointer to a menu tree to be freed
 *
 *    RESULT
 *   Guaranteed NULL.
 *
 *    ADDITIONAL CONSIDERATIONS
 *   This routine assumes the necessary libraries to be open.
 *         This routine uses recursion.
 *
 *    BUGS
 *   None reported sofar.
 *
 *    AUTHOR
 *   W.G.J. Langeveld (WGL)
 *
 *
**/
struct Menu *FreeSMenu( struct Menu *menu )
{
	ULONG *foo;
	
	if (menu == NULL)
		return(NULL);
	
	FreeSMenu(menu->NextMenu);
	
	if (menu->FirstItem)
		menu->FirstItem = FreeItem(menu->FirstItem);
	
	foo = (ULONG *) menu->MenuName;
	foo--;
	FreeMem( foo, *foo );
//	free(menu->MenuName);
	FreeMem(menu, MENUSIZE);
	
	return(NULL);
}

/**
 *
 *   Adjust the menu items and subitems.
 *
**/
void AdjustItems( struct Menu *menu )
{
	struct MenuItem *item;
	
	while (menu) 
	{
		item = menu->FirstItem;
		FixupItemLength(item, menu->LeftEdge);
		while (item) 
		{
			if (item->SubItem)
				FixupItemLength(item->SubItem,
			menu->LeftEdge + item->LeftEdge + item->Width - SUBMENUINDENT);
			item = item->NextItem;
		}
		menu = menu->NextMenu;
	}
	return;
}


/**
 *
 *   Internal routines
 *   ======== ========
 *
**/

/**
 *
 *   Internal routine: creates a new menu item with a certain line spacing.
 *
**/
static struct MenuItem *SItemY( struct Menu *menu, UBYTE *s, BYTE c, long excl, int dy )
{
	struct Menu *lm = NULL;
	struct MenuItem *i, *li = NULL;
	int itemy = 0, itemx = 0;
	
	lm = LastMenu(menu);
	if (lm == NULL)
		return(NULL);
	
	li = LastItem(lm->FirstItem);
	if (li) 
	{
		if (!dy)
			itemx = li->LeftEdge + li->Width;
		itemy = li->TopEdge + dy;
	}
	
	i = MakeMenuItem(itemx, itemy, s, c, excl);
	
	if (li)
		li->NextItem  = i;
	else
		lm->FirstItem = i;
	
	return(i);
	
	cleanup:
	if (i)
		FreeItem(i);
	
	return(NULL);
}

/**
 *
 *   Internal routine: creates a new subitem with a certain line spacing.
 *
**/
static struct MenuItem *SSItemY( struct Menu *menu, UBYTE *s, BYTE c, long excl, int dy )
{
	struct Menu *lm = NULL;
	struct MenuItem *i, *li = NULL, *lsi = NULL;
	int sitemy = 0, sitemx = 0;
	
	lm = LastMenu(menu);
	if (lm == NULL)
		return(NULL);
	
	li = LastItem(lm->FirstItem);
	if (li == NULL)
		return(NULL);
	
	sitemx = li->LeftEdge + li->Width - SUBMENUINDENT;
	
	lsi = LastItem(li->SubItem);
	if (lsi) 
	{
		if (!dy)
			sitemx = lsi->LeftEdge + lsi->Width;
		sitemy = lsi->TopEdge + dy;
	}
	
	i = MakeMenuItem(sitemx, sitemy, s, c, excl);
	
	if (lsi)
		lsi->NextItem = i;
	else
		li->SubItem   = i;
	
	return(i);
	
	cleanup:
	if (i)
		FreeItem(i);
	
	return(NULL);
}

/**
 *
 *   Internal routine: returns the last non-NULL menu pointer in a list of menus.
 *
**/
static struct Menu *LastMenu( struct Menu *menu )
{
	struct Menu *lm = menu;
	
	while (menu) 
	{
		menu = lm->NextMenu;
		if (menu)
			lm = menu;
	}
	return(lm);
}

/**
 *
 *   Internal routine: returns the last non-NULL item in a list of items.
 *
**/
static struct MenuItem *LastItem( struct MenuItem *item )
{
	struct MenuItem *li = item;
	
	while (item) 
	{
		item = item->NextItem;
		if (item)
			li = item;
	}
	return(li);
}

/**
 *
 *   Internal routine: computes the longest menu item and makes all others
 *   have the same length hitbox.
 *
**/
static void FixupItemLength( struct MenuItem *item, int leftedge )
{
	int l = 0, m = 0, commset = 0, le = 0;
	struct MenuItem *fi = item;
	
	while (item) 
	{
		m = strlen( ((struct IntuiText *) item->ItemFill)->IText ) * CHARWIDTH;
		if (item->Command) 
		{
			commset = 1;
			if (item->Command == -1)
				m -= COMMANDSIZE;
		}
		if (m > l)
			l = m;
		
		item = item->NextItem;
	}
	
	if (commset)
		l += COMMANDSIZE;
	else
		l += CHARWIDTH;
	/*
	 *   Fix up menus that fall off the edge
	 */
	if (l + leftedge > 630)
		le = 630 - l - leftedge;
	
	item = fi;
	while (item) 
	{
		item->Width     = l;
		item->LeftEdge += le;
		if (item->SubItem)
			FixupSubItemPos(item);
		item = item->NextItem;
	}
	return;
}

/**
 *
 *   Internal routine: Goes through a list of items and aligns all subitem
 *   hitboxes with the right edge.
 *
**/
static void FixupSubItemPos( struct MenuItem *item )
{
	int l = 0, m = 0;
	struct MenuItem *i = NULL;
	
	if (item == NULL)
		return;
	
	if (i = item->SubItem)
		m = 5000;
	while (i) 
	{
		if (i->LeftEdge < m)
			m = i->LeftEdge;
		i = i->NextItem;
	}
	
	l = item->LeftEdge + item->Width - SUBMENUINDENT - m;
	
	i = item->SubItem;
	while (i) 
	{
		i->LeftEdge += l;
		i = i->NextItem;
	}
	return;
}

/**
 *
 *   Internal routine: Allocate a MenuItem plus room for a UserData pointer
 *   and initialize it.
 *
**/
static struct MenuItem *MakeMenuItem( WORD x, WORD y, UBYTE *s, BYTE c, long excl )
{
	struct MenuItem *i = NULL;
	struct IntuiText *MakeSIText();
	
	i = AllocMem(ITEMSIZE, MEMF_CLEAR);
	if (i == NULL)
		return(NULL);
	
	
	i->Flags = ITEMTEXT + ITEMENABLED + HIGHCOMP;
	if (c && (c != -1))
		i->Flags |= COMMSEQ;
	if (excl)
		i->Flags |= CHECKIT;
	if (excl == -1L)
		i->Flags |= MENUTOGGLE;
	
	i->LeftEdge = x;
	i->TopEdge  = y;
	i->Height   = LINESPACING;
	
	i->Width = strlen(s) * CHARWIDTH;
	if (c && (c != -1))
		i->Width += COMMANDSIZE;
	
	if (excl && excl != -1L)
		i->MutualExclude = excl;
	i->Command  = c;
	i->ItemFill = (APTR) MakeSIText(0, 1, (int) JAM1, 0, VSLOP, s);
	
	return(i);
}

/**
 *
 *   Internal routine: free all resources associated with an item list.
 *
**/
static struct MenuItem *FreeItem( struct MenuItem *item )
{
	if (item == NULL)
		return(NULL);
	
	FreeItem(item->NextItem);
	
	if (item->SubItem)
		item->SubItem = FreeItem(item->SubItem);
	
	FreeSIText(item->ItemFill);
	FreeMem(item, ITEMSIZE);
	
	return(NULL);
}

