/* gnome-stones - cave.c
 *
 * Time-stamp: <2001/11/14 15:44:16 benesch>
 *
 * Copyright (C) 1998 Carsten Schaar
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "cave.h"
#include "player.h"
#include "status.h"
#include "object.h"


extern GList *object_list;

static GStonesSignal signal_option_changed= (GStonesSignal) 0;
static GStonesSignal signal_cave_pre_scan = (GStonesSignal) 0;
static GStonesSignal signal_cave_post_scan= (GStonesSignal) 0;
static GStonesSignal signal_player_die    = (GStonesSignal) 0;
static GStonesSignal signal_player_start  = (GStonesSignal) 0;



/*****************************************************************************/
/* Gnome-Stones signal stuff.  */


GStonesSignal
gstones_signal (const gchar *sig_name)
{
  GStonesSignal         signal;
  static GHashTable    *sig_table= NULL;
  static GStonesSignal  sig_next= 0;
  
  if (!sig_table)
    {
      sig_table= g_hash_table_new (g_str_hash, g_str_equal);
      if (!sig_table)
	return (GStonesSignal) 0;
    }

  signal= GPOINTER_TO_UINT (g_hash_table_lookup (sig_table, sig_name));
  if (signal) return (GStonesSignal) signal;
  
  ++sig_next;
  g_hash_table_insert (sig_table, g_strdup (sig_name), 
		       GUINT_TO_POINTER (sig_next));
  return sig_next;
}


/*****************************************************************************/
/* Additional object management.  */


/* Makes cave require a special object.  */
gboolean
cave_add_object (GStonesCave *cave, GStonesObject *object)
{
  GStonesObjContext *context;
  
  g_return_val_if_fail (cave, FALSE);
  g_return_val_if_fail (object, FALSE);
  
  /* Check, wheather this object has already been added to this cave.
     If so, than don't add it again.  */
  if (g_hash_table_lookup (cave->contexts, object))
    return TRUE;

  /* Create a new context for this object.  */
  context= object_context_new (object);
  if (!context)
    return FALSE;

  g_hash_table_insert (cave->contexts, object, context);

  if (object->description->init_cave_function)
    {
      if (!object->description->init_cave_function (cave, context))
	{
	  /* There has been an error while initializing the object.  */
	  g_hash_table_remove (cave->contexts, object);
	  object_context_free (context);
	  return FALSE;
	}
    }

  return TRUE;
}


void
cave_remove_object (GStonesCave *cave, GStonesObject *object)
{
  GStonesObjContext *context;
  
  g_return_if_fail (cave);
  g_return_if_fail (object);

  context= g_hash_table_lookup (cave->contexts, object);

  if (context)
    {
      if (object->description->destroy_function)
	object->description->destroy_function (cave, context);
      
      g_hash_table_remove (cave->contexts, object);
      
      object_context_free (context);
    }
}


gboolean
cave_object_removable (GStonesCave *cave, GStonesObject *object)
{
  /* This feature is not implemented yet, so we always return 'FALSE'.  */

  return FALSE;
}


static GStonesObjContext *
cave_get_object_context (GStonesCave *cave, GStonesObject *object)
{
  return (GStonesObjContext *) g_hash_table_lookup (cave->contexts, object);
}
 


/*****************************************************************************/
/* Object options stuff.  */

void
cave_set_object_option (      GStonesCave   *cave,
			      GStonesObject *object,
			const gchar         *name,
	                const gchar         *value)
{
  GStonesObjContext *context;

  /* Start checking the parameter.  */
  g_return_if_fail (cave);
  g_return_if_fail (object);
  g_return_if_fail (name);

  /* 'value' must not be checked. Setting a NULL value means, we want
     to unset this option.  */

  context= cave_get_object_context (cave, object);
  g_return_if_fail (context);

  object_context_set_option (context, name, value);

  /* We now send a signal to this object, telling him, that his options
     just changed.  */
  if (object->description->signal_function)
    object->description->signal_function (cave, signal_option_changed, context);
}


gchar *
cave_get_string_object_option (      GStonesCave   *cave,
				     GStonesObject *object,
			       const gchar         *name)
{
  GStonesObjContext *context;

  /* Start checking the parameter.  */
  g_return_val_if_fail (cave, NULL);
  g_return_val_if_fail (object, NULL);
  g_return_val_if_fail (name, NULL);

  context= cave_get_object_context (cave, object);
  g_return_val_if_fail (context, NULL);

  return object_context_get_string_option (context, name);
}


