/* vi: set ts=2 shiftwidth=2 expandtab:
 *
 * Copyright (C) 2003-2008  Simon Baldwin and Mark J. Tilford
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License
 * as published by the Free Software Foundation.
 *
 * 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
 */

/*
 * Module notes:
 *
 * o ...
 */

#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include "scare.h"
#include "scprotos.h"
#include "scgamest.h"


/* Assorted definitions and constants. */
static const sc_char NUL = '\0';


/*
 * res_has_sound()
 * res_has_graphics()
 *
 * Return TRUE if the game uses sound or graphics.
 */
sc_bool
res_has_sound (sc_gameref_t game)
{
  const sc_prop_setref_t bundle = gs_get_bundle (game);
  sc_vartype_t vt_key[2];
  sc_bool has_sound;
  assert (gs_is_game_valid (game));

  vt_key[0].string = "Globals";
  vt_key[1].string = "Sound";
  has_sound = prop_get_boolean (bundle, "B<-ss", vt_key);
  return has_sound;
}

sc_bool
res_has_graphics (sc_gameref_t game)
{
  const sc_prop_setref_t bundle = gs_get_bundle (game);
  sc_vartype_t vt_key[2];
  sc_bool has_graphics;
  assert (gs_is_game_valid (game));

  vt_key[0].string = "Globals";
  vt_key[1].string = "Graphics";
  has_graphics = prop_get_boolean (bundle, "B<-ss", vt_key);
  return has_graphics;
}


/*
 * res_set_resource()
 * res_clear_resource()
 * res_compare_resource()
 *
 * Convenience functions to set, clear, and compare resource fields.
 */
static void
res_set_resource (sc_resourceref_t resource, const sc_char *name,
                  sc_int offset, sc_int length)
{
  resource->name = name;
  resource->offset = offset;
  resource->length = length;
}

void
res_clear_resource (sc_resourceref_t resource)
{
  res_set_resource (resource, "", 0, 0);
}

sc_bool
res_compare_resource (sc_resourceref_t from, sc_resourceref_t with)
{
  return strcmp (from->name, with->name) == 0
         && from->offset == with->offset && from->length == with->length;
}


/*
 * res_handle_resource()
 *
 * General helper for handling graphics and sound resources.  Supplied with a
 * partial key to the node containing resources, it identifies what resource
 * is appropriate, and sets this as the requested resource in the game, for
 * later use on sync'ing, using the handler appropriate for the game version.
 *
 * The partial format is something like "sis" (the bit to follow I<- or S<-
 * in prop_get), and the partial key is guaranteed to contain at least
 * strlen(partial_format) elements.
 */
