/*
 * 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_io.h"
#include "image.h"
#include "game.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <SDL.h>


/*
 * Local functions
 */

static struct t_level *Level_Load_Ascii(char *filename);
#ifdef KEEP_BINARY_FILE_FORMAT
static struct t_level *Level_Load_Binary(char *filename);
#endif
static int  Level_Load_Common(struct t_level *l);
static const char *Level_ChooseRandomTheme(void);
static void jump_space(unsigned char **ptr);
static void jump_line(unsigned char **ptr);
static void jump_word(unsigned char **ptr);
static void read_dataline(unsigned char **ptr, int sizex, unsigned int *dst);
static void read_datasquare(unsigned char **ptr, struct t_square *square, const char *theme);

/**
 * This function load a level in ascii format
 *
 */
static struct t_level *
Level_Load_Ascii(char *filename)
{
  unsigned char *buffer=NULL;
  unsigned char *ptr;
  FILE *fic;
  int size, current_square, current_line_map;
  struct t_level *level=NULL;

  /* Read all the level file in memory */
  fic = fopen(filename,"r");
  if (fic==NULL)
    return NULL;
  fseek(fic,0,SEEK_END);
  size = ftell(fic);
  rewind(fic);

  buffer=malloc(size+1);
  if (buffer==NULL)
   {
     fprintf(stderr,_("Can't alloc %d bytes\n"),size+1);
     fclose(fic);
     return NULL;
   }
  if (fread((void *)buffer,1,size,fic)!=size)
   {
     fprintf(stderr,_("We have not read all the level file\n"));
     fclose(fic);
     free(buffer);
     return NULL;
   }
  fclose(fic);
  buffer[size]=0;

  level = malloc(sizeof(struct t_level));
  if (!level)
   {
     free(buffer);
     return NULL;
   }

  memset(level,0,sizeof(struct t_level));
  level->nsquares=1; /* one because the first square contains system sprite */
  level->theme=Level_ChooseRandomTheme();

  /* First pass:
   *   find the size of the map
   *   find the number of square
   */

  ptr=buffer;
  while (*ptr)
   {
     if ((*ptr=='#') ||/* Is it a comment ? */
	 (*ptr=='\n')) /* Is it a empty line ? */
      {
	jump_line(&ptr);
      }
     else /* It's a valid line; each parameter is separated by a space or a tabulation */
      {
	switch (*ptr)
	 {
	  case 'L':
	    jump_word(&ptr);
	    jump_space(&ptr);
	    level->sizey++;
	    if (level->sizex==0)
	     { /* If the size x of the map isn't initialize read the line to find this size */
	       while (*(ptr)!='\n')
		{
		  jump_word(&ptr);
		  jump_space(&ptr);
		  level->sizex++;
		}
	     }
	    jump_line(&ptr);
	    break;
	  case 'Z':
	    level->nsquares++;
	    jump_line(&ptr);
	    break;
	  default:
	    jump_line(&ptr);
	 }
      }
   }

  trace("Sizex: %d\tSizey: %d\tSquares: %d",level->sizex,level->sizey,level->nsquares);

  /* Second pass:
   *   alloc the map, squares
   *   load the map
   */
  level->map = malloc(level->sizex*level->sizey * sizeof(int));
  if (level->map == NULL)
   {
     free(buffer);
     return NULL;
   }

  level->squares = malloc(level->nsquares * sizeof(struct t_square));
  if (level->squares == NULL)
   {
     free(buffer);
     free(level->map);
     return NULL;
   }
  memset(level->squares,0,level->nsquares * sizeof(struct t_square));

  current_line_map=0;
  current_square=1; /* one because the first square contain system sprite */
  ptr=buffer;
  while (*ptr)
   {
     if ((*ptr=='#') ||/* Is it a comment ? */
	 (*ptr=='\n')) /* Is it a empty line ? */
      {
	jump_line(&ptr);
      }
     else /* It's a valid line; each parameter is separated by a space or a tabulation */
      {
	switch (*ptr)
	 {
	  case 'B':
	     {
	       char bg_filename[1024];
	       unsigned char *begin;
	       jump_word(&ptr);
	       jump_space(&ptr);
	       begin=ptr;
	       jump_line(&ptr);
	       *--ptr=0;
	       snprintf(bg_filename,1023,"backgrounds/%s",begin);
	       level->background = Image_Load(bg_filename,1);
	       ptr++;
	       break;
	     }
	  case 'C':
	     {
	       unsigned char *begin;
	       jump_word(&ptr);
	       jump_space(&ptr);
	       begin=ptr;
	       jump_line(&ptr);
	       *--ptr=0;
	       trace("Comment: %s",begin);
	       level->comment=(unsigned char *)strdup(begin);
	       ptr++;
	       break;
	     }
	  case 'L':
	    jump_word(&ptr);
	    jump_space(&ptr);
	    read_dataline(&ptr,level->sizex,level->map+(current_line_map*level->sizex));
	    current_line_map++;
	    break;
	  case 'Z':
	    jump_word(&ptr);
	    jump_space(&ptr);
	    read_datasquare(&ptr,level->squares+current_square,level->theme);
	    current_square++;
	    break;
	  case 'T': /* Time easy medium hard */
	    jump_word(&ptr);
	    jump_space(&ptr);
	    read_dataline(&ptr,3,level->times);
	    trace("Time: Easy(%d) Medium(%d)  Hard(%d)",level->times[0],level->times[1],level->times[2]);
	    break;
	  default:
	    fprintf(stderr,"Warning, this character %c isn't reconized\n",*ptr);
	    jump_line(&ptr);
	 }
      }
   }

  free(buffer);

  return level;
}

