/*
 * 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.
 *
 * Batch processing department.
 */

#include <stdio.h>
#include <ctype.h>

#include <exec/types.h>
#include <intuition/intuition.h>
#include "mandel.h"

#ifdef DEBUG
#   undef STATIC
#   define STATIC   /* EMPTY */
#endif

/*
 *  QUIT
 *  STOP
 *  WAIT
 *  FIXRAINBOW
 *  COLOR pen r g b
 *  DRAW
 *  FILLIN
 *  SHIFT hfraction vfraction
 *  SAVEAS filename
 *  OPEN filename
 *  FIXDISPLAY
 *  BATCH filename
 *  SELECT depth pen
 *  MODULO
 *  RANGES
 *  PROGRAM
 *  PRELUDE
 *  R
 *  C
 *  END
 *  REG 	regno value
 *  LEFTEDGE	rval
 *  RIGHTEDGE	rval
 *  TOPEDGE	ival
 *  BOTTOMEDGE	ival
 *  PIXELSTEP	value
 *  NUMCOLORS	value
 *  MAXDEPTH	value
 *  RANGEWIDTH	value
 *  BORDERLESS	value
 *  HIRES	0 or 1
 *  INTERLACE	0 or 1
 *  HALFBRITE	0 or 1
 *  FUNCTION	1 to 3
 *  IPLOT	1 to 2
 *  EPLOT	1 to 2
 *  DRAWPRI	-128 to -1
 *  WBWIDTH	value
 *  WBHEIGHT	value
 *  RAINDIST	value
 *  RAINRMAX	value
 *  RAINGMAX	value
 *  RAINBMAX	value
 */

extern void SetDrawingFunction(), SetIPlotFunction(), SetEPlotFunction();

#define BATCH_MAGIC_BUT_TRUE	(-42)
#define BATCH_COMMAND_NOT_FOUND (-1)

#define BATCHLINESIZE		256

STATIC char BatchLine[BATCHLINESIZE];	/* Batch command line buffer */

#ifdef AREXX

short BatchWaiting;		/* Don't accept any commands */

#endif

struct Program *SetPC;		/* Where to place new instruction */
short ProgramPart;		/* 0 = prelude, 1 = program */

STATIC bool SetDouble(), SetInt(), SetShort(), Cmd_quit(), Cmd_stop(),
    Cmd_wait(), Cmd_fixrainbow(), Cmd_color(), Cmd_draw(), Cmd_shift(),
    Cmd_loadsave(), Cmd_fixdisplay(), Cmd_borderless(), Cmd_viewmodes(),
    Cmd_function(), Cmd_batch(), Cmd_drawpri(), Cmd_select(),
    Cmd_pentablemode(), Cmd_program(), Cmd_r(), Cmd_end(), Cmd_reg();

bool GetLine();

STATIC struct BatchCmdDescription {
    char *keyword;	/* command string to be matched */
    int (*handler)();   /* handler to be called: (*handler)(harg [,args]) */
    void *harg; 	/* special argument to pass to handler */
};

/*
 *  The following commands take no arguments, or
 *  do their own parsing of the command tail.
 */

STATIC struct BatchCmdDescription Batch_void[] = {
    "program",          Cmd_program,        (void *)1,
    "prelude",          Cmd_program,        (void *)0,
    "r",                Cmd_r,              (void *)'r',
    "c",                Cmd_r,              (void *)'c',
    "end",              Cmd_end,            NULL,
    "fixrainbow",       Cmd_fixrainbow,     NULL,
    "color",            Cmd_color,          NULL,
    "draw",             Cmd_draw,           (void *)(long)FALSE,
    "fillin",           Cmd_draw,           (void *)(long)TRUE,
    "shift",            Cmd_shift,          NULL,
    "fixdisplay",       Cmd_fixdisplay,     NULL,
    "batch",            Cmd_batch,          NULL,
    "select",           Cmd_select,         NULL,
    "modulo",           Cmd_pentablemode,   (void *)MODULO,
    "ranges",           Cmd_pentablemode,   (void *)RANGES,
    "reg",              Cmd_reg,            (void *)0,
    "saveas",           Cmd_loadsave,       (void *)SaveAs,
    "open",             Cmd_loadsave,       (void *)OpenAs,
    "stop",             Cmd_stop,           NULL,
    "wait",             Cmd_wait,           NULL,
    "quit",             Cmd_quit,           NULL,
    NULL,		NULL,		    NULL
};

