/*

This little demo show how to write classes which need a long
time to render their contents. In this case, we take a little
fractal algorithm as example. The actual calculations are
done in a separate task, the display is updated from time
to time.

*/

#include "demo.h"
#include <dos/dostags.h>
#include <exec/memory.h>


/* Pixel dimensions of our fractal */

#define FRACTALWIDTH  300
#define FRACTALHEIGHT 300


/* Fractal Description */

struct FractalDesc
{
	float left;
	float right;
	float top;
	float bottom;
};

#define MaxIterations 60

/* Attributes and methods for the custom class */

#define MUISERIALNR_STUNTZI 1
#define TAGBASE_STUNTZI (TAG_USER | ( MUISERIALNR_STUNTZI << 16))

#define MUIM_Class4_Update (TAGBASE_STUNTZI | 0x0001)
struct MUIP_Class4_Update
{
	ULONG id;
	LONG percent;
};

#define MUIM_Class4_Calc   (TAGBASE_STUNTZI | 0x0002)
struct MUIP_Class4_Calc
{
	ULONG id;
	struct FractalDesc *fd;
};

#define MUIA_Class4_Percent (TAGBASE_STUNTZI | 0x0003)


#define STC_START 0
#define STC_STOP  1


/* Instance Data for the fractal class */

struct Data
{
	struct SignalSemaphore sema;    /* data item protection      */
	Object                *app;     /* pointer to application    */
	Object                *self;    /* pointer to ourselves      */
	struct SubTask        *subtask; /* our sub task              */
	struct RastPort        rp;      /* rastport for the sub task */
	BYTE                  *udlines; /* line update flags array   */
};




/**************************************************************/
/* Functions for easy and secure spawning/killing of subtasks */
/**************************************************************/

struct SubTaskMsg
{
	struct Message stm_Message;
	WORD           stm_Command;
	APTR           stm_Parameter;
	LONG           stm_Result;
};

struct SubTask
{
   struct Task      *st_Task;    /* sub task pointer */
	struct MsgPort   *st_Port;    /* allocated by sub task */
	struct MsgPort   *st_Reply;   /* allocated by main task */
	APTR              st_Data;    /* more initial data to pass to the sub task */
	struct SubTaskMsg st_Message; /* Message buffer */
};

#define STC_STARTUP  -2
#define STC_SHUTDOWN -1

LONG SendSubTaskMsg(struct SubTask *st,WORD command,APTR params)
{
	st->st_Message.stm_Message.mn_ReplyPort = st->st_Reply;
	st->st_Message.stm_Message.mn_Length    = sizeof(struct SubTaskMsg);
	st->st_Message.stm_Command              = command;
	st->st_Message.stm_Parameter            = params;
	st->st_Message.stm_Result               = 0;

	PutMsg(command==STC_STARTUP ? &((struct Process *)st->st_Task)->pr_MsgPort : st->st_Port,(struct Message *)&st->st_Message);
	WaitPort(st->st_Reply);
	GetMsg(st->st_Reply);

	return(st->st_Message.stm_Result);
}

struct SubTask *SpawnSubTask(char *name,VOID (*func)(VOID),APTR data)
{
	struct SubTask *st;

	if (st = AllocVec(sizeof(struct SubTask),MEMF_PUBLIC|MEMF_CLEAR))
	{
		if (st->st_Reply = CreateMsgPort())
		{
			st->st_Data = data;

			if (st->st_Task = (struct Task *)CreateNewProcTags(NP_Entry,func,NP_Name,name,TAG_DONE))
			{
				if (SendSubTaskMsg(st,STC_STARTUP,st))
				{
					return(st);
				}
			}
			DeleteMsgPort(st->st_Reply);
		}
		FreeVec(st);
	}
	return(NULL);
}

VOID KillSubTask(struct SubTask *st)
{
	SendSubTaskMsg(st,STC_SHUTDOWN,st);
	DeleteMsgPort(st->st_Reply);
	FreeVec(st);
}

/* the following functions are called from the sub task */

