/** Particle and effect engine for all sorts of glitzy effects. :) 
**/
#include "eruta.h"
#include "monolog.h"


/**
* An effect is a localized graphical effect 
*/
struct Effect_ {
  Point center;
};

/**
* A particle is a single graphical element (that may be a point or whatever) 
* which effects use to visualize themselves. 
*/
struct Particle_ {
  Point position;
};


#define ERUTA_EFFECTS           64
#define ERUTA_EFFECT_PARTICLES  64
#define ERUTA_PARTICLES         (ERUTA_EFFECTS * ERUTA_EFFECT_PARTICLES)

/**
* Effects holds all the data for the visual effects engine.
*/
struct Effects_ {
  struct Effect_   effects  [ERUTA_EFFECTS];
  struct Particle_ particles[ERUTA_PARTICLES];
};

// XXX: todo, implement this when we're ready to do so.

#ifdef COMMENT_

struct GyDropClass_;
typedef struct GyDropClass_ GyDropClass;

struct GyDropClass_ {
  GyDropInitFunction    * init;
  GyDropDrawFunction    * draw;
  GyDropUpdateFunction  * update;
};


GyDropClass * gydropclass_init(GyDropClass * klass, 
                                    GyDropUpdateFunction  * update,
                                    GyDropDrawFunction    * draw,
                                    GyDropInitFunction    * init
                                   ){
  if (!klass) return NULL;
  klass->update = update; 
  klass->draw   = draw;
  klass->init   = init;
  return klass;
}

/**
* A Drop is a 
*/
struct GyDrop_ {
  GyFlow                * flow;
  GyColor                 color;
  GyImage               * image;
  char                    * text;
  GyDropDrawFunction    * draw;
  GyDropUpdateFunction  * update;
  int                       kind;
  int                       lifetime;
  GyNumber                x, y;
  GyNumber                vx, vy;
  GyNumber                ax, ay;
};

/** Returns TRUE if this drop is idle(unused) or NULL, FALSE if not. */
int gydrop_idle(GyDrop * drop) { 
  if (!drop) return TRUE;
  if (drop->lifetime < 1) return TRUE;
  return FALSE;
}  

/** Returns TRUE if this drop is still active, FALSE if not. */
int gydrop_active(GyDrop * drop) { 
  return !gydrop_idle(drop);
}  

/** Deactivates a drop. */
void gydrop_disable(GyDrop * drop) {
  if(!drop) return;
  drop->lifetime = 0;
}

/** Deactivates a drop if it is of the given kind. */
void gydrop_disablekind(GyDrop * drop, int kind) {
  if (!drop) return;
  if (drop->kind != kind) return;
  gydrop_disable(drop);
}



GyDrop * gydrop_init(GyDrop * drop, GyFlow * flow) {
  if(!drop) return NULL  ;
  drop->flow      = flow;
  drop->lifetime  = 0;
  drop->vx        = 0;
  drop->vy        = 0;
  drop->ax        = 0;
  drop->ay        = 0;
  drop->text      = NULL;
  drop->image     = NULL;
  drop->update    = NULL;
  drop->draw    = NULL;
  return drop;
}



GyDrop * gydrop_update(GyDrop * drop, int time) {
  if (gydrop_idle(drop)) return NULL;
  // automatically reduce lifetime
  drop->lifetime -= time;
  
  if (!drop->update)        {
    LOG_WARNING("cannot update drop!\n");
    return NULL;
  }  
  // skip drops that are not active or not set up correctly. 
  drop->update(drop, time);
  return drop; 
}

// Draws the drop as a small filled rectangle
GyDrop * gydrop_drawslab(GyDrop * drop, GyImage * im) {
  gyimage_slab(im, drop->x, drop->y, 5, 5, drop->color);
  return drop;
}

// Draws the drop as a small filled circle
GyDrop * gydrop_drawdisk(GyDrop * drop, GyImage * im) {
  gyimage_slab(im, drop->x, drop->y, 3, 3, drop->color);
  return drop;
}

// Draws the drop as snow
GyDrop * gydrop_drawsnow(GyDrop * drop, GyImage * im) {
  gyimage_disk(im, drop->x, drop->y, 3, drop->color);
  // gyimage_slab(im, drop->x, drop->y, 3, 3, drop->color);
  return drop;
}


// Draws a generic drop.
GyDrop * gydrop_draw(GyDrop * drop, GyImage * im) {
  // Skip drops that are not active or not set up correctly.
  if (gydrop_idle(drop)) return NULL;
  
  // Draw images for drops that have an image
  if (drop->image) {
    gyimage_blit(im, drop->x, drop->y, drop->image); 
    return drop;
  }  
  // Use the draw function for drops that have one
  if (drop->draw)  {
     drop->draw(drop, im);
     return drop;
  }
  // Otherwise just draw a filled rectangle. 
  gydrop_drawslab(drop, im);
  return drop; 
}