/*
 *  The following commands take 1 double %lf argument
 */

STATIC struct BatchCmdDescription Batch_lf[] = {
    "leftedge",         SetDouble,          &LeftEdge,
    "rightedge",        SetDouble,          &RightEdge,
    "topedge",          SetDouble,          &TopEdge,
    "bottomedge",       SetDouble,          &BottomEdge,
    NULL,		NULL,		    NULL
};

/*
 *  The following commands take 1 int (decimal) %d argument
 */

STATIC struct BatchCmdDescription Batch_d[] = {
    "pixelstep",        SetInt,             &PixelStep,
    "numcolors",        SetInt,             &NumColors,
    "maxdepth",         SetInt,             &MaxDepth,
    "rangewidth",       SetInt,             &RangeWidth,
    "borderless",       Cmd_borderless,     NULL,
    "hires",            Cmd_viewmodes,      (void *)HIRES,
    "interlace",        Cmd_viewmodes,      (void *)LACE,
    "halfbrite",        Cmd_viewmodes,      (void *)EXTRA_HALFBRITE,
    "function",         Cmd_function,       (void *)SetDrawingFunction,
    "iplot",            Cmd_function,       (void *)SetIPlotFunction,
    "eplot",            Cmd_function,       (void *)SetEPlotFunction,
    "drawpri",          Cmd_drawpri,        NULL,
    NULL,		NULL,		    NULL
};

/*
 *  The following commands take 1 short int (decimal) %hd argument
 */

STATIC struct BatchCmdDescription Batch_hd[] = {
    "wbwidth",          SetShort,           &WBWidth,
    "wbheight",         SetShort,           &WBHeight,
    "raindist",         SetShort,           &RainbowDistance,
    "rainrmax",         SetShort,           &RainbowRMax,
    "raingmax",         SetShort,           &RainbowGMax,
    "rainbmax",         SetShort,           &RainbowBMax,
    NULL,		NULL,		    NULL
};

/*
 *  This is a simple function:
 *  it just assigns a value to a variable of type double.
 */

STATIC bool SetDouble(harg, arg1)
void *harg;
double arg1;
{
    *(double *)harg = arg1;

    return TRUE;
}

/*
 *  This is a simple function:
 *  it just assigns a value to a variable of type int.
 */

STATIC bool SetInt(harg, arg1)
void *harg;
int arg1;
{
    *(int *)harg = arg1;

    UpdateOptDrawResCm();

    return TRUE;
}

/*
 *  This is a simple function:
 *  it just assigns a value to a variable of type short.
 */

STATIC bool SetShort(harg, arg1)
void *harg;
short arg1;
{
    *(short *)harg = arg1;

    return TRUE;
}

/*
 *  Handle QUIT command.
 */

STATIC bool Cmd_quit()
{
    finished = TRUE;

    return TRUE;
}

/*
 *  Handle STOP command.
 */

STATIC bool Cmd_stop()
{
    StopDrawing();

    return TRUE;
}

STATIC bool Cmd_wait()
{
#ifdef AREXX
    /*
     *	Halt all Arexx traffic as well...
     *	This admittedly is a bit too rude, but as long as I can't test
     *	better approaches...
     */
    BatchWaiting = TRUE;
#endif

    return BATCH_MAGIC_BUT_TRUE;
}

STATIC bool Cmd_fixrainbow()
{
    extern SHORT RRMax; /* variable of the palette */

    RRMax = -5; 	/* force color updating */
    ModifyColors();     /* routine of the palette */

    return TRUE;
}

STATIC bool Cmd_color(harg, tail)
void *harg;
char *tail;
{
    ULONG color, newred, newgreen, newblue;

    if (sscanf(tail, "%ld %ld %ld %ld",
	       &color, &newred, &newgreen, &newblue) == 4) {
	if (color < NumColors && color < MAXCOL) {
	    struct ViewPort *vp = &MandelScreen->ViewPort;
	    SetRGB4(vp, color, newred, newgreen, newblue);
	    return TRUE;
	}
    }

    return FALSE;
}

STATIC bool Cmd_draw(fillin)
void *fillin;
{
    return DrawPicture((bool)(long)fillin);
}

STATIC bool Cmd_shift(harg, tail)
void *harg;
char *tail;
{
    double HShift, VShift;
    double Width  = RightEdge - LeftEdge,
	   Height = BottomEdge - TopEdge;

    if (sscanf(tail, "%lf %lf", &HShift, &VShift) == 2) {
	LeftEdge   = LeftEdge + HShift * Width;
	RightEdge  = RightEdge + HShift * Width;
	TopEdge    = TopEdge + VShift * Height;
	BottomEdge = BottomEdge + VShift * Height;

	return TRUE;
    }

    return FALSE;
}

