/*
 * Stonage: a puzzle game
 * Copyright (C) 2001 Luc Saillard <luc@saillard.org>
 * 
 * Source for loading, manage Level file
 * 
 * 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 "stonage.h"
#include "level.h"
#include "level_screen.h"
#include "image.h"
#include "game.h"
#include <stdlib.h>
#include <stdio.h>
#include <SDL.h>
#include <string.h>


#ifdef ENABLE_DEBUG_TRACE
struct enum_list movement_type_text2enum[] = 
{
   {"move down",MOVE_DOWN},
   {"move up",MOVE_UP},
   {"move right",MOVE_RIGHT},
   {"move left",MOVE_LEFT}
};
#endif




/**
 * Free all memory use by this pointer
 */
void
Level_Free(struct t_level *l)
{
  int i;

  for (i=0;i<l->nsquares;i++)
    Square_Free(&l->squares[i]);
  free(l->squares);

  if (l->map)
    free(l->map);
  if (l->background)
    SDL_FreeSurface(l->background);
  if (l->timebar)
    SDL_FreeSurface(l->timebar);
  if (l->board)
    SDL_FreeSurface(l->board);
  if (l->comment)
    free(l->comment);
  free(l);
}

/**
 *
 * Do some to test to check if this level is valid
 * 
 */
int
Level_Sanity_Check(struct t_level *l)
{
  int find_exit;
  int i,teleport=0;
  int *map;

  /* Check that square[x][y]<nsquares and if one exit exist */
  map = l->map;
  for (find_exit=0,i=0; i < (l->sizex*l->sizey);i++,map++)
   {
     if (*map >= l->nsquares)
      {
	fprintf(stderr,_("This level doesn't pass the sanity check.\nWe found a "
	      "square number (%d) at [x=%d,y=%d] that is more important than the "
	      "total of square description (%d).\nAborting\n"),
	    *map,i%l->sizex,i/l->sizex,l->nsquares);
	return 0;
      }
     else if (l->squares[*map].properties & FINISH)
       find_exit=1;
     if (l->squares[*map].properties & TELEPORT)
       teleport++;
   }

  if (l->start_x==-1 || l->start_y==-1)
   {
     fprintf(stderr,_("This level doesn't have a starting point.\nAborting\n"));
     return 0;
   }
  if (teleport==1)
   {
     fprintf(stderr,_("This level doesn't have at least two teleporter.\nAborting\n"));
     return 0;
   }

  if (!find_exit)
   {
     fprintf(stderr,_("This level doesn't have at least a ending point.\nAborting\n"));
     return 0;
   }

  return 1;
}


/**
 * Function: Level_TestCanGoAbove
 * Comment:
 *   We test if the dinausor can go abose the square, we make a function,
 *   because we can test if the user have the good key, and all other actions.
 * INPUT:
 * 
 * OUTPUT:
 *  0: can't move
 *  1: ok.
 */

static int
Level_TestCanGoAbove(struct t_level *l, unsigned int square_properties)
{
  if (   (square_properties&CAN_GO_ABOVE)
      && !((square_properties&NEED_KEY_RED) && !l->keys_red)
      && !((square_properties&NEED_KEY_BLUE) && !l->keys_blue)
      && !((square_properties&NEED_KEY_GREEN) && !l->keys_green))
   {
     return 1;
   }
  return 0;
}

/**
 * Function: Level_MoveDinoTo
 * INPUT:
 *  direction: 0 down, 1:up, 2:right, 3:left
 *  automove: true,false
 * OUTPUT:
 *  0: can't move
 *  1: The dino have moved. (and optionnaly the square)
 *  2: The dino AND the square have moved.
 */
