/*
 * 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.
 *
 * The drawing task and interface code.
 *
 * This code MUST NOT be compiled with stack checking, since the drawing task
 * has its own stack. This is also true for anything that might get called
 * from here: DisableSizing(), EnableSizing(), StopFraming(), Sure(),
 * EnableSystemGadgets()...
 *
 * It must also be linked as one of the last files before the library, so it
 * is within 32K from the startup code containing geta4(). Otherwise, it
 * would need a4 in order to get a4.
 */

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

extern double	ReMouse,
		ImMouse;

int		DrawPri = 0;	/* Set with SetDrawPri() */

struct Program	Program[PROGRAMSIZE];
double		PrgReg[2 * PROGRAMREGS];

/* Complex multiplication using pointers to reduce overhead.   */
/* YOU must make sure there are no (dynamic) aliases around... */

STATIC void
MulCplx(ReRes, ImRes, ReA, ImA, ReB, ImB)
double	       *ReRes,
	       *ImRes,
	       *ReA,
	       *ImA,
	       *ReB,
	       *ImB;
{
    *ReRes = *ReA * *ReB - *ImA * *ImB;
    *ImRes = *ImA * *ReB + *ReA * *ImB;
}

/*
 * Private cache of the RastPort to speed up drawing
 */

STATIC struct RastPort *RastPort;

/* Z^2-C: 4 multiplications per loop */

int
ZQuadMinC(ReC, ImC)
double		ReC,
		ImC;
{
    register double ReZ = 0.0,
		    ImZ = 0.0;
    register double ReQuad,
		    ImQuad;
    register int    Depth = -1;

    while (ImQuad = ImZ * ImZ, ReQuad = ReZ * ReZ, Depth++,
	   (ImQuad + ReQuad < 8) && (Depth <= MaxDepth)) {
	ImZ = 2 * ImZ * ReZ - ImC;
	ReZ = ReQuad - ImQuad - ReC;
    }

    PrgReg[RE(0)] = ReZ;
    PrgReg[IM(0)] = ImZ;

    return Depth;
}

/* Z^2-C: 4 multiplications per loop */

int
I_ZQuadMinC(ReC, ImC)
double		ReC,
		ImC;
{
    register double ReZ = 0.0,
		    ImZ = 0.0;
    register double ReQuad,
		    ImQuad;
    register int    Depth = -1;

    while (ImQuad = ImZ * ImZ, ReQuad = ReZ * ReZ, Depth++,
	   (ImQuad + ReQuad < 8) && (Depth <= MaxDepth)) {
	ImZ = 2 * ImZ * ReZ - ImC;
	ReZ = ReQuad - ImQuad - ReC;

	PrgReg[RE(0)] = ReZ;
	PrgReg[IM(0)] = ImZ;

	(*IPlotFunc)();
    }

    PrgReg[RE(0)] = ReZ;
    PrgReg[IM(0)] = ImZ;

    return Depth;
}

/* Z*C*(1-Z): 10 multiplications per loop */

int
ZC1MinZ(ReC, ImC)
double		ReC,
		ImC;
{
#define ReZ	PrgReg[RE(0)]
#define ImZ	PrgReg[IM(0)]
    auto double     NewReZ,
		    NewImZ;
    auto double     Re1MinZ,
		    Im1MinZ;
    register int    Depth = -1;

    ReZ = ReC;
    ImZ = ImC;

    while (Depth++, (ImZ * ImZ + ReZ * ReZ < 8) && (Depth <= MaxDepth)) {
	Re1MinZ = 1 - ReZ;
	Im1MinZ = -ImZ;
	MulCplx(&NewReZ, &NewImZ, &ReZ, &ImZ, &ReC, &ImC);
	MulCplx(&ReZ, &ImZ, &NewReZ, &NewImZ, &Re1MinZ, &Im1MinZ);
    }

#undef ReZ
#undef ImZ

    return Depth;
}

/* Z^3+Z*(C-1)-C: 12 multiplications per loop */