VOID ExitSubTask(struct SubTask *st,struct SubTaskMsg *stm)
{
	/*
	** We reply after a Forbid() to make sure we're really gone
	** when the main task continues.
	*/

	if (st->st_Port)
		DeleteMsgPort(st->st_Port);

	Forbid();
	stm->stm_Result = FALSE;
	ReplyMsg((struct Message *)stm);
}

struct SubTask *InitSubTask(void)
{
	struct Task *me = FindTask(NULL);
	struct SubTask *st;
	struct SubTaskMsg *stm;

	/*
	** Wait for our startup message from the SpawnSubTask() function.
	*/

	WaitPort(&((struct Process *)me)->pr_MsgPort);
	stm  = (struct SubTaskMsg *)GetMsg(&((struct Process *)me)->pr_MsgPort);
	st   = (struct SubTask *)stm->stm_Parameter;

	if (st->st_Port = CreateMsgPort())
	{
		/*
		** Reply startup message, everything ok.
		** Note that if the initialization fails, the code falls
		** through and replies the startup message with a stm_Result
		** of 0 after a Forbid(). This tells SpawnSubTask() that the
		** sub task failed to run.
		*/

		stm->stm_Result = TRUE;
		ReplyMsg((struct Message *)stm);
		return(st);
	}
	else
	{
		ExitSubTask(st,stm);
		return(NULL);
	}
}


/*******************************************************/
/* Subtask which does all the time-consuming rendering */
/*******************************************************/

VOID __asm __saveds RenderFunc(VOID)
{
	struct SubTask *st;
	struct Library *GfxBase;

	if (GfxBase = OpenLibrary("graphics.library",37)) /* dont share library pointers! */
	{
		if (st = InitSubTask())
		{
			struct Data *data = st->st_Data;
			BOOL running = TRUE;
			BOOL worktodo = FALSE;
			LONG x,y;
			struct SubTaskMsg *stm;

			/*
			** after the sub task is up and running, we go into
			** a loop and process the messages from the main task.
			*/

			for (;;)
			{
				float left,top,right,bottom;

				while (stm = (struct SubTaskMsg *)GetMsg(st->st_Port))
				{
					switch (stm->stm_Command)
					{
						case STC_SHUTDOWN:
							/*
							** This is the shutdown message from KillSubTask().
							*/
							running = FALSE;
							break;

						case STC_START:
							/*
							** we received a start message with a fractal description.
							** clear the rastport and the line update array and start
							** rendering.
							*/
							SetRast(&data->rp,1);
							memset(data->udlines,0,FRACTALHEIGHT);
							left   = ((struct FractalDesc *)stm->stm_Parameter)->left  ;
							top    = ((struct FractalDesc *)stm->stm_Parameter)->top   ;
							right  = ((struct FractalDesc *)stm->stm_Parameter)->right ;
							bottom = ((struct FractalDesc *)stm->stm_Parameter)->bottom;
							y = 0;
							worktodo = TRUE;
							break;

						case STC_STOP:
							/* this message is not used in this example */
							worktodo = FALSE;
							break;
					}

					/*
					** If we received a shutdown message, we do not reply it
					** immediately. First, we need to free our resources.
					*/
					if (!running) break;

					ReplyMsg((struct Message *)stm);
				}

				if (!running) break;

				if (worktodo)
				{
					/* if there is work to do, i.e. if the fractal is not
					** finished yet, we calculate the next line and draw
					** it to the offscreen rastport.
	   			*/

					for (x=0;x<FRACTALWIDTH;x++)
					{
						float zr = 0.0;
						float zi = 0.0;
						float cr = left + x * (right - left) / FRACTALWIDTH;
						float ci = top  + y * (bottom - top) / FRACTALHEIGHT;
						float rr = zr * zr;
						float ii = zi * zi;
						int counter;

						for (counter=0;counter<MaxIterations;counter++)
						{
							zi = ci + zr * zi * 2;
							zr = cr + rr - ii;
							rr = zr * zr;
							ii = zi * zi;

							if (rr + ii >= 4.0)
							{
								/*
								** set the pixel in the offscreen rastport.
								** this demo is kind of dirty, as it does no
								** nice color allocation and palette stuff.
								** dont be so dirty in your own programs! :-)
								*/

								SetAPen(&data->rp,1+counter);
								WritePixel(&data->rp,x,y);
								break;
							}

							if (++counter==MaxIterations)
							{
								break;
							}
						}
					}

					/*
					** after the line is finished, we set the corresponding
					** flag in the line update array to FALSE. This shows the
					** main task that this line needs to be redrawn the next
					** time it gets the chance.
					*/

					ObtainSemaphore(&data->sema);
					data->udlines[y] = FALSE;
					if (data->app)
					{
						/*
						** if our class is attached to an application, we send ourselves
						** an update method. Note that because we are in a separate task,
						** we cannot send this method directly but instead have to use
						** the MUIM_Application_PushMethod call. This is the only method
						** that you may send to a MUI object from a separate task. What it
						** does is to copy the method to a private buffer and wait until
						** the next time the main task calls the input method. Then, our
						** update method will be executed under the main tasks context.
						**
						** If our class is not attached to an application
						** (i.e. we are outside of MUIM_Setup/MUIM_Cleanup), there is
						** nobody who could render something anyway so we just skip
						** the update method and continue to render in our private
						** buffer.
						*/

						DoMethod(data->app,MUIM_Application_PushMethod,data->self,2,MUIM_Class4_Update,100*(y+1)/FRACTALHEIGHT);
					}
					ReleaseSemaphore(&data->sema);

					if (++y==FRACTALHEIGHT)
					{
						/* check if we are finished to draw our fractal */
						worktodo = FALSE;
					}

					/* Since we are very busy working, we do not Wait() for signals. */
				}
				else
				{
					/* We have nothing to do, just sit quietly and wait for something to happen */
					WaitPort(st->st_Port);
				}
			}
			ExitSubTask(st,stm);
		}
		CloseLibrary(GfxBase);
	}
}



