/*
 * explode.c - makes all Amiga windows "explode" when opening
 *
 * Bruno Costa - 30 Aug 89 - 31 Dec 89
 *
 * NOTE:
 *   This program is VERY sensitive to compiler environment.
 *  Compile under Lattice 5.0 with options -b0 -r0 -v.
 */

/* TODO:
    - check for windows where 4*size(Window) > 5*size(Screen)
*/

#define DELAY   1
#define FASTDIV 0
#define DETACH  1
#define VERBOSE 1

#include <exec/types.h>
#include <intuition/intuition.h>
#include <graphics/gfxmacros.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/layers.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include "intuition.lvo"
#include "common.h"

#define MAX(a,b) (((a)>(b))?(a):(b))
#define MIN(a,b) (((a)<(b))?(a):(b))

#if DETACH
long _stack = 2000;
char *_procname = "Window_Exploder";
long _priority = 5;
long _BackGroundIO = 1;
extern BPTR _Backstdout;
#else
BPTR _Backstdout;
#endif

/*
 * Resident Amiga libraries
 */
struct Library *IntuitionBase;
struct Library *GfxBase;
struct Library *LayersBase;

/*
 * CLI Messages
 */
#define BANNER   "\x1b[33mExplode 1.2b\x1b[31m - \xa9 1989 by Bruno Costa\n"
#define USAGE    "usage: \x1b[1mexplode\x1b[0m [-0..2] [-f] [-d] [-s] [-q] [<filename>]\n"
#define REMOVING "\x1b[1mExplode :\x1b[0m handler removed.\n"

/*
 * pointer to old OpenWindow function
 */
void (* openWindow) (void);

/*
 * external assembly routine interface
 */
extern void __far ExplodeWindow(void);

/*
 * Patterns
 */
#define DASHED 0x5555
#define FULL   0xffff

/*
 * These are globals just for the sake of efficiency
 *   (avoids stack parameter passing).
 */
static struct Screen scr;
static struct RastPort *rp;
static int x0, y0, x, y, w, h;
static int xi, yi, xf, yf;

/*
 * Extended message port - contains global data
 */
#define PORTNAME "Explode_port"

struct ExtMsgPort {
  struct MsgPort theport;
  unsigned long int tagcode;
  char tag[TAGSIZE];		/* tag to mark start of data */
  int timedelay, pattern, nsteps;
  struct {
    struct {
      int x, y;
    } topleft, botright;
  } step[MAXSTEPS];
};

static struct ExtMsgPort myport = {
 {NULL},
 TAG_CODE,
 TAG_ID,
 DEFDELAY, DEFPAT, 21,
 {
  {    0,    0,    0,    0 },	/* left, top, right, bottom */
  {  135,  -22,  135,  -22 },
  {  265,  -30,  265,  -30 },
  {  389,  -25,  389,  -25 },
  {  506,   -6,  506,   -6 },
  {  615,   23,  615,   23 },
  {  717,   62,  717,   62 },
  {  811,  111,  811,  111 },
  {  895,  168,  895,  168 },
  {  971,  231,  971,  231 },
  { 1036,  300, 1036,  300 },
  { 1090,  373, 1090,  373 },
  { 1134,  449, 1134,  449 },
  { 1166,  527, 1166,  527 },
  { 1186,  605, 1186,  605 },
  { 1194,  683, 1194,  683 },
  { 1188,  759, 1188,  759 },
  { 1169,  833, 1169,  833 },
  { 1135,  902, 1135,  902 },
  { 1087,  966, 1087,  966 },
  { 1024, 1024, 1024, 1024 }	/* 21th entry */
 }
};
/*
 *  The points above were generated using an HP-28S and a bezier curve
 * plotting program. The curve was broken in 20 line segments and the
 * control points were: (0, 0) ; (922, -205) ; (1587, 665) ; (1024, 1024)
 */


#define xclip(x) (MAX(0, MIN(x, scr.Width - 1)))
#define yclip(y) (MAX(0, MIN(y, scr.Height - 1)))

/*
 *  Draws a line performing a simple clipping that only works for
 * horizontal or vertical lines (the ones I draw).
 */
