/*
// Seth - Draws a "chaotic" n-gon on the screen
//
//   The n-gon is labeled 0 through n-1 at its corners
//   respectively.  The concept is
//
//   1.  pick a point inside the n-gon
//   2.  put a dot there
//   3.  roll an n-sided dice
//   4.  make the next point the point half way between the current
//       point and the corner with the label corresponding to the
//       number of dots showing on the dice.
//   5.  go to 2
//
// Motivated by the Nova episode on "Chaos", WGBH Boston. on PBS.
//  
// Rev History
//   rph - Robert Hale
//   vaw - Victor A. Wagner, Jr.
//   <> who     yr mo da reason
//  100 vaw     89 07 04 Initial production release
//   99 vaw     89 07 04 more finishing up
//   92 rph&vaw 89 07 03 finishing up
//   01 rph&vaw 89 05 12 Converted to C and added 16 colo(u)rs
//   00 rph     89 02 20 add auto-equalatiral polygoning;
//   00 rph     89 02 05 Start                                  
*/

#include <exec/types.h>
#include <graphics/gfxmacros.h>
#include <intuition/intuition.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <proto/intuition.h>

#define MaxSides 15
#define ScreenSize 380
#define HalfScreenSize 96
#define Bad -1
#define adjust 10000
#define IREV 0
#define TriSize 380

#define SIDESBASE    0
#define HISTORYBASE 20

#define SPECIALBASE 30
#define PAUSE      30
#define CLEAR      31
#define LINEDOT    32
#define WHITEBLACK 33

#define RATIOBASE   40

#define SIDESLEFT   460
#define SIDESMIDDLE 520
#define HISTORYLEFT 580
#define RATIOLEFT   620

#define SHAPESCALE   50
#define HISTORYSCALE 36
#define SPECIALTOP   (6*SHAPESCALE)

#define RatioSize ((sizeof(ratios)/sizeof(struct wwxys))-2)

struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;
struct Window *w;
struct Screen *s;
struct RastPort *WindowPort;
struct TmpRas   myTmpRas;
struct AreaInfo myAreaInfo;

int corner[20];
UWORD areabuffer[50];
UWORD color[16] = {    0, 0xf00, 0xaaa, 0xfc0, 0xcf0, 0xc60, 0x0c0, 0x0f6,
                   0x0fc, 0x0cf, 0x04b, 0x00f, 0x0cc, 0xc0f, 0xf0c, 0xc06};
                   
char * specialtext[] = {"Pause ",
                        "Clear ",
                        "Lines ",
                        "White ",
                        " Run  ",
                        "xxxxxx",
                        " Dot  ",
                        "Black "};
int cdepth;                        
int divisor,amount;
int NumSides;
int DebugMode;
int LineMode;
int WindowHeight;
int WindowCenter;
int RatioTop, RatioCurrent;
int Paused;


struct wwxys {
  int x,y;
} xy[MaxSides];

struct wwxys ratios[] = {
#include "seth.ci"
};

PLANEPTR myplane;

char asides [30];


void quit(int v)
{
  FreeRaster(myplane, 640, 400);
  CloseWindow(w);
  CloseScreen(s);
  exit(v);
}

int gadgetid(int x, int y)
{
   if ( x < SIDESMIDDLE)
     return((y-w->BorderTop) / SHAPESCALE + 3);
   if ( x < HISTORYLEFT)
     if (y-w->BorderTop < SPECIALTOP)
       return((y-w->BorderTop) / SHAPESCALE + 10);
     else
       return((y-w->BorderTop-SPECIALTOP)/12 + SPECIALBASE);
   if ( x < RATIOLEFT)
     return((y-w->BorderTop) / HISTORYSCALE) + HISTORYBASE;
   if ( x > RATIOLEFT)
     return (RATIOBASE);
}