/***************************************************************************/
/* Here is the beginning of our new class...                               */
/***************************************************************************/


SAVEDS ULONG mNew(struct IClass *cl,Object *obj,Msg msg)
{
	struct Data *data;

	if (!(obj = (Object *)DoSuperMethodA(cl,obj,msg)))
		return(0);

	data = INST_DATA(cl,obj);

	/* store a pointer to ourselves so the subtask knows about us */
	data->self = obj;

	/*
	** initialization and allocation of data structures.
	** note that if something fails here, we *must* do a
	** CoerceMethod(cl,obj,OM_DISPOSE) to give ourselves
	** (and MUI!) a chance to clean up.
	*/

	InitSemaphore(&data->sema);
	InitRastPort(&data->rp);

	if (data->udlines = AllocVec(FRACTALHEIGHT,MEMF_CLEAR))
	{
		if (data->rp.BitMap = AllocBitMap(FRACTALWIDTH,FRACTALHEIGHT,8,BMF_CLEAR,NULL))
		{
			SetRast(&data->rp,1);

			/* the following call starts the sub task */

			if (data->subtask = SpawnSubTask("Class4-Render-Task",RenderFunc,data))
			{
				SetTaskPri(data->subtask->st_Task,-1);
				return((ULONG)obj);
			}
		}
	}

	CoerceMethod(cl,obj,OM_DISPOSE);
	return(0);
}



SAVEDS ULONG mDispose(struct IClass *cl,Object *obj,Msg msg)
{
	struct Data *data = INST_DATA(cl,obj);

	if (data->subtask)
		KillSubTask(data->subtask);

	if (data->rp.BitMap)
		FreeBitMap(data->rp.BitMap);

	if (data->udlines)
		FreeVec(data->udlines);

	return(DoSuperMethodA(cl,obj,msg));
}



/*
** AskMinMax method will be called before the window is opened
** and before layout takes place. We need to tell MUI the
** minimum, maximum and default size of our object.
*/

