/*
 * worm.c
 * 
 * Adapted from the Scientific American article 12/87. Original for Sun UNIX.
 * Original Author - Brad Taylor (sun!brad)
 * Adapted to the Amiga by Chuck McManis (sun!cmcmanis)
 * Basically there are five switches they are :
 *	-l n Set the length of the worms to 'n' units, this defaults to 32.
 *	-s n Set the size of the worm units to n X n, defaults to 1 but try
 *	     some larger sizes too, their kinda neat.
 *	-n n Set the number of worms on the screen, defaults to 16 worms which
 *	     is fairly slow, 4 is pretty quick. Numbers like 3000 are neat too.
 *	-r n Set the screen resolution, 0 = low res (320 X 200), 1 = medium (640 X 200)
 *	     and 2 = high (640 X 400)
 *	-d n Set number of bitplanes, 1-5 are acceptable numbers.
 *
 * Note this uses a random number generator that Leo used in demo.c
 * Copyright (C) 1987 by Charles McManis
 * Freely Redistributable if you leave the above comment and following declaration
 * intact ...
 */

static char copyright[] = "Copyright (C) 1987, Charles McManis, ok to copy.";

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <graphics/gfxbase.h>
#include <graphics/gfx.h>
#include <graphics/rastport.h>
#include <devices/inputevent.h>
#include <libraries/diskfont.h>
#include <stdio.h>
#include <math.h>
#ifdef LATTICE
#include <proto/dos.h>
#include <proto/graphics.h>
#endif


/* Declare the required Intuition pointers */
struct	IntuitionBase	*IntuitionBase;
struct	GfxBase		*GfxBase;
struct	DiskfontBase	*DiskfontBase;

/* Declare some global tool stuff */
struct 	Window		*MyWindow;
struct	Menu		*MyMenu, *CurrentMenu;
struct	Screen		*MyScreen;
struct  TextFont	*MyFont;

/* Declare some defines that make the code a bit more readable */
#define SIGBIT(w)	(1L << (w->UserPort->mp_SigBit))
#define GetIntuiMessage(w) (struct IntuiMessage *)GetMsg(w->UserPort)
struct TextAttr	ta = {"topaz.font",11,0,0};

#define CMSIZE 		30	/* Max color map size			    */
#define CMFUDGE 	2	/* colors needed for borders and background */
#define SEGMENTS  	36	/* Size of sin and cos tables		    */
#define MAXCOLOR	15	/* Most intense color 			    */

extern char *malloc();

int wormlength = 32;
int circsize = 1;
int nworms = 16;
int black,white;
int cmsize;
int sintab[SEGMENTS];
int costab[SEGMENTS];


/*
 * Initialized data section. These structures are pre-initialized with 
 * the values that the program knows ahead of time.
 */
 
struct NewScreen ns = {
	0,0,640,400,3,	/* (X,Y) (W,H) (bitplanes) 	*/
	1,0,		/* Foreground, Background  	*/
	0,		/* View Modes (defaults to none */
	CUSTOMSCREEN,	/* What else is new	   	*/
	NULL,		/* Font is set up below   	*/
	"Worms, implementation by Chuck McManis V1.0",
	NULL,		/* No special Gadgets	   	*/
	NULL		/* No Custom Bitmap	   	*/
	};

struct NewWindow nw = {
	0,0,640,400,	/* (X,Y) (W,H)			*/
	7,6,		/* Foreground, Background	*/
	CLOSEWINDOW+REFRESHWINDOW,
	WINDOWCLOSE+SIMPLE_REFRESH+ACTIVATE,
	NULL,		/* No special Gadgets		*/
	NULL,		/* Use the default Checkmark	*/
	"Squiggly Worms, Implementation by Chuck McManis",
	NULL,		/* This will be our screen pointer */
	NULL,		/* We'll use the Bitmap we get	*/
	640,400,640,400, /* No resizing going on */
	CUSTOMSCREEN	/* We'll use our own screen thankyou */
	};


/*
 * Some declarations for worm, 
 */

USHORT	colors[32];	/* A place to create the color table */
int	ScreenRes = 1;	/* Screen Resolution (0=low 2=hi)    */
ULONG	MaxX, MaxY;	/* Window inside dimensions 	     */

struct wormstuff {
	int *xcirc;
	int *ycirc;
	int dir;
	int tail;
	int x;
	int y;
	int id;
};


void worm_doit(),drawseg(),goaway(),usage(), color_setup();
short	rnd();
struct wormstuff *worm_init();

/* 
 * Ok, declarations are out of the way, on with the main function 
 */
void main(argc,argv)

int	argc;
char	*argv[];	
	
