#include <exec/memory.h>
#include <exec/execbase.h>
#include <graphics/displayinfo.h>
#include <graphics/gfxbase.h>
#include <graphics/gfxmacros.h>
#include <intuition/intuitionbase.h>
#include <intuition/gadgetclass.h>
#include <libraries/gadtools.h>
#include <workbench/startup.h>
#include <workbench/workbench.h>
#include <dos/stdio.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>
#include <dos/rdargs.h>

#include <clib/asl_protos.h>
#include <clib/diskfont_protos.h>
#include <clib/commodities_protos.h>
#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/graphics_protos.h>
#include <clib/intuition_protos.h>
#include <clib/macros.h>
#include <clib/alib_protos.h>

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#ifdef __SASC /* some stuff for SAS-C */

#include <pragmas/commodities_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/gadtools_pragmas.h>
#include <pragmas/graphics_pragmas.h>
#include <pragmas/intuition_pragmas.h>

#define VOID_INTERRUPT void __interrupt __saveds
#define REGARGS(rv) rv __regargs
#define CHIP(dt)    dt __chip

//void NewList(struct List *);

UBYTE *VersionString = "$VER: Snow 0.1(compiled with SAS/C)";

void chkabort(void)
{}

#else /* DICE */

CxObj *HotKey(UBYTE *,struct MsgPort *,long);

UBYTE *VersionString = "$VER: Snow 0.1(compiled with DICE)";

void main(LONG,UBYTE **);

void wbmain(struct WBStartup *WBS)

{
 if (WBS->sm_NumArgs) (void)CurrentDir(WBS->sm_ArgList->wa_Lock);

 main(0L,(UBYTE **)WBS);
}
#endif

#define MASK(n) (1L<<(n))
#define WHITEPEN 1
#define BLACKPEN 0

extern struct IntuitionBase *IntuitionBase;
extern struct GfxBase *GfxBase;
extern struct Library *GadToolsBase;
extern struct Library *DOSBase;

WORD LeftEdge=12,TopEdge=12;
struct Window *TinyWindow;
struct TextAttr TinyAttr={"topaz.font",TOPAZ_EIGHTY,FS_NORMAL,FPF_ROMFONT};
struct Screen *PubScreen;
struct RastPort *RP;
LONG Depth;
UWORD backdrop=FALSE;

/*

    The following functions are taken from Matthias Schelers' resource tracker
    library for SAS/C. Thanks to Matthias.

*/

struct ToolNode
 {
  struct ToolNode *Next;
  void *Tool;
  void (*RemProc)(void *,LONG); /* requires stack arguments !!! */
  LONG Size;
 } *ToolList;

REGARGS(void) RemTool(void *Tool)

{
 struct ToolNode **Ptr,*ToolNode;

 Ptr=&ToolList;
 while (ToolNode=*Ptr)
  if (ToolNode->Tool==Tool)
   {
    *Ptr=ToolNode->Next;

    ToolNode->RemProc(ToolNode->Tool,ToolNode->Size);
    FreeVec ((APTR)ToolNode);

    return; /* This one was missing in ASwarm II V1.0-V1.1 and in
               ASwarm III :-) */
   }
  else Ptr=&ToolNode->Next;
}

void RemoveAll(void)

{
 while (ToolList) RemTool (ToolList->Tool);
}

REGARGS(void) AddTool(void *NewTool,void *ProcPtr,LONG NewSize)

{
 struct ToolNode *Ptr;
 void (*NewRemProc)(void *,LONG);
 static BOOL First=TRUE;

 NewRemProc=(void (*)(void *,LONG))ProcPtr;
 if (NewTool==NULL) exit (10);

 if (First)
  {
   First=FALSE;
   if (atexit(RemoveAll))
    {
     NewRemProc (NewTool,NewSize);
     exit (20);
    }
  }

 if ((Ptr=AllocVec(sizeof(struct ToolNode),0L))==NULL)
  {
   NewRemProc (NewTool,NewSize);
   exit (20);
  }
 Ptr->Next=ToolList;
 Ptr->Tool=NewTool;
 Ptr->RemProc=NewRemProc;
 Ptr->Size=NewSize;
 ToolList=Ptr;
}

void CloseTinyWindow(void)

{
 if (TinyWindow)
    {
     CloseWindow (TinyWindow);
     TinyWindow=NULL;
    }
}

