/*
  XBubble - sprite.c

  Copyright (C) 2002  Ivan Djelic <ivan@savannah.gnu.org>
  
  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 <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "utils.h"
#include "rectangle.h"
#include "frame.h"
#include "sprite.h"

extern Display *display;
extern Window root;

enum SpriteState {
  SPIRIT_NEW,
  SPIRIT_UNCHANGED,
  SPIRIT_CHANGED,
  SPIRIT_NEEDS_REPAINT,
  SPIRIT_FINISHED
};

struct _Sprite {
  int layer;
  int frame;
  int clock;
  int x;
  int y;
  int cycle;
  Set overlap;
  Set animation;
  XRectangle r;
  RectangleList redraw_boxes;
  enum SpriteState state;
};

struct _SpritePool {
  int nb_layers;
  Set pool;
  Set *layer;
  GC redraw_gc;
  GC normal_gc;
};

/*********************** Sprite routines *********************************/

Sprite new_sprite( int layer, int overlap_max, Set a, int frame, int cycle ) {
  Sprite s = ( Sprite ) xmalloc( sizeof( struct _Sprite ));
  s->layer = layer;
  s->animation = a;
  s->frame = frame;
  s->cycle = cycle;
//  s->cycle = 1;
  s->clock = 0;
  s->r.x = -1;
  s->r.y = -1;
  s->x   = -1;
  s->y   = -1;
  s->overlap = set_new(overlap_max);
  s->redraw_boxes = new_rectangle_list(overlap_max);
  s->state = SPIRIT_UNCHANGED;
  return s;
}

extern Set bubble_animation[NB_COLORS][NB_BUBBLE_STATES];

Sprite new_sprite_bubble( int layer, int overlap_max, int color, int state ) {
  Sprite s = ( Sprite ) xmalloc( sizeof( struct _Sprite ));
  s->layer = layer;
  s->animation = bubble_animation[color][state];
  s->frame = 0;
  s->cycle = bubble_animation[color][state]->size > 1;
  s->clock = 0;
  s->r.x = -1;
  s->r.y = -1;
  s->x   = -1;
  s->y   = -1;
  s->overlap = set_new(overlap_max);
  s->redraw_boxes = new_rectangle_list(overlap_max);
  s->state = SPIRIT_UNCHANGED;
  return s;
}   

void delete_sprite(Sprite s) { 
  set_free(s->overlap);
  delete_rectangle_list(s->redraw_boxes);
  free(s);
}

void set_sprite_position( Sprite s, int x, int y ) {
  if (( x != s->x )||( y != s->y )) {
    s->x = x;
    s->y = y;
    if ( s->state == SPIRIT_UNCHANGED || s->state == SPIRIT_NEEDS_REPAINT )
      s->state = SPIRIT_CHANGED;
  }
}

void set_sprite_animation( Sprite s, Set a, int frame, int cycle ) {
  s->animation = a;
  s->frame = frame;
  s->clock = 0;
  s->cycle = cycle;
  s->cycle = 1;
  if ( s->state == SPIRIT_UNCHANGED )
    s->state = SPIRIT_CHANGED;
}

void set_sprite_bubble_animation( Sprite s, int color, int state)  {
  s->animation = bubble_animation[color][state];
  s->frame = 0;
  s->clock = 0;
  s->cycle = bubble_animation[color][state]->size>1;
  if ( s->state == SPIRIT_UNCHANGED )
    s->state = SPIRIT_CHANGED;
}

Set get_sprite_animation( Sprite s ) {
  return s->animation;
}

void set_sprite_layer( Sprite s, int layer ) {
  if ( layer != s->layer ) {
    s->layer = layer;
    if ( s->state == SPIRIT_UNCHANGED )
      s->state = SPIRIT_CHANGED;
  }
}

void set_sprite_frame( Sprite s, int frame ) {
  Frame old_frame;
  Frame new_frame;
  int frame_max = s->animation->size;
  if ( frame >= frame_max)
    frame = ( s->cycle )? frame % frame_max : frame_max-1;
  if ( s->frame != frame ) {
    old_frame = s->animation->element[s->frame];
    new_frame = s->animation->element[frame];
    if (( old_frame->pixmap != new_frame->pixmap )&&( s->state == SPIRIT_UNCHANGED ))
      s->state = SPIRIT_CHANGED;
    s->frame = frame;
  }
}

void increase_sprite_frame(Sprite s, int increase ) {
  set_sprite_frame( s, s->frame + increase );
}

void increase_sprite_clock(Sprite s, int dt ) {
  Frame frame = s->animation->element[s->frame];
  s->clock += dt;
  while ( s->clock >= frame->delay ) {
    s->clock -= frame->delay;
    set_sprite_frame( s, s->frame + 1 );
    frame = s->animation->element[s->frame];
  }
}