SAVEDS ULONG mAskMinMax(struct IClass *cl,Object *obj,struct MUIP_AskMinMax *msg)
{
	/*
	** let our superclass first fill in what it thinks about sizes.
	** this will e.g. add the size of frame and inner spacing.
	*/

	DoSuperMethodA(cl,obj,(Msg)msg); /*FR* Added (Msg) */

	/*
	** now add the values specific to our object. note that we
	** indeed need to *add* these values, not just set them!
	*/

	msg->MinMaxInfo->MinWidth  += 10;
	msg->MinMaxInfo->DefWidth  += 100;
	msg->MinMaxInfo->MaxWidth  += FRACTALWIDTH;

	msg->MinMaxInfo->MinHeight += 10;
	msg->MinMaxInfo->DefHeight += 100;
	msg->MinMaxInfo->MaxHeight += FRACTALHEIGHT;

	return(0);
}



/*
** Draw method is called whenever MUI feels we should render
** our object. This usually happens after layout is finished
** or when we need to refresh in a simplerefresh window.
** Note: You may only render within the rectangle
**       _mleft(obj), _mtop(obj), _mwidth(obj), _mheight(obj).
*/

SAVEDS ULONG mDraw(struct IClass *cl,Object *obj,struct MUIP_Draw *msg)
{
	struct Data *data = INST_DATA(cl,obj);

	/*
	** let our superclass draw itself first, area class would
	** e.g. draw the frame and clear the whole region. What
	** it does exactly depends on msg->flags.
	**
	** Note: You *must* call the super method prior to do
	** anything else, otherwise msg->flags will not be set
	** properly!
	*/

	DoSuperMethodA(cl,obj,(Msg)msg); /*FR* Added (Msg) */

	if (msg->flags & MADF_DRAWUPDATE)
	{
		/*
		** This flag indicates that we were called from our
		** update method. We needn't render the complete
		** image, we only need to update the lines that
		** were changed. So what we do is to browse through
		** the line flag array and blit each changed line
		** from the offscreen buffer into the display.
		** We could do a better and more efficient job
		** by collecting subsequent changed lines to blit
		** larger rectangles, but hey... this is only a demo! :-)
		*/

		int l;

		/*
		** note the usage of semaphores to protect access
		** to variables use by both tasks.
		*/

		ObtainSemaphore(&data->sema);

		for (l=0;l<_mheight(obj);l++)
		{
			if (!data->udlines[l])
			{
				/*
				** once we copied the line, we set the corresponding line flag
				** to indicate that this line is uptodate and does not need
				** to be redrawn the next time. When our sub task gets the message
				** to calculate a new fractal, it will reset the flag to FALSE again.
				*/

				BltBitMapRastPort(data->rp.BitMap,0,l,_rp(obj),_mleft(obj),_mtop(obj)+l,_mwidth(obj),1,0xc0);
				data->udlines[l] = TRUE;
			}
		}

		ReleaseSemaphore(&data->sema);
	}
	else if (msg->flags & MADF_DRAWOBJECT)
	{
		/*
		** we were called directly from MUI because the window needs refresh.
		** no need to care about our line array here, we just copy the complete
		** offscreen buffer to our display.
		*/

		ObtainSemaphore(&data->sema);
		BltBitMapRastPort(data->rp.BitMap,0,0,_rp(obj),_mleft(obj),_mtop(obj),_mwidth(obj),_mheight(obj),0xc0);
		ReleaseSemaphore(&data->sema);
	}

	return(0);
}


SAVEDS ULONG mSetup(struct IClass *cl,Object *obj,struct MUIP_HandleInput *msg)
{
	struct Data *data = INST_DATA(cl,obj);

	if (!(DoSuperMethodA(cl,obj,(Msg)msg))) /*FR* Added (Msg) */
		return(FALSE);

	/*
	** set a pointer to our application in our instance data.
	** this indicates the sub task that we should be notified
	** when a new line is calculated.
	*/

	ObtainSemaphore(&data->sema);
	get(obj,MUIA_ApplicationObject,&data->app);
	ReleaseSemaphore(&data->sema);

	return(TRUE);
}


