/* -copyright-
#-# Copyright © 2021 Eric Bina, Dave Black, TJ Phan, 
#-#    Vincent Renardias, Jim Rees, Willem Vermin
#-# 
#-# Permission is hereby granted, free of charge, to any person 
#-# obtaining a copy of this software and associated documentation 
#-# files (the “Software”), to deal in the Software without 
#-# restriction, including without limitation the rights to use, 
#-# copy, modify, merge, publish, distribute, sublicense, and/or 
#-# sell copies of the Software, and to permit persons to whom 
#-# the Software is furnished to do so, subject to the following 
#-# conditions:
#-# 
#-# The above copyright notice and this permission notice shall 
#-# be included in all copies or substantial portions of the Software.
#-# 
#-# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 
#-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
#-# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
#-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
#-# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
#-# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
#-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
#-# OTHER DEALINGS IN THE SOFTWARE.
#-# 
*/

/*

 *      Original Author Unknown.

 *      8/10/88 - Ported from X10 to X11R3 by:

 Jonathan Greenblatt (jonnyg@rover.umd.edu)

 *      Cleaned up by Dave Lemke (lemke@sun.com)

 *      Ported to monocrome by Jonathan Greenblatt (jonnyg@rover.umd.edu)

 *     05/02/1996 Added TrueColor support by TJ Phan (phan@aur.alcatel.com)

*/

#include <sys/types.h>
#include <sys/time.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <gtk/gtk.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/xpm.h>

// wroot.h must be included after all othe x11 related includes
#include "vroot.h"

#include <ctype.h>
#include <assert.h>

#include "xfishtank.h"
#include "bubbles.h"
#include "debug.h"
#include "transwindow.h"
#include "utils.h"
#include "fishes.h"
#include "ixpm.h"
#include "ui.h"
#include "xdo.h"
#include "windows.h"



#ifdef DOUBLE_BUFFER
#include <X11/extensions/Xdbe.h>
static int             UseXdbe;
static XdbeSwapAction  BMETHOD;
static int             XdbeAvailable = -42;
#endif

extern unsigned char *ReadBitmap();

/* externals for pixmap and bimaps from xfishy.h */


/* typedefs for bubble and fish structures, also caddr_t (not used in X.h) */
typedef struct _bubble {
   float x; 
   float y; 
   int   size;            // size of bubble
   float bstep;           // increment in placement
} bubble;

typedef struct _fish {
   float x;
   float y;
   int   direction;        // direction: 0: r->l
   float y_direction;      // vertical direction: -1: to the top, 0: no movement, 1: to bottom
   int   frame;            // animation frames: 0 .. NUM_FRAMES
   int   type; 
   float fstep;            // increment in placement
   int   animtime;         // # drawings between change of frame
} fish;

typedef struct _fishtype {
   Pixmap pix;
   GC     gc;
   int    w;
   int    h;
} fishtype;


static int      binc[] = { 0, 64, 56, 48, 40, 32, 24, 16, 8 }; /* bubble increment and yes check tables */
static int      DoubleBuf = 0;               /* Should we use double buffering */
static int      screen;                      /* Default screen of this display */
static int      inxscreensaver = 0;          /* are we running in xscreensaver? */
static float    smooth = 0.02;               /* smoothness increment multiplier */
static bubble  *binfo = NULL;                /* bubble info structures, allocated dynamically  */
static fish    *finfo = NULL;                /* fish info structures, allocated dynamically */
static Window   root_window;
static fishtype xfish[NUM_FISH][2][NUM_FRAMES];        /* [type][left/right][frame] */

static Pixmap xbubbles[9];              /* bubbles bitmaps (1 to 8, by size in pixels) */
static Window UserWindow = 0;
static Pixel  white;
static Pixel  black;
static Pixel  bcolor;
static Pixel  bgcolor;
static GC bgc;
//static XdbeBackBuffer backbuf = 0;
static Drawable backbuf = 0;
static int  WantXdbe = 1;
static const int maxanimtime = 20;
static int ForceRoot = 0;
static int HaltedByInterrupt = 0;
static int Done = 0;

