#include <exec/types.h>
#include <exec/memory.h>
#include <exec/libraries.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <intuition/screens.h>
#include <graphics/gfx.h>
#include <graphics/text.h>
#include <graphics/gfxmacros.h>
#include <clib/macros.h>
#include <clib/exec_protos.h>
#include <clib/intuition_protos.h>
#include <clib/graphics_protos.h>

void sprintf (void *,...);

#define	DEMO		FALSE

#define	PATTERN 	TRUE
#define	WINDOW		TRUE
#define	MULTIDRAW	TRUE

#if MULTIDRAW
#define	PolyDraw	MultiDraw
#endif

#define	NUMPOINTS 5L
#define	TOTPOINTS (NUMPOINTS * 2L)
#define	SHIFTED (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT)

struct Cursor
{
    struct Window *cs_Win;	/* Coordinate window */
    struct TextFont *cs_Font;	/* Font to use */
    ULONG cs_IDCMP;		/* Previous IDCMP settings */
    ULONG cs_WinFlags;		/* Previous window flags */
    UWORD cs_Points[TOTPOINTS];	/* Cursor point array */
    UWORD cs_Pattern;		/* Pattern to use for cursor */

    /* These could easily be flags */
    SHORT cs_Mode;		/* Drawing mode */
    BOOL cs_Drag;		/* Dragging cursor? */
    BOOL cs_Going;		/* Doing cursor? */
    BOOL cs_Keyboard;		/* Done with mouse, now by key? */

    /* Text positioning information */
    WORD cs_Left, cs_Line1, cs_Line2;

    BYTE cs_Buffer[8];		/* Coordinate buffer */
    struct NewWindow cs_NW;	/* Our own NewWindow */
    struct IBox cs_Box;		/* Cursor rectangle */
};

/* dtcursor.c */
VOID MultiDraw (struct RastPort *rp, LONG num, Point *xy);
struct Cursor *AllocAnts (struct TextAttr * tattr);
VOID FreeAnts (struct Cursor * cs);
BOOL WhereIsMouse (struct IntuiMessage * msg, struct Cursor * cs);
VOID AbortCut (struct Window * win, struct Cursor * cs);
struct IBox *DoButtons (struct Window * win, struct IntuiMessage * msg, struct Cursor * cs);
struct IBox *DoMouseMove (struct Window * win, struct IntuiMessage * msg, SHORT xadj, SHORT yadj, struct Cursor * cs);
VOID AdjUpperLeft (SHORT xadj, SHORT yadj, struct Cursor * cs);
VOID SetUpperLeft (struct IntuiMessage * msg, struct Cursor * cs);
VOID AdjLowerRight (SHORT xadj, SHORT yadj, struct Cursor * cs);
VOID SetLowerRight (struct IntuiMessage * msg, struct Cursor * cs);
VOID ShowCoords (struct Window * win, struct Cursor * cs);

#define	WWIDTH	72
#define	WHEIGHT	24

static struct NewWindow Coord =
{
    0, 0, WWIDTH, WHEIGHT,	/* Window dimensions */
    0, 0,			/* No detail or block */
    CLOSEWINDOW,		/* No IDCMP port */
    BORDERLESS | NOCAREREFRESH | REPORTMOUSE,
    NULL, NULL, NULL,
    NULL,			/* Screen... */
    NULL,
    0, 0, 0, 0,
    WBENCHSCREEN		/* For now... */
};

static struct TextAttr TOPAZ80 =
{(STRPTR) "topaz.font", TOPAZ_EIGHTY, 0, 0};

VOID MultiDraw (struct RastPort *rp, LONG num, Point *xy)
{
	register Point *coord = xy;
	register LONG i, j = (num - 1);

	for (i = 0L; i < j; i++)
	{
	    Move(rp, (LONG)coord->x, (LONG)coord->y);
	    coord++;
	    Draw(rp, (LONG)coord->x, (LONG)coord->y);
	}
}

struct Cursor *AllocAnts (struct TextAttr * tattr)
{
    extern struct Library *IntuitionBase;
    struct Cursor *cs;