SAVEDS ULONG mCleanup(struct IClass *cl,Object *obj,struct MUIP_HandleInput *msg)
{
	struct Data *data = INST_DATA(cl,obj);

	ObtainSemaphore(&data->sema);
	data->app = NULL;
	ReleaseSemaphore(&data->sema);

	return(DoSuperMethodA(cl,obj,(Msg)msg)); /*FR* Added (Msg) */
}


/*
** a simple method that sends a START msg with
** fractal description packet to the sub task.
*/

SAVEDS ULONG mCalc(struct IClass *cl,Object *obj,struct MUIP_Class4_Calc *msg)
{
	struct Data *data = INST_DATA(cl,obj);
	SendSubTaskMsg(data->subtask,STC_START,msg->fd);
	return(0);
}


/*
** thats the method that is called through MUIM_Application_PushMethod
** from the subtask.
*/

SAVEDS ULONG mUpdate(struct IClass *cl,Object *obj,struct MUIP_Class4_Update *msg)
{
	/* Tell MUI to redraw our object. Set the update flag
	** so we know that only the changed lines are subject
	** to render.
	*/

	MUI_Redraw(obj,MADF_DRAWUPDATE);

	/*
	** Also the the percentage attribute. The class itself doesnt
	** have any use for this, but if we set it, its possible
	** for other objects (e.g. a gauge) to receive notifications
	*/

	set(obj,MUIA_Class4_Percent,msg->percent);

	return(0);
}


/*
** Here comes the dispatcher for our custom class.
** Unknown/unused methods are passed to the superclass immediately.
*/

SAVEDS ASM ULONG MyDispatcher(REG(a0) struct IClass *cl,REG(a2) Object *obj,REG(a1) Msg msg)
{
	switch (msg->MethodID)
	{
		case OM_NEW            : return(mNew      (cl,obj,(APTR)msg));
		case OM_DISPOSE        : return(mDispose  (cl,obj,(APTR)msg));
		case MUIM_AskMinMax    : return(mAskMinMax(cl,obj,(APTR)msg));
		case MUIM_Draw         : return(mDraw     (cl,obj,(APTR)msg));
		case MUIM_Setup        : return(mSetup    (cl,obj,(APTR)msg));
		case MUIM_Cleanup      : return(mCleanup  (cl,obj,(APTR)msg));
		case MUIM_Class4_Update: return(mUpdate   (cl,obj,(APTR)msg));
		case MUIM_Class4_Calc  : return(mCalc     (cl,obj,(APTR)msg));
	}

	return(DoSuperMethodA(cl,obj,msg));
}



/****************************************************************************/
/* Misc Help Functions                                                      */
/****************************************************************************/

LONG xget(Object *obj,ULONG attribute)
{
	LONG x;
	get(obj,attribute,&x);
	return(x);
}

char *getstr(Object *obj)
{
	return((char *)xget(obj,MUIA_String_Contents));
}



/***************************************************************************/
/* Thats all there is about it. Now lets see how things are used...        */
/***************************************************************************/