/* GyWell is a a point location source where particles are generated. 
 It's in other words a kind of initial configuration for particles. */
struct GyWell_ {
  int                         kind;
  GyColor                   color;  
  GyNumber                  x, y;  
  GyImage               *   image;
  char                    *   text;
  GyDropDrawFunction    *   draw;
  GyDropUpdateFunction  *   update;
  GyDropInitFunction    *   init;
};


/* GyFlow models a particle engine. */
struct GyFlow_ {
  GyCamera   * camera;
  size_t         size;
  GyDrop     * particles;
  size_t         active;
  int screenw, screenh;
};

GyFlow * gyflow_init(GyFlow * flow, size_t size) {
  int index;
  if(!flow) return NULL;
  flow->size      = size;
  flow->active    = 0;
  flow->screenw   = 640;
  flow->screenh   = 480;
  flow->particles = GY_MALLOC(sizeof(GyDrop) * flow->size);
  if(!flow->particles) {
    flow->size = 0;
    return NULL;
  }
  for(index = 0; index < flow->size; index++) {
    gydrop_init(flow->particles+index, flow);
  }
  
  return flow;
}


GyFlow * gyflow_done(GyFlow * flow) {
  if(!flow) { return NULL; }  
  flow->size   = 0;
  flow->active = 0;
  GY_FREE(flow->particles);
  flow->particles = NULL;
  return flow;  
}

GyFlow * gyflow_make(size_t size) {
  GyFlow * flow = GY_ALLOCATE(GyFlow);
  return gyflow_init(flow, size);
}

void gyflow_free(GyFlow * flow) {
  gyflow_done(flow);
  GY_FREE(flow);
}

void gyflow_update(GyFlow * flow, int time) {
  int index;
  // XXX: should perhaps be limited to active particles only?
  for(index = 0; index < flow->size ; index++) {
    gydrop_update(flow->particles+index, time);
  }
}
 
void gyflow_draw(GyFlow * flow, GyImage * im) {
  int index;
  for(index = 0; index < flow->size; index++) {
    gydrop_draw(flow->particles+index, im);
  }
}


// Automatically integrate motion based upon speed
// To be called in your custom callback, etc. 
GyDrop * gydrop_updatespeed(GyDrop * drop, int time) { 
  drop->x               += GY_DROP_DIVIDE(drop->vx * time);
  drop->y               += GY_DROP_DIVIDE(drop->vy * time);
  return drop;
}

// Automatically update speed based on accelleration
// To be called in your custom callback, etc. 
GyDrop * gydrop_updateaccell(GyDrop * drop, int time) { 
  drop->vx              += GY_DROP_DIVIDE(drop->ax * time);
  drop->vy              += GY_DROP_DIVIDE(drop->ay * time);
  return drop;
}


GyDrop * gydrop_initsnow(GyDrop * drop, GyWell * well) { 
  drop->x         = gyrandom(0, drop->flow->screenw);
  drop->y         = gyrandom(0, drop->flow->screenh);
  drop->ax        = 0;
  drop->ay        = GY_DROP_MULTIPLY(100);
  drop->vx        = 0;
  drop->vy        = gyrandom(GY_DROP_MULTIPLY(1), GY_DROP_MULTIPLY(3));
  drop->lifetime  = gyrandom(10, 100);
  return drop;
}  
      
GyDrop * gydrop_updatesnow(GyDrop * drop, int time) {
  if (!(drop->lifetime % 5)) { 
    drop->vx = GY_DROP_MULTIPLY(gyrandom(-1, 1));
  } else {
    drop->vx = 0;
  }
  gydrop_updateaccell(drop, time);
  return gydrop_updatespeed(drop, time);
}
      