#ifdef KEEP_BINARY_FILE_FORMAT
/**
 * This function load a level in binary format
 *
 */
static struct t_level *
Level_Load_Binary(char *filename)
{
  struct level_desc lvl;
  struct t_level *level;
  int file;
  int nsquares, i, j;
  char bg_filename[1024];

  file=open(filename,O_RDONLY);
  if (file==-1)
    return NULL;

  /* Load the header file */
  read(file,&lvl,sizeof(struct level_desc));
  if (!(lvl.version[0] == 'L' && lvl.version[1] == 'V' && lvl.version[2] == 'L' && lvl.version[3] == '1'))
   {
     close(file);
     return Level_Load_Ascii(filename);
   }

  level = malloc(sizeof(struct t_level));
  if (!level)
   {
     close(file);
     return NULL;
   }

  level->sizex = ntohs(*(unsigned short int *)lvl.sizex);
  level->sizey = ntohs(*(unsigned short int *)lvl.sizey);

  level->nsquares = nsquares = ntohs(*(unsigned short int *)lvl.nsquares);

  /* Load the background filename */
  strcpy(bg_filename,"backgrounds/");
  i=strlen(bg_filename); 
  while(i<1023)
   {
     read(file,bg_filename+i,1);
     trace("%c",bg_filename[i]);
     if (bg_filename[i]==0)
       break;
     i++;
   }
  bg_filename[i]=0;
  if (i==strlen("backgrounds/"))
   {
     /* Load background at random */
     level->background = Image_Load_Random(bg_filename);
   }
  else
   {
     level->background = Image_Load(bg_filename,1);
   }

  /* Load the data squares */
  level->squares = malloc(nsquares * sizeof(struct t_square));
  if (level->squares==NULL)
   {
     close(file);
     free(level);
     return NULL;
   }
  memset(level->squares,0,nsquares * sizeof(struct t_square));

  for (i=1;i<nsquares;i++)
   {
     int blksz;
     struct square_desc s;
     char *images, *p;
     read(file,&s,sizeof(struct square_desc));
     level->squares[i].properties = ntohl(*(unsigned int *)s.properties);
     level->squares[i].nimages = ntohs(*(unsigned short int *)s.nimages);
     level->squares[i].delay = ntohs(*(unsigned short *)s.delay);
     blksz = ntohs(*(unsigned short *)s.filenamesz);
     trace("Square: %d",i);
     trace("Properties: %.4x",level->squares[i].properties);
     trace("Images: %d",level->squares[i].nimages);
     trace("Delay: %dms",level->squares[i].delay);
     images = malloc(blksz);
     if (images==NULL)
      {
	fprintf(stderr,"Not enough memory need to allocate (%d)\n",blksz);
	close(file);
	free(level);
	return NULL;
      }
     read(file,images,blksz);
     level->squares[i].images = malloc(sizeof(SDL_Surface *)* level->squares[i].nimages);
     if (level->squares[i].images==NULL)
      {
	fprintf(stderr,"Not enough memory need to allocate (%d)\n",sizeof(SDL_Surface *)* level->squares[i].nimages);
	close(file);
	free(level);
	free(images);
	return NULL;
      }
     memset(level->squares[i].images,0,sizeof(SDL_Surface *)* level->squares[i].nimages);
     for (j=0,p=images;j<level->squares[i].nimages;j++)
      {
	SDL_Surface *image = Square_Load("blue",p);
	level->squares[i].images[j] = image;
	if (level->squares[i].images[j]==NULL)
	 {
	   fprintf(stderr,"Can't read graphics square (%s)\n",p);
	   Square_Free(&(level->squares[i]));
	   close(file);
	   free(level);
	   free(images);
	   return NULL;
	 }
	while (*p)
	  p++;
	p++;
      }
     free(images);
   }

  /* Load the data map */
  level->map = malloc(level->sizex*level->sizey*sizeof(int));
  if (level->map==NULL)
   {
     close(file);
     for (i=0;i<nsquares;i++)
       Square_Free(&(level->squares[i]));
     free(level);
     return NULL;
   }
  for (i=0;i<level->sizex*level->sizey;i++)
   {
     if (nsquares<256)
      {
	unsigned char value;
	read(file,&value,1);
	level->map[i]=value;
      }
     else
      {
	unsigned short int value;
	read(file,&value,2);
	value = htons(*(unsigned short int *)&value);
	level->map[i]=value;
      }
   }
  close(file);

  return level;
}
#endif /* KEEP_BINARY_FILE_FORMAT */