STATIC char *ParseString(line)
char *line;
{
    static char tmp[80];

    if (line[0] == '"' || line[0] == '\'') {    /* Quoted ? */
	char *end;

	strncpy(tmp, ++line, sizeof(tmp)-2);
	tmp[sizeof(tmp)-1] = '\0';
	end = index(tmp, line[-1]);
	if (end == NULL)
	    return NULL;
	*end = '\0';

	return tmp;
    }

    return line;
}

STATIC bool Cmd_loadsave(harg, tail)
void *harg;	/* Either OpenAs or SaveAs */
register char *tail;
{
    tail = ParseString(tail);

    /*
     *	Cast harg into a pointer to a function returning bool
     *	and call that function (OpenAs or SaveAs) with the name.
     */

    return (* (bool (*)()) harg) (tail);
}

STATIC bool Cmd_borderless(harg, d)
void *harg;
int d;
{
    if (d)
	DoBorderless(MainWindow, &borderinfo);
    else
	UndoBorderless(MainWindow, &borderinfo);

    UpdateOptViewResCm();

    return TRUE;
}

STATIC bool Cmd_viewmodes(harg, d)
void *harg;
int d;
{
    if (d)
	MandelNScreen.ViewModes |= (USHORT)(long)harg;
    else
	MandelNScreen.ViewModes &= ~(USHORT)(long)harg;

    UpdateOptViewResCm();

    return TRUE;
}

STATIC bool Cmd_fixdisplay()
{
    return !ReInitDisplay();    /* returns failure... */
}

STATIC bool Cmd_function(harg, d)      /* Numbered from 1 */
void *harg;
int d;
{
    (* (void (*)()) harg) (d - 1);
    UpdateDrwCm();

    return TRUE;
}

STATIC bool Cmd_batch(harg, tail)
void *harg;
char *tail;
{
    tail = ParseString(tail);

    return OpenBatch(tail);
}

STATIC bool Cmd_drawpri(harg, dpri)
void *harg;
int dpri;
{
    SetDrawPri(dpri);
    UpdateOptPriCm();

    return TRUE;
}

STATIC bool Cmd_select(harg, tail)
void *harg;
char *tail;
{
    int depth, pen;

    if (sscanf(tail, "%d %d", &depth, &pen) == 2 &&
	depth < MAXDEPTH &&
	depth >= 0
    ) {
	PenTable[depth] = pen;
	PenTableMode = SELECT;
	UpdateOptColorCm();

	return TRUE;
    }

    return FALSE;
}


STATIC bool Cmd_pentablemode(harg)
void *harg;
{
    PenTableMode = (long) harg;
    InitPenTable();
    UpdateOptColorCm();

    return TRUE;
}

/*-
 *  After the PROGRAM command, a small program of the following form
 *  must follow:
 *
 *	{"r", "c"} reg1 "=" reg2 [ {"i", "+", "-", "*"} reg3 ]
 *
 *  This fits in the sscanf pattern
 *
 *	"%1s %d = %d %1s %d"
 *
 *  which should match either 3 or 5 arguments.
 *
 *  This function only resets the program counter to the correct place.
 *  The program lines with r or c are handled by Cmd_r().
-*/

STATIC bool Cmd_program(harg)
void *harg;		    /* 1 for PROGRAM, 0 for PRELUDE */
{
    int match;
    register struct Program *pc = SetPC;

    /* Find where to begin this program */
    pc = &Program[0];
    ProgramPart = match = (long)harg;

    while (match--) {
	while (pc->pr_OpCode != End) {
	    pc++;
	    /*
	     *	This should not even be necessary since End == 0 and
	     *	uninitialized static arrays contain binary zeros.
	     */
	    if (pc >= &Program[PROGRAMSIZE]) {
		pc = &Program[0];
		Program[0].pr_OpCode = End;
		Program[1].pr_OpCode = End;
		goto break_loops;
	    }
	}
	pc++;
    }
break_loops:
    SetPC = pc;

    return TRUE;
}