gboolean
cave_get_bool_object_option   (      GStonesCave   *cave, 
			             GStonesObject *object,
			       const gchar         *name)
{
  GStonesObjContext *context;

  /* Start checking the parameter.  */
  g_return_val_if_fail (cave, FALSE);
  g_return_val_if_fail (object, FALSE);
  g_return_val_if_fail (name, FALSE);

  context= cave_get_object_context (cave, object);
  g_return_val_if_fail (context, FALSE);

  return object_context_get_bool_option (context, name);
}

gint
cave_get_int_object_option   (      GStonesCave   *cave, 
			            GStonesObject *object,
			      const gchar         *name)
{
  GStonesObjContext *context;

  /* Start checking the parameter.  */
  g_return_val_if_fail (cave, 0);
  g_return_val_if_fail (object, 0);
  g_return_val_if_fail (name, 0);

  context= cave_get_object_context (cave, object);
  g_return_val_if_fail (context, 0);

  return object_context_get_int_option (context, name);
}

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


GStonesCave *
cave_new (void)
{
  GStonesObject *frame= NULL;
  GStonesCave   *cave = NULL;
  guint x;
  guint y;
  
  /* If there are no signals defined yet, this is the right time to do
     so.  */
  if (!signal_cave_pre_scan)
    {
      signal_option_changed= gstones_signal ("option.changed");
      signal_cave_pre_scan = gstones_signal ("cave.pre_scan");
      signal_cave_post_scan= gstones_signal ("cave.post_scan");
      signal_player_die    = gstones_signal ("player.die");
      signal_player_start  = gstones_signal ("player.start");
    }
  
  frame= object_find_object_by_name ("default:frame");
  g_return_val_if_fail (frame, NULL);

  cave= g_new0 (GStonesCave, 1);
  g_return_val_if_fail (cave, NULL);

  cave->animated_curtain= object_find_object_by_name ("default:curtain");
  /*  g_return_val_if_fail (cave->animated_curtain, NULL); */

  cave->is_intermission    = FALSE;

  cave->contexts           = g_hash_table_new (g_direct_hash, g_direct_equal);

  /* First we clear the hole cave.  */
  for (y= 0 ; y <= CAVE_MAX_HEIGHT+1; y++)
    for (x= 0 ; x <= CAVE_MAX_WIDTH+1; x++)
      {
	cave->entry[x][y].object = frame;
	cave->entry[x][y].scanned= FALSE;
      }

  return cave;
}


void
cave_free (GStonesCave *cave)
{
  if (cave)
    {
      g_free (cave->name);
      g_free (cave->next);
      
      g_free (cave->message);
      
      /* FIXME: objects must be freed.  */
      
      g_free (cave);
    }
}

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


gboolean
cave_set_entry (GStonesCave   *cave, 
		guint          x, 
		guint          y, 
		GStonesObject *object,
		guint          state)
{
  
  register GStonesCaveEntry *entry= &cave->entry[x][y];
  
  entry->object= object;
  entry->state = state;

  return TRUE;
}


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


void
cave_set_player (GStonesCave *cave, GStonesPlayer *player)
{
  g_return_if_fail (cave);
  
  cave->player= player;

  player_set_diamonds (cave->player, MAX (cave->diamonds_needed-
					  cave->diamonds_collected, 0));

  player_set_max_time (cave->player, cave->level_time);
  player_set_time     (cave->player, cave->timer, TRUE);
}


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


void
cave_player_die (GStonesCave *cave)
{
  cave_emit_signal (cave, signal_player_die);
}


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