int
Level_MoveDinoTo(struct t_level *l, enum movement_type direction, int automove)
{
  unsigned int properties;
  int *map;

  map = l->map + (l->ydino * l->sizex) + l->xdino;
  properties = l->squares[*map].properties;

  trace ("direction: %s",movement_type_text2enum[direction].text);

  switch(direction)
   {
     /* Each test is done in that order:
      *   1. Test against border of the playfield
      *   2. Test if the dinosaur can move to the next square
      *   3. Test if the dinosaur is on a moveable square and the square can
      *      move in the good direction.
      *    3-1. Test if the square can move (a empty case)
      *         Then move the square and the dinosaur
      */
    case MOVE_RIGHT:
      if (l->xdino >= l->sizex-1)
	break;
      map++;
      if (!automove && Level_TestCanGoAbove(l,l->squares[*map].properties))
       {
	 Anim_Push(ANIMATION_DINO,l->xdino,l->ydino);
	 l->xdino++;
	 return 1;
       }
      if (properties&CAN_MOVE_TO_EAST)
       {
	 if (*map==0)
	  {
	    l->xdino++;
	    *map=*(map-1);
	    *(map-1)=0;
	    Anim_Push(ANIMATION_SQUARE,l->xdino,l->ydino);
	    Anim_Push(ANIMATION_DINO,l->xdino-1,l->ydino);
	    return 2;
	  }
       }
      break;

    case MOVE_LEFT:
      if (l->xdino <= 0)
	break;
      map--;
      if (!automove && Level_TestCanGoAbove(l,l->squares[*map].properties))
       {
	 Anim_Push(ANIMATION_DINO,l->xdino,l->ydino);
	 l->xdino--;
	 return 1;
       }
      if (properties&CAN_MOVE_TO_WEST)
       {
	 if (*map==0)
	  {
	    l->xdino--;
	    *map=*(map+1);
	    *(map+1)=0;
	    Anim_Push(ANIMATION_SQUARE,l->xdino,l->ydino);
	    Anim_Push(ANIMATION_DINO,l->xdino+1,l->ydino);
	    return 2;
	  }
       }
      break;

    case MOVE_UP:
      if (l->ydino < 1)
	break;
      map-=l->sizex;
      if (!automove && Level_TestCanGoAbove(l,l->squares[*map].properties))
       {
	 Anim_Push(ANIMATION_DINO,l->xdino,l->ydino);
	 l->ydino--;
	 return 1;
       }
      if (properties&CAN_MOVE_TO_NORTH)
       {
	 if (*map==0)
	  {
	    l->ydino--;
	    *map=*(map + l->sizex);
	    *(map + l->sizex)=0;
	    Anim_Push(ANIMATION_SQUARE,l->xdino,l->ydino);
	    Anim_Push(ANIMATION_DINO,l->xdino,l->ydino+1);
	    return 2;
	  }
       }
      break;

    case MOVE_DOWN:
      if (l->ydino >= l->sizey-1)
	break;
      map+=l->sizex;
      if (!automove && Level_TestCanGoAbove(l,l->squares[*map].properties))
       {
	 Anim_Push(ANIMATION_DINO,l->xdino,l->ydino);
	 l->ydino++;
	 return 1;
       }
      if (properties&CAN_MOVE_TO_SOUTH)
       {
	 if (*map==0)
	  {
	    l->ydino++;
	    *map=*(map - l->sizex);
	    *(map - l->sizex)=0;
	    Anim_Push(ANIMATION_SQUARE,l->xdino,l->ydino);
	    Anim_Push(ANIMATION_DINO,l->xdino,l->ydino-1);
	    return 2;
	  }
       }

      break;

    default:
      return 0;
   }
  /* Special case for teleport square */
  if (properties & TELEPORT)
    return 1;
  return 0;
}

/**
 * Function: Level_MoveKinesistTo
 * INPUT:
 * direction: 0 down, 1:up, 2:right, 3:left
 * OUTPUT:
 *  0: can't move
 *  1: ok.
 */
int
Level_MoveKinesistTo(struct t_level *l, enum movement_type direction)
{
  int *map;
  /* Some sanity check over buffer overflow */
  if (  (direction==MOVE_RIGHT && l->xkinesist >= l->sizex-1)
      ||(direction==MOVE_LEFT  && l->xkinesist == 0)
      ||(direction==MOVE_DOWN  && l->ykinesist >= l->sizey-1)
      ||(direction==MOVE_UP    && l->ykinesist == 0))
   {
     return 0;
   }
  
  map = l->map + (l->ykinesist * l->sizex) + l->xkinesist;
  trace ("direction: %s",movement_type_text2enum[direction].text);
  switch(direction)
   {
    case MOVE_RIGHT:
      Anim_Push(ANIMATION_KINESIST,l->xkinesist,l->ykinesist);
      l->xkinesist++;
      return 1;

    case MOVE_LEFT:
      Anim_Push(ANIMATION_KINESIST,l->xkinesist,l->ykinesist);
      l->xkinesist--;
      return 1;

    case MOVE_UP:
      Anim_Push(ANIMATION_KINESIST,l->xkinesist,l->ykinesist);
      l->ykinesist--;
      return 1;

    case MOVE_DOWN:
      Anim_Push(ANIMATION_KINESIST,l->xkinesist,l->ykinesist);
      l->ykinesist++;
      return 1;

    default:
      return 0;
   }
}