STATIC bool Cmd_r(harg, tail)
void *harg;		    /* 'r' or 'c' */
char *tail;
{
    auto char op[2];
    auto int reg1, reg2, reg3;
    int match;
    register struct Program *pc = SetPC;

    pc->pr_OpCode = End;    /* safety */
    pc->pr_Dest = ProgramPart;

    match = sscanf(tail, "%d = %d %1s %d", &reg1, &reg2, &op[0], &reg3);

    pc->pr_Dest = reg1;
    pc->pr_Op1 = reg2;

    if (pc >= &Program[PROGRAMSIZE-1])
	return FALSE;

    if (match == 2) {
	/* Must be an assignment */
	pc->pr_OpCode = Rassign;
    } else if (match == 4) {
	pc->pr_Op2 = reg3;
	switch (op[0]) {
	case 'i':
	    pc->pr_OpCode = Ri;
	    break;
	case '+':
	    pc->pr_OpCode = Rplus;
	    break;
	case '-':
	    pc->pr_OpCode = Rminus;
	    break;
	case '*':
	    pc->pr_OpCode = Rtimes;
	    break;
	}
    } else {
	return FALSE;	    /* Parse errors are done rudely */
    }
    if (tolower((char)(long)harg) == 'c') {
	pc->pr_OpCode++;    /* Make the opertation complex */
    }
    pc++;

    SetPC = pc;
    return TRUE;
}


STATIC bool Cmd_end(harg, tail)
void *harg;
char *tail;
{
    register struct Program *pc = SetPC;

    pc->pr_OpCode = End;    /* safety */
    pc->pr_Dest = ProgramPart++;

    SetPC++;

    return TRUE;
}

STATIC bool Cmd_reg(harg, tail)
void *harg;
char *tail;
{
    auto int regno;
    auto double value;

    if (sscanf(tail, "%d %lf", &regno, &value) == 2) {
	if (regno >= 0 && regno < 2*PROGRAMREGS) {
	    PrgReg[regno] = value;
	    return TRUE;
	}
    }
    return FALSE;
}

/*-----------------------------------------------------------------------*/

STATIC char *SkipNonWhite(line)
register char *line;
{
    if (line)
	while (*line && !isspace(*line))
	    line++;

    return line;
}

STATIC char *SkipWhite(line)
register char *line;
{
    if (line)
	while (*line && isspace(*line))
	    line++;

    return line;
}

/*
 *   This is the function we use to see if the command matches
 *   the command string.  Not case sensitive.  Make sure all commands
 *   are given in lower case! [taken from MinRexx by Radical Eye Software]
 *   0 means equal enough, all other values mean not equal enough.
 */

STATIC int cmdcmp(c, m)
register char *c, *m ;
{
    while (*c && ((*c == *m) || (*c == *m + 32 && ('a' <= *c && *c <= 'z')))) {
	c++ ;
	m++ ;
    }
    if (*m && !isspace(*m))
	return 1;
    return(*c) ;
}

/*
 *  Search a table of BatchCmdDescriptions for a keyword.
 *  When found, return a pointer to it.
 *  Otherwise, return NULL.
 */

STATIC struct BatchCmdDescription *SearchTable(table, string)
register struct BatchCmdDescription *table;
register char *string;
{
    while (table->keyword) {
	if (cmdcmp(table->keyword, string) == 0)
	    return table;
	table++;
    }

    return NULL;
}

/*
 *  This executes a batch command line. Return value indicates success.
#ifdef AREXX
 *  Can also be called from ARexx interface code.
#endif
 */

bool ExecuteBatchCommand(command)
register char *command;
{
    char *tail;
    char *ptr;
    bool success;
    struct BatchCmdDescription *bcmdd;

    command = SkipWhite(command);

    if (command == NULL   ||
	command[0] == ';' ||
	command[0] == '/' ||
	command[0] == '#' )
	return TRUE;

    tail = ptr = SkipNonWhite(command);
    if (*tail)
	tail = SkipWhite(tail);

    if (bcmdd = SearchTable(Batch_void, command)) {
	success = (*bcmdd->handler)(bcmdd->harg, tail);
    } else if (bcmdd = SearchTable(Batch_d, command)) {
	auto int d;
	success = (sscanf(tail, "%d", &d) == 1) &&
		  (*bcmdd->handler)(bcmdd->harg, d);
    } else if (bcmdd = SearchTable(Batch_hd, command)) {
	auto short hd;
	success = (sscanf(tail, "%hd", &hd) == 1) &&
		  (*bcmdd->handler)(bcmdd->harg, hd);
    } else if (bcmdd = SearchTable(Batch_lf, command)) {
	auto double lf;
	success = (sscanf(tail, "%lf", &lf) == 1) &&
		  (*bcmdd->handler)(bcmdd->harg, lf);
    } else
	success = BATCH_COMMAND_NOT_FOUND;

    return success;

}

