/*
 * M A N D E L B R O T	C O N S T R U C T I O N   S E T
 *
 * (C) Copyright 1989 by Olaf Seibert.
 * Mandel may be freely distributed. See file 'doc/Notice' for details.
 *
 * Main Program, including some general routines
 */

#include <exec/types.h>
#include <intuition/intuition.h>
#ifdef DEBUG
#   include <stdio.h>
#   undef STATIC
#   define STATIC   /* EMPTY */
#endif
#include "mandel.h"

extern struct NewWindow XYNWindow;
extern struct Window *XYwindow;

#define BORDERLEFT	3
#define BORDERTOP	11
#define BORDERBOTTOM	3

#define CHARS		16	/* Change UpdateXYwindow also with this */

struct NewWindow XYNWindow =
{
    2 * BORDERLEFT, 2 * BORDERTOP,
    CHARS*8+BORDERLEFT*2, 2*8+BORDERTOP+BORDERBOTTOM,
    2, 1,		    /* DetailPen, BlockPen */
    0,			    /* CLOSEWINDOW */
    WINDOWCLOSE | WINDOWDRAG | WINDOWDEPTH | NOCAREREFRESH | SIMPLE_REFRESH,
    NULL,		    /* FirstGadget */
    NULL,		    /* default CheckMark */
    (UBYTE *) "Re and Im",  /* Title */
    NULL,		    /* Screen */
    NULL,		    /* BitMap */
    0, 0,		    /* MinWidth, MinHeight */
    0, 0,		    /* MaxWidth, MaxHeight */
    CUSTOMSCREEN	    /* Screen type */
};

double ReMouse, ImMouse;

/*
 *  We always ask for INTUITICKS, since this window will only be open
 *  as long as we have the flashing lines.
 */

void OpenXYwindow()
{
    if (XYwindow == NULL) {
	XYNWindow.Screen = MandelScreen;

	if (XYwindow = OpenWindow(&XYNWindow)) {
	    XYwindow->UserPort = MainWindow->UserPort;
	    ModifyIDCMP(XYwindow, (long)CLOSEWINDOW | MOUSEBUTTONS | INTUITICKS);
	    SetDrMd(XYwindow->RPort, (long)JAM2);
	    SetAPen(XYwindow->RPort, (long)1);
	    SetBPen(XYwindow->RPort, (long)0);
	}
    }
}

void CloseXYwindow()
{
    if (XYwindow) {
	CloseWindowSafely(XYwindow);
	XYwindow = NULL;
    }
}

void UpdateXYwindow(x, y)   /* corrected: 0..max */
int x, y;
{
    register int chars;
    register struct RastPort *rp;
    register long left, top;
    char buffer[CHARS+2];	/* 1 extra for \0, and 1 for safety */
    static char format[] = "%-16.10g";      /* 16 == CHARS */
    /* -1.234567890e-99 is 16 chars with a precision of 10 digits */

    ReMouse = LeftEdge + CXStep * x;
    ImMouse = TopEdge - CYStep * y;

    if (XYwindow) {
	rp = XYwindow->RPort;
	left = XYwindow->BorderLeft;
	top = XYwindow->BorderTop;

	chars = sprintf(buffer, format, ReMouse);
	Move(rp, left, top+7);
	Text(rp, buffer, (long)chars);

	chars = sprintf(buffer, format, ImMouse);
	Move(rp, left, top+15);
	Text(rp, buffer, (long)chars);
    }
}