void RatioFix(int where)
{
  amount = ratios[where].x;
  divisor = ratios[where].y;
  sprintf(&w->Title[63], "%2d/%-2d History", divisor-amount, divisor);
  SetWindowTitles(w, w->Title, 0);
  if (LineMode) LineMode = 1;
  if (where != RatioCurrent)
    {
    if (where > RatioCurrent)
      {
        SetAPen(WindowPort, 3);
        RectFill(WindowPort, RATIOLEFT+10, RatioCurrent+RatioTop, 639, where+RatioTop);
      }
    else
      {
        SetAPen(WindowPort, 1);
        RectFill(WindowPort, RATIOLEFT+10, where+RatioTop, 639, RatioCurrent+RatioTop);
      }
    SetAPen(WindowPort, 0);
    Move(WindowPort, RATIOLEFT+10, where+RatioTop);
    Draw(WindowPort, 639, where+RatioTop);
    RatioCurrent = where;
    }
}

void historygadget(int where, int background)
{
  SetOPen(WindowPort, 1);
  SetAPen(WindowPort, background);
  RectFill(WindowPort, HISTORYLEFT, where*HISTORYSCALE+w->BorderTop+2, RATIOLEFT, where*HISTORYSCALE+w->BorderTop+32);
  sprintf(asides, "%1d", where);
  SetDrMd(WindowPort, JAM1);
  SetAPen(WindowPort, 3);
  Move(WindowPort, HISTORYLEFT+14, where*HISTORYSCALE+w->BorderTop+21);
  Text(WindowPort, asides,1);
  SetAPen(WindowPort, 1);
  Move(WindowPort, HISTORYLEFT+15, where*HISTORYSCALE+w->BorderTop+20);
  Text(WindowPort, asides, 1);
  BNDRYOFF(WindowPort);
}

void specialgadget(int which, int onoff)
{
  SetAPen(WindowPort, 9);
  SetBPen(WindowPort, 10);
  SetDrMd(WindowPort, JAM2);
  
  Move(WindowPort, SIDESMIDDLE, (which-SPECIALBASE)*12+SPECIALTOP+w->BorderTop+7);
  Text(WindowPort, specialtext[(which-SPECIALBASE)+(onoff?4:0)], 6);
  if (LineMode) LineMode = 1;
}


/* linedraw (fromx,fromy, tox,toy)
// color is already setup
*/
void linedraw (int fromx, int fromy, int tox, int toy)
{
  fromy /= adjust;
  fromx /= adjust;
  tox /= adjust;
  toy /= adjust;
  Move (WindowPort,w->BorderLeft+fromx,w->BorderTop+2+fromy);
  Draw (WindowPort,w->BorderLeft+tox,w->BorderTop+2+toy);
}

/*
// plot a dot. coords are kept at Offset*plot point.
*/
void dot (int x, int y)
{
  char buf[20];
  x = x / adjust;
  y = y / adjust;
    SetAPen (WindowPort, corner[cdepth]+1);
  if (LineMode)
    if (LineMode < 0)
      Draw(WindowPort, w->BorderLeft+x,w->BorderTop+2+y);
    else
      {
        Move(WindowPort,w->BorderLeft+x,w->BorderTop+2+y);
        LineMode = -1;
      }
  else
  if (WritePixel(WindowPort,w->BorderLeft+x,w->BorderTop+2+y) == Bad) {
    printf ("Bad plot at %d,%d\n",x,y);
    quit (FALSE);
  }
  if (DebugMode) {
    printf ("x,y = %d,%d (g?)", x, y);
    gets(buf);
    if (buf[0] == 'g')
      DebugMode = FALSE;
    if (buf[0] == 'l')
      LineMode = TRUE;
    if (buf[0] == 'L')
      LineMode = FALSE;
  }   
}

int polygon(int ulx, int uly, int height, int width, int sides) {

float r;
int cx, cy, i, j;

if (sides & 1 == 1) { /* odd? */
      r = height / (1+cos(PI/sides));
      cy = uly + r;
  }
  else {
      r = height / 2 / cos(PI/sides);
      cy = uly + height/2;
  }
  
  cx = ulx + width / 2;
    
  for (i=0, j=sides-1; i<=j; i++, j--)
  {
    float angle;
    int dx, dy;
    
      angle=(2.0*i+1)*PI/sides;
      dx = r * sin(angle);
      dy = r * cos(angle);
      xy[i].y = xy[j].y = adjust*(cy + dy);
      xy[i].x = adjust*(cx + dx);
      xy[j].x = adjust*(cx - dx);
  }
return(cy);
}

