/* --------------------------------------------------------------------
 *
 *             A christmas blanker inspired by xsnow
 *
 *     This is an advanced but slower version compared to snow
 *
 *	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, random } bool;

bool BIGFLAKES;
int  NUMSNOW;
bool COLLECT;
bool SHOWSTARS;
bool SHOWSNOWMEN;
bool SHOWTREES;
bool SHOWBG;
int  NUMOBJ;


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;
int *starx;
int *stary;
int starnum;

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 */
    short int mem[5];           /* stores the pixels below its shape */
  } snowflake;


/* Color pens */
#define CBLACK 0
#define CWHITE 1
#define CGREEN 2
#define CBROWN 3
#define CDGREY 5
#define CMGREY 6
#define CLGREY 7
#define CYELLOW 4


Triplet *ColorTable = 0L;


#define PR_FLAKE   0
#define PR_BIG     2
#define PR_COLLECT 3
#define PR_STARS   5
#define PR_BG      6
#define PR_SNMAN   8
#define PR_TREE    9
#define PR_OBJ     11
#define PR_MODE    13

VOID Defaults( PrefObject *Prefs )
{
  Prefs[PR_FLAKE].po_Level = 200;
  Prefs[PR_BIG].po_Level = 1;
  Prefs[PR_COLLECT].po_Level = 1;
  Prefs[PR_STARS].po_Level = 1;
  Prefs[PR_BG].po_Level = 1;
  Prefs[PR_SNMAN].po_Level = 1;
  Prefs[PR_OBJ].po_Level = 20;
  Prefs[PR_TREE].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;
      for (i=0; i<5; i++)
	{ if (flake->mem[i] != -1)
            { SetAPen(rp,flake->mem[i]);
              WritePixel(rp,flake->x+(*startx),flake->y+(*starty));
            }
   	  startx++;
	  starty++;
	}
    }
  else
    { if (flake->mem[0] != -1)
        { SetAPen(rp,flake->mem[0]);
          WritePixel(rp,flake->x,flake->y);
        }
    }
}


__inline void WriteLine(struct RastPort *rp, int width, int height, int x, int y, int scale, 
                        int x1, int y1, int x2, int y2)
{ x1=x1*4*scale; y1=y1*4*scale; x2=x2*4*scale; y2=y2*4*scale;
  /* all coordinates are now 1.000 times too big */
  Move(rp,x+x1/1000,y+y1/1000);
  Draw(rp,x+x2/1000,y+y2/1000);
}


void draw_tree(struct RastPort *rp, int width, int height)
{ int x,y;
  int scale;
  x = RangeRand(width-42);
  y = RangeRand((height*3)/4-42)+height/4;
  scale = (y*1000)/height;
  SetAPen(rp,CBROWN);
  WriteLine(rp,width,height,x,y,scale,4,10,6,10);
  WriteLine(rp,width,height,x,y,scale,4,10,4,8);
  WriteLine(rp,width,height,x,y,scale,6,10,6,8);
  SetAPen(rp,CGREEN);
  WriteLine(rp,width,height,x,y,scale,0,8,10,8);
  WriteLine(rp,width,height,x,y,scale,0,8,3,6);
  WriteLine(rp,width,height,x,y,scale,10,8,7,6);
  WriteLine(rp,width,height,x,y,scale,3,6,1,6);
  WriteLine(rp,width,height,x,y,scale,7,6,9,6);
  WriteLine(rp,width,height,x,y,scale,1,6,3,4);
  WriteLine(rp,width,height,x,y,scale,9,6,7,4);
  WriteLine(rp,width,height,x,y,scale,3,4,2,4);
  WriteLine(rp,width,height,x,y,scale,7,4,8,4);
  WriteLine(rp,width,height,x,y,scale,2,4,4,2);
  WriteLine(rp,width,height,x,y,scale,8,4,6,2);
  WriteLine(rp,width,height,x,y,scale,4,2,3,2);
  WriteLine(rp,width,height,x,y,scale,6,2,7,2);
  WriteLine(rp,width,height,x,y,scale,3,2,5,0);
  WriteLine(rp,width,height,x,y,scale,7,2,5,0);
}


__inline void WriteBall(struct RastPort *rp, int x, int y, int scale, 
                        int x1, int y1, int r)
{ int i,j;
  x1=x1*4*scale; y1=y1*4*scale; r=(r*4*scale)/1000;
  /* all coordinates are now 1.000 times too big */
  for (j=1; j<r; j++)
    { i=r+1-j;
      SetAPen(rp,16-((i*8)/r));
      DrawEllipse(rp,x+x1/1000,y+y1/1000,i,i);
    }
}


void draw_snowman(struct RastPort *rp, int width, int height)
{ int x,y;
  int scale;
  x = RangeRand(width-42);
  y = RangeRand((height*3)/4-42)+height/4;
  scale = (y*1000)/height;
  SetAPen(rp,CLGREY);
  /*if (scale > 400) WriteLine(rp,width,height,x,y,scale,1,4,7,4);*/
  WriteBall(rp,x,y,scale,4,7,3);
  WriteBall(rp,x,y,scale,4,4,2);
  WriteBall(rp,x,y,scale,4,2,1);
}