void OpenTinyWindow(void)

{

 ULONG Flags;
 ULONG w,h;

 Flags=WFLG_DRAGBAR| WFLG_DEPTHGADGET| WFLG_CLOSEGADGET|WFLG_RMBTRAP|
       WFLG_SMART_REFRESH| WFLG_SIZEGADGET| WFLG_SIZEBBOTTOM;

 if (TinyWindow==NULL)
  {
   if ((PubScreen=LockPubScreen(NULL))==NULL) return;

   Depth=PubScreen->BitMap.Depth;
   if(backdrop)
    {
     Flags=Flags|WFLG_BACKDROP|WFLG_BORDERLESS;
     h=PubScreen->Height;
     w=PubScreen->Width;
    }
   else
    {
     w=300;
     h=200;
    }


   if ((TinyWindow=OpenWindowTags(NULL,
                    WA_Left,LeftEdge,
                    WA_Top,TopEdge,

                    WA_IDCMP,IDCMP_CLOSEWINDOW|IDCMP_REFRESHWINDOW|
                    IDCMP_NEWSIZE|IDCMP_VANILLAKEY|LISTVIEWIDCMP,

                    WA_Flags,Flags,

                    WA_AutoAdjust,TRUE,
                    WA_Title,(ULONG)"Snow - happy X-Mas!",
                    WA_PubScreen,PubScreen,

                    WA_InnerWidth,w,
                    WA_InnerHeight,h,
                    WA_MinWidth,50,
                    WA_MaxWidth,PubScreen->Width,
                    WA_MinHeight,50,
                    WA_MaxHeight,PubScreen->Height,
                    WA_GimmeZeroZero, TRUE,
                    TAG_DONE))==NULL)
    {
     CloseTinyWindow();
     return;
    }

   GT_RefreshWindow (TinyWindow,NULL);

  }
 UnlockPubScreen(NULL,PubScreen);
 ScreenToFront (TinyWindow->WScreen);
 if(!backdrop)WindowToFront (TinyWindow);
 ActivateWindow (TinyWindow);
 RP=TinyWindow->RPort;
}

LONG RDArgsLong(LONG Param,LONG Default,LONG Min,LONG Max)

{
 LONG *Ptr;

 if ((Ptr=(LONG *)Param)==NULL) return Default;

 if ((*Ptr<Min)||(*Ptr>Max)) return Default;
 else return *Ptr;
}


#define RAND(m) (Random(m)-(m)/2)
#define MAXRAND 32768.0

/* Ill's strange and genius Random Function :-) */

int Random(int Max)
{
 static ULONG Num=0L; /* So the last random-number is still stored ! */
 ULONG Sec,Mic;

 CurrentTime((LONG *)&Sec,(LONG *)&Mic);

 Num*=Sec+1/Sec;
 Num+=Mic;

 while (Num>MAXRAND) Num=Num>>1;

 return (int)(Num % Max);
}

/* Template for ReadArg */
static char *Template = "SNOWFLAKES/N/K,BACKDROP/S";


#include "snowmap.h"

typedef unsigned long Pixel;

#define SCAMPER_EVENT	(LASTEvent + 1)

unsigned int display_width, display_height;
int center_x, center_y;

int done = 0;
int eventBlock = 0;
int errorVal = 0;

int flake;

Snow *snowflakes;
int maxSnowflakes = 100;
int curSnowflakes = 0;

#define SNOWCOLOR 1
#define SANTACOLOR 2
#define BAUMCOLOR 3
#define HATCOLOR 4

Santa Claus;

void Usage(void);
void InitSnowflake(int);
void UpdateSnowflake(int);
void DrawSnowflake(int);
void EraseSnowflake(int);
void DrawTannenbaum(int,int);
void InitSanta(void);
void DrawSanta(void);
void EraseSanta(void);
void UpdateSanta(void);
void usleep(unsigned int);
void main_initsanta(int, char**);
void drawsnowstorm(void);
int RandInt(int);
int SnowInRect(Snow*, int, int, int, int, unsigned int, unsigned int);