    /* Allocate our record */
    if (cs = (struct Cursor *) AllocVec (sizeof (struct Cursor), MEMF_CLEAR))
    {
	register SHORT i;

#if WINDOW
	/* We can only use the font if using 2.0 */
	if (IntuitionBase->lib_Version >= 36)
	{
	    struct TextAttr *attr = tattr;

	    /* Copy the NewWindow from the template */
	    cs->cs_NW = *(&Coord);

	    /* Did they provide a font? */
	    if (attr == NULL)
	    {
		/* Use topaz 8 */
		attr = &(TOPAZ80);
	    }

	    /* Open the font */
	    if (cs->cs_Font = OpenFont (attr))
	    {
		struct TextFont *f = cs->cs_Font;
		struct RastPort rp;

		InitRastPort (&rp);
		SetFont (&rp, f);

		/* Probably not the most intelligent way of getting the width,
		 * (especially when dealing with proportional fonts) ... */
		cs->cs_NW.Width = TextLength (&rp, " 999,999 ", 9) + 2;
		cs->cs_NW.Height = (f->tf_YSize * 2) + 5;

		cs->cs_Left = TextLength (&rp, " ", 1);
		cs->cs_Line1 = 2 + f->tf_Baseline;
		cs->cs_Line2 = 1 + f->tf_YSize + cs->cs_Line1;
	    }
	}
#endif

	/* Initialize the record */
#if PATTERN
	cs->cs_Pattern = 0xF0F0;
#else
	cs->cs_Pattern = 0xFFFF;
#endif
	cs->cs_Drag = FALSE;
	cs->cs_Going = FALSE;
	cs->cs_Keyboard = FALSE;
	cs->cs_Mode = 0;

	/* Clear the array */
	for (i = 0; i < TOTPOINTS; i++)
	{
	    cs->cs_Points[i] = 0;
	}

	/* Clear the box */
	cs->cs_Box.Left = 0;
	cs->cs_Box.Top = 0;
	cs->cs_Box.Width = 0;
	cs->cs_Box.Height = 0;
    }

    return (cs);
}

VOID FreeAnts (struct Cursor * cs)
{

    /* Make sure we have a record */
    if (cs)
    {
	if (cs->cs_Win)
	{
	    CloseWindow (cs->cs_Win);
	    cs->cs_Win = NULL;
	}

	/* Font open? */
	if (cs->cs_Font)
	{
	    /* Close the font */
	    CloseFont (cs->cs_Font);
	}

	/* Free the record */
	FreeVec (cs);
    }
}

VOID
ShowCoords (struct Window * win, struct Cursor * cs)
{

    if (win)
    {
	struct IBox *box = &(cs->cs_Box);
	struct Screen *scr = win->WScreen;
	struct IntuiMessage *msg;

	/* Move the little window around */
	ChangeWindowBox (win,
			 (scr->MouseX + 10), (scr->MouseY - win->Height),
			 win->Width, win->Height);

	/* Print the width & height */
	sprintf (cs->cs_Buffer, "%3d,%3d", box->Width, box->Height);
	Move (win->RPort, cs->cs_Left, cs->cs_Line2);
	Text (win->RPort, cs->cs_Buffer, 7);

	/* Clear out any outstanding messages. */
	while (msg = (struct IntuiMessage *) GetMsg (win->UserPort))
	{
	    ReplyMsg ((struct Message *) msg);
	}
    }
}

VOID AbortCut (struct Window * win, struct Cursor * cs)
{
    /* Is the cursor active? */
    if (cs->cs_Going)
    {
	struct RastPort crp = *win->RPort;

	/* No mask... */
	crp.Mask = ~0;

	SetDrMd (&crp, COMPLEMENT);
	SetDrPt (&crp, cs->cs_Pattern);
	Move (&crp, cs->cs_Points[0], cs->cs_Points[1]);
	PolyDraw (&crp, NUMPOINTS, (Point *)&(cs->cs_Points[0]));
    }

    /* Is the coordinate window open? */
    if (cs->cs_Win)
    {
	/* Close it */
	CloseWindow (cs->cs_Win);
	cs->cs_Win = NULL;
    }

    /* Clear stuff */
    cs->cs_Drag = cs->cs_Keyboard = cs->cs_Going = FALSE;
    cs->cs_Mode = 0;
}

VOID BoundCheckMouse (struct IntuiMessage * msg, struct Cursor * cs)
{
    struct Window *win = msg->IDCMPWindow;

    /* Freshen the values */
    msg->MouseX = win->MouseX;
    msg->MouseY = win->MouseY;

    /* Make sure mouse is in the window */
    {
	WORD tx = win->BorderLeft;
	WORD ty = win->BorderTop;
	WORD bx = win->Width - win->BorderRight - 1;
	WORD by = win->Height - win->BorderBottom - 1;

	/* Range check the horizontal value */
	if (msg->MouseX < tx)
	    msg->MouseX = tx;
	else if (msg->MouseX > bx)
	    msg->MouseX = bx;

	/* Range check the vertical value */
	if (msg->MouseY < ty)
	    msg->MouseY = ty;
	else if (msg->MouseY > by)
	    msg->MouseY = by;
    }
}