void MyExit(status)
char *status;
{
    static char message[] = "\
\0\144\25Mandelbrot Construction Set -- By KosmoSoft Productions\0a\
\0\144\41								  \0";
/*	3^5^  10^    ^	20^    ^  30^	 ^  40^    ^  50^  55^58^     */
    register char *c=message+63;

#ifdef AREXX
    dnRexxPort();
#endif

    if (status) {
	/* Center the message. */

	{
	    register int halfway = 320 - (strlen(status) << 2);
	    message[60] = halfway >> 8; /* High byte */
	    message[61] = halfway;	/* Low byte */
	}
	while ((*(c++) = *(status++)) && c < message + 127);
	*c = 0;

	/* Let's be paranoid for a change... */
	if (!IntuitionBase) IntuitionBase = (struct IntuitionBase *)
	    OpenLibrary ("intuition.library", 0L);
	if (IntuitionBase)
	    DisplayAlert(RECOVERY_ALERT, message, 50L);
	else {	/* AT_Recovery | AG_OpenLib | AO_Intuition */
	    CPTR AlertParameter = (CPTR) FindTask(NULL);
	    Alert(0x00038004L, &AlertParameter);
	}

	status=1;
    }
    CleanupDisplay((bool) TRUE);
    if (DrawSigBit != -1) FreeSignal((long)DrawSigBit);
    if (IntuitionBase) CloseLibrary(IntuitionBase);
    if (LayersBase) CloseLibrary(LayersBase);
    if (GfxBase) CloseLibrary(GfxBase);
    exit ((int) (status != NULL));
}

void _abort()
{
    MyExit("_abort() called...");
}

bool Sure()
{
    bool Result;
    ULONG OldIDCMP = MainWindow->IDCMPFlags;

    static struct IntuiText Body[] =
    {
	{   MYFRONTPEN, AUTOBACKPEN, AUTODRAWMODE,
	    25, 10, NULL, (UBYTE *) "You are going to", &Body[1]    },
	{   MYFRONTPEN+1, AUTOBACKPEN, AUTODRAWMODE,
	    57, 25, &Topaz60, (UBYTE *) "destroy", &Body[2] },
	{   MYFRONTPEN, AUTOBACKPEN, AUTODRAWMODE,
	    33, 40, NULL, (UBYTE *) "your picture !", NULL  }
    };

    if (Saved) return TRUE;

    ModifyIDCMP(MainWindow, OldIDCMP &~ (MENUVERIFY | SIZEVERIFY | REQVERIFY));
    Result = AutoRequest(MainWindow, &Body[0], &PositiveText, &NegativeText,
	NULL, NULL, 200L, 90L);
    ModifyIDCMP(MainWindow, OldIDCMP);

    return Result;
}

/*
 * Render a requester in a window. If it won't fit, open a new window.
 * This new window will share the IDCMP port with the original
 * window. This is to save memory, signal bits and VERIFY deadlocks.
 * Returns the window in which the requester actually appears.
 */

struct Window *MyRequest(request, window)
struct Requester *request;
struct Window *window;
{
    static struct NewWindow newwindow = {
	0, 0, 0, 0, 2, 1,
	NULL,		/* IDCMP flags -- port shared with main window */
	WINDOWDEPTH | WINDOWDRAG | ACTIVATE | SIMPLE_REFRESH | NOCAREREFRESH,
	NULL, NULL, NULL, NULL,
	NULL, 0, 0, 0, 0, NULL	};
    int width, height;
    int borderleft, borderright, bordertop, borderbottom;
    struct Window *oldwindow = window;

    borderleft	 = window->WScreen->WBorLeft;
    borderright  = window->BorderRight;
    bordertop	 = window->WScreen->BarHeight + 1;
    borderbottom = window->BorderBottom;

    /* Center the requester in the given window.
     * If impossible, open a new window to the place the requester in.
     */

     width = window->GZZWidth;
     height = window->GZZHeight;

     if (width < request->Width || height < request-> Height) {
	/* Window too small. Open a new one */
	newwindow.Screen = window->WScreen;
	newwindow.Type = window->WScreen->Flags & SCREENTYPE;
	newwindow.Title = window->Title;
	newwindow.Width = request->Width + 2 * borderleft;
	newwindow.Height = request->Height + bordertop + borderbottom;
	newwindow.LeftEdge = (newwindow.Screen->Width - newwindow.Width) / 2;
	newwindow.TopEdge = (newwindow.Screen->Height - newwindow.Height) / 2;

	if (window = OpenWindow(&newwindow)) {
	    window->UserPort = oldwindow->UserPort;
	    /* Upen up the other port */
	    ModifyIDCMP(window, GADGETUP);
	}

	request->LeftEdge = borderleft;
	request->TopEdge = bordertop;
    } else {	/* The requester fits. Center it! */
	request->LeftEdge = ((width - request->Width) >> 1);
	request->TopEdge = ((height - request->Height) >> 1);
	if (!(window->Flags & GIMMEZEROZERO)) {
	    request->LeftEdge += borderleft;
	    request->TopEdge += bordertop;
	}
    }