static Pixel AllocNamedColor(Display *display, char *colorName, Pixel dfltPix);
static char  *display_name=NULL;
static int   do_move(void *dummy);
static int   do_testfish(void *dummy);
static int   do_write_flags(void *dummy);
static void  erasebubble(bubble *b);
static void  erasefish(fish *f);
static void  init_pixmap(void);
static void  init_signals(void);
static void  initialize(void);
static void  move_fish(void);
static void  draw_fish(fish *f);
static void  new_bubble(bubble *b, int init);
static void  new_fish(fish *f0, int init);
static void  parse(int argc, char **argv);
static void  putbubble(bubble *b, unsigned long c);
static void  remove_all_fishes(void);
static float random_y_direction(void);
static float random_fstep(void);
static void  setanimtime(fish*f);
static void  step_bubbles(void);
static void  Usage(void);
static void  Thanks(void);
static void  SigHandler(int signum);
static void  print_changelog(void);
static void  selfrep(void);


Display *Dpy = NULL;
Window   xfishtankWin;
int counter;
int     blimit = 32;                 /* bubble limit */
int     flimit = 10;                 /* fish limit */
char   *bcolorstring  = NULL;
char   *bgcolorstring = NULL;
float   speedfactor;
int     allworkspaces = 1;
int     monitor = -1;
char   *DesktopSession;
xdo_t  *myxdo;
int     nomenu = 0;
GtkWidget *gtkwin;
int      width;                       /* width of initial window in pixels */
int      height;                      /* height of initial window in pixels */
int      fullwidth;                   /* width of full screen window in pixels */
int      fullheight;                  /* height of full screen window in pixels */
int      xfishtank_trans = 0;
Window   wid;                      /* aquarium window */
Window   wid1;



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
   parse command line 
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void parse(int argc, char *argv[])
{

   int p = 0;

#define CHECK do{if(++p >= argc){ Usage(); Thanks(); exit(1);}}while(0)
   while (++p < argc)
   {
      P("argv[p] %s\n",argv[p]);
      if (!strcmp(argv[p],"-b")) 
      {
	 CHECK;
	 blimit = strtod(argv[p],NULL);
      } 
      else if (!strcmp(argv[p],"-f")) 
      {
	 CHECK;
	 flimit = strtod(argv[p],NULL);
      } 
      else if (!strcmp(argv[p],"-display")) 
      {
	 CHECK;
	 display_name = strdup(argv[p]);
      } 
      else if (!strcmp(argv[p],"-bc")) 
      {
	 CHECK;
	 if(bcolorstring)
	    free(bcolorstring);
	 bcolorstring = strdup(argv[p]);
      } 
      else if (!strcmp(argv[p],"-bgc")) 
      {
	 CHECK;
	 if(bgcolorstring)
	    free(bgcolorstring);
	 bgcolorstring = strdup(argv[p]);
      } 
      /*
	 else if (!strcmp(argv[p],"-double")) 
	 {
	 CHECK;
	 WantXdbe = (strtod(argv[p],NULL)>0);
      // without double buffering, xfishtank misbehaves
      } 
      */
      else if (!strcmp(argv[p],"-speed")) 
      {
	 CHECK;
	 speedfactor = (strtod(argv[p],NULL)/100.0);
      } 
      else if (!strcmp(argv[p],"-allworkspaces"))
      {
	 CHECK;
	 allworkspaces = strtod(argv[p],NULL);
      }
      else if (!strcmp(argv[p],"-monitor"))
      {
	 CHECK;
	 monitor = strtod(argv[p],NULL);
      }
      else if (!strcmp(argv[p],"-h")) 
      {
	 Usage();
	 Thanks();
      } 
      else if (!strcmp(argv[p],"-v")) 
      {
	 printf("xfishtank version %s\n",VERSION);
	 Thanks();
      } 
      else if (!strcmp(argv[p],"-defaults")) 
      {
	 set_defaults();
      } 
      else if (!strcmp(argv[p],"-nomenu")) 
      {
	 nomenu = 1;
      } 
      else if (!strcmp(argv[p],"-root")) 
      {
	 ForceRoot = 1;
      } 
      else if (!strcmp(argv[p],"--root")) 
      {
	 ForceRoot = 1;
      } 
      // this is used by xscreensaver:
      else if (!strcmp(argv[p],"-window-id")) 
      {
	 CHECK;
	 UserWindow = strtod(argv[p],NULL); 
      } 
      else if (!strcmp(argv[p],"--window-id")) 
      {
	 CHECK;
	 UserWindow = strtod(argv[p],NULL); 
      } 
      else if (!strcmp(argv[p],"-changelog")) 
      {
	 print_changelog();
	 Thanks();
      } 
#ifdef SELFREP
      else if (!strcmp(argv[p],"-selfrep")) 
      {
	 selfrep();
	 exit(0);
      } 
#endif
      else 
      {
	 printf("Not understood: %s\n",argv[p]);
	 Usage();
	 Thanks();
      }
   }
}

