/*
 * Sprite.c - Sprite Handling
 * By: Odis
 */
#include <windows.h>
#include "main.h"
#include "game.h"

/* local */
int get_action( RECT col, RECT bounds, POINT oldpos );

int get_action( RECT col, RECT bounds, POINT oldpos )
{
  int val = 0;
  RECT sub;
  
  if ( &col == NULL || &bounds == NULL )
   return ERROR;

  if ( IntersectRect( &sub, &col, &bounds ) == 0 )
   return ERROR;

  return val;
}
/* /local */

void link_by_zorder( SPRITE *sprite )
{
  SPRITE *loop;

  if ( sprite == NULL )
   return;

 if ( lvl->first_sprite != NULL )
  for (loop = lvl->first_sprite; loop != NULL; loop = loop->next)
   if (sprite->z <= loop->z)
   {
     INSERT( sprite, loop, lvl->first_sprite, next, prev );
     return;
   }

    /* we have the highest, so add to the end! */
    LINK( sprite, lvl->first_sprite, lvl->last_sprite, next, prev );
}

/* sprite creation and deletion */
SPRITE *sprite_from_bitmap( BMP *bitmap, int frames, int delay, char *name )
{
  SPRITE *sprite;
  CREATE( sprite, SPRITE, 1 );
  
 if ( sprite == NULL )
  return NULL;
  
  sprite->bitmap = bitmap;

  sprite->position.x = sprite->position.y = 0;
  sprite->velocity.x = sprite->velocity.y = 0;

  sprite->z = 0;

  sprite->name = (char *)strdup( name );

  SetRect(&sprite->bounds, 0, 0, PRG_WIDTH, PRG_HEIGHT );
  sprite->action = STOP;

  sprite->hidden = FALSE;
  sprite->drag = TRUE; /* all sprites start out dragable */
  sprite->gravity = TRUE; /* yep, gravity affects this sprite! */
  sprite->proto = FALSE;

  sprite->type = NONE; /* type currently unspecified */

  /* if this sprite is animated, do it yourself, because i'm assuming its not! */
  sprite->frames = UMAX(1,frames);
  sprite->delay = UMAX(2,delay);
  sprite->current_frame = sprite->trigger = 0;
  
  sprite->width = sprite->bitmap->width/sprite->frames;
  sprite->height = sprite->bitmap->height;

  sprite->direction = RIGHT;
  
  CalcCollisionRect( sprite );
  
  link_by_zorder( sprite );
  
  lvl->num_sprites++;
  
  /* create the clear bitmask */
  sprite->bitmask = bitmask_create(sprite->width,sprite->height);
  
  return sprite;
}

SPRITE *sprite_from_initial( BMP *bitmap, POINT position, POINT velocity,
                            int z, RECT bounds, int action, int width, int height, char *name, int type )
{
  SPRITE *sprite;
  CREATE( sprite, SPRITE, 1 );
  
  if ( sprite == NULL )
   return NULL;
   
  sprite->bitmap = bitmap;
  
  sprite->position.x = position.x;
  sprite->position.y = position.y;
  
  sprite->velocity = velocity;
  sprite->z = z;

  sprite->name = (char *)strdup(name);

  CopyRect(&sprite->bounds, &bounds );

  sprite->action = action;
  sprite->hidden =  FALSE;
  sprite->proto  =  FALSE;
  sprite->gravity = TRUE; /* yep, gravity affects this sprite! */

/* if this sprite is animated, do it yourself, because i'm assuming its not! */
  sprite->frames = 1;
  sprite->current_frame = sprite->delay = sprite->trigger = 0;

  sprite->direction = RIGHT;

  sprite->width = width;
  sprite->height = height;
  
  sprite->type = type;

  CalcCollisionRect( sprite );
  
  link_by_zorder( sprite );
  
  lvl->num_sprites++;
  
  /* create the clear bitmask */
  sprite->bitmask = bitmask_create(sprite->width,sprite->height);

  return sprite;
}