int main(int argc,char *argv[])
{
	Object *app,*window,*MyObj;
	Object *strleft,*strtop,*strright,*strbottom,*start,*gauge;
	struct MUI_CustomClass *mcc;
	ULONG signals;
	BOOL running = TRUE;

	if (((struct Library *)SysBase)->lib_Version < 39)
	{
		fprintf(stderr,"runs only with V39 and up\n");
		exit(20);
	}

	init();

	/* Create the new custom class with a call to MUI_CreateCustomClass(). */
	/* Caution: This function returns not a struct IClass, but a           */
	/* struct MUI_CustomClass which contains a struct IClass to be         */
	/* used with NewObject() calls.                                        */
	/* Note well: MUI creates the dispatcher hook for you, you may         */
	/* *not* use its h_Data field! If you need custom data, use the        */
	/* cl_UserData of the IClass structure!                                */

	if (!(mcc = MUI_CreateCustomClass(NULL,MUIC_Area,NULL,sizeof(struct Data),MyDispatcher)))
		fail(NULL,"Could not create custom class.");

	app = ApplicationObject,
		MUIA_Application_Title      , "Class4",
		MUIA_Application_Version    , "$VER: Class4 19.5 (12.02.97)",
		MUIA_Application_Copyright  , "©1993, Stefan Stuntz",
		MUIA_Application_Author     , "Stefan Stuntz",
		MUIA_Application_Description, "Demonstrate rendering from sub tasks.",
		MUIA_Application_Base       , "Class4",

		SubWindow, window = WindowObject,
			MUIA_Window_Title, "Subtask rendering",
			MUIA_Window_ID   , MAKE_ID('C','L','S','4'),
			WindowContents, VGroup,

				Child, HGroup, GroupSpacing(8),
					Child, ColGroup(2),
						Child, Label2("_Left:"  ), Child, strleft   = MUI_MakeObject(MUIO_String,"_L",30),
						Child, Label2("_Right:" ), Child, strright  = MUI_MakeObject(MUIO_String,"_R",30),
						End,
					Child, ColGroup(2),
						Child, Label2("_Top:"   ), Child, strtop    = MUI_MakeObject(MUIO_String,"_T",30),
						Child, Label2("_Bottom:"), Child, strbottom = MUI_MakeObject(MUIO_String,"_B",30),
						End,
					Child, MUI_MakeObject(MUIO_VBar,2),
					Child, start = VGroup,
						GroupSpacing(0),
						MUIA_Weight, 0,
						ButtonFrame,
						MUIA_InputMode , MUIV_InputMode_RelVerify,
						MUIA_Background, MUII_ButtonBack,
						Child, VSpace(0),
						Child, TextObject, MUIA_Text_Contents, "\33c  Start  ", End,
						Child, VSpace(0),
						End,
					End,

				Child, gauge = GaugeObject,
					GaugeFrame,
					MUIA_Gauge_Horiz, TRUE,
					MUIA_Gauge_Max, 100,
					MUIA_FixHeight, 8,
					End,

				Child, MyObj = NewObject(mcc->mcc_Class,NULL,
					TextFrame,
					MUIA_Background, MUII_BACKGROUND,
					TAG_DONE),

				End,
			End,
		End;

	if (!app)
		fail(app,"Failed to create Application.");

	set(window,MUIA_Window_DefaultObject, MyObj);

	DoMethod(window,MUIM_Notify,MUIA_Window_CloseRequest,TRUE,
		app,2,MUIM_Application_ReturnID,MUIV_Application_ReturnID_Quit);

	DoMethod(start,MUIM_Notify,MUIA_Pressed,FALSE,
		app,2,MUIM_Application_ReturnID,1);

	DoMethod(MyObj,MUIM_Notify,MUIA_Class4_Percent,MUIV_EveryTime,
		gauge,3,MUIM_Set,MUIA_Gauge_Current,MUIV_TriggerValue);

	set(strleft  ,MUIA_String_Contents,"-2.0");
	set(strright ,MUIA_String_Contents,"1.0");
	set(strtop   ,MUIA_String_Contents,"1.5");
	set(strbottom,MUIA_String_Contents,"-1.5");

/*
** Input loop...
*/

	set(window,MUIA_Window_Open,TRUE);

	while (running)
	{
		switch (DoMethod(app,MUIM_Application_Input,&signals))
		{
			case MUIV_Application_ReturnID_Quit:
				running = FALSE;
				break;

			case 1:
			{
				struct FractalDesc fd;

				fd.left   = atof(getstr(strleft  ));
				fd.right  = atof(getstr(strright ));
				fd.top    = atof(getstr(strtop   ));
				fd.bottom = atof(getstr(strbottom));

				if (fd.right > fd.left && fd.top > fd.bottom)
					DoMethod(MyObj,MUIM_Class4_Calc,&fd);
				else
					DisplayBeep(0);
			}
			break;
		}

		if (running && signals) Wait(signals);
	}

	set(window,MUIA_Window_Open,FALSE);


/*
** Shut down...
*/

	MUI_DisposeObject(app);     /* dispose all objects. */
	MUI_DeleteCustomClass(mcc); /* delete the custom class. */
	fail(NULL,NULL);            /* exit, app is already disposed. */
}