int
Z3PlusZCMin1MinC(ReC, ImC)
double		ReC,
		ImC;
{
#define ReZ	PrgReg[RE(0)]
#define ImZ	PrgReg[IM(0)]
    auto double     ReCMin1;
    auto double     ReZ2,
		    ImZ2,
		    ReZ3,
		    ImZ3;
    register double ReQuad,
		    ImQuad;
    register int    Depth = -1;
    register void (*iplot)() = IPlotFunc;

    ReZ = ReC;
    ImZ = ImC;

    while (ImQuad = ImZ * ImZ, ReQuad = ReZ * ReZ, Depth++,
	   (ImQuad + ReQuad < 8) && (Depth <= MaxDepth)) {
	/* Calculate z^2 */
	ReZ2 = ReQuad - ImQuad;
	ImZ2 = 2 * ReZ * ImZ;

	/* Make z^3 */
	MulCplx(&ReZ3, &ImZ3, &ReZ2, &ImZ2, &ReZ, &ImZ);

	/* Calculate z(c-1) while destroying z^2 */
	ReCMin1 = ReC - 1;
	MulCplx(&ReZ2, &ImZ2, &ReZ, &ImZ, &ReCMin1, &ImC);

	/* Add everything */
	ReZ = ReZ3 + ReZ2 - ReC;
	ImZ = ImZ3 + ImZ2 - ImC;
    }

#undef ReZ
#undef ImZ

    return Depth;
}

/*
 * User programmed function...
 */

int
UserProgFunc(ReC, ImC)
double		ReC,
		ImC;
{
    register struct Program *pc;
    register struct Program *MainPC;
    register int    Depth = -1;
    register int    pr_Dest;
    register void (*iplot)() = IPlotFunc;

    PrgReg[RE(2)] = ReC;
    PrgReg[IM(2)] = ImC;

    PrgReg[RE(3)] = ReMouse;
    PrgReg[IM(3)] = ImMouse;

#define ReZ	PrgReg[RE(0)]
#define ImZ	PrgReg[IM(0)]

#define ReQuad	PrgReg[RE(1)]
#define ImQuad	PrgReg[IM(1)]

#define ReC	PrgReg[RE(2)]
#define ImC	PrgReg[IM(2)]

    ReZ = 0.0;
    ImZ = 0.0;

    /* Do the prelude: */
    pc = &Program[0];
    goto InterpretLoop; 	/* I know this is ugly */
PreludeDone:
    MainPC = pc + 1;

    while (ImQuad = ImZ * ImZ, ReQuad = ReZ * ReZ, Depth++,
	   (ImQuad + ReQuad < 8) && (Depth <= MaxDepth)) {
	pc = MainPC;
InterpretLoop:
	for (;;) {
	    pr_Dest = pc->pr_Dest;

	    switch (pc->pr_OpCode) {
	    case End:
		if (pr_Dest)    /* == 1 */
		    goto EndOfProgram;
		else
		    goto PreludeDone;

	    case Cassign:
		PrgReg[IM(pr_Dest)] = PrgReg[IM(pc->pr_Op1)];
	    case Rassign:
		PrgReg[RE(pr_Dest)] = PrgReg[RE(pc->pr_Op1)];
		break;

	    case Ci:
		PrgReg[IM(pr_Dest)] = (double) pc->pr_Op2;
	    case Ri:
		PrgReg[RE(pr_Dest)] = (double) pc->pr_Op1;
		break;

	    case Cplus:
		PrgReg[IM(pr_Dest)] = PrgReg[IM(pc->pr_Op1)] +
		    PrgReg[IM(pc->pr_Op2)];
	    case Rplus:
		PrgReg[RE(pr_Dest)] = PrgReg[RE(pc->pr_Op1)] +
		    PrgReg[RE(pc->pr_Op2)];
		break;

	    case Cminus:
		PrgReg[IM(pr_Dest)] = PrgReg[IM(pc->pr_Op1)] -
		    PrgReg[IM(pc->pr_Op2)];
	    case Rminus:
		PrgReg[RE(pr_Dest)] = PrgReg[RE(pc->pr_Op1)] -
		    PrgReg[RE(pc->pr_Op2)];
		break;

	    case Ctimes:
		MulCplx(
			&PrgReg[RE(pr_Dest)], &PrgReg[IM(pr_Dest)],
			&PrgReg[RE(pc->pr_Op1)], &PrgReg[IM(pc->pr_Op1)],
			&PrgReg[RE(pc->pr_Op2)], &PrgReg[IM(pc->pr_Op2)]
		    );
		break;

	    case Rtimes:
		PrgReg[RE(pr_Dest)] = PrgReg[RE(pc->pr_Op1)] *
		    PrgReg[RE(pc->pr_Op2)];
		break;
	    }			/* end of switch */
	    pc++;
	}			/* end of for */
EndOfProgram:
	(*iplot)();
    }				/* end of while */

    return Depth;

#undef ReZ
#undef ImZ

#undef ReQuad
#undef ImQuad

#undef ReC
#undef ImC

}