void main_initsanta(ac, av)
int ac;
char *av[];
{
    int i;

    display_width = TinyWindow->Width;
    display_height = TinyWindow->Height;
    center_x = display_width / 2;
    center_y = display_height / 2;


    /*
        P I X M A P S
    */

    /* Create the snowflake pixmaps */
    /* Allocate structures containing the coordinates and things */
    AddTool(snowflakes=(Snow *)AllocVec((sizeof(Snow)) * MAXSNOWFLAKES,MEMF_CLEAR),FreeVec,0L);

    SetAPen(RP,1);

    SetRast(RP,3);
    SetBPen(RP,3);
    SetDrMd(RP,JAM2);

    /* Initialize all snowflakes */
    for (i=0; i<maxSnowflakes; i++) InitSnowflake(i);

    InitSanta();

    for (i=0; i<maxSnowflakes; i++) DrawSnowflake(i);

}

#define USEPRT(msg) fprintf(stderr, msg)

void drawsnowstorm()
{

      int i;

      for (i=0; i<maxSnowflakes; i++) UpdateSnowflake(i);

      /* Draw dear Andrew */
      DrawTannenbaum(display_width-150, display_height-500);
      DrawTannenbaum(display_width-100, display_height-200);
      DrawTannenbaum(100, display_height-200);
      DrawTannenbaum(50, display_height-150);
      DrawTannenbaum(center_x, display_height-100);
      DrawTannenbaum(200,400);

      /* Dear Santa */
      UpdateSanta();

}

/*
   Generate random integer between 0 and maxVal-1.
*/
int
RandInt(maxVal)
int maxVal;
{
	return rand() % maxVal;
}

/*
   Check for snow completely in specified rectangle.
*/
int
SnowInRect(snow, rx, ry, x, y, width, height)
Snow *snow;
int rx;
int ry;
int x;
int y;
unsigned int width;
unsigned int height;
{
    if (rx < x) return 0;
    if ((rx + snowPix[snow->whatFlake].Width) > (x + width)) return 0;
    if (ry < y) return 0;
    if ((ry + snowPix[snow->whatFlake].Height) > (y + height)) return 0;

    return 1;
}


/*
   Give birth to a snowflake.
*/
void
InitSnowflake(rx)
int rx;
{
    Snow *r;

    r = &snowflakes[rx];

    if (curSnowflakes < maxSnowflakes) {
        r->whatFlake = Random(SNOWFLAKEMAXTYPE+1);
	r->intX = Random(display_width - snowPix[r->whatFlake].Width);
	r->intY =  Random(display_height/10);
        r->yStep = Random(MAXYSTEP+1)+1;
        r->xStep = Random(r->yStep/4+1);
        if (Random(1000) > 500) r->xStep = -r->xStep;
        r->active = 1;
    }
}


/*
   Move a snowflake by erasing and redraw
*/
void
UpdateSnowflake(rx)
int rx;
{
    Snow *snow;

    /* move an active snowflake */

    snow = &snowflakes[rx];

    if (!snow->active)  {
      InitSnowflake(rx);  /* For next time  */
      }
    else {
      /* This is an active snowflake */
      EraseSnowflake(rx);

      snow->intX = snow->intX + snow->xStep;
      snow->intY = snow->intY + snow->yStep;

      /* If flake disappears offscreen it becomes inactive */

      snow->active = SnowInRect(snow, snow->intX, snow->intY,
                            0, 0, display_width, display_height);

      /* Adjust horizontal speed to mimic whirling */
      snow->xStep = snow->xStep + Random(WHIRLFACTOR+1);
      if (Random(1000) > 500) snow->xStep = -snow->xStep;
      if (snow->xStep > MAXXSTEP) snow->xStep = MAXXSTEP;
      if (snow->xStep < -MAXXSTEP) snow->xStep = -MAXXSTEP;

      /* Draw if still active */
      if (snow->active) DrawSnowflake(rx);
    }

}


/*
   Draw a snowflake.
*/
void
DrawSnowflake(rx)
int rx;
{
    Snow *snow;

    snow = &snowflakes[rx];

    DrawImage(RP,&snowPix[snow->whatFlake],snow->intX, snow->intY);
}


/*
   Erase a snowflake.
*/
void
EraseSnowflake(rx)
int rx;
{
    Snow *snow;

    snow = &snowflakes[rx];

    SetAPen(RP,3);
    SetBPen(RP,1);
    if (snow->intX >= 0) RectFill(RP,snow->intX,
                                     snow->intY,
                                     snow->intX+snowPix[snow->whatFlake].Width,
                                     snow->intY+snowPix[snow->whatFlake].Height);
}