/*  
      # Draws a single particle. 
      # Override this in child classes.
      def render_particle(x, y, surface, particle, ci) 
        x2 = x
        y2 = y + rand(5) + 2
        l  = 7 + rand(3)
        w  = 1 #  + rand(2) 
        # surface.draw_line(x, y, x2, y2, ci)
        surface.draw_rect(x, y, w, l, ci, true)
      end
    end
  
  
    class Blood < Stream
      BLOOD_G = DUST_TIME + 1
      def initialize(color = [255, 0, 0], amount = 15, max = 100)
        super
      end
      
      # Sets up an individual particle. Overide this in child clases.
      def init_particle()
        x     = 0.0
        y     = 0.0
        vx    = (rand(1001)- 500) / 1000.0  
        vy    = 0
        g     = 0.00000001
        # Speeds set randomly for this kind of stream          
        time  = 0 # rand(100)
        # Randomly shorten their lifetime
        return Stardust.make_particle(x, y, vx, vy, time, g)
      end
      
      def update_particle(particle, time)
        time = particle[DUST_TIME]
        if time < @timemax * 0.1
            particle[DUST_Y] -= (rand(100) + 1) / 25.0 
        elsif time  < @timemax * 0.2
          particle[DUST_Y] += (rand(100) + 1) / 25.0 
          particle[DUST_VX] /= 2   
        else
          particle[DUST_VY]  = 0
          particle[DUST_VX]  = 0   
        end
        # particle[BLOOD_G]
        # particle[DUST_VY] = 2 if particle[DUST_VY] > -2
        if time > @timemax
          return false
        end     
        return true     
      end  
     
      # Draws a single particle. 
      # Override this in child classes.
      def render_particle(x, y, surface, particle, ci) 
        surface.draw_circle(x, y, 2,  ci, true, 0.5)
      end
    end


    class Slash < Stream
      SLASH_SX = DUST_TIME + 1
      SLASH_SY = DUST_TIME + 2
      SLASH_W  = DUST_TIME + 3
      def initialize(color = [255, 0, 0], amount = 1, max = 30)
        super
      end
      
      # Sets up an individual particle. Overide this in child clases.
      def init_particle()
        x     = 0.0
        y     = 0.0
        vx    = 0.5
        vy    = 1
        g     = 0.00000001
        # Speeds set randomly for this kind of stream          
        time  = 0 # rand(100)
        # Randomly shorten their lifetime
        return Stardust.make_particle(x, y, vx, vy, time, 0.0, 0.0, 1)
      end
      
      def update_particle(particle, time)
        time = particle[DUST_TIME]
        # particle[BLOOD_G]
        # particle[DUST_VY] = 2 if particle[DUST_VY] > -2
        if time > @timemax
          return false
        end     
        return true     
      end  
     
      # Draws a single particle. 
      # Override this in child classes.
      def render_particle(x, y, surface, particle, ci) 
        dx = x - particle[DUST_X]
        dy = y - particle[DUST_Y]
        sx = dx + particle[SLASH_SX]
        sy = dy + particle[SLASH_SY]
        sw = particle[SLASH_W]
        surface.draw_line(x, y, sx, sy, ci, true)
        surface.draw_line(x, y+sw, sx, sy+sw, ci, true)
        surface.draw_line(x, y-sw, sx, sy-sw, ci, true)
      end
    end
*/




GyWell * gywell_init(GyWell * well, int kind,  
                          int x, int y, GyColor color, 
                          GyImage * im, char * text) {
  if(!well) return NULL; 
  well->x       = x;
  well->y       = y;
  well->color   = color;
  well->kind    = kind;
  well->text    = text;
  well->image   = im;
  well->draw    = gydrop_drawslab;
  well->update  = gydrop_updatespeed;
  well->init    = gydrop_initsnow;
  
  switch(well->kind) {
    case GyFlowSnow:
      well->draw   = gydrop_drawsnow;
      well->update = gydrop_updatesnow;
      return well;
    default:  
      LOG_WARNING("Unknown effect type %d:", well->kind);
      return well;
  }
  return well;
} 

/** Initializes a drop with info from a Well */
GyDrop * gydrop_initwell(GyDrop * drop, GyFlow * flow, GyWell *well)
{
  if(!gydrop_init(drop, flow)) return NULL;
  if(!well) return NULL;
  drop->x         = well->x;
  drop->y         = well->y;
  drop->color     = well->color;
  drop->image     = well->image;
  drop->text      = well->text;
  drop->kind      = well->kind;
  drop->update    = well->update;
  drop->draw      = well->draw;
  
  if(well->init) {
    well->init(drop, well);
  }
  return drop;
}


/**
* Gets the next idle (unused) drop from the particle engine. 
* Returns NULL if no idle drop is available. 
*/
GyDrop * gyflow_idle(GyFlow * flow) {
  int index;
  if(!flow) return NULL;
  for (index = 0; index < flow->size; index++) {
    GyDrop * drop = flow->particles + index;
    if(gydrop_idle(drop)) return drop;
  } 
  return NULL;
} 

/**
* Activates amount Drops (particles) in the GyFlow flow. 
* The particle will be of kind kind, and will be placed, depending on the 
* kind, at x a,d y, and displayed using given color, image or text 
*/
GyFlow * gyflow_activate(GyFlow * flow, 
  int amount, int kind, int x, int y, GyColor color, 
  GyImage * im, char * text) {
  int index;
  GyWell well;
  if(!gywell_init(&well, kind, x, y, color, im, text)) { 
    return NULL; 
  } 
  for(index = 0; index < amount; index++) {
    GyDrop  * drop;
    drop = gyflow_idle(flow);
    if(!drop) {
      LOG_WARNING("No idle drops: %d!\n", amount ); 
      return NULL;
    }  
    gydrop_initwell(drop, flow, &well);
  } 
  return flow;
}



/**
* Deactivates all Drops of the given kind of effect.
*/
GyFlow * gyflow_disablekind(GyFlow * flow, int kind) {  
  int index;
  for (index = 0; index < flow->size; index++) {
    GyDrop * drop = flow->particles + index;
    gydrop_disablekind(drop, kind);    
  }   
  return flow;
}

#endif


