/* toon_core.c - core functions for advancing a frame of the animation
 * -copyright-
#-# 
#-# Copyright (C) 1999-2001  Robin Hogan, 2021-2026 Willem Vermin
#-# 
#-# This program is free software; you can redistribute it and/or modify
#-# it under the terms of the GNU General Public License as published by
#-# the Free Software Foundation; either version 2 of the License, or
#-# (at your option) any later version.
#-# 
#-# This program is distributed in the hope that it will be useful,
#-# but WITHOUT ANY WARRANTY; without even the implied warranty of
#-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#-# GNU General Public License for more details.
#-# 
#-# You should have received a copy of the GNU General Public License
#-# along with this program; if not, write to the Free Software
#-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#-# 
*/
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <math.h>
#include "toon.h"
#include "debug.h"
#include "wmctrl.h"
#include "utils.h"

/* Error handler for X */
int ToonXErrorHandler(Display *display, XErrorEvent *error)
{
   toon_errno = error->error_code; 
   return 0;
   (void)display;
}

void ClearScreen()
{
   // remove all our penguin-related drawings
   P("clearscreen %#lx\n",toon_root1);
#ifdef DOUBLE_BUFFER
   if (UseXdbe)
      return;
#endif
   XClearArea(toon_display, toon_root1, 0,0,0,0,False); /*letop*/
   XFlush(toon_display);
}

/* CORE FUNCTIONS */

/* Attempt to move a toon based on its velocity */
/* `mode' can be TOON_MOVE (move unless blocked), TOON_FORCE (move
   regardless) or TOON_STILL (test the move but don't actually do it) */
/* Returns TOON_BLOCKED if blocked, TOON_OK if unblocked, or 
   TOON_PARTIALMOVE if limited movement is possible */