VOID UpdateBox (struct Cursor * cs)
{

    /* Revise the box */
    cs->cs_Box.Left = MIN (cs->cs_Points[0], cs->cs_Points[4]);
    cs->cs_Box.Top = MIN (cs->cs_Points[1], cs->cs_Points[5]);
    cs->cs_Box.Width = MAX (cs->cs_Points[0], cs->cs_Points[4]);
    cs->cs_Box.Height = MAX (cs->cs_Points[1], cs->cs_Points[5]);
    cs->cs_Box.Width = cs->cs_Box.Width - cs->cs_Box.Left + 1;
    cs->cs_Box.Height = cs->cs_Box.Height - cs->cs_Box.Top + 1;
}

struct IBox *
DoButtons (struct Window * win, struct IntuiMessage * msg, struct Cursor * cs)
{
    /* Freshen & revise the coordinates */
    BoundCheckMouse (msg, cs);

    switch (msg->Code)
    {
	case SELECTDOWN:

	    /* See if the cursor is already active */
	    if (cs->cs_Going)
	    {
		/* Turn it off */
		AbortCut (win, cs);
	    }

	    /* Set coordinates */
	    SetUpperLeft (msg, cs);
	    SetLowerRight (msg, cs);

	    /* Revise the box */
	    UpdateBox (cs);

	    /* Draw the first frame */
	    {
		struct RastPort crp = *win->RPort;

		/* No mask... */
		crp.Mask = ~0;

		SetDrMd (&crp, COMPLEMENT);
		Move (&crp, cs->cs_Points[0], cs->cs_Points[1]);
		PolyDraw (&crp, NUMPOINTS, (Point *)&(cs->cs_Points[0]));
	    }

	    /* Set some pertinant values */
	    cs->cs_Drag = TRUE;
	    cs->cs_Going = TRUE;
	    cs->cs_Keyboard = FALSE;
	    cs->cs_Mode = 1;

	    /* Do we need to do a coordinate window? */
	    if ((cs->cs_Win == NULL) && cs->cs_Font)
	    {
		struct Screen *scr = win->WScreen;
		struct IBox *box = &(cs->cs_Box);

		/* Adjust the upper-left */
		cs->cs_NW.LeftEdge = (scr->MouseX + 10);
		cs->cs_NW.TopEdge = (scr->MouseY - cs->cs_NW.Height);
		cs->cs_NW.Type = CUSTOMSCREEN;
		cs->cs_NW.Screen = scr;

		if (cs->cs_Win = OpenWindowTags (&(cs->cs_NW),
						 WA_AutoAdjust, TRUE,
						 TAG_DONE))
		{
		    /* Clear the area */
		    SetRast (cs->cs_Win->RPort, 1);
		    SetAPen (cs->cs_Win->RPort, 2);
		    RectFill (cs->cs_Win->RPort,
		     1, 1, (cs->cs_Win->Width - 2), (cs->cs_Win->Height - 2));

		    /* Make it readable */
		    SetAPen (cs->cs_Win->RPort, 1);
		    SetBPen (cs->cs_Win->RPort, 2);
		    SetDrMd (cs->cs_Win->RPort, JAM2);
		    SetFont (cs->cs_Win->RPort, cs->cs_Font);

		    /* Print the upper-left coordinates */
		    sprintf (cs->cs_Buffer, "%3d,%3d",
			     (box->Left - win->BorderLeft),
			     (box->Top - win->BorderTop));
		    Move (cs->cs_Win->RPort, cs->cs_Left, cs->cs_Line1);
		    Text (cs->cs_Win->RPort, cs->cs_Buffer, 7);
		}
	    }

	    /* Show the coordinates */
	    ShowCoords (cs->cs_Win, cs);

	    /* Save & modify the window flags */
	    cs->cs_IDCMP = win->IDCMPFlags;
	    cs->cs_WinFlags = win->Flags;
	    win->Flags |= RMBTRAP;
	    break;

	case MENUDOWN:
	    /* See if the cursor is already active */
	    if (cs->cs_Going)
	    {
		/* Turn it off */
		AbortCut (win, cs);
	    }

	    /* Restore the window flags */
	    if (!(cs->cs_WinFlags & RMBTRAP))
	    {
		win->Flags &= ~RMBTRAP;
	    }
	    break;

	case SELECTUP:
	    /* set the bounding box rectangle if we where doing bounds */
	    if (cs->cs_Mode == 1)
	    {
		/* Revise the box */
		UpdateBox (cs);
	    }

	    /* Clear some values */
	    cs->cs_Drag = FALSE;
	    cs->cs_Keyboard = TRUE;
	    cs->cs_Mode = 2;

	    /* Restore the window flags */
	    if (!(cs->cs_WinFlags & RMBTRAP))
	    {
		win->Flags &= ~RMBTRAP;
	    }

	    if (cs->cs_Win)
	    {
		CloseWindow (cs->cs_Win);
		cs->cs_Win = NULL;
	    }


	    break;
    }

    return (&(cs->cs_Box));
}