int update_sprite( SPRITE *sprite )
{
  POINT newpos, size, pos = sprite->position;
  RECT bounds;
  POINT velo = sprite->velocity;
  
  CopyRect(&bounds, &sprite->bounds );

  newpos.x = pos.x + velo.x;
  newpos.y = pos.y + velo.y;
  
  size.x = sprite->width;
  size.y = sprite->height;
  
  update_frame( sprite );

  /* gravity! */
/* No gravity in this game */
/*
  if ( sprite->gravity == TRUE )
   sprite->velocity.y++;
*/

  if (sprite->action == WRAP)
  {
    if ((newpos.x + size.x) < bounds.left)
        newpos.x = bounds.right - size.x/2;
    else if (newpos.x > bounds.right)
      newpos.x = bounds.left - size.x/2;

   if ( sprite->bitmap != NULL )
   {
      if ((newpos.y + size.y) < bounds.top)
        newpos.y = bounds.bottom;
      else if (newpos.y > bounds.bottom)
        newpos.y = bounds.top - size.y;
   }
   else
       newpos.y = UMIN(320, UMAX(0, (int)newpos.y));
  }
  else if (sprite->action == BOUNCE)
  {
    bool bounce = FALSE;
    POINT ptNewVelocity = sprite->velocity;
    
    if (newpos.x < bounds.left)
    {
      bounce = TRUE;
      newpos.x = bounds.left;
      ptNewVelocity.x = -ptNewVelocity.x;
    }
    else if ((newpos.x + size.x) > bounds.right)
    {
      bounce = TRUE;
      newpos.x = bounds.right - size.x;
      ptNewVelocity.x = -ptNewVelocity.x;
    }
    if (newpos.y < bounds.top)
    {
      bounce = TRUE;
      newpos.y = bounds.top;
      ptNewVelocity.y = -ptNewVelocity.y;
    }
    else if ((newpos.y + size.y) > bounds.bottom)
    {
      bounce = TRUE;
      newpos.y = bounds.bottom - size.y;
      ptNewVelocity.y = -ptNewVelocity.y;
    }
    if (bounce)
      SetVelocity(sprite, ptNewVelocity.x, ptNewVelocity.y);
  }
  else if (sprite->action == DIE)
  {
    if (newpos.x < bounds.left ||
         newpos.x > (bounds.right - size.x) ||
       newpos.y < bounds.top ||
       newpos.y > (bounds.bottom - size.y))
      return rKILL;
  }
  /* guess this sprite is stopping */
  else
  {
    if (newpos.x  < bounds.left || newpos.x > (bounds.right - size.x))
    {
      newpos.x = UMAX(bounds.left, UMIN(newpos.x, bounds.right - size.x));
      
      SetVelocity(sprite, 0, sprite->velocity.y);
    }
    if (newpos.y  < bounds.top ||
        newpos.y > (bounds.bottom - size.y))
    {
      newpos.y = UMAX(bounds.top, UMIN(newpos.y, bounds.bottom - size.y));
      SetVelocity(sprite, 0, 0);
    }
  }
  
  SetPosition(sprite, newpos);
  return rNONE;
}

void draw_sprite(SPRITE *sprite, HDC hDC)
{
  if (sprite->bitmap != NULL && sprite->hidden == FALSE)
  {
   if ( sprite->frames == 1 )
   {
    int x = sprite->position.x - lvl->pos.x;
    int y = sprite->position.y - lvl->pos.y;
    
    Draw(sprite->bitmap, hDC, x, y, TRUE, FALSE);
   }
   else
    DrawFrame( sprite, hDC );
  }
}

void DrawFrame( SPRITE *sprite, HDC hDC )
{
  if ( sprite == NULL || sprite->bitmap == NULL )
   return;

  int x = sprite->position.x - lvl->pos.x;
  int y = sprite->position.y - lvl->pos.y;

  DrawTransparentBitmap(hDC, sprite->bitmap->bitmap, x, y,
       (sprite->current_frame+sprite->direction)*sprite->width, 0, sprite->width, sprite->height, RGB(255, 0, 255));
}