void set_defaults()
{
   flimit        = 10;
   blimit        = 32;
   if(bcolorstring)
      free(bcolorstring);
   bcolorstring  = strdup("lightblue");
   if(bgcolorstring)
      free(bgcolorstring);
   bgcolorstring = strdup("darkblue");
   speedfactor   = 1.0;
   allworkspaces = 1;
   monitor       = -1;
   set_monitor(monitor);
}

void Usage()
{
   printf("\nUsage: xfishtank [options]\n\n");
   printf("Options:\n");
   printf("        -b n       number of bubbles (default 32)\n");
   printf("        -f n       number of fish (default 10)\n");
   printf("        -bc color  color of bubbles (default \"lightblue\")\n");
   printf("        -bgc color color of background (default \"darkblue\")\n");
   printf("                   only when running under xscreensaver or in a non-compositing WM\n");
   // printf("        -double n  1: double buffering, 0: do not use (default: 1)\n");
   printf("        -speed     speed of fishes (default: 100)\n");
   printf("        -defaults  all options to default\n");
   printf("        -nomenu    minimize menu (NOTE: in xmonad e.d. the 'hide menu' button will not work)\n");
   printf("        -display   DISPLAY to draw on (default \"\")\n");
   printf("        -root      use root-window or xscreensaver-provided window to draw in\n");
   printf("        --root     same as -root\n");
   // printf("        -window-id window to draw on\n");
   // printf("        --window-id same as -window-id\n");
   printf("        -h         show this info\n");
   printf("        -v         show version\n");
   printf("        -changelog show ChangeLog and exit\n");
#ifdef SELFREP
   printf("        -selfrep   output gzipped tarfile of the source and exit\n");
#endif
   printf("\nXSCREENSAVER\n");
   printf("        If you want to run xfishtank under xscreensaver, add the following\n");
   printf("        line to $HOME/.xscreensaver:\n");
   printf("           xfishtank -root   \\n\\\n");

   printf("\n");
}

int do_write_flags(void *dummy)
{
   if (FlagsChanged)
   {
      FlagsChanged = 0;
      WriteFlags();
   }
   return TRUE;
   (void)dummy;
}


void erasefish(fish *f)
{
   /*
    * for something as small as a bubble, it was never worth the
    * effort of using clipmasks to only turn of the bubble itself, so
    * we just clear the whole rectangle.
    */
   //XClearArea(Dpy, wid, f->x, f->y, rwidth[f->type], rheight[f->type], False);
   fishtype *ft = &xfish[f->type][f->direction][f->frame];
   if(xfishtank_trans)
   {
      XClearArea(Dpy, wid, f->x, f->y, ft->w, ft->h, False);
   }
   else
   {
      XClearArea(Dpy, wid, monitor_x+f->x, monitor_y+f->y, ft->w, ft->h, False);
   }
}


// draw fish
void draw_fish(fish *f)
{
   fishtype *ft = &xfish[f->type][f->direction][f->frame];
   if(xfishtank_trans)
   {
      XSetClipOrigin(Dpy, ft->gc, f->x, f->y);
      // we have a separate window for each monitor
      XCopyArea(Dpy, ft->pix, wid,ft->gc, 0, 0, ft->w, ft->h, f->x, f->y);
   }
   else
   {
      // we don't have a separate window for each monitor
      XSetClipOrigin(Dpy, ft->gc, f->x, f->y);
      if (screen == -1) // full screen mode
      {
	 XCopyArea(Dpy, ft->pix, wid,ft->gc, 0, 0, ft->w, ft->h, f->x, f->y);
      }
      else
      {
	 XSetClipOrigin(Dpy, ft->gc, monitor_x + f->x, monitor_y + f->y);

	 //if (f->x + ft->w < width)
	 //  XCopyArea(Dpy, ft->pix, wid,ft->gc, 0, 0, ft->w,        ft->h, f->x, f->y);
	 //else
	 //  XCopyArea(Dpy, ft->pix, wid,ft->gc, 0, 0, width - f->x, ft->h, f->x, f->y);
	 P("myx myy: %d %d %d %d\n",monitor_x,monitor_y,(int)(monitor_x+f->x),(int)(monitor_y+f->y));
	 XCopyArea(Dpy, ft->pix, wid,ft->gc, 0, 0, width - f->x, height - f->y, monitor_x+f->x, monitor_y+f->y);
      }
   }


   P("draw_fish: %#lx %d %d %d %d %d %d %d\n",wid,f->type,f->direction,f->frame,(int)f->x,(int)f->y,ft->w,ft->h);
   f->animtime --;
   if (f->animtime <= 0)
   {
      setanimtime(f);
      f->frame = (f->frame + 1)%NUM_FRAMES;
   }
}