/**
 * Function: Level_CanMoveSquare
 * Comment:
 *   Return all direction where this square can go. If the square can't move,
 *   return 0; Direction is the same as properties
 * Input:
 *   l: the level
 * Output:
 *   0|CAN_MOVE_TO_SOUTH|CAN_MOVE_TO_NORTH|CAN_MOVE_TO_WEST|CAN_MOVE_TO_EAST
 */
int
Level_CanMoveSquare(const struct t_level *l)
{
  int square_can_move;
  unsigned int properties;
  int *map;

  map = l->map + (l->ykinesist * l->sizex) + l->xkinesist;
  properties = l->squares[*map].properties;

  /* If the dinosaur is on the square, this square can't move */
  if ((l->xkinesist == l->xdino) && (l->ykinesist == l->ydino))
    return 0;

  square_can_move=0;

  properties&=CAN_MOVE_TO_MASK;
  if (!properties) /* If the square can't move, exit now */
    return 0;

  /* For each direction,
   *  test if the square can move to this place,
   */
  if ((properties&CAN_MOVE_TO_WEST) && (l->xkinesist > 0))
   {
     if (*(map - 1)==0)
      {
	square_can_move|=CAN_MOVE_TO_WEST;
      }
   }
  
  if ((properties&CAN_MOVE_TO_EAST) && (l->xkinesist < l->sizex-1))
   {
     if (*(map + 1)==0)
      {
	square_can_move|=CAN_MOVE_TO_EAST;
      }
   }

  if ((properties&CAN_MOVE_TO_NORTH) && (l->ykinesist > 0))
   {
     if (*(map - l->sizex)==0)
      {
	square_can_move|=CAN_MOVE_TO_NORTH;
      }
   }
  
  if ((properties&CAN_MOVE_TO_SOUTH) && (l->ykinesist < l->sizey-1))
   {
     if (*(map + l->sizex)==0)
      {
	square_can_move|=CAN_MOVE_TO_SOUTH;
      }
   }

#if ENABLE_DEBUG_TRACE
   { int i;
     char properties_text[1024];
     properties_text[0]=0;
     for (i=0;i<32 && square_properties_text2enum[i].text;i++)
      {
	if (square_properties_text2enum[i].flags & square_can_move)
	 {
	   if (properties_text[0]!=0)
	     strcat(properties_text,",");
	   strcat(properties_text,square_properties_text2enum[i].text);
	  }
      }
     trace("square_can_move: %d (%s)",square_can_move,properties_text);
   }
#endif
  
  return square_can_move;
}

/**
 * Move the square in the direction indicated.
 * @param l the level descriptor
 * @param direction
 * @return return TRUE if the square have moved.
 */
int
Level_MoveSquareTo(struct t_level *l, enum movement_type direction)
{
  int properties;
  int *map;

  map = l->map + (l->ykinesist * l->sizex) + l->xkinesist;
  properties = l->squares[*map].properties;

  trace ("direction: %s",movement_type_text2enum[direction].text);
  if (direction == MOVE_RIGHT && (properties&CAN_MOVE_TO_EAST))
   {
     map++;
     if ((l->xkinesist < l->sizex-1) && *map==0)
      {
	l->xkinesist++;
	*map=*(map-1);
	*(map-1)=0;
	Anim_Push(ANIMATION_SQUARE,l->xkinesist,l->ykinesist);
	Anim_Push(ANIMATION_KINESIST,l->xkinesist-1,l->ykinesist);
	return 1;
      }
     return 0;
   }
  else if (direction == MOVE_LEFT && (properties&CAN_MOVE_TO_WEST))
   {
     map--;
     if ((l->xkinesist > 0) && *map==0)
      {
	l->xkinesist--;
	*map=*(map+1);
	*(map+1)=0;
	Anim_Push(ANIMATION_SQUARE,l->xkinesist,l->ykinesist);
	Anim_Push(ANIMATION_KINESIST,l->xkinesist+1,l->ykinesist);
	return 1;
      }
     return 0;
   }
  else if (direction == MOVE_UP && (properties&CAN_MOVE_TO_NORTH))
   {
     map-=l->sizex;
     if ((l->ykinesist > 0) && *map==0)
      {
	l->ykinesist--;
	*map=*(map + l->sizex);
	*(map + l->sizex)=0;
	Anim_Push(ANIMATION_SQUARE,l->xkinesist,l->ykinesist);
	Anim_Push(ANIMATION_KINESIST,l->xkinesist,l->ykinesist+1);
	return 1;
      }
     return 0;
   }
  else if (direction == MOVE_DOWN && (properties&CAN_MOVE_TO_SOUTH))
   {
     map+=l->sizex;
     if ((l->ykinesist < l->sizey-1) && *map==0)
      {
	l->ykinesist++;
	*map=*(map - l->sizex);
	*(map - l->sizex)=0;
	Anim_Push(ANIMATION_SQUARE,l->xkinesist,l->ykinesist);
	Anim_Push(ANIMATION_KINESIST,l->xkinesist,l->ykinesist-1);
	return 1;
      }
     return 0;
   }
 return 0;
}