    if (window && Request(request, window)) return window;

    if (window) CloseWindowSafely(window);
    return NULL;
}

void EndMyRequest(request, window, original)
struct Requester *request;
struct Window *window, *original;
{
    EndRequest(request, window);
    if (window != original) CloseWindowSafely(window);
}

/*
 * Wait on a request posted by MyRequest.
 * Returns when a gadget with GadgetID >= POSGADGETID is released,
 * so Gadgets with an ID < POSGADGETID will be ignored.
 * NEGGADID > POSGADID.
 * Any messages other than GADGETUP will be ignored.
 */

int WaitMyRequest(window)
struct Window *window;
{
    int ID = 0;
    struct IntuiMessage *message;
    struct Gadget *Gadget;
    ULONG Class;
    ULONG OldIDCMP = window->IDCMPFlags;
    ULONG SigMask;

    if (!window)
	return NEGGADGETID;

    ModifyIDCMP(window, GADGETUP);

#ifdef AREXX
    SigMask = RexxMask | (1L << MainWindow->UserPort->mp_SigBit);
#else
    SigMask = (1L << MainWindow->UserPort->mp_SigBit);
#endif

    while (ID < POSGADGETID) {
	Wait(SigMask);
#ifdef AREXX
	dispRexxPort();
#endif
	while (message = (struct IntuiMessage *) GetMsg(window->UserPort) ) {
	    Class = message->Class;
	    Gadget = (struct Gadget *)message->IAddress;
	    ReplyMsg(message);
	    if (Class != GADGETUP) continue;
	    ID = Gadget->GadgetID;
	    if (ID >= POSGADGETID) break; /* Also gets out of outer loop */
	}
    }
    ModifyIDCMP(window, OldIDCMP);
    return ID;
}

void RectDraw(rp, x1, y1, x2, y2)
struct RastPort *rp;
SHORT x1, y1, x2,y2;
{
    Move(rp, (long) x1, (long) y1);
    Draw(rp, (long) x2, (long) y1);
    Draw(rp, (long) x2, (long) y2);
    Draw(rp, (long) x1, (long) y2);
    if (y2 > y1) y1++; else y1--;   /* Don't XOR the first pixel twice */
    Draw(rp, (long) x1, (long) y1);
}

void CrossDraw(rp, x, y, left, right, top, bottom)
struct RastPort *rp;
SHORT x, y, top, bottom, left, right;
{
    Move(rp, (long) left, (long) y);
    Draw(rp, (long) right, (long) y);
    Move(rp, (long) x, (long) top);
    Draw(rp, (long) x, (long) bottom);
}

void DisableSystemGadgets(gadget)
struct Gadget *gadget;
{
    while (gadget) {
	if (gadget->GadgetType & SYSGADGET) {
	    /* Ghost everything except the Title/Dragbar */
	    if ((gadget->GadgetType & 0x00F0) != WDRAGGING)
		OffGadget(gadget, MainWindow, NULL);
	    gadget->Flags |= GADGDISABLED;
	}
	gadget = gadget->NextGadget;
    }
}

void EnableSystemGadgets(gadget)
struct Gadget *gadget;
{
    USHORT Flags = 0;

    while (gadget) {
	if (gadget->GadgetType & SYSGADGET) {
	    Flags |= gadget->Flags;
	    gadget->Flags &= ~GADGDISABLED;
	}
	gadget = gadget->NextGadget;
    }
    /* Unghost everthing if necessary */
    if (Flags & GADGDISABLED)   RefreshWindowFrame(MainWindow);
}

void StopFraming()
{
    if (MainWindow) {
	EnableSystemGadgets(MainWindow->FirstGadget);
	ModifyIDCMP(MainWindow, MainWindow->IDCMPFlags & ~INTUITICKS);
    }
    MouseStatus = NOTFRAMING;
    CloseXYwindow();
}