int ToonAdvance(Toon *toon, int mode)
{
   float newx, newy;
   int new_zone;
   int width, height;
   int move_ahead    = 1;
   int result        = TOON_OK;
   ToonData *data    = toon_data[toon->genus] + toon->type;
   int nocycle       = (( (data->conf) & TOON_NOCYCLE ) > 0);
   int stationary    = 0;

   P("p: %d %d %d %f %f\n",toon->genus, toon->x, toon->y, toon->u, toon->v);
   if (mode == TOON_STILL) move_ahead = 0;

   width  = data->width;
   height = data->height;

   const float epsuv = 0.1;
   if (fabs(toon->u) < epsuv)
      toon->u = 0;
   if (fabs(toon->v) < epsuv)
      toon->v = 0;

   float uu,vv;
   uu = toon->u*SpeedFactor;
   vv = toon->v*SpeedFactor;

   newx = toon->x + uu;
   newy = toon->y + vv;

   stationary = (toon->u == 0 && toon->v == 0);

   if (data->conf & TOON_NOBLOCK) 
   {
      /* Just consider blocking by the sides of the screen */
      if (toon_edge_block)
      {
	 if (newx < 0) 
	 {
	    newx  = 0;
	    result = TOON_PARTIALMOVE;
	 }
	 else if ((newx + data->width) > toon_display_width) 
	 {
	    newx  = toon_display_width-data->width;
	    result = TOON_PARTIALMOVE;
	 }
      }
   }
   else 
   {
      /* Consider all blocking */
      P("w: %d h: %d\n",toon_display_width, toon_display_height);
      if (toon_edge_block) {
	 if (newx < 0) 
	 {
	    newx  = 0;
	    result = TOON_PARTIALMOVE;
	 }
	 else if ((newx + data->width) > toon_display_width) 
	 {
	    newx  = toon_display_width-data->width;
	    result = TOON_PARTIALMOVE;
	 }
	 if (newy < 0 && toon_edge_block != 2) 
	 {
	    newy  = 0;
	    result = TOON_PARTIALMOVE;
	 }
	 else if ((newy + data->height) > toon_display_height) 
	 {
	    newy  = toon_display_height-data->height;
	    result = TOON_PARTIALMOVE;
	 }
	 // In general, one should not compare floats for equalness, but here
	 // it seems to be justified, more or less:
	 if (newx == toon->x && newy == toon->y && !stationary) // wwvv
	 {
	    // Investigate if there is a real blocking:
	    float tryx = fsgnf(toon->u) + newx;
	    float tryy = fsgnf(toon->v) + newy;

	    // check if blocked by screen edges:
	    if (     tryx          < 0 
		  || tryx + width  > toon_display_width
		  || tryy + height > toon_display_height)
	    {
	       result = TOON_BLOCKED;
	       P("%d BLOCKED %d %d\n",counter++,toon->x, toon->y);
	    }
	    // in practice, the next checks are never done:
	    // perform one step in x direction:
	    if (result != TOON_BLOCKED)
	    {
	       P("Check x\n");
	       if (XRectInRegion(toon_windows, tryx, newy, width, height) != RectangleOut)
	       {
		  result = TOON_BLOCKED;
		  P("%d BLOCKED %d %d\n",counter++,(int)toon->x, (int)toon->y);
	       }
	    }
	    // in y direction:
	    if (result != TOON_BLOCKED)
	    {
	       P("Check y\n");
	       if (XRectInRegion(toon_windows, newx, tryy, width, height) != RectangleOut)
	       {
		  result = TOON_BLOCKED;
		  P("%d BLOCKED %d %d\n",counter++,(int)toon->x, (int)toon->y);
	       }
	    }
	 }
      }

      /* Is new toon location fully/partially filled with windows? */
      new_zone = XRectInRegion(toon_windows,newx,newy,width,height);
      if (new_zone != RectangleOut && mode == TOON_MOVE 
	    && result != TOON_BLOCKED && !stationary) 
      {
	 float tryx, tryy, step=1;
	 float u = newx - toon->x; 
	 float v = newy - toon->y;
	 result  = TOON_BLOCKED;
	 move_ahead=0;
	 /* How far can we move the toon? */
	 if ( fabsf(v) < fabsf(u) ) 
	 {
	    if (newx > toon->x) step=-1;
	    //for (tryx = newx+step; fabsf(tryx - toon->x)>0.5; /*tryx != toon->x;*/ tryx += step) 
	    for (tryx = newx+step; step*tryx < step*toon->x; tryx += step) 
	    {
	       tryy = toon->y + ((tryx - toon->x)*v)/u;
	       if (XRectInRegion(toon_windows,tryx,tryy,width,height) == RectangleOut) 
	       {
		  newx      = tryx;
		  newy      = tryy;
		  result     = TOON_PARTIALMOVE;
		  move_ahead = 1;
		  break;
	       }
	    }
	 }
	 else 
	 {
	    if (newy > toon->y) step=-1;
	    //for (tryy = newy + step; fabs(tryy - toon->y) > 0.5; /*tryy != toon->y;*/ tryy=tryy+step) 
	    for (tryy = newy + step; step*tryy < step*toon->y; tryy=tryy+step) 
	    {
	       tryx = toon->x + ((tryy-toon->y)*u)/v;
	       if (XRectInRegion(toon_windows,tryx,tryy,width,height) == RectangleOut) 
	       {
		  newx      = tryx;
		  newy      = tryy;
		  result     = TOON_PARTIALMOVE;
		  move_ahead = 1;
		  break;
	       }
	    }
	 }
      }
   }

   if (move_ahead) {
      toon->x = newx;
      toon->y = newy;
      if ( (++(toon->frame)) >= data->nframes) {
	 toon->frame = 0;
	 ++(toon->cycle);
	 if (nocycle) { 
	    toon->active = 0;
	 }
      }
   }
   else if (nocycle) {
      if ( (++(toon->frame)) >= data->nframes) {
	 toon->frame = 0;
	 toon->cycle = 0;
	 toon->active = 0;
      }
   }
   return result;
}

/* Build up an X-region corresponding to the location of the windows 
   that we don't want our toons to enter */
/* Returns 0 on success, 1 if windows moved again during the execution
   of this function */