void
res_handle_resource (sc_gameref_t game,
                     const sc_char *partial_format,
                     const sc_vartype_t vt_partial[])
{
  const sc_prop_setref_t bundle = gs_get_bundle (game);
  sc_vartype_t vt_key[2], *vt_full;
  sc_int partial_length, resource_start_offset;
  sc_bool embedded;
  sc_char *format;
  assert (gs_is_game_valid (game));
  assert (partial_format && vt_partial);

  /*
   * Check for resources.  If this game doesn't use any, exit now to avoid the
   * overhead of pointless lookups and allocations.
   */
  if (!(res_has_sound (game) || res_has_graphics (game)))
    return;

  /*
   * Get the global offset for all resources.  For version 3.9 games this
   * should be zero.  For version 4.0 games, it's the start of resource data
   * in the TAF file where resources are embedded.
   */
  vt_key[0].string = "ResourceOffset";
  resource_start_offset = prop_get_integer (bundle, "I<-s", vt_key);

  /*
   * Get the flag that indicated embedded resources.  For version 3.9 games
   * this should be false.  If not set, offset and length are forced to zero
   * for interface functions.
   */
  vt_key[0].string = "Globals";
  vt_key[1].string = "Embedded";
  embedded = prop_get_boolean (bundle, "B<-ss", vt_key);

  /*
   * Allocate a format for use with properties calls, five characters longer
   * than the partial passed in.  Build a key one element larger than the
   * partial supplied, and copy over all supplied elements.
   */
  partial_length = strlen (partial_format);
  format = sc_malloc (partial_length + 5);

  vt_full = sc_malloc ((partial_length + 1) * sizeof (vt_partial[0]));
  memcpy (vt_full, vt_partial, partial_length * sizeof (vt_partial[0]));

  /* Search for sound resources, and offer if found. */
  if (res_has_sound (game))
    {
      const sc_char *soundfile;
      sc_int soundoffset, soundlen;

      /* Get soundfile property from the node supplied. */
      vt_full[partial_length].string = "SoundFile";
      strcpy (format, "S<-");
      strcat (format, partial_format);
      strcat (format, "s");
      soundfile = prop_get_string (bundle, format, vt_full);

      /* If a sound is defined, handle it. */
      if (!sc_strempty (soundfile))
        {
          if (embedded)
            {
              /* Retrieve offset and length. */
              vt_full[partial_length].string = "SoundOffset";
              strcpy (format, "I<-");
              strcat (format, partial_format);
              strcat (format, "s");
              soundoffset = prop_get_integer (bundle, format, vt_full)
                            + resource_start_offset;

              vt_full[partial_length].string = "SoundLen";
              strcpy (format, "I<-");
              strcat (format, partial_format);
              strcat (format, "s");
              soundlen = prop_get_integer (bundle, format, vt_full);
            }
          else
            {
              /* Coerce offset and length to zero. */
              soundoffset = 0;
              soundlen = 0;
            }

          /*
           * If the sound is the special "##", latch stop, otherwise note
           * details to play on sync.
           */
          if (!strcmp (soundfile, "##"))
            {
              game->stop_sound = TRUE;
              res_clear_resource (&game->requested_sound);
            }
          else
            {
              res_set_resource (&game->requested_sound,
                                soundfile, soundoffset, soundlen);
            }
        }
    }

  /* Now do the same thing for graphics resources. */
  if (res_has_graphics (game))
    {
      const sc_char *graphicfile;
      sc_int graphicoffset, graphiclen;

      /* Get graphicfile property from the node supplied. */
      vt_full[partial_length].string = "GraphicFile";
      strcpy (format, "S<-");
      strcat (format, partial_format);
      strcat (format, "s");
      graphicfile = prop_get_string (bundle, format, vt_full);

      /* If a graphic is defined, handle it. */
      if (!sc_strempty (graphicfile))
        {
          if (embedded)
            {
              /* Retrieve offset and length. */
              vt_full[partial_length].string = "GraphicOffset";
              strcpy (format, "I<-");
              strcat (format, partial_format);
              strcat (format, "s");
              graphicoffset = prop_get_integer (bundle, format, vt_full)
                              + resource_start_offset;

              vt_full[partial_length].string = "GraphicLen";
              strcpy (format, "I<-");
              strcat (format, partial_format);
              strcat (format, "s");
              graphiclen = prop_get_integer (bundle, format, vt_full);
            }
          else
            {
              /* Coerce offset and length to zero. */
              graphicoffset = 0;
              graphiclen = 0;
            }

          /* Graphics resource retrieved, note to show on sync. */
          res_set_resource (&game->requested_graphic,
                            graphicfile, graphicoffset, graphiclen);
        }
    }

  /* Free allocated memory. */
  sc_free (format);
  sc_free (vt_full);
}


/*
 * res_sync_resources()
 *
 * Bring resources into line with the game; called on undo, restart,
 * restore, and so on.
 */
void
res_sync_resources (sc_gameref_t game)
{
  assert (gs_is_game_valid (game));

  /* Deal with any latched sound stop first. */
  if (game->stop_sound)
    {
      if (game->sound_active)
        {
          if_update_sound ("", 0, 0, FALSE);
          game->sound_active = FALSE;

          res_clear_resource (&game->playing_sound);
        }
      game->stop_sound = FALSE;
    }

  /* Look for a change of sound, and pass to interface on change. */
  if (!res_compare_resource (&game->playing_sound,
                             &game->requested_sound))
    {
      const sc_char *name;
      sc_char *clean_name;
      sc_bool is_looping;

      /* If the sound name ends '##', this is a looping sound. */
      name = game->requested_sound.name;
      is_looping = !strcmp (name + strlen (name) - 2, "##");

      clean_name = sc_malloc (strlen (name) + 1);
      strcpy (clean_name, name);
      if (is_looping)
        clean_name[strlen (clean_name) - 2] = NUL;

      if_update_sound (clean_name,
                       game->requested_sound.offset,
                       game->requested_sound.length, is_looping);
      game->playing_sound = game->requested_sound;
      game->sound_active = TRUE;

      sc_free (clean_name);
    }

  /* Look for a change of graphic, and pass to interface on change. */
  if (!res_compare_resource (&game->displayed_graphic,
                             &game->requested_graphic))
    {
      if_update_graphic (game->requested_graphic.name,
                         game->requested_graphic.offset,
                         game->requested_graphic.length);
      game->displayed_graphic = game->requested_graphic;
    }
}


/*
 * res_cancel_resources()
 *
 * Turn off sound and graphics, and reset the game's tracking of resources in
 * use to match.  Called on game restart or restore.
 */
void
res_cancel_resources (sc_gameref_t game)
{
  assert (gs_is_game_valid (game));

  /* Request that everything stops and clears. */
  game->stop_sound = FALSE;
  res_clear_resource (&game->requested_sound);
  res_clear_resource (&game->requested_graphic);

  /* Synchronize to have the above take effect. */
  res_sync_resources (game);
}
