/*
 *  Copyright (c) 1997 Ingo Schmiegel
 *  (Basic version was based on code by Michael D. Bayne)
 *  All rights reserved.
 *
 *  ToDo:       · ?
 *
 *  History:
 *          1.2     11-May-97   · Uses now two different user-selectable fonts for
 *                                normal text cookies and for ascii-pictures
 *                              · Now only "%%" at beginning of lines start a new cookie
 *                              · Anything behind "%%" is treated as a comment
 *          1.1     07-May-97   · Counts non-characters and characters in the cookie
 *                                If there are more non-characters than characters present
 *                                block format is left alignment instead of centering, as
 *                                the loaded cookie is most possible a gfx-cookie (ascii-picture)
 *                              · Uses now SeekAsync() and a table with all cookie beginnings
 *                  06-May-97   Uses now asyncio.library
 *          1.0     26-Apr-97   Added user control: Can select, if after a time another
 *                                                  cookie shall be loaded.
 *          0.2     26-Apr-97   Added an error text, if no cookies were loaded for some reason
 *          0.1     25-Apr-97   Finally got a first version running
 *                              It loads a cookie file, while counting the cookies.
 *                              Then one of the cookies is randomly selected and displayed
 *                              on the screen.
 *                              There's still a gfx display error for long lines.
 *                              The '\n' error in Michael's code is fixed (Occurred in the
 *                              original text blanker only, if more than one '\n' follow
 *                              directly behind each other.
 *
 *
 */

#define NO_DBG_STATUS  1

#include <exec/memory.h>
#include <proto/asyncio.h>
#include <proto/diskfont.h>
#include <libraries/asyncio.h>
#include <string.h>
#include "/includes.h"
#include <stdio.h>
#include <time.h>               /* for time() to get a seed value */
#include <math.h>

#include "Cookie_rev.h"
STATIC const UBYTE VersTag[] = VERSTAG;

#ifndef min
#define min( x, y ) ( (x) < (y) ? (x) : (y) )
#endif

#define PREFS_FILE          0
#define PREFS_NOCOOKIETEXT  2
#define PREFS_PROPFONT      4
#define PREFS_FIXFONT       5
#define PREFS_CHANGECOOKIE  7
#define PREFS_CHANGETIME    8
#define PREFS_MOVEDELAY    10
#define PREFS_COLORS       12
#define PREFS_COLDELAY     13
#define PREFS_MODE         15

#define PREFS_YES   0
#define PREFS_NO    1
#define PREFS_COLORS_CYCLE  0
#define PREFS_COLORS_RANDOM 1
#define PREFS_COLORS_WHITE  2
#define PREFS_COLORS_COPPER 3

struct Library *AsyncIOBase = NULL;
struct AsyncFile *asynccookies = NULL;
#define ASYNC_BUFSIZE 32768
#define MAX_COOKIES 10000
long    whichCookie;
BOOL    isAsciiPicture;

typedef struct _mPoint
{
    LONG x;
    LONG y;
} mPoint;

#define MAX_CHARS_PER_LINE  256     /* 256 characters per line on the screen should be enough,    */
                                    /* because screenwidth/fontwidth->characters, e.g.: 1600/8->200 */
typedef struct _Line
{
    BYTE ln_Text[MAX_CHARS_PER_LINE];
    LONG ln_Length;
    LONG ln_Offset;
} Line;

extern __far struct Custom custom;

LONG vals[] = {
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C,
    0x0D, 0x0E, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06,
    0x05, 0x04, 0x03, 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
};

#define MAX_COOKIE_SIZE     16384
long cookiesInFile;         /* number of cookies in the file (maximum is MAX_COOKIES)*/
long *cookieStart;          /* start positions for all cookies in the file */
char noCookieText[1024];    /* text to appear, if cookie loading fails */
long CookieChars,CookieNonChars;    /* to decide whether left alignment or centering shall be used */
char displayString[MAX_COOKIE_SIZE];  /* Use a large buffer to hold even Monthy Python songs. */
                            /* This buffer should hold one cookie. Regardless how long this   */
                            /* cookie may be.                                               */
                            /* After a cookie is read in and displayed on the screen this   */
                            /* buffer can immediately reused                                */

