/* --------------------------------------------------------------------
 *
 *             A christmas blanker inspired by xsnow
 *
 *	initialization/cleanup code copied from the dragon module
 *      (dragon module: (C)Alexander Kneer and Michael D. Bayne)
 * 
 * -------------------------------------------------------------------- */


#include <exec/memory.h>
#include <math.h>
#include <stdlib.h>

#include "/includes.h"

#define STAGES 2		/* Number of different shapes */
#define BLANKDELAY 100		/* This many snowflakes are displayed before
				 * we check if to continue blanking */
#define COUNTLIMIT 6		/* max. number of moves in one direction */
#define COUNTMIN 1		/* min. number of moves in one direction */

/* random generation function macros */
#define COUNTFUNC RangeRand(COUNTLIMIT-COUNTMIN)+COUNTMIN
#define DXFUNC    RangeRand(5)-2;
#define DYFUNC    RangeRand(2)+1;

typedef enum { false, true } bool;

bool BIGFLAKES;
int  NUMSNOW;
bool COLLECT;

int patx[10] = {  -1,-1, 1, 1, 0  , -1, 1, 0, 0, 0 };
int paty[10] = {  -1, 1,-1, 1, 0  ,  0, 0, 1,-1, 0 };

int *collect;

typedef struct 
  { int x,y;			/* The current coordinates */
    int dx,dy;			/* Movement direction is (dx,dy) */
    int count;			/* How long to keep direction dx */
    int stage;			/* STAGES different shapes */
  } snowflake;


Triplet *ColorTable = 0L;


#define PR_FLAKE   0
#define PR_BIG     2
#define PR_COLLECT 4
#define PR_MODE    6

VOID Defaults( PrefObject *Prefs )
{
  Prefs[PR_FLAKE].po_Level = 200;
  Prefs[PR_BIG].po_Level = 1;
  Prefs[PR_COLLECT].po_Level = 1;
  Prefs[PR_MODE].po_ModeID = getTopScreenMode();
}


/* Remove a single snowflake's image from the screen */
__inline void undraw(snowflake *flake, struct RastPort *rp)
{ 
  if (BIGFLAKES)
    {
      int *startx = patx + 5*(flake->stage);
      int *starty = paty + 5*(flake->stage);
      int i;
      SetAPen(rp,0);
      for (i=0; i<5; i++)
	{ WritePixel(rp,flake->x+(*startx),flake->y+(*starty));
	  startx++;
	  starty++;
	}
    }
  else
    {
      SetAPen(rp,0);
      WritePixel(rp,flake->x,flake->y);
    }
}


/* Draw a single snowflake's image to the screen */
__inline void draw(snowflake *flake, struct RastPort *rp)
{ 
  if (BIGFLAKES)
    {
      int *startx = patx + 5*(flake->stage);
      int *starty = paty + 5*(flake->stage);
      int i;
      SetAPen(rp,1);
      for (i=0; i<5; i++)
	{ WritePixel(rp,flake->x+(*startx),flake->y+(*starty));
	  startx++;
	  starty++;
	}
    }
  else
    { SetAPen(rp,1);
      WritePixel(rp,flake->x,flake->y);
    }
}


/* change the position of a snowflake. If it leaves [0..width]x[0..height],
 * generate a new one with coordinates (<random>,0) */