/**
 * This function finish the work begin by Level_Load_Ascii and
 * Level_Load_Binary
 *
 * @return 0 if we are found an error.
 */
static int
Level_Load_Common(struct t_level *level)
{
  unsigned int *map;
  int i,x,y;
  SDL_Surface *newbg;

  /* Find the start and finish square */
  map = level->map;
  level->start_x=level->start_y=-1;
  for (y=0;y<level->sizey;y++)
   {
     for (x=0;x<level->sizex;x++)
      {
	int square_index = *map++;
	if (square_index)
	 {
	   if (level->squares[square_index].properties&START)
	    {
	      level->xdino = level->start_x=x;
	      level->ydino = level->start_y=y;
	    }
	 }
      }
   }
  if (level->start_x==-1 || level->start_y==-1)
    return 0;

  /* Load all graphics for this level */
  Sprite_Load_Dinosaur(level->theme,&level->dinosaur);

  /* Square 0 is special. We load all sprite we need */
  level->squares[0].nimages = SPRITE_MAX;
  level->squares[0].images = malloc(sizeof(SDL_Surface *)* level->squares[0].nimages);
  if (level->squares[0].images==NULL)
   { 
     fprintf(stderr,"Not enough memory need to allocate (%d)\n",sizeof(SDL_Surface *)* level->squares[0].nimages);
     return 0;
   }
  memset(level->squares[0].images,0,sizeof(SDL_Surface *)* level->squares[0].nimages);

  for (i=0;level_sprites[i].text!=NULL;i++)
   {
     level->squares[0].images[i] = Image_Load(level_sprites[i].text,0);
     if (level->squares[0].images[i] == NULL)
      { /* FIXME: free allocated memory */
	fprintf(stderr,"Can't read the sprite (%s)\n",level_sprites[i].text);
	return 0;
      }
   }
  /* Create the kinesist sprites */
 level->kinesists_selected=Image_Create_Kinesist(32,32,1);
 level->kinesists_notselected=Image_Create_Kinesist(32,32,0);
 
  /* Make the timebar */
  level->timebar = Image_Timebar(game.width);
  level->board = Image_Load("images/board.png",1);
  if (level->board==0)
   {
     fprintf(stderr,"Can't read this image to display the board \"images/board.png\"\n");
     return 0;
   }
 
  /* If background isn't load, load a random background */
  if (level->background == NULL)
    level->background = Image_Load_Random("backgrounds");
  if (level->background)
   {
     SDL_Rect r;
     r.w=SDL_GetVideoSurface()->w; r.h=SDL_GetVideoSurface()->h - level->board->h;

     /* Convert the background to the screen format */
     newbg = SDL_DisplayFormat(level->background);
     SDL_FreeSurface(level->background);
     level->background=newbg;

     if (level->background->w != r.w || level->background->h != r.h)
      {/* Resize the background */
	newbg = SDL_CreateRGBSurface(SDL_HWSURFACE,r.w,r.h,
	    level->background->format->BitsPerPixel,
	    level->background->format->Rmask,
	    level->background->format->Gmask,
	    level->background->format->Bmask,
	    level->background->format->Amask);
	SDL_BlitSurface(level->background,NULL,newbg,NULL);
	SDL_FreeSurface(level->background);
	level->background=newbg;
      }
     /* then fade the background ... */
     Background_Fade(level->background,level);
   }

  /* If time to complete the level is null, correct by a default value */
  for (i=0;i<DIFFICULT_LEVEL;i++)
   {
     if (level->times[i]==0)
      {
	level->times[i]=(DIFFICULT_LEVEL-i)*30;
      }
   }
  /* Some default variable for a level */
  level->control=CONTROL_PLAYER;

  return 1;
}