{
  struct IntuiMessage	*im;	  /* Intuimessage pointer 		*/
  struct Window		*wp;	  /* Temporary window pointer		*/
  ULONG			class;	  /* Message Class holder		*/
  USHORT		code,qual;/* Message Code and qualifiers 	*/
  SHORT			msx,msy;  /* Mouse Co-ordinates for event 	*/
  int 			i,	  /* Your basic counter variable	*/
			xsize,ysize,
			depth;	  /* How many bitplanes did you want?	*/
  struct wormstuff 	**worm;	  /* Worm data				*/
  char 			*cmd;

  cmd = argv[0];
  depth = 3;		/* default depth */
  rnd(0);
  if (argc == 0) { 	/* Started from workbench */
    printf("Worm length : ");
    scanf("%d",&wormlength);
    printf("Number of worms [n] :");
    scanf("%d",&nworms);
    printf("Worm width [n] :");
    scanf("%d",&circsize);
    printf("Screen Resolution [0=Low, 1=Medium, 2=High] :");
    scanf("%d",&ScreenRes);
    printf("Number of bitplanes [1-5 for Low res, 1-4 for medium or high] :");
    scanf("%d",&depth);
  }
  for (i = 1; i < argc; i++) {
    if (i == argc - 1 || argv[i][0] != '-' || argv[i][2] != 0) {
      usage(cmd);
    }
    switch (argv[i][1]) {
      case 'l':
	wormlength = atoi(argv[++i]);
	break;
      case 'n':
	nworms = atoi(argv[++i]);
	break;
      case 's':
	circsize = atoi(argv[++i]);
	break;
      case 'r':
    	ScreenRes = atoi(argv[++i]);
	break;
      case 'd':
	depth = atoi(argv[++i]);
	if ((depth < 1) || (depth > 5)) usage(cmd);
	break;
      default:
        usage(cmd);
    }
  }
  if ((ScreenRes > 0) && (depth == 5)) depth = 4; 
  cmsize = (1 << depth) - CMFUDGE;
  for (i = 0; i < SEGMENTS; i++) {
    sintab[i] = round(circsize * sin(i * 2 * PI / SEGMENTS));
    costab[i] = round(circsize * cos(i * 2 * PI / SEGMENTS));
  }
  /* Open the intuition library first ... */
  IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",0);
  if (! IntuitionBase) goaway();

  /* Now opening the graphics library ... */
  GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0);
  if (! GfxBase) goaway();

  /* Now opening the diskfont library ... */
  DiskfontBase = (struct DiskfontBase *)OpenLibrary("diskfont.library",0);
  if (! DiskfontBase) goaway();
 
  /* This opens a custom screen to the maximum possible size ... */
  ns.Depth = depth;
  ns.Width = GfxBase->NormalDisplayColumns;
  ns.Height = GfxBase->NormalDisplayRows;
  switch (ScreenRes) {
    case 0 : 
      ns.Width /= 2;
      ta.ta_YSize = 8;
      ta.ta_Flags |= FPF_ROMFONT;
      ns.Font = &ta;
      break;
    case 1 :
      ns.ViewModes |= HIRES;
      /* In medium res use Topaz 8 */
      ta.ta_YSize = 8;
      ta.ta_Flags |= FPF_ROMFONT;
      ns.Font = &ta;
      break;
    case 2 :
      ns.Height *= 2;
      ns.ViewModes |= HIRES+LACE;
      /* In Interlace mode us Topaz 11 */
      ns.Font = &ta;
  }

  /* Now we open the font we want so that it will be resident when the 
   * OpenScreen call below needs it.
   */
  MyFont = (struct TextFont *) OpenDiskFont(&ta);
  
  MyScreen = (struct Screen *)OpenScreen(&ns);

  if (! MyScreen) goaway();
  color_setup();		/* Initialize the color map */
  /* OK so far, now lets open a full size window ... */
  nw.Screen = MyScreen;
  nw.TopEdge = 1; /* Leave a row to grab the screen drag bar */
  nw.Width = ns.Width;
  nw.Height = ns.Height-1;
  nw.DetailPen = white;		/* initialized by color_setup() */
  nw.BlockPen = black;
  MyWindow = (struct Window *)OpenWindow(&nw);
  if (! MyWindow) goaway();

  SetFont(MyWindow->RPort,MyFont); /* This should not be required ! */

  MaxX = xsize = MyWindow->Width-MyWindow->BorderLeft-MyWindow->BorderRight;
  MaxY = ysize = MyWindow->Height-MyWindow->BorderTop-MyWindow->BorderBottom;
  SetAPen(MyWindow->RPort,black);
  RectFill(MyWindow->RPort, MyWindow->BorderLeft,MyWindow->BorderTop,
	MyWindow->BorderLeft+xsize, MyWindow->BorderTop+ysize);
  worm = (struct wormstuff **)malloc((unsigned)(sizeof(struct wormstuff *) * nworms));
  for (i = 0; i < nworms; i++) {
    worm[i] = worm_init(xsize, ysize, wormlength);
  }
  if (!cmsize) cmsize = 1;

  for (;;) {
    for (i = 0; i < nworms; i++) {
	worm_doit((void *)MyWindow->RPort, worm[i], xsize, ysize,
		  (((i * cmsize) / nworms) % cmsize));
    }
    /* Note: If we are running continuously we don't Wait(), this lets */
    /* the program continue to update the screen until the user sends  */
    /* it some form of request.           			       */
    while ((im = GetIntuiMessage(MyWindow)) != NULL) {
      class = im->Class;	/* Extract relavent info */
      code  = im->Code;		
      qual  = im->Qualifier;	/* Stuff like shift down etc */
      msx   = im->MouseX;	/* And the mouse position	*/
      msy   = im->MouseY;
      wp    = im->IDCMPWindow;	/* Figure out which window sent this message */
      ReplyMsg(im);		/* Reply quickly! */
      switch (class) {
	case CLOSEWINDOW : 	/* Process a close window message. */
	  goaway(); 		/* Exit the program */
	case REFRESHWINDOW :
	  BeginRefresh(MyWindow);
	  EndRefresh(MyWindow,TRUE);
	  break;
        default : /* We choose to ignore unknown message classes */
	  break;
      } /* switch statement */
    } /* until no messages are still waiting */
  }
}