/*
 * 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)))


//> void Defaults( PrefObject *Prefs )
VOID Defaults( PrefObject *Prefs )
{
    strcpy( Prefs[PREFS_FILE].po_Value, "Data:Documents/Cookies/fortune.cok" );
    strcpy( Prefs[PREFS_NOCOOKIETEXT].po_Value, "You haven't got any cookies???\nThen look at aminet/util/misc/fortune.lha for an example cookie file!" );
    Prefs[PREFS_PROPFONT].po_Attr.ta_YSize = 9;
    strcpy( Prefs[PREFS_PROPFONT].po_Name, "opal.font" );
    Prefs[PREFS_FIXFONT].po_Attr.ta_YSize = 8;
    strcpy( Prefs[PREFS_FIXFONT].po_Name, "topaz.font" );
    Prefs[PREFS_CHANGECOOKIE].po_Active = PREFS_YES;
    Prefs[PREFS_CHANGETIME].po_Level = 45;
    Prefs[PREFS_MOVEDELAY].po_Level = 77;
    Prefs[PREFS_COLORS].po_Active = PREFS_COLORS_CYCLE;
    Prefs[PREFS_COLDELAY].po_Level = 222;
    Prefs[PREFS_MODE].po_ModeID = getTopScreenMode();
}
//<

/* This one is used to read out the contents of the text gadget and copies
   them to another text buffer. Meanwhile all "\n" are compacted to the '\n' character. */
//> void GetStringFromGadget(char *dest, char *src)
void GetStringFromGadget(char *dest, char *src)
{
    do
    {
        if (*src=='\\')
        {
            if (*(src+1)=='n') { *dest++ = '\n'; src++; }
            else *dest++ = *src;
        } else *dest++ = *src;
    } while(*src++);
}
//<

/* insert some of the <String> into the given line construct (<item>), beginning at
   position <Pos>, return the number of characters used from <String>.
   <Scr> is needed to determine how much will fit into one line */
//> long FillLine( Line *Item, STRPTR String, LONG Pos, struct Screen *Scr )
long FillLine( Line *Item, STRPTR String, LONG Pos, struct Screen *Scr )
{
    long i;
    long end = 0;
    STRPTR Ptr;

    if (isAsciiPicture)
    {
        for(i=0,Ptr=String+Pos;i<(MAX_CHARS_PER_LINE-1);Ptr++,i++)
        {
            if (*Ptr == '\n')  /* met end of line */
            {
                Pos++;  /* skip the newline character */
                end = i;
                break;
            }
            else if (!*Ptr)         /* met end of text */
            {
                end = i;
                break;
            }
            else Item->ln_Text[i] = *Ptr;
            if (TextLength(&Scr->RastPort,Item->ln_Text,i+1) >= Scr->Width)
            {   /* line too long for screen, skip all characters that doesn't fit into line */
                while((*Ptr) && (*Ptr!='\n'))  { Ptr++; Pos++; }
                if (*Ptr=='\n') Pos++;
                end = i;
                break;
            }
        }
        if ((!end) && (i>0))
        {   /* line too long for buffer, skip all characters that doesn't fit into line */
            while((*Ptr) && (*Ptr!='\n'))  { Ptr++; Pos++; }
            if (*Ptr=='\n') Pos++;
            end = i;
        }
    }
    else
    {
        while(String[Pos]==' ') Pos++;  /* skip preceeding spaces */

        for(i=0,Ptr=String+Pos;i<(MAX_CHARS_PER_LINE-1);Ptr++,i++)
        {
            if (*Ptr == ' ')          /* got a space */
            {
                end = i;
                Item->ln_Text[i] = *Ptr;
            }
            else if (*Ptr == '\t')    /* got a tab */
            {
                end = i;
                Item->ln_Text[i] = ' ';
            }
            else if (*Ptr == '\n')  /* met end of line */
            {
                Pos++; /* skip the newline character */
                end = i;
                break;
            }
            else if (!*Ptr)         /* met end of text */
            {
                end = i;
                break;
            }
            else Item->ln_Text[i] = *Ptr;
            if (TextLength(&Scr->RastPort,Item->ln_Text,i+1) >= Scr->Width) break;
        }
        if((*Ptr==' ') || (*Ptr=='\n') || !end) end=i;
    } /* if isAsciiPicture */

    Item->ln_Text[end] = '\0';
    Item->ln_Length = TextLength(&Scr->RastPort,Item->ln_Text,strlen(Item->ln_Text));

    return end + Pos;
}
//<