void setanimtime(fish*f)
{
   f->animtime = 0.75*(drand48()+0.333)*maxanimtime;
}

void erasebubble(bubble *b)
{
   if(xfishtank_trans)
      XClearArea(Dpy, wid, b->x, b->y, b->size, b->size, 0);
   else
      XClearArea(Dpy, wid, monitor_x+b->x, monitor_y+b->y, b->size, b->size, 0);

   P("erasebubble: %d %d %d\n",b->x, b->y, b->size);
}


void putbubble(bubble *b, unsigned long c)
{
   XGCValues gcv;

   int s = b->size;
   gcv.foreground = c;
   gcv.clip_mask = xbubbles[s];
   if(xfishtank_trans)
   {
      gcv.clip_x_origin = b->x;
      gcv.clip_y_origin = b->y;
      XChangeGC(Dpy, bgc, GCForeground | GCClipMask | GCClipXOrigin | GCClipYOrigin, &gcv);
      XFillRectangle(Dpy, wid, bgc, b->x, b->y, s, s);
   }
   else
   {
      gcv.clip_x_origin = monitor_x + b->x;
      gcv.clip_y_origin = monitor_y + b->y;
      XChangeGC(Dpy, bgc, GCForeground | GCClipMask | GCClipXOrigin | GCClipYOrigin, &gcv);
      XFillRectangle(Dpy, wid, bgc, monitor_x + b->x, monitor_y + b->y, s, s);
   }
   P("putbubble: %d %d\n", monitor_y + b->x, monitor_y + b->y);
}


/*
   Allocate a color by name.
   */