void CheckMouse(Message)
register struct IntuiMessage *Message;
{
    register SHORT top = MainWindow->BorderTop,
		   bottom = MainWindow->Height - MainWindow->BorderBottom - 1,
		   left = MainWindow->BorderLeft,
		   right = MainWindow->Width - MainWindow->BorderRight - 1;
    static ULONG OldSecs, OldMicros;
    static SHORT MidX, MidY;
    USHORT MouseX, MouseY;

    if (Message->IDCMPWindow == MainWindow) {
	MouseX = Message->MouseX;
	MouseY = Message->MouseY;
    } else {
	MouseX = MainWindow->MouseX;
	MouseY = MainWindow->MouseY;
    }

    if (StillDrawing || MouseStatus != FLASHING &&
	(MouseX < left || MouseX > right || MouseY < top || MouseY > bottom))
	return;

    MouseX -= left;
    MouseY -= top;

    if (Message->Class == MOUSEBUTTONS) {
	if (Message->Code == SELECTDOWN) {
	    /* We selected a point */
	    switch (MouseStatus) {
	    case NOTFRAMING:
	    case FLASHING:
		MouseStatus = NOPOINT;
		DisableSystemGadgets(MainWindow->FirstGadget);
		ModifyIDCMP(MainWindow, MainWindow->IDCMPFlags | INTUITICKS);
		OpenXYwindow();
		/* Now we can select our first corner */
		break;
	    case NOPOINT:
		FrameX1 = FrameX2 = MouseX;
		FrameY1 = FrameY2 = MouseY;
		MouseStatus = POINT1;
		OldMicros = Message->Micros;
		OldSecs = Message->Seconds;
		/* We have the first point. Now go for the second */
		break;
	    case POINT1:
		if (DoubleClick(OldSecs, OldMicros,
				Message->Seconds, Message->Micros)) {
		    /* Did we double-click? Then we have selected a center */
		    MouseStatus = CENTERFRAMING;
		    MidX = FrameX1;
		    MidY = FrameY1;
		    break;
		}
		FrameX2 = MouseX;
		FrameY2 = MouseY;
		/* Fall through to CENTERFRAMING */
	    case CENTERFRAMING:
		if (FrameX1 == FrameX2 || FrameY1 == FrameY2) break;
		EnableSystemGadgets(MainWindow->FirstGadget);
		MouseStatus = FLASHING;
		/* Point 1 should be upper left */
		if (FrameX2 < FrameX1) {
		    /* I DO know I am reusing a variable here. Sorry! */
		    left=FrameX2; FrameX2=FrameX1; FrameX1=left;
		}
		if (FrameY2 < FrameY1) {
		    left=FrameY2; FrameY2=FrameY1; FrameY1=left;
		}
		break;
	    }	/* End switch MouseStatus */
	}   /* End if Code == SELECTDOWN */
	return;
    }	/* End if Class == MOUSEBUTTONS */

    /* We are moving the mouse. Show something! */

    UpdateXYwindow(MouseX, MouseY);

    SetDrMd(MainWindow->RPort, (ULONG) COMPLEMENT);

    switch (MouseStatus) {
    /* case NOTFRAMING: */
    /*	return; */
    case NOPOINT:
	FrameX1 = FrameX2 = MouseX;
	FrameY1 = FrameY2 = MouseY;
	WaitTOF();
	CrossDraw(MainWindow->RPort, FrameX1, FrameY1,
		    0, MainWindow->GZZWidth-1, 0, MainWindow->GZZHeight-1);
	WaitTOF();
	CrossDraw(MainWindow->RPort, FrameX1, FrameY1,
		    0, MainWindow->GZZWidth-1, 0, MainWindow->GZZHeight-1);
	break;
    case CENTERFRAMING:
	FrameX1 = MouseX;
	FrameY1 = MouseY;
	FrameX2 = 2*MidX - FrameX1;
	FrameY2 = 2*MidY - FrameY1;
	skipto flashing;
    case POINT1:
	FrameX2 = MouseX;
	FrameY2 = MouseY;
	/* Deliberate Fall-Through to FLASHING */
    case FLASHING:
flashing:
	WaitTOF();
	RectDraw(MainWindow->RPort, FrameX1, FrameY1, FrameX2, FrameY2);
	WaitTOF();
	RectDraw(MainWindow->RPort, FrameX1, FrameY1, FrameX2, FrameY2);
    }
}