//> Line *CalculateExtent(STRPTR String, struct Screen *Scr, long *MaxWid, long *NumLines)
Line *CalculateExtent(STRPTR String, struct Screen *Scr, long *MaxWid, long *NumLines)
{
    long txHeight = Scr->RastPort.TxHeight;
    long maxlines;
    long length = strlen(String);
    long linePos,i;
    Line *lineArray;

    maxlines = Scr->Height/txHeight;

    if(lineArray = AllocVec(sizeof(Line)*maxlines,MEMF_CLEAR))
    {
        linePos = i = *MaxWid = *NumLines = 0;
        do
        {
            linePos = FillLine( &lineArray[i], String, linePos, Scr );
            if(lineArray[i].ln_Length > *MaxWid) *MaxWid = lineArray[i].ln_Length;
            *NumLines += 1;
        } while((++i<maxlines) && (linePos<length));

        if (isAsciiPicture)
        {
            for(i=0;i<*NumLines;i++) lineArray[i].ln_Offset = 0;
        }
        else
        {
            for(i=0;i<*NumLines;i++) lineArray[i].ln_Offset = (*MaxWid - lineArray[i].ln_Length) / 2;
        }
    }
    return lineArray;
}
//<

/* Count the number of cookies in the file and store for each cookie its starting position (position
 * of first text after "%%"-line)  */
//> void CountCookies(char *fn)
void CountCookies(char *fn)
{
    char c;
    long howmany = 0;
    long pos = 0;
    BOOL beginofline = TRUE;
    BOOL markset = FALSE;
    BOOL incookie = FALSE;
    long cookiepos = 0;
    if (!asynccookies) asynccookies = OpenAsync(fn,MODE_READ,ASYNC_BUFSIZE);
    if (asynccookies)
    {
        while ((c=ReadCharAsync(asynccookies))!=EOF)
        {
            switch(c)
            {
                case '%':   if (markset)
                            {
                                if (incookie)
                                {
                                    if (howmany < MAX_COOKIES)  cookieStart[howmany++] = cookiepos;
                                    else howmany++;
                                    incookie = FALSE;
                                }
                                do /* skip now any comment */
                                {
                                    c=ReadCharAsync(asynccookies);
                                    pos++;
                                } while ((c!=EOF) && (c!='\n'));
                                markset = FALSE;
                            }
                            else
                            {
                                if (beginofline) markset = TRUE;
                            }
                            break;
                case '\n':  beginofline = TRUE;
                            markset = FALSE;
                            break;
                default:    beginofline = FALSE;
                            if (!incookie)
                            {
                                incookie = TRUE;
                                cookiepos = pos;
                            }
                            markset = FALSE;
                            break;
            } /* switch c */
            pos++;
        } /* while c!=EOF */
        /* catch last cookie, even if trailing "%%" is missing */
        if (incookie) cookieStart[howmany++] = cookiepos;
        if (howmany >= MAX_COOKIES) howmany = MAX_COOKIES-1;
        cookiesInFile = howmany;
    }
    else cookiesInFile = 0;
}
//<

