/*
 *  Copyright (c) 1997 Ingo Schmiegel.
 *  All rights reserved.
 *
 *  Please see the documentation accompanying the distribution for distribution
 *  and disclaimer information.
 */

#include <exec/memory.h>
#include <hardware/custom.h>
#include <time.h>               /* for time() to get a seed value */
#include <math.h>               /* for srand48(), lrand48(): 0..2^31 */
#include "/includes.h"          /* to get all garshneblanker includes */

#include "SnowPlants_rev.h"     /* this modules' revision file */
STATIC const UBYTE VersTag[] = VERSTAG;
extern __far struct Custom custom;          /* for the copper table */
Triplet *ColorTable = 0L;                   /* for the multicolour option */

/* always take no. of line in *.ifc and subtract 2 */
#define PREFS_DRAWIT     0
#define PREFS_COLCYCLE   2
#define PREFS_COLDELAY   3
#define PREFS_POINTS     5
#define PREFS_MAXHEIGHT  6
#define PREFS_APECTRL    8
#define PREFS_APERTURE   9
#define PREFS_PIXDELAY  11
#define PREFS_PICDELAY  13
#define PREFS_MODEID    15

#define PREFS_DRAWIT_YES        0
#define PREFS_DRAWIT_NO         1
#define PREFS_COLCYCLE_WHITE    0       /* cycle gadget's item  numeration */
#define PREFS_COLCYCLE_RANDOM   1
#define PREFS_COLCYCLE_CYCLE    2
#define PREFS_COLCYCLE_COPPER   3
#define PREFS_COLCYCLE_MULTICOL 4
#define PREFS_APECTRL_RANDOM    0
#define PREFS_APECTRL_STRICT    1

void Defaults(PrefObject *Prefs)
{
    Prefs[PREFS_DRAWIT].po_Active = PREFS_DRAWIT_NO;            /* draw falling points (YES) or resulting points only (NO) */
    Prefs[PREFS_COLCYCLE].po_Active = PREFS_COLCYCLE_CYCLE;     /* ColorCycling: white,rnd,cycle */
    Prefs[PREFS_COLDELAY].po_Level = 111;                       /* Color cycling speed */
    Prefs[PREFS_POINTS].po_Level = 15000;                       /* Points */
    Prefs[PREFS_MAXHEIGHT].po_Level = 1280;                     /* Maximum height for picture */
    Prefs[PREFS_APECTRL].po_Active = PREFS_APECTRL_RANDOM;      /* Aperture: random, strict */
    Prefs[PREFS_APERTURE].po_Level = 150;                       /* max. aperture of falling direction */
    Prefs[PREFS_PIXDELAY].po_Level = 1;                         /* delay between pixel movements */
    Prefs[PREFS_PICDELAY].po_Level = 10;                        /* delay between pictures */
    Prefs[PREFS_MODEID].po_ModeID = getTopScreenMode();         /* Screenmode ID */
}

/*
 * RndBT                (macro)         "Random from Bottom to Top"
 *
 * Parameters:  min     The minimum of possible random numbers (any of: int,char,word,long; no floats!)
 *              max     The maximum of possible random numbers (dito)
 *              (Do not use negative numbers!)
 *
 * Result:      long    A random number
 *
 * Function:    Returns a random number in the interval of [min..max] (both included)
 *
 * Note:        Call srand48() once before using this function to set a seek value,
 *              e.g.: srand48(time(NULL))
*/
#define RndBT(min,max)  ((min)+(lrand48()%((max)-(min)+1)))

/* RndColourCycling     (function)      "Random Colour Cycling"
 *
 * Parameters:  struct ViewPort    *vp      The viewport which colours should be changed
 *              long                colnum  The number of the colour to change in that viewport
 *
 * Result:      void
 *
 * Function:    First call: Selects randomly a red, a green and a blue value for the
 *                          colour
 *              Further calls: Selects randomly a red, green and blue value to which
 *                             the colour should change, then changes the colour with
 *                             during each call in one of the three values to get
 *                             there. If the current values and the destination values
 *                             are equal, new destination red, green and blue values
 *                             will be chosen.
 * Note: - If called with different <colnum>s you can get randomly chosen
 *         colour spreads in a colour table. You can even do great chaos ;-)
 *       - If called with <dosingle> == TRUE then only a new colour is randomly selected,
 *         and the destination colour will be set to that colour, too
*/
#define     RCC_DOSINGLE    0
#define     RCC_DOCYCLE     1
void RndColourCycling(struct ViewPort *vp, int colnum, int dosingle)
{
    static int r,g,b;        /* current red,green and blue values */
    static int dr,dg,db;     /* destination red, green and blue values */
    static int changewhat;   /* 1:change red, 2:green, 3:blue */

    if (dosingle==RCC_DOSINGLE)         /* get a colour, a destination colour */
    {
        do
        {
            r = dr = RndBT(0,15);       /* choose a colour */
            g = dg = RndBT(0,15);
            b = db = RndBT(0,15);
        } while ((r<6)&&(g<6)&&(b<6)); /* not too dark colours, please */
        changewhat = RndBT(1,3);
        SetRGB4(vp,colnum,r,g,b);
        return;
    }
    if ((r==dr) && (g==dg) && (b==db))  /* current colour == destination colour ? */
    {
        do
        {
            dr = RndBT(0,15);       /* choose a new colour to cycle to */
            dg = RndBT(0,15);
            db = RndBT(0,15);
        } while ((dr<6)&&(dg<6)&&(db<6)); /* not too dark colours, please */
        changewhat = RndBT(1,3);
    }
    switch (changewhat)
    {
        case    1:  if (r>dr)   --r;    /* change red colour value */
                    else if (r==dr) changewhat = RndBT(1,3);
                    else        ++r;
                    break;
        case    2:  if (g>dg)   --g;    /* change green colour value */
                    else if (g==dg) changewhat = RndBT(1,3);
                    else        ++g;
                    break;
        case    3:  if (b>db)   --b;    /* change blue colour value */
                    else if (b==db) changewhat = RndBT(1,3);
                    else        ++b;
                    break;
    }
    SetRGB4(vp,colnum,r,g,b);
}