void PlotIterationCount(Count, x, y)
register int	Count;
long		x,
		y;
{
    if (Count > MaxDepth)
	Count = PenTable[0];
    else
	Count = PenTable[Count];

    if (RastPort->FgPen != (BYTE) Count)
	SetAPen(RastPort, (long) Count);
    WritePixel(RastPort, x, y);
}

void PlotZ()
{
    register int    Color;
    register long   x,
		    y;

    x = (PrgReg[RE(0)] - LeftEdge) / CXStep;
    y = (TopEdge -  PrgReg[IM(0)]) / CYStep;

    /*
     * Color will be -1 + 1 if we are within the window
     */

    if (Color = ReadPixel(RastPort, x, y) + 1) {
	if (RastPort->FgPen != (BYTE) Color)
	    SetAPen(RastPort, (long) Color);
	WritePixel(RastPort, x, y);
    }
}

void None()
{
    /* empty */
}

/* Some private variables */

struct Task    *DrawTask = NULL;
struct Task    *MandelTask = NULL;
struct SignalSemaphore DrawSemaphore;


/*
 * This is what it is all about!
 */

STATIC bool	MyFillIn;	/* Parameter for new task */

STATIC void
ActuallyDrawPicture()
{
    double	    x,		/* Running Real value */
		    y,		/* Running Imaginary value */
		    Leftx;	/* Reset Real value */
    register long   px, 	/* Running horizontal pixel coordinate */
		    py; 	/* Running vertical pixel coordinate */

    long	    Leftpx,	/* Reset horizontal pixel coordinate */
		    minpx;	/* Minimal horizontal pixel coordinate */
    register long   maxpx;	/* Maximal horizontal pixel coordinate */
    long	    minpy,	/* Minimal vertical pixel coordinate */
		    maxpy;	/* Maximal vertical pixel coordinate */
    double	    MyXstep,	/* Real increment */
		    MyYstep;	/* Imaginary increment */
    int 	    MyPixelStep,/* Private copy of PixelStep */
		    XOffset,	/* Running from 0 to MyPixelStep */
		    YOffset;	/* Running from 0 to MyPixelStep */
    struct Library *MathBasBase;/* Pointer to mathxxx.library */

    register int    (*Function) ();
    register void   (*Plot) ();

    geta4();                    /* Manx small memory model */

    ObtainSemaphore(&DrawSemaphore);

#ifdef IEEEDP
    /*
     * We must open this library ourselves, just in case we have a math
     * chip that needs extra context saving to be done. But to really
     * access the library, we use the global library base that the main
     * task has set for us. (but they are the same, anyway)
     */
    MathBasBase = OpenLibrary("mathieeedoubbas.library", LIBRARY_VERSION);
#else
    MathBasBase = OpenLibrary("mathffp.library", LIBRARY_VERSION);
#endif

    StillDrawing = TRUE;
    DisableSizing();
    StopFraming();
    OffMenu(MainWindow, (ULONG) SHIFTMENU(PRJMENU) | SHIFTITEM(PRJNEW) |
	    SHIFTSUB(NOSUB));
    OffMenu(MainWindow, (ULONG) SHIFTMENU(OPTMENU) | SHIFTITEM(OPTRES) |
	    SHIFTSUB(ORFIL));

    if (!MyFillIn && !Sure())
	skipto		exit;

    if (!MyFillIn)
	NameValid = FALSE;
    Saved = FALSE;
    MyPixelStep = PixelStep;
    XOffset = 0;
    YOffset = MyFillIn ? 1 : 0;

    minpx = 0;
    maxpx = MainWindow->GZZWidth - 1;
    minpy = 0;
    maxpy = MainWindow->GZZHeight - 1;

    RastPort = MainWindow->RPort;
    SetDrMd(RastPort, (long) JAM1);

    if (!MyFillIn) {            /* Clear the window */
	SetAPen(RastPort, (long) 2);    /* was PenTable[0] */
	RectFill(RastPort, minpx, minpy, maxpx, maxpy);
    }
    CalcCSteps();               /* Calculate CXStep and CYStep */

    MyXstep = MyPixelStep * CXStep;
    MyYstep = MyPixelStep * CYStep;

    Function = DepthFunc;
    Plot = EPlotFunc;

    ReleaseSemaphore(&DrawSemaphore);

again:
    Leftpx = minpx + XOffset;	/* Start in the upper left-hand corner */
    py = minpy + YOffset;	/* of the window */
    Leftx = LeftEdge + XOffset * CXStep;
    y = TopEdge - YOffset * CYStep;

    for (; py <= maxpy; y -= MyYstep, py += MyPixelStep) {
	ObtainSemaphore(&DrawSemaphore);
	for (px = Leftpx, x = Leftx; px <= maxpx; x += MyXstep, px += MyPixelStep) {
	    (*Plot) ((*Function) (x, y), px, py);
	    /* Check if we are asked to terminate. release s'phore. */
	    if (StillDrawing < 0) {
		ReleaseSemaphore(&DrawSemaphore);
		skipto		exit;
	    }
	}
	ReleaseSemaphore(&DrawSemaphore);
    }

    if (MyFillIn && (++YOffset < MyPixelStep)) {
	backto		again;	/* Draw pixels below current pixel */
    }
    if (MyFillIn && (++XOffset < MyPixelStep)) {
	YOffset = 0;		/* and next to them */
	backto		again;
    }
exit:
    ObtainSemaphore(&DrawSemaphore);
    if (MathBasBase)
	CloseLibrary(MathBasBase);
    EnableSizing();
    OnMenu(MainWindow, (ULONG) SHIFTMENU(PRJMENU) | SHIFTITEM(PRJNEW) |
	   SHIFTSUB(NOSUB));
    OnMenu(MainWindow, (ULONG) SHIFTMENU(OPTMENU) | SHIFTITEM(OPTRES) |
	   SHIFTSUB(ORFIL));
    DrawTask = NULL;
    StillDrawing = FALSE;	/* We are finished, finally... */
    ReleaseSemaphore(&DrawSemaphore);

    Signal(MandelTask, DrawSigMask);    /* and shout it off the roof */
}