__inline void update(snowflake *curr, int width, int height, struct RastPort *rp)
{ bool gen_new;
  int oldx = curr->x;
  if (curr->count > 0)
    { curr->y = curr->y + curr->dy;
      if (curr->y >= height-1)
	{ gen_new = true;
	}
      else
	{ curr->x = curr->x + curr->dx;
	  if (curr->x < 1 || curr->x >= width-1)
	    { gen_new = true;
	    }
	  else 
	    { gen_new = false;
	      (curr->count)--;
	      if (curr->count <= 0)
		{ curr->dx = DXFUNC;
		  curr->count = COUNTFUNC;
		}
	      if (curr->stage)
		{ curr->stage--;
		}
	      else
		{ curr->stage = STAGES-1;
		}
	    }
	}
    }
  
  /* do we have to compute a new flake? */
  if (gen_new)
    { if (collect && curr->y > height-3)
	{ collect[oldx]--;
	  if (collect[oldx]<height-20)
	    { collect[oldx]=height-20;
	    }
	  
	  curr->x = oldx;
	  curr->y = collect[oldx];
	  draw(curr,rp);
	}
      curr->x = RangeRand(width-2)+1;
      curr->y = 1;
      curr->dx = DXFUNC;
      curr->dy = DYFUNC;
      curr->count = COUNTFUNC;
      curr->stage = RangeRand(STAGES);
    }
}




LONG snow(struct Screen *scr, SHORT width, SHORT height)
{
  unsigned int i;
  LONG flg_end;
  snowflake *flake,*curr;
  int blankcount;
  struct RastPort *rp = &(scr->RastPort);

  /* initialize the snow array */
  flake = malloc(NUMSNOW*sizeof(snowflake));
  curr = flake;
  for (i=0; i<NUMSNOW; i++)
    { curr->x = RangeRand(width-2)+1;
      curr->y = RangeRand(height-2)+1;
      curr->dx = DXFUNC;
      curr->dy = DYFUNC;
      curr->count = COUNTFUNC;
      curr->stage = RangeRand(STAGES);
      curr++;
    }

  /* and the collect array */
  if (COLLECT)
    { collect = malloc(sizeof(int)*width);
      for (i=0; i<width; i++)
        { collect[i] = height-2;
        }
      collect[0] = 0;
      collect[width-1] = 0;
    }
  else
    { collect = NULL;
    }

  /* Update all snowflakes forever. Update includes removal from the screen,
   * computation of new position, drawing on the screen */
  blankcount = BLANKDELAY;
  while (1)
    { curr = flake;
      for (i=0; i<NUMSNOW; i++)
	{ /* Remove curr's image */
	  undraw(curr,rp);
	  /* compute new position and appearance */
	  update(curr,width,height,rp);
	  /* and display it */
	  draw(curr,rp);
	  /* can we continue? */
	  if (!blankcount)
	    { blankcount = BLANKDELAY;
	      flg_end = ContinueBlanking();
	      ScreenToFront(scr);
	      if (flg_end != OK)
		{ free(flake);
		  if (collect) free(collect);
		  return flg_end;
		}
	    }
	  else
	    { blankcount--;
	    }
	  curr++;
	}
    }
  return OK;			/* just for the compiler */
}


LONG Blank( PrefObject *Prefs )
{
	struct Screen *Scr;
	struct Window *Wnd;
	LONG RetVal;
	
        BIGFLAKES = Prefs[PR_BIG].po_Level;
	NUMSNOW   = Prefs[PR_FLAKE].po_Level;
	COLLECT   = Prefs[PR_COLLECT].po_Level;

	if( Scr = OpenScreenTags( NULL, SA_Depth, 1,
							 SA_Quiet, TRUE, SA_DisplayID, Prefs[PR_MODE].po_ModeID,
							 SA_Behind, TRUE, SA_Overscan, OSCAN_STANDARD,
							 TAG_DONE ))
	{
		SetRGB4(&( Scr->ViewPort ), 0, 0, 0, 0 );
		ColorTable = RainbowPalette( Scr, 0L, 1L, 0L );
		SetRGB4(&( Scr->ViewPort ), 1, 15, 15, 15 );
		Wnd = BlankMousePointer( Scr );
		
		do
			RetVal = snow( Scr, Scr->Width, Scr->Height );
		while( RetVal == OK );
		
		UnblankMousePointer( Wnd );
		RainbowPalette( 0L, ColorTable, 1L, 0L );
		CloseScreen( Scr );
	}
	else
		RetVal = FAILED;
	
	return RetVal;
}