/*
 * Utility routines used by the Worm Program...
 */

/*
 * Function goaway()
 *
 * This function shuts down after we are done. Since this can happen at 
 * anytime, it checks the various global variables and those that have
 * been initialized (non-null), it removes/clears etc depending on the 
 * variable.
 */
void goaway()

{
  if (MyWindow) CloseWindow(MyWindow);
  if (MyScreen) CloseScreen(MyScreen);
  if (GfxBase) CloseLibrary(GfxBase);
  if (MyFont) CloseFont(MyFont);
  if (DiskfontBase) CloseLibrary(DiskfontBase);
  if (IntuitionBase) CloseLibrary(IntuitionBase);
  exit(0); /* This cleans up FILE pointers and malloc'd memory */
}

struct wormstuff *
worm_init(xsize, ysize, wormlength)
	int xsize;
	int ysize;
	int wormlength;
{
	static int thisworm = 0;
	int i;
	struct wormstuff *ws;

	ws = (struct wormstuff *)malloc((unsigned)sizeof(struct wormstuff));
	ws->xcirc = (int *)malloc((unsigned)(wormlength * sizeof(int)));
	ws->ycirc = (int *)malloc((unsigned)(wormlength * sizeof(int)));
	for (i = 0; i < wormlength; i++) {
		ws->xcirc[i] = xsize / 2;
		ws->ycirc[i] = ysize / 2;
	}
	ws->dir = rnd(SEGMENTS);
	ws->tail = 0;
	ws->x = xsize / 2;
	ws->y = ysize / 2;
	ws->id = thisworm;
	thisworm = (thisworm + 1) % 14;
	return (ws);
}

void
worm_doit(h, priv, xsize, ysize, color)
	void *h;
	void *priv;
	int xsize;
	int ysize;
	int color;
{
	int x;
	int y;
	struct wormstuff *ws = (struct wormstuff *)priv;

	ws->tail = (ws->tail + 1) % wormlength;
	x = ws->xcirc[ws->tail];
	y = ws->ycirc[ws->tail];
	drawseg(h, x, y, black);
	if ((rnd(32765)&(1<<ws->id)) != 0) {
		ws->dir = (ws->dir + 1) % SEGMENTS;
	} else {
		ws->dir = (ws->dir + SEGMENTS - 1) % SEGMENTS;
	}
	x = (ws->x + costab[ws->dir] + xsize) % xsize;
	y = (ws->y + sintab[ws->dir] + ysize) % ysize;
	ws->xcirc[ws->tail] = x;
	ws->ycirc[ws->tail] = y;
	/* Note when using one bitplane worm_doit gets called with color == 0 */
	drawseg(h, x, y, (color != black) ? color : 1);
	ws->x = x;
	ws->y = y;
}



round(x)
	float x;
{
	if (x >= 0) return ((int)(x + .5));
	return ((int)(x - .5));
}

/* Note that drawseg does it's own clipping... */
void
drawseg(priv, x, y, color)
	void *priv;
	int x;
	int y;
	int color;
{
	struct RastPort *gfx = (struct RastPort *)priv;
	ULONG	X,Y;

	X = ((unsigned) x % (MaxX-circsize)) + MyWindow->BorderLeft;
        Y = ((unsigned) y % (MaxY-circsize)) + MyWindow->BorderTop;
	SetAPen(gfx,color);
	if (circsize == 1) WritePixel(gfx,X,Y);
	else RectFill(gfx,X,Y,X+circsize,Y+circsize);
}


/* Sets up some reasonable colors ... note CMSIZE is max colors and 
 * cmsize is the actual number of colors
 */

void
color_setup()

{
	int i;

	for (i = 0; i < cmsize; i++) {
	  colors[i] = rnd(4096);
	}
	colors[cmsize] = 0;
	colors[cmsize+1] = 4095;
  	LoadRGB4(&(MyScreen->ViewPort),colors,cmsize+CMFUDGE); /* Load in our colors */
	black = cmsize;
	white = cmsize+1;
}

void
usage(cmd)
	char *cmd;
{
	(void)fprintf(stderr,
 "usage: %s [-l length] [-s size] [-n number] [-r resolution] [-d depth]\n", cmd);
	exit(1);
}