Pixel AllocNamedColor(Display *display, char *colorName, Pixel dfltPix)
{
   Pixel  pix;
   XColor exactcolor;
   XColor scrncolor;

   if (XAllocNamedColor(display,
	    DefaultColormap(display, DefaultScreen(Dpy)),
	    colorName,
	    &scrncolor,
	    &exactcolor))
      pix = scrncolor.pixel;
   else
      pix = dfltPix;

   return pix;
}




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
   Calibrate the pixmaps and bitmaps.  The right-fish data is coded in xfishy.h,
   this is transformed to create the left-fish.  The eight bubbles are coded
   in bubbles.h as a two dimensional array.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void init_pixmap()
{

   XWindowAttributes attr;
   XGetWindowAttributes(Dpy, wid, &attr);

   XpmAttributes attributes;
   attributes.valuemask = (
	 XpmExactColors | XpmCloseness | XpmDepth);

   attributes.exactColors = False;      // does not matter, true or false
   attributes.closeness   = 40000;      // recommended in xpm manual
   attributes.depth       = attr.depth; // essential

   int k;
   // debugging : print all fishes
   if(0)
      for (k=0; k<NUM_FISH*2; k++)
      {
	 int w,h,c,m,i;
	 sscanf(fishes[k][0],"%d %d %d %d",&w,&h,&c,&m);
	 printf("k %d %d %d %d %d\n",k,w,h,c,m);
	 for (i=0; i<h+c+1; i++)
	    printf("k i: %d %d %s\n",k,i,fishes[k][i]);
      }
   for (k = 0; k < NUM_FISH; k++) 
   {
      int i;
      for (i=0; i<2; i++)
      {
	 int j;
	 for(j=0; j<NUM_FRAMES; j++)
	 {
	    Pixmap pix;
	    fishtype *ft = &xfish[k][i][j];
	    iXpmCreatePixmapFromData(Dpy, wid, fishes[2*k+j], &ft->pix, &pix, &attributes, i);
	    ft->gc = XCreateGC(Dpy, wid, 0, NULL);
	    XSetClipMask(Dpy, ft->gc, pix);
	    XFreePixmap(Dpy,pix);
	    sscanf(fishes[2*k][0],"%d %d",&(ft->w),&(ft->h));
	 }
      }
   }

   int i;
   for (i = 1; i <= 8; i++) 
      xbubbles[i] = XCreateBitmapFromData(Dpy, wid, (char *) xbBits[i], i, i);
}




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
   Initialize signal so that SIGUSR1 causes secure mode to toggle.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void init_signals()
{
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
   Variety of initialization calls, including getting the window up and running.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void initialize()
{
   XWindowAttributes winfo;
   XSetWindowAttributes attr;
   (void)attr;
   XGCValues vals;
   int i,j,k;

   root_window = VirtualRootWindowOfScreen(DefaultScreenOfDisplay(Dpy));

   XGetWindowAttributes(Dpy, root_window, &winfo);
   fullwidth = winfo.width;
   fullheight = winfo.height;
   width = fullwidth;
   height = fullheight;


   DoubleBuf=0;

   int x,y;
   unsigned int w,h,b,depth;
   Window root, searchWin;
   XGetGeometry(Dpy,root_window,&root,
	 &x, &y, &w, &h, &b, &depth);
   gtkwin = gtk_window_new(GTK_WINDOW_TOPLEVEL); 
   gtk_window_set_title             (GTK_WINDOW(gtkwin),"XfishtanK");

   //gtk_window_set_skip_taskbar_hint (GTK_WINDOW(gtkwin),TRUE);
   //gtk_window_set_skip_pager_hint   (GTK_WINDOW(gtkwin),TRUE);
   gtk_window_set_decorated(GTK_WINDOW(gtkwin), FALSE);
   gtk_window_set_type_hint(GTK_WINDOW(gtkwin), GDK_WINDOW_TYPE_HINT_POPUP_MENU);

   if (ForceRoot)
   {
      searchWin = DefaultRootWindow(Dpy);
      // Are we started by xscreensaver?
      if (getenv("XSCREENSAVER_WINDOW"))
      {
	 searchWin = strtol(getenv("XSCREENSAVER_WINDOW"),NULL,0);
	 monitor = -1;
	 inxscreensaver = 1;
	 nomenu = 1;
      }
   }
   else if(UserWindow)
   {
      searchWin = UserWindow;
   }
   else
   {
#if 0 
      int HaveTrans = make_trans_window(gtkwin,
	    1 /*fullscreen*/,
	    allworkspaces /*sticky*/,
	    1 /* below*/,
	    1 /* dock*/ , 
	    NULL,
	    &searchWin);
#else
      int HaveTrans = make_trans_window(
	    Dpy,
	    gtkwin,
	    monitor,
	    allworkspaces /*sticky*/,
	    1 /* below*/,
	    0 /* dock */,
	    NULL /* gdkwindow */,
	    &searchWin,
	    NULL, // wantx
	    NULL  // wanty
	    );
#endif
      if (HaveTrans)
      {
	 xfishtank_trans = 1;
      }
      else
      {
	 searchWin = root_window;
	 // Maybe, it is LXDE: find window with name pcmanfm
	 {
	    char *a = DesktopSession;
	    while (*a) { *a = toupper(*a); a++; }
	    if (!strncmp(DesktopSession,"LXDE",4)) 
	    {
	       Window w = largest_window_with_name(myxdo, "^pcmanfm$");
	       if(w)
	       {
		  searchWin = w;
		  printf("LXDE session found, using window pcmanfm\n");
	       }
	    }
	 }
	 if(DesktopSession)
	    free(DesktopSession);
      }
      if(xfishtank_trans)
      {
	 XMoveWindow(Dpy,searchWin,0,0);
	 XSetWindowBackground(Dpy,searchWin,0);
      }
   }

   P("searchWin: %#lx\n",searchWin);

   wid = searchWin;

   xfishtankWin = searchWin;


   vals.foreground = vals.background = bcolor;
   vals.graphics_exposures = False;
   bgc = XCreateGC(Dpy, wid, GCForeground | GCBackground | GCGraphicsExposures, &vals);

   for (i = 0; i < NUM_FISH; i++) 
      for (j=0; j<2; j++)
	 for (k=0; k<NUM_FRAMES; k++)
	 {
	    fishtype *ft = &xfish[i][j][k];
	    ft->pix = 0;
	    ft->gc  = 0;
	    ft->w   = 42;
	    ft->h   = 42;
	 }

   init_pixmap();
   init_signals();

   binfo = (bubble *) malloc(blimit * sizeof(bubble));

   wid1 = wid;
#ifdef DOUBLE_BUFFER
   int xdbemajor;
   int xdbeminor;
   if (XdbeAvailable == -42)
      XdbeAvailable = XdbeQueryExtension(Dpy,&xdbemajor,&xdbeminor);
#else
   XdbeAvailable = 0;
#endif
   UseXdbe = XdbeAvailable && WantXdbe;

#ifdef DOUBLE_BUFFER
   //BMETHOD = XdbeBackground;
   BMETHOD = XdbeBackground;
   // BMETHOD = XdbeUndefined;
   // BMETHOD = XdbeUntouched;
   // BMETHOD = XdbeCopied;     // use this to check if dbe works; fishes and bubbles will not be erased

   if (UseXdbe)
   {
      if (backbuf)
	 XdbeDeallocateBackBufferName(Dpy,backbuf);
      backbuf = XdbeAllocateBackBufferName(Dpy, wid1, BMETHOD);
      wid = backbuf;
      printf("Using double buffer: %#lx window: %#lx\n",backbuf,wid1);
   }
   else
      printf("Using window: %#lx\n",wid);
#endif

   if(xfishtank_trans)
      printf("This is a new transparent window.\n");
   else
   {
      if(UserWindow)
	 printf("This is an existing window.\n");
      else
	 printf("This is an existing window, probably the root window.\n");
   }
   printf("Size: %dx%d\n",width,height);
   fflush(NULL);
   XClearWindow(Dpy,wid1);

   // drawing on wid, XClearWindow, XSetWindowBackground on wid1
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
   Create a new bubble.  Placement along the x axis is random, as is the size of
   the bubble.  Increment value is determined by speed.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void new_bubble(bubble *b, int init)  // init=0: postions at bottom, else random
{
   int s;

   b->x = 5+(width-10) * drand48();
   if (init)
      b->y = (height / 16) * (16*drand48() + 1) - 1;
   else
      b->y = height - 1;
   b->size = s = 1.0 + 8*drand48();
   if ((b->bstep = smooth * height / (float) binc[s]) == 0)
      b->bstep = 1;
   P("newbubble %f %f %d %d\n",b->x, b->y, monitor_x, width);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
   Erase old bubbles, move and draw new bubbles.  Random left-right factor
   can move bubble one size-unit in either direction.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void step_bubbles()
{
   int i, j, s;
   (void)j;
   (void)s;
   bubble *b;

   for (i = 0; i < blimit; i++) 
   {
      b = &binfo[i];
      s = b->size;
      /* clear */
      /*
	 if ((b->y > 0) && (b->erased == 0)) 
	 {
	 erasebubble(b, s);
	 }
	 */
      if ((b->y -= b->bstep) > 0) 
      {
	 double r = drand48();
	 if (r < 0.25) {
	    b->x -= 1;
	 } else if (r > 0.75) {
	    b->x += 1;
	 }
	 putbubble(b, bcolor);
      } else {
	 if (drand48() < 0.25) {
	    new_bubble(b,0);
	 }
      }
   }
   XFlush(Dpy);
}




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
   Create a new fish.   Placement along the y axis is random, as is the side
   >from which the fish appears.  Direction is determined from side.  Increment
   is also random.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void new_fish(fish *f0, int init)  // init 0: position at edge, else position at random
{
   int i, collide;
   fish *f = f0;

   f->type = drand48() * NUM_FISH;
   fishtype *ft = &xfish[f->type][0][0];
   for (i = 0, collide = 1; (i < 16) && (collide); i++) 
   {
      f->y = (height - ft->h) * drand48();
      f->fstep = random_fstep();
   }
   if (drand48() < 0.5) 
   {
      f->direction = 0;
      if (init)
	 f->x = ft->w + drand48()*width;
      else
	 f->x = width;
   } 
   else 
   {
      f->direction = 1;
      if (init)
	 f->x = drand48()*width-ft->w;
      else
	 f->x = -ft->w;
   }

   f->y_direction = random_y_direction();

   f->frame = 0;
   setanimtime(f);
   P("newfish %d %d\n",(int)f->x,(int)f->y);
}

float random_y_direction()
{
   return 2*drand48()-1;
}

float random_fstep()
{
   float f;
   if ((f = smooth * width / (8.0 * (1.0 + 8*drand48()))) < 0.5) 
      f = 0.5;
   return f;
}



void remove_all_fishes()
{
   int i;
   for (i = 0; i < flimit; i++) 
      erasefish(&finfo[i]);
   XFlush(Dpy);
}

void remove_all_bubbles()
{
   int i;
   for (i = 0; i < blimit; i++) 
      erasebubble(&binfo[i]);
   XFlush(Dpy);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
   Move all the fish.  Clearing old fish is accomplished by masking only the
   exposed areas of the old fish.  Random up-down factor can move fish 1/4 a
   fish height in either direction, if no collisions are caused.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void move_fish()
{
   int i, done = 0;
   fish *f;

   for (i = 0; i < flimit; i++) 
   {
      f = &finfo[i];
      fishtype *ft = &xfish[f->type][f->direction][f->frame];

      //if (f->y > height - ft-> h || f->x > width)
      if (f->x > width)
      {
	 erasefish(f);
	 new_fish(f,1);
	 continue;
      }

      if (f->direction == 0) 
	 done = ((f->x -= speedfactor*f->fstep) < -ft->w);
      else if (f->direction == 1) 
	 done = ((f->x += speedfactor*f->fstep) > width);

      if(1) // spontaneous change direction now and then
	 if (f->x > ft->w && f->x < width - 2*ft->w && drand48() < 0.001) 
	    f->direction = 1-f->direction;

      if(1) // spontaneous change vertical movement now and then
	 if(drand48() < 0.004)
	    f->y_direction = random_y_direction();

      if(1) // spontaneous change speed now and then
	 if(drand48() < 0.004)
	    f->fstep = random_fstep();

      if (1) 
      {
	 if (!done) 
	 {
	    f->y += 0.5*f->fstep * f->y_direction;
	    draw_fish(f);
	 } 
	 else
	 {
	    erasefish(f);
	    new_fish(f,0);
	 }
      } 
   }
   XFlush(Dpy);
}

int do_move(void *dummy)
{
   (void)dummy;

   if (Done)
   {
      gtk_main_quit();
      return FALSE;
   }

#ifdef DOUBLE_BUFFER
   if (UseXdbe)
   {
      XdbeSwapInfo swapInfo;
      swapInfo.swap_window = wid1; 
      swapInfo.swap_action = BMETHOD;
      XdbeSwapBuffers(Dpy, &swapInfo, 1);
      if(xfishtank_trans)
	 XSetWindowBackground(Dpy,wid1,0);
   }
   else
   {
      XFlush(Dpy); 
      remove_all_bubbles();
      remove_all_fishes();
   }
#else
   XFlush(Dpy); 
   remove_all_bubbles();
   remove_all_fishes();
#endif
   move_fish();
   step_bubbles();
   return TRUE;

}

int do_testfish(void *dummy)
{
   (void)dummy;

   static int x = 100;
   static int y = 100;
   int w = 64;
   int h = 41;
   w= 64;
   h=59;
   XClearArea(Dpy, wid, x,y,w,h,0);
   x += 1;
   y += 1;
   if (x > 500)
   {
      x = 100;
      y = 100;
   }

   GC gc = xfish[0][1][0].gc;
   XSetClipOrigin(Dpy, gc, x, y);
   XCopyArea(Dpy, xfish[0][1][0].pix, wid, gc,0,0,w,h,x,y);
   XFlush(Dpy);
   return TRUE;
}

void create_fishes(int n)
{
   P("create_fish %d\n",n);
   int i;
   static int prev = 0;
   flimit = n;
   finfo = (fish *)realloc(finfo,1+sizeof(fish)*flimit); // 1+: otherwise flimit=0 gives allocation error
   if(finfo == NULL)
   {
      fprintf(stderr,"Realloc error in %s:%d\n",__FILE__,__LINE__);
      exit(1);
   }
   for (i = prev; i < flimit; i++)
      new_fish(&finfo[i],1);
   prev = flimit;
}

void create_bubbles(int n)
{
   int i;
   static int prev = 0;
   blimit = n;
   binfo = (bubble *)realloc(binfo,1+sizeof(bubble)*blimit);
   if(binfo == NULL)
   {
      fprintf(stderr,"Realloc error in %s:%d\n",__FILE__,__LINE__);
      exit(1);
   }
   for (i = prev; i < blimit; i++)
      new_bubble(&binfo[i],1);
   prev = blimit;
}

void setspeed(float s)
{
   speedfactor = s;
}

void setbcolor()
{
   bcolor = AllocNamedColor(Dpy,bcolorstring,white)|0xff000000;
}

void setbgcolor()
{
   if (inxscreensaver && !xfishtank_trans && bgcolorstring && strlen(bgcolorstring))
   {
      P("bgcolorstring '%s'\n",bgcolorstring);
      bgcolor = AllocNamedColor(Dpy,bgcolorstring,black)|0xff000000;
      XSetWindowBackground(Dpy, wid1, bgcolor);
      XClearWindow(Dpy,wid1);
   }
}


void Thanks()
{
   if (HaltedByInterrupt)
      printf("\nxfishtank: Caught signal %d\n",HaltedByInterrupt);
   if (Dpy)
   {
      XClearWindow(Dpy,wid1);
      XFlush(Dpy);
      XCloseDisplay(Dpy);
   }
   printf("\nThank you for using xfishtank\n");
   exit(0);
}

void SigHandler(int signum)
{
   HaltedByInterrupt = signum;
   Done = 1;
}



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int main(int argc, char *argv[])
{
   // Circumvent wayland problems:before starting gtk: make sure that the 
   // gdk-x11 backend is used.

   setenv("GDK_BACKEND","x11",1);
   signal(SIGINT,  SigHandler);
   signal(SIGTERM, SigHandler);
   signal(SIGHUP,  SigHandler);


   if (getenv("DESKTOP_SESSION"))
   {
      DesktopSession = strdup(getenv("DESKTOP_SESSION"));
   }
   else
      DesktopSession = strdup("unknown");

   {
      struct timeval tv;
      gettimeofday(&tv,NULL);
      unsigned long x = 1000000*time(NULL) + tv.tv_usec;
      srand48(x);

      P("TD: srand: %ld %f\n",x,drand48());
   }
   counter = 0;
   set_defaults();
   ReadFlags();
   parse(argc, argv);
   gtk_init(&argc, &argv);
   printf("xfishtank version %s\n",VERSION);
   printf("Detected desktop session: %s\n",DesktopSession);

   Dpy = XOpenDisplay(display_name);


   if(!Dpy)
   {
      char *name = display_name;
      if (!display_name)
      {
	 if(getenv("DISPLAY"))
	    name = getenv("DISPLAY");
      }
      printf("Cannot open display: '%s'\n",name);
      Thanks();
      exit(1);
   }
   screen = DefaultScreen(Dpy);

   myxdo = xdo_new_with_opened_display(Dpy,NULL,0);
   if(myxdo == NULL)
   {
      I("xdo problems\n");
      exit(1);
   }
   myxdo->debug = 1;

   white = WhitePixel(Dpy, screen);
   black = BlackPixel(Dpy, screen);

   initialize();

   setbcolor();
   setbgcolor();

   srand((unsigned) getpid());

   ui(nomenu);

   create_bubbles(blimit);

   create_fishes(flimit);


   g_timeout_add_full(G_PRIORITY_DEFAULT, 30,   do_move        ,NULL, NULL);
   if (inxscreensaver == 0)
      g_timeout_add_full(G_PRIORITY_DEFAULT, 500,  do_write_flags ,NULL, NULL);

   if(0)
      g_timeout_add_full(G_PRIORITY_DEFAULT, 40,   do_testfish ,NULL, NULL);

   gtk_main();

   Thanks();

   return 0;
}

void print_changelog()
{
#include "changelog.inc"
}


#ifdef SELFREP
static unsigned char tarfile[] = {
#include "tarfile.inc"
};
void selfrep()
{
   if(sizeof(tarfile) > 1000 && isatty(fileno(stdout)))
   {
      printf("Not sending tar file to terminal.\n");
      printf("Try redirecting to a file (e.g: xfishtank -selfrep > xfishtank.tar.gz),\n");
      printf("or use a pipe (e.g: xfishtank -selfrep | tar zxf -).\n"); 
   }
   else
   {
      ssize_t rc = mywrite(fileno(stdout),tarfile,sizeof(tarfile));
      if (rc < 0)
	 fprintf(stderr,"xfishtank: Problems encountered during production of the tar ball.\n");
   }
}
#endif