void DrawSprites( SPRITE *sprite, HDC hDC )
{
  if ( sprite == NULL )
    return;

  for ( ; sprite != NULL; sprite = sprite->next )
   draw_sprite(sprite, hDC );
}

void UpdateSprites( void )
{
  POINT          oldpos;
  int            action;
  SPRITE         *loop = NULL;
  SPRITE         *next = NULL;
  
  if ( lvl->first_sprite == NULL )
    return;

  for ( loop = lvl->first_sprite; loop != NULL; loop = next)
  {
    next = loop->next;
    loop->oldpos = loop->position;

    /* update */
    action = update_sprite( loop );

    /* handle killing */
    if (action == rKILL)
    {
       lvl->num_sprites--;                       /* subtract from total */
       loop->bitmap = NULL;
       UNLINK( loop, lvl->first_sprite, lvl->last_sprite, next, prev ); /* remove from list */
       loop->next = loop->prev = NULL;
       
       subtract_from_population( loop->name, lvl );

       bitmask_free(loop->bitmask);
       
       DISPOSE( loop->name );
       DISPOSE( loop );                         /* unalloc */
       continue;
    }

      CheckSpriteCollision(loop);
  /*  CheckSpriteCollisionObj(loop); */
  }
}

void CleanSprites( void )
{
  SPRITE *sprite;
  SPRITE *loop_sprite;

  /* remove and delete sprites */
  for ( sprite = lvl->first_sprite; sprite != NULL; sprite = loop_sprite)
  {
   loop_sprite = sprite->next;
   
   if ( sprite != NULL )
   {
      lvl->num_sprites--;                       /* subtract from total */

      /* null pointers */
      sprite->bitmap = NULL;

      UNLINK( sprite, lvl->first_sprite, lvl->last_sprite, next, prev ); /* remove from list */
    
      sprite->next = sprite->prev = NULL; /* should already be done */
      
      subtract_from_population( sprite->name, lvl );

      bitmask_free(sprite->bitmask);
      DISPOSE( sprite->name );
      DISPOSE( sprite );                         /* unalloc */
   }
  }
}

SPRITE *is_point_in_sprite(int x, int y)
{
  SPRITE *sprite;

  for ( sprite = lvl->first_sprite; sprite != NULL; sprite = sprite->next )
    if ( sprite->hidden == FALSE )
    {
       POINT size;
       size.x = sprite->position.x + sprite->width;
       size.y = sprite->position.y + sprite->height;

       if( x >= sprite->position.x && x <= size.x && y >= sprite->position.y && y <= size.y)
         return sprite;
    }

  return NULL;
}

void update_frame( SPRITE *sprite )
{
 if ( sprite->velocity.x != 0 && sprite->velocity.y == 0 )
  if ( (sprite->delay >= 0 ) && (--sprite->trigger <= 0) )
  {
     /* reset */
     sprite->trigger = sprite->delay;
       
     if ( ++sprite->current_frame >= sprite->frames )
        sprite->current_frame = 0;
  }
}

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

/************************/
/* collision stuff here */
/************************/
bool SpriteCollision(SPRITE *hitter, SPRITE *hittee)
{
#ifdef COLLISION_LOG
  glog( "Collision between %s and %s", hitter->name, hittee->name );
#endif
  
  int x,y;
  x = hittee->velocity.x;
  y = hittee->velocity.y;
  
  hittee->velocity.x = hitter->velocity.x;
  hittee->velocity.y = hitter->velocity.y;
  
  hitter->velocity.x = x;
  hitter->velocity.y = y;
  
  POINT pos1, pos2;
  
  pos1 = hittee->position;
  pos2 = hitter->position;
  
  hittee->position = hittee->oldpos;
  hitter->position = hitter->oldpos;
  
  hittee->oldpos = pos1;
  hitter->oldpos = pos2;
  
  return TRUE;
}