/************************ SpritePool routines ******************************/

SpritePool new_sprite_pool( int nb_layers, int nb_sprites_max ) {
  int i;
  XGCValues gcv;
  unsigned long gcm;
  SpritePool sp = (SpritePool) xmalloc( sizeof( struct _SpritePool ));
  sp->layer = (Set*) xmalloc( sizeof( Set ) * nb_layers );
  sp->pool = set_new( nb_sprites_max );
  for ( i = 0; i < nb_layers; i++ )
    sp->layer[i] = set_new( nb_sprites_max );
  sp->nb_layers = nb_layers;
  gcm = GCFunction | GCGraphicsExposures;
  gcv.graphics_exposures = False;
  gcv.function = GXcopy;
  sp->normal_gc = XCreateGC( display, root, gcm, &gcv);
  sp->redraw_gc = XCreateGC( display, root, gcm, &gcv);
  XSetClipRectangles( display, sp->redraw_gc, 0, 0, NULL, 0, Unsorted );
  return sp;
}

void delete_sprite_pool( SpritePool sp ) {
  int i;
  XFreeGC( display, sp->normal_gc);
  XFreeGC( display, sp->redraw_gc);
  for ( i = 0; i < sp->nb_layers; i++ )
    set_free(sp->layer[i]);
  set_free(sp->pool);
  free(sp->layer);
  free(sp);
}

void add_sprite_to_pool( SpritePool sp, Sprite s ) {
  set_add( sp->pool, s);
  s->state = SPIRIT_NEW;
}

void remove_sprite_from_pool( SpritePool sp, Sprite s ) {
  if ( s->state == SPIRIT_NEW )
    set_remove( sp->pool, s );
  else
    s->state = SPIRIT_FINISHED;
}

GC get_sprite_pool_redraw_gc( SpritePool sp ) {
  return sp->redraw_gc;
}

/**********************************************************************/

static void draw_sprite( SpritePool sp, Sprite s, Pixmap dest ) {
  Frame frame = (Frame) s->animation->element[s->frame]; 
  if ( frame->has_mask ) {
    XSetClipOrigin( display, frame->gc, s->r.x, s->r.y );
    XCopyArea( display, frame->pixmap, dest, frame->gc, 
	       0, 0, s->r.width, s->r.height, s->r.x, s->r.y );
  }
  else
    XCopyArea( display, frame->pixmap, dest, sp->normal_gc, 
	       0, 0, s->r.width, s->r.height, s->r.x, s->r.y );
}

static void redraw_sprite( SpritePool sp, Sprite s, Pixmap dest ) {
  int i;
  Rectangle *r;
  Frame frame;
  GC redraw_gc = sp->normal_gc;
  frame = (Frame) s->animation->element[s->frame];
  if ( frame->has_mask ) {
    XSetClipOrigin( display, frame->gc, s->r.x, s->r.y );
    redraw_gc = frame->gc;
  }
  for ( i = 0; i < s->redraw_boxes->size; i++ ) {
    r = &s->redraw_boxes->element[i];
    XCopyArea( display,
	       frame->pixmap,
	       dest,
	       redraw_gc,
	       r->x1,
	       r->y1,
	       r->x2 - r->x1,
	       r->y2 - r->y1,
	       r->x1 + s->r.x,
	       r->y1 + s->r.y );
  }
}

static void sprite_partial_redraw( Sprite s, XRectangle *r ) {
  int x1, y1, x2, y2, tmp;
  XRectangle *q = &( s->r );
  /* intersect rectangles */
  x1 = ( q->x > r->x )? 0 : r->x - q->x;
  y1 = ( q->y > r->y )? 0 : r->y - q->y;
  x2 = ( q->x + q->width < r->x + r->width )? q->width  : r->x+r->width-q->x;
  y2 = ( q->y + q->height< r->y + r->height)? q->height : r->y+r->height-q->y;
  if ( x1 >= x2 ) {
    tmp = x2;
    x2 = x1;
    x1 = tmp;
  }
  if ( y1 >= y2 ) {
    tmp = y2;
    y2 = y1;
    y1 = tmp;
  }
  add_disjoint_rectangle_to_list( s->redraw_boxes, x1, y1, x2, y2 );
}