/* draw a winterly landscape with everything and more :) */
LONG draw_landscape(struct Screen *scr, int width, int height, struct RastPort *rp)
{ int numobj,i;
  LONG flg_end;

  /* first: the landscape */
    { if (SHOWBG)
        { int x,y;
          int *hei = malloc(sizeof(int)*width);
          hei[0]=0;
          for (i=1; i<width; i++)
            { hei[i] = hei[i-1]+RangeRand(3)-1;
            }
          for (y=height/4; y<height; y++)
            { for (x=0; x<width; x++)
                { int val = ((y+hei[x]) * 100) / height;
                  val = val - val/8 + RangeRand(val/4);
                  if (val<0) val = 0;  
                  val = (val * 8)/100;
                  if (val<0) val = 0;
                  if (val>7) val = 7;
                  if (!val) SetAPen(rp,0); 
                  else SetAPen(rp,val+8);
                  WritePixel(rp,x,y);
                }
              for (i=1; i<width; i++)
                { if (!RangeRand(3)) hei[i] = hei[i]+RangeRand(3)-1;
                }              
	      flg_end = ContinueBlanking();
	      ScreenToFront(scr);
	      if (flg_end != OK)
                { free(hei); return flg_end; }
            }
        }
      /* else leave the background black */
    }

  /* possibly stars */
  if (SHOWSTARS)
    { int count = RangeRand(50)+50;
      int i;
      starx = malloc(count*sizeof(int));
      stary = malloc(count*sizeof(int));

      SetAPen(rp,CYELLOW);
      for (i=0; i<count; i++)
        { int x,y;
          do { x = RangeRand(width);
               y = RangeRand(height/4);
             } while (ReadPixel(rp,x,y) != CBLACK);
          WritePixel(rp,x,y);
          starx[i] = x;
          stary[i] = y;
        } 
      starnum = count;
    }

  /* Draw some objects */
  numobj = RangeRand(NUMOBJ/2)+NUMOBJ/2;
  for (i=0; i<numobj; i++)
    { switch(RangeRand(4))
        { case 0: case 1: case 2: if (SHOWTREES)
                                    draw_tree(rp,width,height); break;
          case 3:                 if (SHOWSNOWMEN) 
                                    draw_snowman(rp,width,height); break;
        }
    } 
  return OK;
}


/* 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++)
	{ int xx = flake->x+(*startx);
          int yy = flake->y+(*starty);
          flake->mem[i] = ReadPixel(rp,xx,yy);
          if (flake->mem[i] == CWHITE)
            { flake->mem[i] = -1;
            }
          else
            WritePixel(rp,xx,yy);
	  startx++;
	  starty++;
	}
    }
  else
    { flake->mem[0] = ReadPixel(rp,flake->x,flake->y);
      if (flake->mem[0] == CWHITE)
        { flake->mem[0] = -1;
        }
      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;
  bool firstpass = true;
  struct RastPort *rp = &(scr->RastPort);

  starx = NULL; stary = NULL;
  /* first draw a winterly landscape */
  flg_end = draw_landscape(scr,width,height,rp);
  if (flg_end != OK)
    { if (starx) free(starx);
      if (stary) free(stary);
      return flg_end;
    }

  /* 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;
      { if (SHOWSTARS)
          { int num = RangeRand(starnum);
            int x = starx[num];
            int y = stary[num];
            int val = ReadPixel(rp,x,y);
            if (val != CWHITE)
              { if (val == CYELLOW)
                  { SetAPen(rp,CBLACK);
                  }
                else
                  { SetAPen(rp,CYELLOW);
                  }
                WritePixel(rp,x,y);
              }
          }
      }
              
      for (i=0; i<NUMSNOW; i++)
	{ /* Remove curr's image */
	  if (!firstpass) 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);
                  if (starx) free(starx);
                  if (stary) free(stary);
		  return flg_end;
		}
	    }
	  else
	    { blankcount--;
	    }
	  curr++;
	}
      if (firstpass) firstpass = false;
    }
  return OK;			/* just for the compiler */
}


bool val(bool x)
{ if (x==random)
    return RangeRand(2);
  else return x;
}


LONG Blank( PrefObject *Prefs )
{
	struct Screen *Scr;
	struct Window *Wnd;
	LONG RetVal;
	
        BIGFLAKES   = val(Prefs[PR_BIG].po_Level); 
	NUMSNOW     = Prefs[PR_FLAKE].po_Level;
	COLLECT     = val(Prefs[PR_COLLECT].po_Level);
        SHOWSNOWMEN = val(Prefs[PR_SNMAN].po_Level);
        SHOWSTARS   = val(Prefs[PR_STARS].po_Level);
        SHOWTREES   = val(Prefs[PR_TREE].po_Level);
        SHOWBG      = val(Prefs[PR_BG].po_Level);
        NUMOBJ      = Prefs[PR_OBJ].po_Level;

	if( Scr = OpenScreenTags( NULL, SA_Depth, 4,
							 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 ); /* white */
                SetRGB4(&( Scr->ViewPort ), 2,  0,  10, 0 ); /* green */
                SetRGB4(&( Scr->ViewPort ), 3,  8,  5,  4 ); /* brown */
                SetRGB4(&( Scr->ViewPort ), 4, 15, 15,  8 ); /* yellow */
                SetRGB4(&( Scr->ViewPort ), 5,  4,  4,  4 ); /* dark grey */
                SetRGB4(&( Scr->ViewPort ), 6,  8,  8,  8 ); /* mid grey */
                SetRGB4(&( Scr->ViewPort ), 7,  12,  12,  12 ); /* light grey */
                { int i;
                  for (i=8; i<16; i++)
                    {
                      SetRGB4(&( Scr->ViewPort ), i,  i-7,  i-7,  i-7); /* different gray */
                    } 
                }
		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;
}
