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

struct level_squares_desc
{
  unsigned int properties; /* Define properties of the square */
  unsigned int nimages;    /* number of images for animation  */
  unsigned int delay;      /* delay between 2 images in ms    */
  char **images;
};

static struct t_level *Level_Load_Ascii(char *filename);
static int  Level_Load_Common(struct t_level *l);
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 *squares);

/**
 * 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");
  fseek(fic,0,SEEK_END);
  size = ftell(fic);
  rewind(fic);

  buffer=g_new(unsigned char,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 = g_new0(struct t_level,1);
  if (!level)
   {
     g_free(buffer);
     return NULL;
   }

  level->nsquares=1; /* one because the first square contains system sprite */

  /* 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 = g_new0(int,level->sizex*level->sizey);
  if (level->map == NULL)
   {
     g_free(buffer);
     return NULL;
   }

  level->squares = g_new0(struct t_square, level->nsquares);
  if (level->squares == NULL)
   {
     free(buffer);
     free(level->map);
     return NULL;
   }

  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);
	    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;
}

/**
 * This function finish the work begin by Level_Load_Ascii and
 * Level_Load_Binary
 *
 */
static int
Level_Load_Common(struct t_level *level)
{
  unsigned int *map;
  int i,x,y;

  /* Please keep in sync with enum sprite_index */
  static char *special_images[]= {
     "images/dinosaur.png",
     "images/kinesist.png",
     "images/kinesist2.png",
     "images/key_red.png",
     "images/key_blue.png",
     "images/key_green.png",
     "images/need_key_red.png",
     "images/need_key_blue.png",
     "images/need_key_green.png",
     "images/hourglass.png",
     NULL
  };

  /* 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->start_x=x;
	      level->start_y=y;

	    }
	 }
      }
   }

  /* Square 0 is special. We load all sprite we need */
  level->squares[0].npixmaps = sizeof(special_images) / sizeof (char *);
  level->squares[0].pixmaps = g_new0(GtkWidget *,level->squares[0].npixmaps);
  if (level->squares[0].pixmaps==NULL)
   { /* FIXME: free allocated memory */
     fprintf(stderr,"Not enough memory need to allocate (%d)\n",sizeof(GtkWidget *)* level->squares[0].npixmaps);
     return 0;
   }

  for (i=0;special_images[i]!=NULL;i++)
   {
     level->squares[0].pixmaps[i] = gtk_pixmap_new_from_file(special_images[i],NULL);
     if (level->squares[0].pixmaps[i] == NULL)
      { /* FIXME: free allocated memory */
	fprintf(stderr,"Can't read the sprite (%s)\n",special_images[i]);
	return 0;
      }
   }
  
  /* If background isn't load, load a random background */
#if 0
  if (level->background == NULL)
    level->background = Image_Load_Random("backgrounds");
#endif

  /* 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;
      }
   }
  
  return 1;
}

/**
 *
 *  Load a binary or ascii level from disk
 *
 */
struct t_level *
Level_Load(const char *filename)
{
  int i;
  char tmp[1024];
  struct t_level *l=NULL;

  i=0;
  trace("Trying to load %s",tmp);
  l = Level_Load_Ascii(tmp);
  if (l)
   {
     Level_Load_Common(l);
   }
  return l;
}

/**
 * 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]);
   }
  g_free(l->squares);

  if (l->map)
   {
     g_free(l->map);
     l->map=NULL;
   }
#if 0
  if (l->background)
   {
     SDL_FreeSurface(l->background);
     l->background=NULL;
   }
#endif
  g_free(l);
}

/*
 * Functions to parse ascii 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;
}

static int is_hexadecimal(const unsigned char *p)
{
  while (*p>='0' && *p<='9')
    p++;
  if ((*p>='A' && *p<='F') ||
      (*p>='a' && *p<='f'))
   {
     return 1;
   }
  return 0;
}

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

  while (*p!='\n')
   {
     /* If the number contain one non numeric char, asume is it hexadecimal */
     if (is_hexadecimal(p))
       sscanf(p,"%x",dst);
     else
       sscanf(p,"%d",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 can parse a string formatted like this:
 * CAN_MOVE_TO_EAST,CAN_GO_ABOVE arrow_right.tga,arrow_right1.tga,arrow_right2.tga
 */
static void read_datasquare(unsigned char **ptr, struct t_square *square)
{
  char *p;
  char *begin,saved;
  int part,images;
  int flags=0;

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

  while (*p!='\n')
   {
     begin=p;
     while (*p!=' ' && *p!='\t' && *p!='\n' && *p!=',')
       p++;
     saved=*p;
     *p=0;
     if (part)
       square->npixmaps++;
     *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->pixmaps = g_new(GtkWidget *,square->npixmaps);
  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].value;
	      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->pixmaps[images++]=gtk_pixmap_new_from_file(begin,NULL);
	/* 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;
}

/**
 *
 *
 */
struct level_info *
Level_New(const int sizex, const int sizey)
{
  struct level_info *info;

  info = g_new0(struct level_info,1);
  info->sizex = sizex;
  info->sizey = sizex;
  info->map = g_new0(int,info->sizex*info->sizey);

  return info;
}

/**
 *
 *
 */
void
Level_Draw2Screen(struct level_info *level, struct square_info *squares, GtkWidget *drawing_area)
{
  int i,x,y,square_index,properties;
  int dest_x, dest_y;
  int *map = level->map;
  struct t_square *square;
  GList *squares_list;

  /* Compute some values */

  /* All squares have the same size (for now) */
  //dest.w = l->squares[1].images[0]->w;
  //dest.h = l->squares[1].images[0]->h;

  /* Draw the background */
#if 0
  if (l->background==NULL)
   {
     /* Fill the backcolor in black */
     SDL_FillRect(screen,NULL,0);
   }
  else
   {
     SDL_BlitSurface(l->background, NULL, screen, NULL);
   }
#endif
#if 0
  dest_y=0;
  /* Draw the playfield */
  for (y=0;y<level->sizey;y++)
   {
     dest_x = 0;
     for (x=0;x<level->sizex;x++)
      {
	square_index = *map++;
	if (square_index)
	 {
	   /* Draw the square */
	   squares_list = g_list_nth(squares->squares,square_index);
	   square = (struct t_square *)squares_list->data;

	   gdk_draw_rgb_32_image(drawing_area->window,
	       drawing_area->style->black_gc,
	       dest_x, dest_y,
	       32,
	       32,
	       GDK_RGB_DITHER_MAX,
	       gdk_pixbuf_get_pixels (pixbuf),
	       gdk_pixbuf_get_rowstride (pixbuf));

	   properties = l->squares[square_index].properties;
	   /* Draw the sprite */
	   for (i=0;level_sprites[i].text;i++)
	    {
	      if (properties & level_sprites[i].flags)
	       {
		 SDL_BlitSurface(l->squares[0].images[i], NULL, screen, &dest);
		 break; /* for now, we have only one capability sprite */
	       }
	    }
	 }
	dest.x +=dest.w;
      }
     dest.y+=dest.h;
   }

  /* Draw the dinosaur */
  dest.x = xbegin + (dest.w * l->xdino);
  dest.y = ((screen->h - (dest.h * l->sizey))/2) + (dest.h * l->ydino);
  SDL_BlitSurface(l->squares[0].images[SPRITE_DINOSAUR], NULL, screen, &dest);

  /* ... and draw the kinesist sprite */
  if (l->control==CONTROL_KINESIST ||
      l->control==CONTROL_SQUARE)
   {
     dest.x = xbegin + (dest.w * l->xkinesist);
     dest.y = ((screen->h - (dest.h * l->sizey))/2) + (dest.h * l->ykinesist);
     SDL_BlitSurface(l->squares[0].images[l->control==CONTROL_KINESIST?SPRITE_KINESIST_NOTSELECTED:SPRITE_KINESIST_SELECTED], NULL, screen, &dest);
   }

  /* ... and draw the timeline */
  dest.x=0; 
  dest.y=screen->h-16;
  src.x=0;
  src.y=0;
  src.w=(game.time_remaining * l->timebar->w / l->times[game.difficulty]);
  src.h=8;
  SDL_BlitSurface(l->timebar,&src, screen, &dest);

#endif
}