bool CheckSpriteCollision(SPRITE *sprite)
{
  SPRITE *loop;

  /* see who hit who :P */
  for ( loop = lvl->first_sprite; loop != NULL; loop = loop->next)
  {
    if (sprite == loop) /* cant hit yourself, dumbass! */
      continue;


//    if (sprite->type == loop->type)
//     continue;

//    if (loop->bitmap == sprite->bitmap) /* sprites of same bitmap cannot collide! */
//     continue;

    if ((loop == drag) || (sprite == drag)) /* dont hit the dragged sprites */
     continue;

    if (TestCollision(sprite,loop) != FALSE)       /* someone got hit! */
      return SpriteCollision(loop, sprite);
  }

  /* no one got hit... :( */
  return FALSE;
}

bool CheckSpriteCollisionObj(SPRITE *sprite)
{
  OBJ *loop;
  
  if ( sprite == NULL )
   return FALSE;

  /* see who hit who :P */
  for ( loop = lvl->first_obj; loop != NULL; loop = loop->next)
  {
    if (loop->state == NONE || loop->state == CLICK)
     continue;
     
    if (TestCollisionObj(sprite,loop) == TRUE)       /* this sprite hit an object */
      return SpriteCollisionObj(loop, sprite);
  }

  /* no one got hit */
  return FALSE;
}

/* handle sprite collisions with objects */
bool SpriteCollisionObj(OBJ *obj, SPRITE *sprite)
{
  return TRUE;
}

bool TestCollisionObj( SPRITE *sprite, OBJ *obj )
{
  RECT test1, test2;
  POINT pos = sprite->position;
  POINT lpos = lvl->pos;

  CopyRect( &test1, &sprite->collision );
  CopyRect( &test2, &obj->bounds );

    if ( test1.left <= test2.right &&
         test2.left <= test1.right &&
         test1.top <= test2.bottom &&
         test2.top <= test1.bottom )
      return TRUE;

  return FALSE;
}

bool TestCollision( SPRITE *sprite1, SPRITE *sprite2 )
{
  int xoffset, yoffset;
  
  if ( sprite1 == NULL || sprite2 == NULL )
   return FALSE;

 xoffset = (sprite2->position.x-sprite1->position.x);
 yoffset = (sprite2->position.y-sprite1->position.y);

 return bitmask_overlap(sprite1->bitmask, sprite2->bitmask, xoffset, yoffset);
}

void CalcCollisionRect( SPRITE *sprite )
{
  POINT pos = sprite->position;

  RECT rect = { pos.x, pos.y, pos.x+sprite->width, pos.y+sprite->height };
  
  CopyRect(&sprite->collision, &rect);
  
  InflateRect(&sprite->collision, -1, -1);
}

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

/********************/
/* helper functions */
/********************/
SPRITE *get_proto( char *name, LEVEL *level )
{
   SPRITE *loop;
   
   if ( (!name || name[0] == '\0') || level == NULL )
    return NULL;
    
   for ( loop = level->first_proto; loop != NULL; loop = loop->next )
   {
      if ( !stricmp( loop->name, name ) )
       return loop;
   }

   return NULL;
}

SPRITE *get_sprite( char *name, LEVEL *level )
{
   SPRITE *loop;

   if ( (!name || name[0] == '\0') || level == NULL )
    return NULL;

   for ( loop = level->first_sprite; loop != NULL; loop = loop->next )
   {
      if ( !stricmp( loop->name, name ) )
       return loop;
   }

   return NULL;
}

/*
 * SEt a sprites velocity and direction
 */
void SetVelocity( SPRITE *sprite, int x, int y )
{
   sprite->velocity.x = URANGE( -MAXSPEED, x, MAXSPEED);
   sprite->velocity.y = URANGE( -MAXSPEED, y, MAXSPEED);
     
   /* Update direction */
   if ( sprite->velocity.x < 0 )
    sprite->direction = LEFT;
   else if ( sprite->velocity.x > 0 )
    sprite->direction = RIGHT;
}

/*
 * Change the location of a sprite
 */
void SetPosition( SPRITE *sprite, POINT position )
{
  sprite->oldpos.x = sprite->position.x;
  sprite->oldpos.y = sprite->position.y;

  sprite->position.x = position.x;
  sprite->position.y = position.y;
  CalcCollisionRect( sprite );
}