void CloseBatch()
{
    if (BatchFILE) {
	fclose(BatchFILE);
	BatchFILE = NULL;
    }

    OffMenu(MainWindow, MENU(BATMENU, BATCONT,  NOSUB));
    OffMenu(MainWindow, MENU(BATMENU, BATWAIT,  NOSUB));
    OffMenu(MainWindow, MENU(BATMENU, BATABORT, NOSUB));
}

bool OpenBatch(name)
register char *name;
{
    CloseBatch();

#ifdef AREXX
    if (asyncRexxCmd(name))
	return TRUE;
#endif

    BatchFILE = fopen(name, "r");

    OnMenu(MainWindow, MENU(BATMENU, BATABORT, NOSUB));

    if (BatchFILE == NULL) {
	printf("Mandel: Cannot open batch file `%s'.\n", name);
    } else if (isatty(fileno(BatchFILE))) {
	printf("Mandel: Batch file `%s' is interactive.\n", name);
	CloseBatch();
    }

    return BatchFILE != NULL;
}

STATIC bool GetLine()
{
    if (fgets(BatchLine, BATCHLINESIZE, BatchFILE) != NULL) {
	register char *newline;
	if (newline = index(BatchLine, '\n')) {
	    *newline = '\0';
	} /* else silently truncate line */

	return TRUE;
    }

    return FALSE;
}

/*
 *  This is the main entry point into the batch system.
 *  It fetches lines from the batch file (until EOF) and executes it.
 *  It stops doing that if a routine returns the special value
 *  BATCH_MAGIC_BUT_TRUE, or FALSE.
 *
 *  Ideally, this should also run as a separate task.
 */

bool Batch()
{
    bool result = TRUE;

    BatchWaiting = FALSE;

    if (BatchFILE != NULL) {
	OnMenu( MainWindow, MENU(BATMENU, BATWAIT, NOSUB));
	OffMenu(MainWindow, MENU(BATMENU, BATCONT, NOSUB));

	while (GetLine()) {
	    result = ExecuteBatchCommand(BatchLine);
	    if (result == FALSE) {
		printf("Batch command failed:\n%s\n", BatchLine);
	    } else if (result == BATCH_COMMAND_NOT_FOUND) {
		printf("Unknown batch command:\n%s\n", BatchLine);
		break;
	    } else if (result == BATCH_MAGIC_BUT_TRUE) {
		/* Give up volontarily (wait command) */
		OnMenu(MainWindow, MENU(BATMENU, BATCONT, NOSUB));
		break;
	    }
	    if (MainWindow->UserPort->mp_MsgList.lh_Head->ln_Succ) {
		/*
		 *  Give up because of an intui-message. Signal
		 *  ourselves to continue later on. This should not
		 *  be done when we just did a wait command (see above).
		 */
		Signal(MandelTask, DrawSigMask);
		break;
	    }
	}

	OffMenu(MainWindow, MENU(BATMENU, BATWAIT, NOSUB));

	if (feof(BatchFILE)) {
	    CloseBatch();
	}
    }

    return result;
}

/*
 *  This routine checks command line options
 */

void Options(argc, argv)
int argc;
char **argv;
{
    if (argc > 2 || (argc == 1) && argv[1][0] == '?')
	printf("Usage: %s [batchfile]\n", argv[0]);
    else if (argc == 2) {
	OpenBatch(argv[1]);
    }
}

/*
 *  Batch menu handler
 */

void BatchMenu(Code)
USHORT Code;
{
    char Name[DNAME_SIZE + FNAME_SIZE + 3];
    int Item = ITEMNUM(Code);

    switch(Item) {
    case BATFILE:
	NameValid = FALSE;
	if ( get_fname(MainWindow, "Select a BATCH file",
		FileName, DirName) == NULL ) break;
	CatFileComponents(Name, DirName, FileName);
	OpenBatch(Name);
	/* Fall through to start batch */
    case BATCONT:
	Signal(MandelTask, DrawSigMask);    /* Set the batch signal */
	break;
    case BATWAIT:
	SetSignal(0L, DrawSigMask);         /* Clear the batch signal */
	OnMenu( MainWindow, MENU(BATMENU, BATCONT, NOSUB));
	break;
    case BATABORT:
	CloseBatch();
	break;
    }
}