void redraw_sprite_pool( SpritePool sp, Pixmap pixmap, Pixmap background, int forceful ) {
  int i, j, layer;
  Sprite s, t;
  Frame frame;
  XRectangle r;
  Region redraw_region = XCreateRegion();
  
  if (forceful) {
     /* flag all sprites as new */
     for ( i = 0; i < sp->pool->size; i++ ) {
	s = sp->pool->element[i];
	if (s->state == SPIRIT_UNCHANGED)
	  s->state = SPIRIT_CHANGED;
     }
  }
   
  /* empty layers */
  for ( layer = 0; layer < sp->nb_layers; layer++ )
    set_empty( sp->layer[layer] );

  /* pass 1 : redraw background */

  for ( i = 0; i < sp->pool->size; i++ ) {
    s = sp->pool->element[i];
    /* check if sprite needs to be erased */
    if (( s->state == SPIRIT_CHANGED )||( s->state == SPIRIT_FINISHED )) {
      XUnionRectWithRegion( &(s->r), redraw_region, redraw_region );
      /* remove s from overlap sets that won't be recomputed */
      for ( j = 0; j < s->overlap->size; j++ ) {
	t = s->overlap->element[j];
	if (( t->state == SPIRIT_UNCHANGED )||( t->state == SPIRIT_NEEDS_REPAINT )) {
	  t->state = SPIRIT_NEEDS_REPAINT;
	  /* partially redraw overlapping sprite */
	  sprite_partial_redraw( t, &(s->r) );
	  set_remove( t->overlap, s);
	}
      }
      if ( s->state == SPIRIT_FINISHED ) {
	set_remove_at( sp->pool, i);
	i--;
      }
    }
    /* check if sprite needs full drawing */
    if (( s->state == SPIRIT_CHANGED )||( s->state == SPIRIT_NEW )) {
      /*  compute new sprite location */
      frame = s->animation->element[s->frame];
      s->r.width = frame->width;
      s->r.height = frame->height;
      s->r.x = s->x - frame->cx;
      s->r.y = s->y - frame->cy;
      s->state = SPIRIT_CHANGED;
      set_empty( s->overlap );
    }
  }  
  /* clear background */
  XSetRegion( display, sp->redraw_gc, redraw_region );
  XClipBox( redraw_region, &r );
  XCopyArea( display, background, pixmap, sp->redraw_gc,
	     r.x,
	     r.y,
	     r.width,
	     r.height,
	     r.x,
	     r.y );
  /*
     at this point, sprites can be in the following states:
     SPIRIT_CHANGED -> needs full redraw and overlapping set computation.
     SPIRIT_UNCHANGED
     SPIRIT_NEEDS_REPAINT -> needs partial redraw.
  */

  /* pass 2 : (re)compute overlapping sets */
  for ( i = 0; i < sp->pool->size; i++ ) {
    s = sp->pool->element[i];
    if ( s->state != SPIRIT_UNCHANGED ) {
      /* sort sprites into layers */
      set_add( sp->layer[s->layer], s );
      if ( s->state == SPIRIT_CHANGED ) {
	for ( j = 0; j < sp->pool->size; j++ ) {
	  t = sp->pool->element[j];
	  if ((( t->state != SPIRIT_CHANGED )||( j > i ))&&
	      /* sprite overlap ? */
	      ( t->r.x < s->r.x + s->r.width )&&
	      ( t->r.y < s->r.y + s->r.height )&&
	      ( t->r.x + t->r.width > s->r.x )&&
	      ( t->r.y + t->r.height > s->r.y )) {
	    
	    set_add( t->overlap, s);
	    set_add( s->overlap, t);
	    /* partially redraw overlapping sprites on upper layers */
	    if (( t->layer > s->layer )&&( t->state != SPIRIT_CHANGED )) {
	      t->state = SPIRIT_NEEDS_REPAINT;
	      sprite_partial_redraw( t, &(s->r) );
	    }
	  }
	}
      }
    }
  }
  /* pass 3 : redraw each layer starting from the bottom */
  for ( layer = 0; layer < sp->nb_layers; layer++ )
    for ( i = 0; i < sp->layer[layer]->size; i++ ) {
      s = sp->layer[layer]->element[i];
      if ( s->state == SPIRIT_CHANGED ) {
	draw_sprite( sp, s, pixmap );
	XUnionRectWithRegion( &(s->r), redraw_region, redraw_region );
      }
      else
	redraw_sprite( sp, s, pixmap );
      s->state = SPIRIT_UNCHANGED;
      empty_rectangle_list(s->redraw_boxes);
    }
  /* make GC */
  XSetRegion( display, sp->redraw_gc, redraw_region );
  XDestroyRegion( redraw_region );
}

void draw_sprite_pool( SpritePool sp,
		       Pixmap pixmap,
		       Pixmap background,
		       int width,
		       int height ) {
  int i;
  Sprite s;
  /* redraw background */
  XCopyArea( display, background, pixmap, sp->normal_gc, 
	     0, 0, width, height, 0, 0 );
  /* flag all sprites as new */
  for ( i = 0; i < sp->pool->size; i++ ) {
    s = sp->pool->element[i];
    s->state = SPIRIT_NEW;
  }
  
  redraw_sprite_pool( sp, pixmap, background, 0/* not forcefully*/ );
}