//> void LoadAsyncCookieNumber(char *fn, long number)
void LoadAsyncCookieNumber(char *fn, long number)
{
    char c;
    long cnt;
    BOOL markset = FALSE;
    BOOL beginofline = TRUE;
    BOOL endofcookie = FALSE;

    if (!asynccookies) asynccookies = OpenAsync(fn,MODE_READ,ASYNC_BUFSIZE);
    if (asynccookies)
    {
        SeekAsync(asynccookies,cookieStart[number-1],MODE_START);
        do        /* probably, but not necessarily, cookie begins with one or more '\n' */
        {
            c = ReadCharAsync(asynccookies);
        } while ((c=='\n') && (c!=EOF));   /* skip them */


        /* next characters in file are the cookie */
        CookieChars = CookieNonChars = 0;
        cnt=0;
        do
        {
            switch(c)
            {
                case '%':   if (markset)    endofcookie = TRUE;
                            else
                            {
                                if (beginofline) markset = TRUE;
                                else             displayString[cnt++] = '%';
                            }
                            break;
                case '\n':  beginofline = TRUE;
                            if (markset)
                            {
                                displayString[cnt++] = '%';
                                markset = FALSE;
                            }
                            displayString[cnt++] = '\n';
                            break;
                default:    beginofline = FALSE;
                            if (markset)
                            {
                                displayString[cnt++] = '%';
                                markset = FALSE;
                            }
                            displayString[cnt++] = c;
                            /* just for deciding, whether it's an ascii-pic or a textual cookie */
                            if (((c>='a') && (c<='z')) || ((c>='A') && (c<='Z')) || ((c>='0') && (c<='9')))
                                 CookieChars++;
                            else CookieNonChars++;
                            break;
            } /* switch c */
        } while ((!endofcookie) && ((c=ReadCharAsync(asynccookies))!=EOF));

        while (displayString[--cnt]=='\n');   /* cutoff trailing '\n' */
        displayString[++cnt] = '\0';
        if (CookieChars > CookieNonChars) isAsciiPicture = FALSE;
        else                              isAsciiPicture = TRUE;
    }
    else
    {
        GetStringFromGadget (displayString,noCookieText);
        isAsciiPicture = FALSE;
    }
}
//<

//> void LoadAnAsyncCookie(char *fn)
void LoadAnAsyncCookie(char *fn)
{
    long whichCookie;

    if (cookiesInFile)
    {
        whichCookie = RndBT(1,cookiesInFile);   /* select one of the cookies */
        LoadAsyncCookieNumber(fn,whichCookie);       /* load that cookie */
    }
    else GetStringFromGadget (displayString,noCookieText);
}
//<