/**
 *
 * Find the next teleporter in the level
 *
 */
static int
Level_FindTeleporter(struct t_level *l, int square_index)
{
  int *map = l->map;
  int i,x,y;

  for (i=square_index;i<l->nsquares;i++)
   {
     if (l->squares[i].properties & TELEPORT)
       break;
   }
  if (i >= l->nsquares)
   {
     for (i=0;i<square_index;i++)
      {
	if (l->squares[i].properties & TELEPORT)
	  break;
      }
     if (i >= square_index)
      {
	/* Strange, we didn't find another teleporter in this level */
	trace ("Strange, we didn't find another teleporter in this level\n");
	return 0;
      }
   }

  /* Find the coordinate for this square (i) */
  for (y=0;y<l->sizey;y++)
   {
     for (x=0;x<l->sizex;x++,map++)
      {
	if (i == *map && (x != l->xdino || y != l->ydino))
	 {
	   /* Warp the dino to this position */
	   Anim_Push(ANIMATION_DINO,l->xdino,l->ydino);
	   l->xdino = x;
	   l->ydino = y;
	   return 1;
	 }
      }
   }
  return 0;
}


/**
 * Function: Level_DoPostMoveAction
 * Comment:
 *   Execute function after the move and before the animation.
 * Input:
 *
 * Output:
 *   TRUE or FALSE
 */
int
Level_DoPostMoveAction(struct t_level *l)
{
  int square_index;
  unsigned int properties, *map;
  int change_properties=0;
  
  map = l->map + (l->ydino * l->sizex) + l->xdino;
  square_index=*map;
  properties = l->squares[square_index].properties;


  if (properties&KEY_RED)
   {
     l->keys_red++; change_properties=1;
   }
  else if (properties&NEED_KEY_RED)
   {
     l->keys_red--; change_properties=1;
   }

  if (properties&KEY_BLUE)
   {
     l->keys_blue++; change_properties=1;
   }
  else if (properties&NEED_KEY_BLUE)
   {
     l->keys_blue--; change_properties=1;
   }

  if (properties&KEY_GREEN)
   {
     l->keys_green++; change_properties=1;
   }
  else if (properties&NEED_KEY_GREEN)
   {
     l->keys_green--; change_properties=1;
   }

  if (change_properties)
   {
     properties&=~(KEY_RED|NEED_KEY_RED|KEY_BLUE|NEED_KEY_BLUE|KEY_GREEN|NEED_KEY_GREEN);
     square_index = Square_FindProperties(l->squares,l->nsquares,properties);
     if (square_index < -1 )
      {
	fprintf(stderr,"Argh, we have found a bug in this level. When we have created "\
	    "the level, we didn't specify another square with the same properties of the " \
	    "square that have the key.\n");
	exit(1);
      }
     *map = square_index;
   }
  
  if (properties & TELEPORT)
     Level_FindTeleporter(l,square_index);

  if (properties & HOURGLASS)
   {
     l->times[game.difficulty]*=2;
     *map = Square_FindProperties(l->squares,l->nsquares,properties&~HOURGLASS);
     if (*map == -1)
       *map = 0; /* It's a bug ..., so draw it like a bug */
   }

  /**********************************/
  /* Do action on the last position */
  /**********************************/

  map = l->map + (l->ydino_old * l->sizex) + l->xdino_old;
  properties = l->squares[*map].properties;

  if (properties&AFTER_MOVE_CRASH)
   {
     *map=0;
   }

  /* Save the last position */
  l->xdino_old=l->xdino;
  l->ydino_old=l->ydino;

  return 0;
}


/**
 * Function: Level_IsFinish
 * Comment:
 *   return true when the user have accomplished all tasks. For now,
 *   we only test if the user is on a Finish square
 * Input:
 *
 * Output:
 *   TRUE or FALSE
 */
int
Level_IsFinish(struct t_level *l)
{
  int square_index;
  unsigned int properties;
  
  square_index=l->map[ (l->ydino * l->sizex) + l->xdino];
  properties = l->squares[square_index].properties;

  if (properties&FINISH)
    return 1;
  else
    return 0;
}