void InitBigPolygon(int sides)
{
int i;
  (void) polygon (0, 0, WindowHeight, SIDESLEFT, sides);
  NumSides = sides;
  for (i = 0; i < NumSides; i++) {
    SetAPen(WindowPort, i+1);
    if (i != NumSides-1)
      linedraw ((xy[i].x+xy[i+1].x)/2, (xy[i].y+xy[i+1].y)/2,
        xy[i].x, xy[i].y);
    if (i != 0)
      linedraw ((xy[i-1].x+xy[i].x)/2, (xy[i-1].y+xy[i].y)/2,
        xy[i].x, xy[i].y);
  }
  i = NumSides-1;
  linedraw ((xy[i].x+xy[0].x)/2, (xy[i].y+xy[0].y)/2,
    xy[i].x,xy[i].y);
  SetAPen(WindowPort,1);
  linedraw ((xy[i].x+xy[0].x)/2, (xy[i].y+xy[0].y)/2,
    xy[0].x,xy[0].y);

}

/*
// move x,y 1/2 way to a random corner.  
// Attempt to avoid too much rounding
// by adding 1 in if old coord was odd.
*/
void update (int *x, int *y)
{
  int i, tx, ty;
  tx = *x;
  ty = *y;
  for (i = cdepth; i; i--)
    corner[i] = corner[i-1];
  corner[0] = (rand () / 37) % NumSides;
  *x = ((*x*amount)+(divisor-amount)*xy[corner[0]].x) / divisor;
  *y = ((*y*amount)+(divisor-amount)*xy[corner[0]].y) / divisor;
  if (DebugMode)
    printf ("-> %d, ", corner[0]);
    
}
char tolc (char c)
{
  return (char)((('A' <= c) && (c <= 'Z'))? c-'A'+'a': c);
}
struct Screen *Create_Screen ( name, leftedge, topedge, width, height, 
                               depth, view )
UBYTE *name;
int leftedge, topedge, width, height, depth;
USHORT view;
  {
  struct NewScreen NewScreen;
  struct Screen *Screen;

  NewScreen.LeftEdge = leftedge;
  NewScreen.TopEdge = topedge;
  NewScreen.Width = width;
  NewScreen.Height = height;
  NewScreen.Depth  = depth;
  NewScreen.DetailPen = 0;
  NewScreen.BlockPen = 0;
  NewScreen.ViewModes = view;
  NewScreen.Type = NULL;
  NewScreen.Font = NULL;
  NewScreen.DefaultTitle = name;
  NewScreen.Gadgets = NULL;
  NewScreen.CustomBitMap = NULL;

  if ( ( Screen = OpenScreen ( &NewScreen ) ) == NULL ) {
    printf("ERROR - Unable to Open %s Screen\n", name );
    }
  else
    LoadRGB4 ( &Screen->ViewPort, &color, 16 );
  return ( Screen );
  }

/*
// MAIN:
//   check the arg params
//   open the intui library
//   plot the corner dots
//   go through the plot loop
//   if close gadget hit, quit
*/