/**
 * Load a level from a file.
 * This file can be a binary format (obsolet) or text format.
 * @param filename the name of the file to load. (If the filename begin with a /
 *                 the file is not relative).
 * @return a pointer describing the level or NULL otherthise
 */
struct t_level *
Level_Load(const char *filename)
{
  int i;
  char tmp[1024];
  struct t_level *l=NULL;

  if (filename[0]=='/')
   {
#ifdef KEEP_BINARY_FILE_FORMAT
     l = Level_Load_Binary(tmp);
#else
     l = Level_Load_Ascii(tmp);
#endif
     if (l==NULL)
      {
	fprintf(stderr,_("Can't load this level %s\n"),filename);
	return NULL;
      }
   }
  else
   {
     i=0;
     while (datadir_path[i])
      {
	snprintf(tmp,1024,"%s/levels/%s",datadir_path[i],filename);
	trace("Trying to load %s",tmp);
#ifdef KEEP_BINARY_FILE_FORMAT
     l = Level_Load_Binary(tmp);
#else
     l = Level_Load_Ascii(tmp);
#endif
	if (l)
	  break;
	i++;
      }
     if (datadir_path[i]==NULL)
      {
	fprintf(stderr,_("Can't load this level %s\n"),filename);
	return NULL;
      }
   }

  if (Level_Load_Common(l)==0)
   {
     fprintf(stderr,_("Can't initialize this level %s\n"),filename);
     return NULL;
   }

  return l;
}

/*
 * Functions to parse file
 */
static void jump_space(unsigned char **ptr)
{
  char *p=*ptr;
  while (*p==' ' || *p=='\t' || *p=='\n')
    p++;
  if (*(p-1)=='\n')
    p--;
  *ptr=p;
}

static void jump_word(unsigned char **ptr)
{
  char *p=*ptr;
  while (*p!=' ' && *p!='\t' && *p!='\n')
    p++;
  *ptr=p;
}

static void jump_line(unsigned char **ptr)
{
  char *p=*ptr;
  while (*p!='\n')
    p++;
  *ptr=++p;
}

/* Number can be:
 *   $xx : this is a hexadecimale number
 *   [0-9]+: this is a decimale number
 *   [0-9a-fA-F]+: this is a hexadecimale number
 * @p: pointer to the text
 * @dst: pointer to a int to store the converted number
 */