void line (int x1, int y1, int x2, int y2)
{
 int x1c, y1c, x2c, y2c;

 x1c = xclip (x1);
 y1c = yclip (y1);
 x2c = xclip (x2);
 y2c = yclip (y2);

 if ( ((x1c == x1) && (y1c == y1)) || ((x2c == x2) && (y2c == y2)) )
 {
   Move (rp, x1c, y1c);
   Draw (rp, x2c, y2c);
 }
}

/*
 * Draws the outline from (xi,yi)-(xf,yf)
 */
void DrawOutline (void)
{
 line (xi, yi, xf, yi);
 line (xf, yi, xf, yf);
 line (xf, yf, xi, yf);
 line (xi, yf, xi, yi);
}

/*
 * Does the animation of the outline along the path
 */
void MoveOutline (void)
{
 register int i;
 int dxi, dyi, dxf, dyf;
 int nxi, nyi, nxf, nyf;

 dxi = x - x0;
 dyi = y - y0;
 dxf = (x + w - 1) - x0;
 dyf = (y + h - 1) - y0;

 for (i = 0; i < myport.nsteps; i++)
 {
#if FASTDIV
   /*
    * "Divides" faster by shifting (does it work for negative numbers ?)
    */
   nxi = x0 + ((myport.step[i].topleft.x * dxi) >> 10);
   nyi = y0 + ((myport.step[i].topleft.y * dyi) >> 10);
   nxf = x0 + ((myport.step[i].botright.x * dxf) >> 10);
   nyf = y0 + ((myport.step[i].botright.y * dyf) >> 10);
#else
   nxi = x0 + ((myport.step[i].topleft.x * dxi) / 1024);
   nyi = y0 + ((myport.step[i].topleft.y * dyi) / 1024);
   nxf = x0 + ((myport.step[i].botright.x * dxf) / 1024);
   nyf = y0 + ((myport.step[i].botright.y * dyf) / 1024);
#endif

   if (i)
     DrawOutline ();	/* erases outline */

   xi = nxi;
   yi = nyi;
   xf = nxf;
   yf = nyf;

   DrawOutline ();	/* draws outline */

#if DELAY
   Delay (myport.timedelay);		/* quick pause */
#endif
 }
 DrawOutline ();
}

/*
 * C routine that does the real explosion
 */
void __far DoExplode(struct NewWindow *nw)
{
 if (GetScreenData (&scr, sizeof(struct Screen), nw->Type, nw->Screen))
 {
   /*
    *  I am working on a copy of the RastPort, so I won't care to restore
    * old values like drawing modes and current position.
    */

   rp = &scr.RastPort;
   SetDrMd (rp, COMPLEMENT | JAM1);
   SetDrPt (rp, myport.pattern);

   x0 = xclip (scr.MouseX);   	/* assure that coordinates are */
   y0 = yclip (scr.MouseY);   	/* inside this screen. */

   x = nw->LeftEdge;
   y = nw->TopEdge;
   w = nw->Width;
   h = nw->Height;

   LockLayers (&scr.LayerInfo);

   MoveOutline ();	/* Now move window along the path */

   UnlockLayers (&scr.LayerInfo);
 }
}

#if VERBOSE
/*
 * window text shadows
 */

static struct IntuiText TextS3 = {
 2,0,JAM1,
 26,30,
 NULL,
 "(September, 26th 1989)",
 NULL
};

static struct IntuiText TextS2 = {
 2,0,JAM1,
 25,19,
 NULL,
 "written by Bruno Costa",
 &TextS3
};

static struct IntuiText TextS1 = {
 2,0,JAM1,
 71,6,
 NULL,
 "Explode 1.2",
 &TextS2
};

#define windshadow TextS1

static struct IntuiText IText3 = {
 1,0,JAM1,
 26,30,
 NULL,
 "(September, 26th 1989)",
 NULL
};

static struct IntuiText IText2 = {
 1,0,JAM1,
 25,19,
 NULL,
 "written by Bruno Costa",
 &IText3
};