/*
   Draw a tree
*/
void
DrawTannenbaum(x,y)
int x,y;
{

   DrawImage(RP,&tannenbaumPix[0],x,y);

}


/*
   Give birth to a Santa. (What a conception)
*/
void
InitSanta()
{
  Claus.x = 0;
  Claus.y = Random(display_height / 3);
  Claus.xStep = 1;
  Claus.yStep = 1;
  Claus.whatSanta = 0;
}


/*
  Update Santa (How can you update the oldest icon in the world? Oh well.
*/
void
UpdateSanta()
{
  EraseSanta();

  /* Move forward */
  Claus.x = Claus.x + Claus.xStep;

  /* Move down */
  if (Random(10) > 3) Claus.y = Claus.y + Claus.yStep;
  if (Claus.y < 0) Claus.y = 0;
  if (Random(100) > 80) Claus.yStep = -Claus.yStep;

  if (Claus.x >= display_width) InitSanta();

  /* Next sleigh */
  Claus.whatSanta++;
  if (Claus.whatSanta > 2) Claus.whatSanta = 0;

  DrawSanta();
}




/*
  Draw Santa
*/
void
DrawSanta()
{
  DrawImage(RP,&sleighPix[Claus.whatSanta],Claus.x,Claus.y);

  /* The Man in the Red Suit himself */
  DrawImage(RP,&santaPix[0],Claus.x,Claus.y);

  SetAPen(RP,HATCOLOR);
  WritePixel(RP,Claus.x+5,Claus.y+1);
}

/*
  Erase Santa
*/
void
EraseSanta()
{
  if (Claus.x >= 0) {
      SetAPen(RP,3);
      RectFill(RP,Claus.x,Claus.y,Claus.x+sleighPix[Claus.whatSanta].Width,
                  Claus.y+sleighPix[Claus.whatSanta].Height);
   }
}

#define MASK(n) (1L<<(n))

void main(LONG argc,UBYTE **argv)

{
 struct IntuiMessage *IntMsg;
 struct RDArgs *RDArgs;
 LONG Array[2];


 /* open our Libraries */

 AddTool (IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",
          37L),CloseLibrary,0L);
 AddTool (GadToolsBase=OpenLibrary("gadtools.library",37L),CloseLibrary,0L);
 AddTool (GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",37L),
          CloseLibrary,0L);

 /*
    Process command line options.
 */

 (void)memset(Array,'\0',sizeof(Array));
 if (RDArgs=ReadArgs(Template,Array,NULL))
 {
  if(Array[0]) maxSnowflakes=RDArgsLong(Array[1],100,1,200);
  if(Array[1]) backdrop=Array[1];
 }
 else
 {
  PrintFault(IoErr(),"Snow");
  exit (10);
 }
 if (RDArgs) FreeArgs(RDArgs);


 AddTool ((void *)-1L,CloseTinyWindow,0L);
 OpenTinyWindow();

 main_initsanta(argc, argv);

 /* start the Loop */

 FOREVER
  {
   /* process Window Events */

   while((TinyWindow!=NULL)&&(IntMsg=GT_GetIMsg(TinyWindow->UserPort)))
    switch (IntMsg->Class)
     {
      UWORD Code;

      case IDCMP_CLOSEWINDOW:
       GT_ReplyIMsg (IntMsg);
       CloseTinyWindow();
       exit(0);
       break;
      case IDCMP_REFRESHWINDOW:
       GT_BeginRefresh (TinyWindow);
       GT_EndRefresh (TinyWindow,TRUE);
       break;
      case IDCMP_VANILLAKEY:
       Code=IntMsg->Code;
       GT_ReplyIMsg (IntMsg);
       switch ((char)Code)
        {
         case 27: /* ESC */
          CloseTinyWindow();
          exit(0);
          break;
        }
       break;
      case IDCMP_NEWSIZE:
       GT_ReplyIMsg (IntMsg);
       main_initsanta(argc, argv);
       break;
      default:
       GT_ReplyIMsg (IntMsg);
     }

   /* Busy Loop draw *grin :-)* */
   drawsnowstorm();

   if (SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
     exit(0);
   }


  }

}