static void read_number(const unsigned char *p, unsigned int *dst)
{
  const unsigned char *old_p=p;
  *dst=0;
  if (*p=='$')
   {
     sscanf(p+1,"%x",dst);
   }
 
  while (*p>='0' && *p<='9')
    p++;
  if ((*p>='A' && *p<='F') ||
      (*p>='a' && *p<='f'))
   {
     sscanf(old_p,"%x",dst);
   }
  else
   {
     sscanf(old_p,"%d",dst);
   }
}

static void read_dataline(unsigned char **ptr, int sizex, unsigned int *dst)
{
  char *p=*ptr;

  while (*p!='\n')
   {
     /* If the number contains one non numeric char, assume is it hexadecimal */
     read_number(p,dst);
     dst++;
     sizex--;
     while (*p!=' ' && *p!='\t' && *p!='\n')
       p++; 
     while (*p==' ' || *p=='\t' || *p=='\n')
       p++;
     if (*(p-1)=='\n')
       p--;
   }
  if (sizex!=0)
   {
     *p=0;
     fprintf(stderr,"Warning: data line is too %s <%s>\n",(sizex<0?"long":"short"),*ptr);
   }
  *ptr=++p;
}

/**
 * This function parse the line who describe a square in the ASCII level format
 * CAN_MOVE_TO_EAST,CAN_GO_ABOVE arrow_right.tga,arrow_right1.tga,arrow_right2.tga
 * @param ptr  the string pointer of the level
 * @param square where to place information load
 * @param theme Theme use to load graphics
 * @return nothing
 */
static void read_datasquare(unsigned char **ptr, struct t_square *square, const char *theme)
{
  char *p;
  char *begin,saved;
  int part,images;
  int flags=0;

  /* 
   * First pass:
   *   Count image number
   */
  p=*ptr;
  part=0;
  square->nimages=0;

  while (*p!='\n')
   {
     begin=p;
     while (*p!=' ' && *p!='\t' && *p!='\n' && *p!=',')
       p++;
     saved=*p;
     *p=0;
     if (part)
       square->nimages++;
     *p=saved;
     if (saved!=',')
      {
	part++;
	while (*p==' ' || *p=='\t' || *p=='\n')
	  p++;
	if (*(p-1)=='\n')
	  p--;
      }
     else
       p++;
   }

  /*
   * Second pass:
   *   Alloc space for images
   *   Load image and properties
   */

  p=*ptr;
  images=part=0;
  square->images = malloc(sizeof(SDL_Surface *)* square->nimages);
  square->delay=50;

  while (*p!='\n')
   {
     begin=p;
     while (*p!=' ' && *p!='\t' && *p!='\n' && *p!=',')
       p++;
     saved=*p;
     *p=0;
     if (!part)
      {
	int i;
	for (i=0;square_properties_text2enum[i].text!=NULL;i++)
	 {
	   if (strcmp(begin,square_properties_text2enum[i].text)==0)
	    {
	      flags|=square_properties_text2enum[i].flags;
	      break;
	    }
	 }
	if (square_properties_text2enum[i].text==NULL)
	 {
	   if (strcmp(begin,"NONE")==0)
	     flags=0;
	   else
	    {
	      fprintf(stderr,"Warning, this capabity isn't reconize <%s>\n",begin);
	    }
	 }
      }
     else
      {
	/* Add this image to the list */
	square->images[images++]=Square_Load(theme,begin);
	/* FIXME: This function can fail */
      }
     *p=saved;
     if (saved!=',')
      {
	part++;
	while (*p==' ' || *p=='\t' || *p=='\n')
	  p++;
	if (*(p-1)=='\n')
	  p--;
      }
     else
       p++;
   }
  *ptr=++p;

  square->properties=flags;
}


static const char *Level_ChooseRandomTheme(void)
{
  static const char *themes[]=
   { "blue","green","yellow","steel","brown"};

  return themes[RANDOM()%(sizeof(themes)/sizeof(char *))];
}