static struct IntuiText IText1 = {
 3,0,JAM1,
 71,6,
 NULL,
 "Explode 1.2",
 &IText2
};

#define windtext IText1

static struct NewWindow wind = {
 207, 71, 227, 47, 0, 1, NULL,
 SIMPLE_REFRESH + RMBTRAP + NOCAREREFRESH,
 NULL, NULL, NULL, NULL, NULL,
 0, 0, 0, 0, WBENCHSCREEN
};
#endif

void usage (void)	/* dead end */
{
 Write (_Backstdout, USAGE, sizeof(USAGE));
#if DETACH
 Close (_Backstdout);
#endif
 exit (5);
}

main(int argc, char *argv[])
{
 int i, removed, verbose = TRUE, stay = FALSE;
 struct Window *w;
 struct ExtMsgPort *port;

#if !DETACH
 _Backstdout = Output();
#endif

 port = (struct ExtMsgPort *) FindPort (PORTNAME);
 if (!port)
 {
   port = &myport;
   stay = TRUE;
 }

 if (_Backstdout && argc)
 {
   if (stay)
     Write (_Backstdout, BANNER, sizeof(BANNER));
   for (i = 1; i < argc; i++)
   {
     if (argv[i][0] == '-')
     {				/* argument is an option */
       if (argv[i][1] >= '0' && argv[i][1] <= '2')
         port->timedelay = argv[i][1] - '0';
       else if (argv[i][1] == 'f')
         port->pattern = FULL;
       else if (argv[i][1] == 'd')
         port->pattern = DASHED;
       else if (argv[i][1] == 's')
         verbose = FALSE;
       else if (argv[i][1] == 'q')
       {
         if (stay)
           stay = FALSE;
         else
         {
           Signal (port->theport.mp_SigTask, 1L << port->theport.mp_SigBit);
           port = &myport;
         }
         Write (_Backstdout, REMOVING, sizeof(REMOVING));
       }
       else
         usage();
     }
     else
     {				/* argument is a filename */
       usage();
     }
   }
#if DETACH
   Close (_Backstdout);
#endif
 }

 if (stay)
 {
   /* create port */
   myport.theport.mp_Node.ln_Name = PORTNAME;
   myport.theport.mp_Node.ln_Pri = 0;
   myport.theport.mp_Node.ln_Type = NT_MSGPORT;
   myport.theport.mp_Flags = PA_SIGNAL;
   myport.theport.mp_SigBit = AllocSignal (-1);
   myport.theport.mp_SigTask = FindTask (NULL);
   AddPort (&myport.theport);
   port = &myport;
 }
 else
   goto final;

 GfxBase = OpenLibrary ("graphics.library", 0);
 LayersBase = OpenLibrary ("layers.library", 0);
 IntuitionBase = OpenLibrary ("intuition.library", 0);

 openWindow = SetFunction (IntuitionBase, _LVOOpenWindow, ExplodeWindow);

#if VERBOSE
 if (verbose)
 {
   w = OpenWindow (&wind);	/* notify that handler is installed */
   if (w)
   {
     PrintIText (w->RPort, &windshadow, 2, 1);
     PrintIText (w->RPort, &windtext, 0, 0);
     Delay (200);
     CloseWindow (w);
   }
 }
#endif

 do
 {
   Wait (1L << port->theport.mp_SigBit);	/* wait for termination signal */

   Forbid ();

   if (SetFunction (IntuitionBase, _LVOOpenWindow, openWindow) != ExplodeWindow)
   {
     /*
      *   Someone has interposed a handler after me. My handler can't be
      *  removed: otherwise the system will crash after the other handler
      *  removes itself.
      */
     (void) SetFunction (IntuitionBase, _LVOOpenWindow, ExplodeWindow);
     removed = FALSE;
   }
   else
     removed = TRUE;

   Permit();

 } while (!removed);

 CloseLibrary (IntuitionBase);
 CloseLibrary (LayersBase);
 CloseLibrary (GfxBase);

 /* Delete port */
 RemPort (&port->theport);
 FreeSignal (port->theport.mp_SigBit);

final:
 exit (0);
}