LONG Blank(PrefObject *Prefs)
{
    LONG RetVal = OK;             /* needed for screen blanking control */
    struct RastPort *rp;
    struct ViewPort *vp;
    struct Screen *scr;
    struct Window *win;
    
    long    Width,Height,Right,Bottom;                                 /* screen dimensions */
                                                        /* Right:=Width-1, Bottom:=Height-1 */
    long    DrawIt,ColCtrl,ColDelay,MaxHeight,Points,PixDelay,ApeCtrl,PicDelay;  /* blanker parameters */
    double  Aperture;

    long    x,y,oldx,oldy,miny;     /* actual coordiantes */
    double  dx,dy,accx,accy,ta;     /* pixel movement control */
    long    cnt,waitpic;
    long    cntcoldelay,cntpixdelay;
    const long  ScrToFront  = 100;  /* do a ScreenToFront() only every hundreth pixel */
    long    cntscrtofront;
    long    scrdepth,numcolours,curcolour;
    srand48(time(NULL));    /* Set seek value for Unix's lrand48() function */
                            /* lrand48() provides a number from 0..2**31    */

    DrawIt      = Prefs[PREFS_DRAWIT].po_Active;
    ColCtrl     = Prefs[PREFS_COLCYCLE].po_Active;
    ColDelay    = Prefs[PREFS_COLDELAY].po_Level;
    Points      = Prefs[PREFS_POINTS].po_Level;
    MaxHeight   = Prefs[PREFS_MAXHEIGHT].po_Level;
    ApeCtrl     = Prefs[PREFS_APECTRL].po_Active;
    Aperture    = Prefs[PREFS_APERTURE].po_Level /2.0;  /* not 0..180 but 0..90 degree */
    PixDelay    = Prefs[PREFS_PIXDELAY].po_Level;
    PicDelay    = Prefs[PREFS_PICDELAY].po_Level;

    if (Prefs[PREFS_COLCYCLE].po_Active == PREFS_COLCYCLE_MULTICOL)
            scrdepth = Prefs[PREFS_MODEID].po_Depth;
    else    scrdepth = 1;
    scr = OpenScreenTags( 0L, SA_Depth, scrdepth, SA_Overscan, OSCAN_STANDARD,
                         SA_DisplayID, Prefs[PREFS_MODEID].po_ModeID, SA_Behind, TRUE,
                         SA_Quiet, TRUE, TAG_DONE );
    if (scr)
    {
        /* any other ressource allocation should be inserted here */

        Width  = scr->Width;
        Height = scr->Height;
        Right  = Width-1;
        Bottom = Height-1;

        vp = &(scr->ViewPort);
        rp = &(scr->RastPort);
        numcolours = 1L << rp->BitMap->Depth;
        SetRast(rp,0);
        SetRGB4(vp,0,0,0,0);
        switch (ColCtrl)
        {
            case    PREFS_COLCYCLE_WHITE:   SetRGB4(vp,1,15,15,15);         /* white */
                                            break;
            case    PREFS_COLCYCLE_RANDOM:                                  /* random (once) */
            case    PREFS_COLCYCLE_CYCLE:   RndColourCycling(vp,1,RCC_DOSINGLE);   /* cycle */
                                            break;
            case    PREFS_COLCYCLE_MULTICOL:ColorTable = RainbowPalette(scr,0,1,0); /* rainbow colours */
                                            break;
            case    PREFS_COLCYCLE_COPPER:  setCopperList(Height, 1, vp, &custom);   /* copper colours */
                                            break;
        }
        if (MaxHeight>Height)   MaxHeight=Height;   /* limit Maximum height to screen dimensions */
        MaxHeight = Height - MaxHeight;             /* now flip the y-coordinate */

        win = BlankMousePointer(scr);             /* get screen to front */
        ScreenToFront(scr);
        
        cnt = 0;
        y = Height-2;
        x = RndBT(0,Width-1);       /* random number between 0 and screen width-1 */
        oldx = x;
        oldy = y;
        miny = Height;
        waitpic = 0;
        cntcoldelay = ColDelay;
        cntpixdelay = PixDelay;
        cntscrtofront = ScrToFront;
        curcolour = 1;

        if (Aperture)
        {
            if (Aperture>=89.99) Aperture = 89.99;
            Aperture = Aperture *PI/180.0;
            if (ApeCtrl==PREFS_APECTRL_STRICT) ta = tan(Aperture);
            else                               ta = tan(Aperture*drand48());
            if (ta>1.0) { dx = 1.0; dy = 1.0/ta; }   /* move horizontally */
            else        { dx = ta;  dy = 1.0;    }   /* move vertically */
            if (RndBT(0,1)) dx *= -1.0; /* direction left or right */
        }
        else    /* always straight vertical */
        {
            dx = 0.0;
            dy = 1.0;
        }
        accx = accy = 0.0;
        if (DrawIt==PREFS_DRAWIT_NO)   SetAPen(rp,1);
        while(RetVal == OK)
        {
            /*WaitTOF();  */
            
            if (waitpic)
            {
                Delay(TICKS_PER_SECOND);    // delay one second
                ScreenToFront(scr);
                if (!--waitpic)
                {
                    SetRast(rp,0);
                    cnt = 0;
                    waitpic = 0;
                }
            }
            else
            {
                if (!--cntpixdelay)     /* draw and move a pixel */
                {
                    cntpixdelay = PixDelay;
                    if (DrawIt==PREFS_DRAWIT_YES)
                    {
                        SetAPen(rp,0);          /* erase old pixel */
                        WritePixel(rp,oldx,oldy);
                        SetAPen(rp,curcolour);  /* draw new one */
                        WritePixel(rp,x,y);
                    }

                    oldx = x;                   /* remember old point */
                    oldy = y;

                    accx += dx;                 /* calculate pixel movement */
                    accy += dy;
                    if (accx >= 1.0)
                    {
                        accx -= 1.0;
                        if (++x==Width) x=0;
                    }
                    else if (accx <= -1.0)
                    {
                        accx += 1.0;
                        if (!x--) x=Right;
                    }
                    if (accy >= 1.0)
                    {
                        accy -= 1.0;
                        y++;
                    }
                    if (ReadPixel(rp,x,y) || (y==Height))
                    {
                        if (y==MaxHeight) waitpic = PicDelay; /* picture reached its maximum height */
                        if (DrawIt==PREFS_DRAWIT_NO)
                        {
                            SetAPen(rp,curcolour);
                            WritePixel(rp,oldx,oldy);
                            if (oldy<miny)  miny = oldy;
                            y = miny-2;     /* start low */
                            if (y<0) y=0;
                        }
                        else
                        {
                            y = 0;
                        }
                        oldy = 0; /* now the point in the figure won't be cleared */
                        x = RndBT(0,Right);

                        if (ApeCtrl==PREFS_APECTRL_RANDOM)
                        {
                            if (Aperture)
                            {
                                ta = tan(Aperture*drand48());
                                if (ta>1.0) { dx = 1.0; dy = 1.0/ta; }   /* move horizontally */
                                else        { dx = ta;  dy = 1.0;    }   /* move vertically */
                                if (RndBT(0,1)) dx *= -1.0; /* direction left or right */
                            }
                            else    /* always straight vertical */
                            {
                                dx = 0.0;
                                dy = 1.0;
                            }
                        }
                        accx = accy = 0.0;

                        if (++cnt==Points) waitpic = PicDelay;    /* All points? Then wait */

                        if (ColCtrl==PREFS_COLCYCLE_CYCLE)
                        {
                            if (!--cntcoldelay)
                            {
                                cntcoldelay = ColDelay;
                                RndColourCycling(vp,1,RCC_DOCYCLE);           /* cycle the colour */
                            }
                        }
                        else
                        if (ColCtrl==PREFS_COLCYCLE_MULTICOL)
                        {
                            if (!--cntcoldelay)
                            {
                                cntcoldelay = ColDelay;
                                if (++curcolour==numcolours) curcolour = 1; /* cycle the colour */
                            }
                        }

                        if (!--cntscrtofront)
                        {
                            cntscrtofront = ScrToFront;
                            ScreenToFront(scr);
                        }
                    }
                }
            }
            RetVal = ContinueBlanking();
        }
        UnblankMousePointer(win);
        if (Prefs[PREFS_COLCYCLE].po_Active==PREFS_COLCYCLE_COPPER) clearCopperList(vp);
    }
    else RetVal = FAILED;   /* no screen available */

    if (Prefs[PREFS_COLCYCLE].po_Active==PREFS_COLCYCLE_MULTICOL) RainbowPalette(0,ColorTable,1,0);

    if(scr) CloseScreen(scr);

    return RetVal;
}