long Blank(PrefObject *Prefs)
{
    long Wid, Hei;
    long dx,dy;
    long count = 0;
    long i;
    long c1=0, c2=14, c3=28;
    long RetVal = OK;
    mPoint new, old, size, min, max;
    struct Library *DiskfontBase;
    struct TextFont *propfont,*fixfont,*originalfont;
    struct Screen *TextScr;
    struct RastPort *Rast;
    struct ViewPort *View;
    struct Window *Wnd;
    long MaxWid, Lns;           /* data describing one cookie */
    Line *Lines;
    long NewMaxWid,NewLns;      /* data describing the new cookie */
    Line *NewLines;
    unsigned long changetime = Prefs[PREFS_CHANGETIME].po_Level; /* seconds to wait between changing */
    unsigned long changeat;     /* time when to change to next cookie */

    srand48(time(NULL));    /* Set seek value for Unix's lrand48() function */
                            /* lrand48() provides a number from 0..2**31    */
    strcpy(noCookieText,Prefs[PREFS_NOCOOKIETEXT].po_Value);

    if (cookieStart = AllocVec(MAX_COOKIES*sizeof(long),MEMF_ANY))
    {
        if (AsyncIOBase = OpenLibrary("asyncio.library",39))
        {                               /* Open the file and load the first two buffers of characters */
            asynccookies = OpenAsync(Prefs[PREFS_FILE].po_Value,MODE_READ,ASYNC_BUFSIZE);

            if (DiskfontBase = OpenLibrary("diskfont.library",37))
            {
                CountCookies(Prefs[PREFS_FILE].po_Value);
                
                LoadAnAsyncCookie(Prefs[PREFS_FILE].po_Value);
                                        /* if loading a cookie fails, the <nocookietext> is used */

                Prefs[PREFS_PROPFONT].po_Attr.ta_Name = Prefs[PREFS_PROPFONT].po_Name;
                propfont = OpenDiskFont(&Prefs[PREFS_PROPFONT].po_Attr);
                if (!propfont) Prefs[PREFS_PROPFONT].po_Attr.ta_Name = "topaz.font";
                Prefs[PREFS_FIXFONT].po_Attr.ta_Name = Prefs[PREFS_FIXFONT].po_Name;
                fixfont = OpenDiskFont(&Prefs[PREFS_FIXFONT].po_Attr);
                if (!fixfont) Prefs[PREFS_FIXFONT].po_Attr.ta_Name = "topaz.font";

                TextScr = OpenScreenTags(NULL,
                                         SA_DisplayID, Prefs[PREFS_MODE].po_ModeID,
                                         SA_Depth, 1,
                                         SA_Quiet, TRUE,
                                         SA_Behind, TRUE,
                                         SA_Overscan, OSCAN_STANDARD,
                                         SA_Font, &(Prefs[PREFS_PROPFONT].po_Attr),
                                         TAG_DONE);
                if(TextScr)
                {
                    Rast = &(TextScr->RastPort);
                    View = &(TextScr->ViewPort);
                    Wid = TextScr->Width;
                    Hei = TextScr->Height;
                    originalfont = Rast->Font;

                    if (isAsciiPicture) SetFont(Rast,fixfont);
                    else                SetFont(Rast,propfont);
                    Lines = CalculateExtent(displayString,TextScr,&MaxWid,&Lns);
                    if (Lines)
                    {
                        SetRGB4(View,0,0L,0L,0L);
                        switch(Prefs[PREFS_COLORS].po_Active)
                        {
                            case PREFS_COLORS_CYCLE: SetRGB4(View, 1,vals[c1],vals[c2],vals[c3]);
                                                     break;
                            case PREFS_COLORS_RANDOM: SetRGB4(View, 1,RndBT(5,15),RndBT(5,15),RndBT(5,15));
                                                      break;
                            case PREFS_COLORS_COPPER: setCopperList(Hei, 1,View,&custom);
                            case PREFS_COLORS_WHITE:  SetRGB4(View, 1,15,15,15);
                                                      break;
                        }

                        dx = (2*(RndBT(0,1)))-1;   /* Thus it will be 1 or -1 */
                        dy = (2*(RndBT(0,1)))-1;   /* Thus it will be 1 or -1 */

                        size.x = MaxWid + 2;
                        if(isAsciiPicture) size.y = Lns * fixfont->tf_YSize + 2;
                        else               size.y = Lns * propfont->tf_YSize + 2;
                        new.x = old.x = (Wid-size.x) / 2;
                        new.y = old.y = (Hei-size.y) / 2;

                        min.x = 0;
                        min.y = 0;
                        max.x = Wid-size.x-3;
                        max.y = Hei-size.y-3;

                        SetAPen(Rast,1);
                        for(i=0;i<Lns;i++)
                        {
                            if (isAsciiPicture)
                                 Move(Rast, old.x + Lines[i].ln_Offset + 1,
                                            old.y + i*fixfont->tf_YSize + fixfont->tf_Baseline + 1);
                            else Move(Rast, old.x + Lines[i].ln_Offset + 1,
                                            old.y + i*propfont->tf_YSize + propfont->tf_Baseline + 1);
                            Text(Rast,Lines[i].ln_Text,strlen(Lines[i].ln_Text));
                        }

                        Wnd = BlankMousePointer(TextScr);
                        ScreenToFront(TextScr);
                        changeat = time(NULL) + changetime;

                        while(RetVal==OK)
                        {
                            if(!(count++ % 600)) ScreenToFront(TextScr);

                            if ((Prefs[PREFS_CHANGECOOKIE].po_Active==PREFS_YES) && cookiesInFile)
                            {
                                if (time(NULL)>=changeat)
                                {   /* Load a new cookie */
                                    LoadAnAsyncCookie(Prefs[PREFS_FILE].po_Value);
                                    if (isAsciiPicture) SetFont(Rast,fixfont);
                                    else                SetFont(Rast,propfont);
                                    NewLines = CalculateExtent(displayString,TextScr,&NewMaxWid,&NewLns);
                                    if (NewLines)
                                    {   /* New cookie is available */
                                        FreeVec(Lines);
                                        Lines = NewLines;
                                        MaxWid = NewMaxWid;
                                        Lns = NewLns;
                                        dx = (2*(RndBT(0,1)))-1;    /* select a new direction */
                                        dy = (2*(RndBT(0,1)))-1;
                                        size.x = MaxWid + 2;        /* adjust size */
                                        if (isAsciiPicture)
                                             size.y = Lns * fixfont->tf_YSize + 2;
                                        else size.y = Lns * propfont->tf_YSize + 2;
                                        min.x = 0;
                                        min.y = 0;
                                        max.x = Wid-size.x-3;
                                        max.y = Hei-size.y-3;
                                        new.x = old.x = (Wid-size.x) / 2;
                                        new.y = old.y = (Hei-size.y) / 2;
                                        SetRast(Rast,0);            /* clear screen */
                                        SetAPen(Rast,1);            /* draw the new cookie */
                                        for(i=0;i<Lns;i++)
                                        {
                                            if (isAsciiPicture)
                                                 Move(Rast,old.x+Lines[i].ln_Offset+1,
                                                           old.y+i*fixfont->tf_YSize+fixfont->tf_Baseline+1);
                                            else Move(Rast,old.x+Lines[i].ln_Offset+1,
                                                           old.y+i*propfont->tf_YSize+propfont->tf_Baseline+1);
                                            Text(Rast,Lines[i].ln_Text,strlen(Lines[i].ln_Text));
                                        }
                                        changeat = time(NULL)+changetime; /* next change time */
                                    }
                                }
                            }

                            if((Prefs[PREFS_COLORS].po_Active==PREFS_COLORS_CYCLE) &&
                               ((!Prefs[PREFS_COLDELAY].po_Level) ||
                                                                (!(count%Prefs[PREFS_COLDELAY].po_Level))))
                            {
                                c1 = ++c1 % 42;
                                c2 = ++c2 % 42;
                                c3 = ++c3 % 42;
                                SetRGB4(View,1,vals[c1],vals[c2],vals[c3]);
                            }

                            if(!Prefs[PREFS_MOVEDELAY].po_Level || !(count%Prefs[PREFS_MOVEDELAY].po_Level))
                            {
                                new.x += dx;
                                new.y += dy;

                                if (new.x<=min.x)      { new.x = min.x; dx = 1; }
                                else if (new.x>=max.x) { new.x = max.x; dx = -1; }

                                if (new.y<=min.y)      { new.y = min.y; dy = 1; }
                                else if (new.y>=max.y) { new.y = max.y; dy = -1; }

                                WaitTOF();
                                BltBitMapRastPort(Rast->BitMap, old.x, old.y,
                                                  Rast, new.x, new.y,
                                                  size.x, size.y,
                                                  0xC0);
                                old = new;
                            }
                            RetVal = ContinueBlanking();
                        }
                        UnblankMousePointer(Wnd);

                        if(Prefs[PREFS_COLORS].po_Active==PREFS_COLORS_COPPER) clearCopperList(View);
                        FreeVec(Lines);
                    }
                    else RetVal = FAILED;
                    SetFont(Rast,originalfont);
                    CloseScreen(TextScr);
                }
                else RetVal = FAILED;
                if (propfont) CloseFont(propfont);
                if (fixfont)  CloseFont(fixfont);
                CloseLibrary(DiskfontBase);
            }
            else RetVal = FAILED;
            if (asynccookies) CloseAsync(asynccookies);
            CloseLibrary(AsyncIOBase);
        }
        else RetVal = FAILED;
        FreeVec(cookieStart);
    }
    else RetVal = FAILED;
    return RetVal;
}