void
cave_iterate (GStonesCave *cave,
	      gint         x_direction,
	      gint         y_direction,
	      gboolean     push)
{
  guint x;
  guint y;

  g_return_if_fail (cave != NULL);
  
  cave->frame++;

  if (cave->flags & CAVE_PAUSING)
    return;

  /* Decrement timer.  */
  if ((cave->timer > 0) && (cave->flags & CAVE_PLAYER_EXISTS))
    {
      cave->timer= cave->timer-cave->frame_rate;
      if (cave->timer <= 0)
	{
	  cave_player_die (cave);
	  cave->timer= 0;
	}

      player_set_time (cave->player, cave->timer, TRUE);
    }
  
  cave->player_x_direction= x_direction;
  cave->player_y_direction= y_direction;
  cave->player_push       = push;

  cave_emit_signal (cave, signal_cave_pre_scan);

  x=cave->player_x;
  y=cave->player_y;
  if (cave->entry[x][y].object->description->scan_function)
    {
      cave->entry[x][y].object->description->scan_function 
        (cave, x, y, cave_get_object_context (cave, cave->entry[x][y].object));
      cave->entry[x][y].scanned=TRUE;
    }

  /* Now we iterate through the whole cave.  */
  for (y= 1 ; y <= CAVE_MAX_HEIGHT; y++)
    for (x= 1 ; x<= CAVE_MAX_WIDTH ; x++)
      if (!cave->entry[x][y].scanned && 
	  (cave->entry[x][y].object->description->scan_function))
	cave->entry[x][y].object->description->scan_function 
	  (cave, x, y, cave_get_object_context (cave, cave->entry[x][y].object));
  
  /* Remove the scanned flag on every entry.  */
  for (y= 1 ; y <= CAVE_MAX_HEIGHT; y++)
    for (x= 1 ; x <= CAVE_MAX_WIDTH; x++)
      cave->entry[x][y].scanned= FALSE;

  cave_emit_signal (cave, signal_cave_post_scan);
}

void
cave_toggle_pause_mode (GStonesCave *cave)
{
  cave->flags^= CAVE_PAUSING;
}



/* The following function starts the cave by replacing the entrance
   with our gnome.  */

void
cave_start (GStonesCave *cave)
{
  /* We should only start, if the cave it still in the intro state,
     that means: ~PLAYER_EXISTS and ~FINISHED.  */

  if (!(cave->flags & CAVE_PLAYER_EXISTS) && !(cave->flags & CAVE_FINISHED))
    {
      cave_emit_signal (cave, signal_player_start);

      cave->flags|= CAVE_PLAYER_EXISTS;
    }
}



/*****************************************************************************/
/* Sends a signal to every registered object.  */


typedef struct _EmitData EmitData;

struct _EmitData
{
  GStonesCave   *cave;
  GStonesSignal  signal;
};


static void
cave_emit_signal0 (GStonesObject     *object, 
		   GStonesObjContext *context, 
		   EmitData *emit)
{
  if (object->description->signal_function)
    object->description->signal_function (emit->cave, emit->signal, context);
}


void
cave_emit_signal (GStonesCave *cave, GStonesSignal signal)
{
  EmitData emit;
  
  g_return_if_fail (cave);

  emit.cave  = cave;
  emit.signal= signal;

  g_hash_table_foreach (cave->contexts, (GHFunc) cave_emit_signal0, &emit);  
}


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


guint
cave_time_to_frames (GStonesCave *cave, gdouble time)
{
  return (time*1000.0)/cave->frame_rate;
}


static gint
cave_get_image_index (GStonesCave *cave, guint x, guint y)
{
  register GStonesObject *object= cave->entry[x][y].object;

  /* Determine the image index, that belongs to the objects state.  */
  if (object->description->animation_function)
    return object->description->animation_function 
      (cave, x, y, cave_get_object_context (cave, object));
  else
    return OBJECT_DEFAULT_IMAGE;
}


GdkPixmap *
cave_get_image (GStonesCave *cave, guint x, guint y)
{
  g_return_val_if_fail (cave != NULL, NULL);
  
  /* Return the right GdkPixmap belonging to index 'idx'.  */
  return object_get_image (cave->entry[x][y].object, 
			   cave_get_image_index (cave, x, y));
}


GdkPixbuf *
cave_get_pixbuf_image (GStonesCave *cave, guint x, guint y)
{
  g_return_val_if_fail (cave != NULL, NULL);
  
  /* Return the right GdkPixmap belonging to index 'idx'.  */
  return object_get_pixbuf_image (cave->entry[x][y].object, 
				 cave_get_image_index (cave, x, y));
}



/* Local Variables: */
/* mode:c */
/* eval:(load-library "time-stamp") */
/* eval:(make-local-variable 'write-file-hooks) */
/* eval:(add-hook 'write-file-hooks 'time-stamp) */
/* eval:(setq time-stamp-format '(time-stamp-yyyy/mm/dd time-stamp-hh:mm:ss user-login-name)) */
/* End: */