bool
DrawPicture(FillIn)
bool		FillIn;
{
    register long   Priority;

    if (StillDrawing == FALSE) {
	Priority = MandelTask->tc_Node.ln_Pri + DrawPri;
	MyFillIn = FillIn;

	SetSignal(0L, DrawSigMask);     /* Clear the drawing signal */

	DrawTask = CreateTask("Mandelbrot_Drawing.task", Priority,
			      ActuallyDrawPicture, 2048L);
    }
    return DrawTask != NULL;
}

/* Interface because I wanted to CreateTask the drawing... */

void
WaitForDrawing()
{
    Forbid();

    if (StillDrawing)
	Wait(DrawSigMask);

    Permit();
}

void
StopDrawing()
{
    Forbid();

    if (StillDrawing) {
	StillDrawing = -1;	/* Indicate termination is wanted.  */
	WaitForDrawing();       /* Clears the draw/batch signal.    */
    }				/* Bug or feature? Need Batch/Cont. */
    Permit();
}

void
SetDrawPri(Priority)
int		Priority;
{
    DrawPri = Priority;

    Forbid();

    if (StillDrawing) {
	Priority = MandelTask->tc_Node.ln_Pri + DrawPri;
	SetTaskPri(DrawTask, (long) Priority);
    }
    Permit();
}

/*
 * This is the external interface to the DrawSemaphore. Use it with care.
 * Always balance calls to SuspendDrawing() with ResumeDrawing() !!!
 */

void
SuspendDrawing()
{
    ObtainSemaphore(&DrawSemaphore);
}

void
ResumeDrawing()
{
    ReleaseSemaphore(&DrawSemaphore);
}

void
CalcCSteps()
{
    CXStep = (double) (RightEdge - LeftEdge) / (MainWindow->GZZWidth - 1);
    CYStep = (double) (TopEdge - BottomEdge) / (MainWindow->GZZHeight - 1);
}