int ToonLocateWindows()
{
   P("--------------------------------------ToonLocateWindows %d\n",counter++);
   XWindowAttributes attributes;
   int wx;
   XRectangle *window_rect;
   int x, y;
   int height, width;
   int oldnwindows;

   XRectangle *rects = NULL;
   int nrects, rectord, irect;
   XSetErrorHandler(ToonXErrorHandler);

   /* Rebuild window region */
   XDestroyRegion(toon_windows);
   toon_windows = XCreateRegion();

   /* Get children of root */
   oldnwindows=toon_nwindows;
   WinInfo *wins;
   GetWindows(&wins,&toon_nwindows);
   P("---------------------------------toon_nwindows %d\n",toon_nwindows);
   if (toon_nwindows>oldnwindows) {
      if (toon_windata)
	 free(toon_windata);
      if ((toon_windata = (ToonWindowData *)calloc(toon_nwindows, sizeof(ToonWindowData)))
	    == NULL) {
	 fprintf(stderr, _("Error: out of memory\n"));
	 ToonExitGracefully(1);
      }
   }
   /* Check to see if toon_root1 has moved with respect to toon_parent */
   XGetWindowAttributes(toon_display, toon_root1, &attributes);
   toon_display_width  = attributes.width;
   toon_display_height = attributes.height - toon_lift;
   P("w: %d h: %d\n",toon_display_width,toon_display_height);
   if (toon_root1 != toon_parent) 
   {
      toon_x_offset = attributes.x;
      toon_y_offset = attributes.y;
      P("x: %d y: %d\n",toon_x_offset,toon_y_offset);
   }
   /* Add windows to region */
   for (wx=0; wx<toon_nwindows; wx++) 
   {
      WinInfo *win = &wins[wx];
      toon_errno = 0;

      P("win %#lx hidden: %d dock: %d x: %d y: %d w: %d h: %d\n",win->id,win->hidden,win->dock,win->x,win->y,win->w,win->h);
      toon_windata[wx].wid = win->id;
      toon_windata[wx].solid = 0;
      XGetWindowAttributes(toon_display, win->id, &attributes);
      if (toon_errno) continue;

      /* Popup? */
      if ((!toon_solid_popups) && attributes.save_under) continue;
      /* is toon_root1? */
      if (win->id == toon_root1)
      {
	 P("skiproot: %#lx\n",win->id);
	 continue;
      }
      {
	 // skip if name contains "Desktop"
	 XTextProperty text_prop;
	 int rc = XGetWMName(toon_display,win->id,&text_prop);
	 if(rc)
	    if (strstr((char *)text_prop.value,"Desktop") ||
		  strstr((char *)text_prop.value,"desktop"))
	    {
	       P("skipping: %s\n",text_prop.value);
	       continue;
	    }
      }
      if(!win->hidden && !win->dock)
      {
	 x      = win->x;
	 y      = win->y;
	 width  = win->w;
	 height = win->h;
	 /* Entirely offscreen? */
	 if (x >= toon_display_width)     continue;
	 if (y >= toon_display_height)    continue;
	 if (y <= 0)                      continue;
	 if (x + width < 0)               continue; 
	 P("***********made it %#lx %d %d %d %d\n",win->id,x,y,width,height);
	 toon_windata[wx].solid = 1;
	 window_rect = &(toon_windata[wx].pos);
	 window_rect->x = x;
	 window_rect->y = y;
	 window_rect->height = height;
	 window_rect->width = width;
	 /* The area of the windows themselves */
	 if (!toon_shaped_windows) 
	 {
	    XUnionRectWithRegion(window_rect, toon_windows, toon_windows);
	 }
	 else 
	 {
	    rects = XShapeGetRectangles(toon_display, win->id, ShapeBounding,
		  &nrects, &rectord);
	    if (nrects <= 1) 
	    {
	       XUnionRectWithRegion(window_rect, toon_windows, toon_windows);
	    }
	    else 
	    {
	       for (irect=0;irect<nrects;irect++) 
	       {
		  rects[irect].x += x;
		  rects[irect].y += y;
		  XUnionRectWithRegion(rects+irect, toon_windows, toon_windows);
	       }
	    }
	    if ((rects) && (nrects > 0)) 
	    {
	       XFree(rects);
	    }
	 }
      }
   }
   free(wins);
   XSetErrorHandler((ToonErrorHandler *) NULL);
   return 0;
}