void main (int argc, char **argv)
{
  struct NewWindow nw;
  struct IntuiMessage *message;
  int i, j, tcolor;
  int x,y;
  short sMX, sMY;
  char *p;
  BOOL done = FALSE;

  LineMode = FALSE;
  NumSides = 3;
  amount = 1;
  divisor = 2;
  cdepth = 0;
  x = 100;
  y = 100;
  sMX = sMY = 0;
  Paused = FALSE;
  
  for (i = 1; i < argc; i++) {
    p = argv[i];
    switch (tolc (*p++)) {
      case 's':
        sscanf (p, "%d", &NumSides);
        break;
        
      case 'a':
        sscanf (p, "%d", &amount);
        break;
        
      case 'd':
        sscanf (p, "%d", &divisor);
        break;
        
      case 'h':
        sscanf (p, "%d", &cdepth);
        break;
        
      case 'x':
        sscanf (p, "%d", &x);
        break;
        
      case 'y':
        sscanf (p, "%d", &y);
        break;
        
      case 'b':
        sscanf (p, "%x", &tcolor);
        color[0] = tcolor;
        break;
        
      case '?':
        DebugMode = TRUE;
        break;
        
      case 'l':
        LineMode = TRUE;
        break;
        
      default:
    printf ("Usage: seth Ssides [Aamount Ddivisor] [Hdepth] [Bbackgroundcolor] [Xx Yy]\n"); 
    printf ("where x,y is the start point\n");
    printf (" amount/divisor is the amount towards the corner to move\n");
    printf (" and depth is the number of moves back to graph\n");
    exit (FALSE);
    }
  }
  if (amount > divisor)
  {
    printf ("amount must be less than divisor");
    exit (FALSE);
  }
  if ((x > TriSize) || (y > TriSize)) {
    printf ("0 < x,y < %d\n", TriSize);
    exit (FALSE);
  }
  
  if (cdepth > 20) {
    printf ("Assuming you want the most depth (20)\n");
    cdepth = 20;
  }
  
  if (NumSides > MaxSides) {
    printf ("Max is %d. Assuming %d.\n",MaxSides,MaxSides);
    NumSides = MaxSides;
  }
  
  if (divisor > 32) {
    printf ("Assuming you want the biggest divisor (32)\n");
    divisor = 20;
  }
  
  for (i = 0; i < cdepth; i++)
    corner[i] = 14;
  
  w = NULL;
  
  IntuitionBase = (struct IntuitionBase *) OpenLibrary ("intuition.library", IREV);
  if ( IntuitionBase == NULL ) {
    printf ("Cant open intuition library.\n");
    exit (FALSE);
  }
   GfxBase = (struct GfxBase *)
      OpenLibrary("graphics.library", 0);
   if (GfxBase ==NULL) {
    printf ("Cant open graphics library.\n");
    exit(FALSE);
   }
   
  if ((s = Create_Screen("Seth", 0, 0, 640, 400, 4, HIRES | LACE)) == NULL) {
    printf ("Cant open a Hi-res/Interlace screen.\n");
    exit(FALSE);
  }
/*
// open a window
*/
  nw.LeftEdge = 0;
  nw.TopEdge = 0;
  nw.Width = 640;
  nw.Height = 400;
  nw.DetailPen = 0;
  nw.BlockPen = 1;
  nw.Title = "Seth 1.00                                              Ratio =  1/2  History  ";
  nw.Flags = SIMPLE_REFRESH | BORDERLESS | WINDOWCLOSE | ACTIVATE;
  
  nw.IDCMPFlags = CLOSEWINDOW | MOUSEBUTTONS | INTUITICKS | ACTIVEWINDOW;
  nw.Type = CUSTOMSCREEN;
  nw.FirstGadget = NULL;
  nw.CheckMark = NULL;
  nw.Screen = s;
  nw.BitMap = NULL;
  nw.MinWidth = 0;
  nw.MinHeight = 0;
  nw.MaxWidth = 0;
  nw.MaxHeight = 0;
  
  if ((w = (struct Window *) OpenWindow (&nw)) == NULL) {
    printf ("Couldn't open window\n");
    CloseScreen(s);
    exit(FALSE);
  }
  
  if ((myplane = AllocRaster(640,400)) == NULL)
    {
      CloseWindow(w);
      CloseScreen(s);
      exit(FALSE);
    }
 
  WindowPort = w->RPort;
  WindowPort->AreaInfo = &myAreaInfo;
  InitArea(&myAreaInfo, &areabuffer[0], 20);
  WindowPort->TmpRas = InitTmpRas(&myTmpRas, myplane, RASSIZE(640,400));
  SetOPen(WindowPort, 2);
 
  for(i=3; i<16; i++)
  {
  int ulx, uly, cy;
  
    cy = polygon(ulx = i>9?SIDESMIDDLE:SIDESLEFT, uly = i>9?(i-10)*SHAPESCALE:(i-3)*SHAPESCALE, 46, SHAPESCALE, i);
    SetAPen(WindowPort, i);
    AreaMove(WindowPort, xy[0].x/adjust+w->BorderLeft, xy[0].y/adjust+w->BorderTop+2);
    for (j=1; j<i; j++)
      AreaDraw(WindowPort, xy[j].x/adjust+w->BorderLeft, xy[j].y/adjust+w->BorderTop+2);
    AreaEnd(WindowPort);
    sprintf(asides, "%1d", i);
    SetDrMd(WindowPort, JAM1);
    SetAPen(WindowPort, ((i-3)>0)?(i-3):1);
    Move(WindowPort, ulx+(i>9?15:19), w->BorderTop+cy+7);
    Text(WindowPort, asides, i>9?2:1);
    SetAPen(WindowPort, i-2);
    Move(WindowPort, ulx+(i>9?16:20), w->BorderTop+cy+6);
    Text(WindowPort, asides, i>9?2:1);
  }
  
  for(i=0; i<10; i++)
  {
    historygadget(i, 2);
  }
  
  WindowHeight = 400 - w->BorderTop - w->BorderBottom - 4;
  WindowCenter = WindowHeight / 2 + w->BorderTop + 2;
  RatioTop = WindowCenter - RatioSize/2;
  RatioCurrent = 0;
  SetAPen(WindowPort, 1);
  RectFill (WindowPort, RATIOLEFT + 10, RatioTop, 639, RatioTop + RatioSize);
  RatioFix (161);
  
  for(i=0; i<RatioSize; i++)
    if(ratios[i].y < 16)
      {
        Move(WindowPort, RATIOLEFT+5, RatioTop+i);
        SetAPen(WindowPort, ratios[i].y);
        Draw(WindowPort, RATIOLEFT+9, RatioTop+i);
      }
  
  
  specialgadget(CLEAR, FALSE);
  specialgadget(PAUSE, Paused);
  specialgadget(LINEDOT, LineMode);
  specialgadget(WHITEBLACK, color[0]);
  
  InitBigPolygon(NumSides);
  
/*
//  start the loop
*/ 
  SetAPen (WindowPort,1);
  y = ScreenSize-y;
  x = x * adjust;
  y = y * adjust;
  srand(time(NULL));
  historygadget(cdepth, 12);
  
  while (!done) 
  {
  ULONG class;
  USHORT code;
  short MX, MY;
  
    if (!Paused)
      for (i = 0; i < 100; i++) {
        dot (x, y);
        update (&x, &y);
      }
    else
      {
        WaitPort(w->UserPort);
      }
      
    while (message = (struct IntuiMessage *)GetMsg(w->UserPort))
    {
       if ((class = message->Class) == CLOSEWINDOW) done = TRUE;
       else {
          code = message->Code;
          MX = message->MouseX;
          MY = message->MouseY;
          
          if (class == ACTIVEWINDOW)
            RatioFix (RatioCurrent);
          if (class == MOUSEBUTTONS)
           {
           if(MX>SIDESLEFT && MY>w->BorderTop && ReadPixel(WindowPort, MX, MY) > 0)
             {
                if (code == SELECTDOWN)
                {
                  sMX = MX;
                  sMY = MY;
                }
                else if (code == SELECTUP)
                  {
                  int temp;
                  
                    if ((temp = gadgetid(sMX, sMY)) == gadgetid(MX, MY))
                    {
                      if (temp < HISTORYBASE)
                        {
                          InitBigPolygon(temp-SIDESBASE);
                        }
                      else if (temp < SPECIALBASE)
                        {
                          historygadget(cdepth, 2);
                          cdepth = temp - HISTORYBASE;
                          historygadget(cdepth, 12);
                        }
                      else if (temp < RATIOBASE)
                        {
                          switch (temp)
                          {
                            case CLEAR:
                              SetAPen(WindowPort, 0);
                              RectFill(WindowPort, w->BorderLeft, w->BorderTop, 459, 399);
                              InitBigPolygon(NumSides);
                              break;
                            case LINEDOT:
                              specialgadget(LINEDOT, LineMode = !LineMode);
                              break;
                            case PAUSE:
                              specialgadget(PAUSE, Paused = !Paused);
                              break;
                            case WHITEBLACK:
                              specialgadget(WHITEBLACK, color[0] = color[0]?0:0xFFF);
                              LoadRGB4 (&s->ViewPort, &color, 16 );
                              break;
                          }
                        }
                      else
                        {
                          RatioFix(MY-RatioTop);
                        }
                    }
                    sMX = sMY = 0;
                  }
             }
            else
             {
               sMX = sMY = 0;
             }
           }
       }
       ReplyMsg(message);
    }
  }
    
/*
// we're all done
*/

quit(TRUE);
}