void InitPenTable()
{
    register int i;

    switch (PenTableMode) {
    case MODULO:
	PenTable[0] = 0;
	for (i=1; i<MAXDEPTH; i++) PenTable[i] = 1 + i % (NumColors - 1);
	break;
    case RANGES:
	PenTable[0] = 0;
	for (i=1; i<MAXDEPTH; i++)
	    PenTable[i] = 1 + (i/RangeWidth) % (NumColors - 1);
    case SELECT:
	/* Don't change the table if it is user-defined */
	break;
    }
}

void SelectMenu(MenuNum, CheckIt)
LONG MenuNum;
bool CheckIt;
{
    struct MenuItem *Item = ItemAddress(MandelMenu, MenuNum);

    ClearMenuStrip(MainWindow);

    if (CheckIt)
	Item->Flags |= CHECKED;
    else
	Item->Flags &= ~CHECKED;

    SetMenuStrip(MainWindow, MandelMenu);
}

void MakeMAND(mand)
struct Mand *mand;
{
    int i;

    mand->MandID = MAND;
    mand->Size = sizeof(struct Mand) - 2 * sizeof(LONG);
    mand->MaxDepth = MaxDepth;
    mand->RangeWidth = RangeWidth;

    mand->RainDist = RainbowDistance;
    mand->RainRMax = RainbowRMax;
    mand->RainGMax = RainbowGMax;
    mand->RainBMax = RainbowBMax;

    for (i=0; i < sizeof(mand->Coords); i++)
	mand->Coords[i] = '\0';
    sprintf(&mand->Coords[0], "%1.10g %1.10g %1.10g %1.10g",
	LeftEdge, RightEdge, TopEdge, BottomEdge);

    mand->FunctionNr = FunctionNr;
    mand->PenTableMode = PenTableMode;
    mand->WBWidth = WBWidth;
    mand->WBHeight = WBHeight;

}

bool InterpretMAND(mand, ilbminfo)
struct Mand *mand;
struct ILBM_info *ilbminfo;
{
    double NewLeftEdge, NewRightEdge, NewTopEdge, NewBottomEdge;
    double Ratio;

    /* Perform some checks on correctness of the chunk */
    if (mand->MandID != MAND ||
	 mand->Size > sizeof(struct Mand) - 2 * sizeof(LONG) ||
	  mand->MaxDepth > MAXDEPTH ||
	   mand->RangeWidth > MAXDEPTH)
	return FALSE;

    if (sscanf(&mand->Coords[0], "%lf %lf %lf %lf",
	&NewLeftEdge, &NewRightEdge, &NewTopEdge, &NewBottomEdge) < 4)
	return FALSE;

    MaxDepth	= mand->MaxDepth;
    RangeWidth	= mand->RangeWidth;

    /* Compensate for different sized windows */

    Ratio	= (double)MainWindow->GZZWidth / (double)ilbminfo->header.w;
    LeftEdge	= NewLeftEdge;
    if (Ratio != 1.0)
	RightEdge = NewLeftEdge + Ratio * (NewRightEdge - NewLeftEdge);
    else	    /* avoid round-off when almost exactly 1.0000 */
	RightEdge = NewRightEdge;

    Ratio	= (double)MainWindow->GZZHeight / (double)ilbminfo->header.h;
    TopEdge	= NewTopEdge;
    if (Ratio != 1.0)
	BottomEdge = NewTopEdge - Ratio * (NewTopEdge - NewBottomEdge);
    else	    /* avoid round-off when almost exactly 1.0000 */
	BottomEdge = NewBottomEdge;

    CalcCSteps();

    RainbowDistance = mand->RainDist;
    RainbowRMax = mand->RainRMax;
    RainbowGMax = mand->RainGMax;
    RainbowBMax = mand->RainBMax;

    if (mand->Size > OFFSETOF(FunctionNr, Mand) - 2*sizeof(long)) {
	SetDrawingFunction(mand->FunctionNr);
	PenTableMode = mand->PenTableMode;
	WBWidth = mand->WBWidth;
	WBHeight = mand->WBHeight;
	InitPenTable();
    }

    UpdateCheckmarks();

    return TRUE;
}