struct IBox *
DoMouseMove (struct Window * win, struct IntuiMessage * msg,
		   SHORT xadj, SHORT yadj, struct Cursor * cs)
{
#if PATTERN
    if (cs->cs_Going)
#else
    if (cs->cs_Drag)
#endif
    {
	struct RastPort crp;
	USHORT dir;

	/* Copy the RastPort */
	crp = *(win->RPort);
	crp.Mask = ~0;

	/* Check the mouse coordinates */
	BoundCheckMouse (msg, cs);

	/* Erase the prior image */
	SetDrMd (&crp, COMPLEMENT);
	SetDrPt (&crp, cs->cs_Pattern);
	Move (&crp, cs->cs_Points[0], cs->cs_Points[1]);
	PolyDraw (&crp, NUMPOINTS, (Point *)&(cs->cs_Points[0]));

#if PATTERN
	/* Get the direction that we're traveling */
	dir = ((cs->cs_Points[0] < cs->cs_Points[2]) ||
	       (cs->cs_Points[1] > cs->cs_Points[5])) ? 0 : 1;

	/* Adjust the ants according to direction */
	if (dir)
	{
	    cs->cs_Pattern = ((cs->cs_Pattern << 1) & 0xfffe) |
	      ((cs->cs_Pattern & 0x8000) >> 15);
	}
	else
	{
	    cs->cs_Pattern = ((cs->cs_Pattern >> 1) & 0x7fff) |
	      ((cs->cs_Pattern & 1) << 15);
	}
#endif

	if ((xadj && yadj) || cs->cs_Keyboard)
	{
	    if (msg->Qualifier & SHIFTED)
		AdjLowerRight (xadj, yadj, cs);
	    else
		AdjUpperLeft (xadj, yadj, cs);
	}
	else
	{
	    SetLowerRight (msg, cs);
	}

	/* Draw the new ants */
	SetDrPt (&crp, cs->cs_Pattern);
	Move (&crp, cs->cs_Points[0], cs->cs_Points[1]);
	PolyDraw (&crp, NUMPOINTS, (Point *)&(cs->cs_Points[0]));

	/* Update the box */
	if (cs->cs_Mode == 1)
	{
	    /* Revise the box */
	    UpdateBox (cs);
	}
    }

    if (cs->cs_Drag)
    {
	ShowCoords (cs->cs_Win, cs);
    }

    return (&(cs->cs_Box));
}

VOID AdjUpperLeft (SHORT xadj, SHORT yadj, struct Cursor * cs)
{

    cs->cs_Points[0] += xadj;
    cs->cs_Points[6] = cs->cs_Points[8] = cs->cs_Points[0];

    cs->cs_Points[1] += yadj;
    cs->cs_Points[3] = cs->cs_Points[9] = cs->cs_Points[1];
}

VOID SetUpperLeft (struct IntuiMessage * msg, struct Cursor * cs)
{

    cs->cs_Points[0] = cs->cs_Points[2] = msg->MouseX;
    cs->cs_Points[4] = cs->cs_Points[6] = msg->MouseX;
    cs->cs_Points[8] = msg->MouseX;

    cs->cs_Points[1] = cs->cs_Points[3] = msg->MouseY;
    cs->cs_Points[5] = cs->cs_Points[7] = msg->MouseY;
    cs->cs_Points[9] = msg->MouseY;
}

VOID AdjLowerRight (SHORT xadj, SHORT yadj, struct Cursor * cs)
{

    cs->cs_Points[2] += xadj;
    cs->cs_Points[4] = cs->cs_Points[2];

    cs->cs_Points[5] += yadj;
    cs->cs_Points[7] = cs->cs_Points[5];
}

VOID SetLowerRight (struct IntuiMessage * msg, struct Cursor * cs)
{

    cs->cs_Points[2] = cs->cs_Points[4] = msg->MouseX;
    cs->cs_Points[5] = cs->cs_Points[7] = msg->MouseY;
}
