/* Swfdec
 * Copyright (C) 2006-2008 Benjamin Otte <otte@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "swfdec_resource.h"
#include "swfdec_access.h"
#include "swfdec_as_object.h"
#include "swfdec_as_internal.h"
#include "swfdec_as_interpret.h"
#include "swfdec_as_strings.h"
#include "swfdec_character.h"
#include "swfdec_debug.h"
#include "swfdec_decoder.h"
#include "swfdec_image_decoder.h"
#include "swfdec_loader_internal.h"
#include "swfdec_movie_clip_loader.h"
#include "swfdec_player_internal.h"
#include "swfdec_sandbox.h"
#include "swfdec_script.h"
#include "swfdec_sprite.h"
#include "swfdec_stream_target.h"
#include "swfdec_swf_decoder.h"
#include "swfdec_utils.h"


static void swfdec_resource_stream_target_init (SwfdecStreamTargetInterface *iface);
G_DEFINE_TYPE_WITH_CODE (SwfdecResource, swfdec_resource, SWFDEC_TYPE_GC_OBJECT,
    G_IMPLEMENT_INTERFACE (SWFDEC_TYPE_STREAM_TARGET, swfdec_resource_stream_target_init))

/*** SWFDEC_STREAM_TARGET interface ***/

static gboolean 
swfdec_resource_is_root (SwfdecResource *resource)
{
  g_return_val_if_fail (SWFDEC_IS_RESOURCE (resource), FALSE);

  return
    resource == SWFDEC_MOVIE (SWFDEC_PLAYER (swfdec_gc_object_get_context (resource))->priv->roots->data)->resource;
}

static SwfdecPlayer *
swfdec_resource_stream_target_get_player (SwfdecStreamTarget *target)
{
  return SWFDEC_PLAYER (swfdec_gc_object_get_context (target));
}

static void
swfdec_resource_stream_target_image (SwfdecResource *instance)
{
  SwfdecPlayer *player = SWFDEC_PLAYER (swfdec_gc_object_get_context (instance));
  SwfdecSpriteMovie *movie = (SwfdecSpriteMovie *) SWFDEC_AS_VALUE_GET_MOVIE (instance->movie);

  if (!SWFDEC_IS_SPRITE_MOVIE (movie) || movie->sprite != NULL ||
      swfdec_movie_get_own_resource (SWFDEC_MOVIE (movie)) != instance)
    return;

  if (SWFDEC_IS_SWF_DECODER (instance->decoder)) {
    SwfdecSwfDecoder *dec = SWFDEC_SWF_DECODER (instance->decoder);
    SwfdecSandbox *old_sandbox;

    old_sandbox = instance->sandbox;
    instance->sandbox = swfdec_sandbox_get_for_url (player,
	swfdec_loader_get_url (instance->loader), instance->version,
	SWFDEC_SWF_DECODER (instance->decoder)->use_network);
    if (instance->sandbox) {
      movie->sprite = dec->main_sprite;
      g_assert (movie->sprite->parse_frame > 0);
      movie->n_frames = movie->sprite->n_frames;
      swfdec_movie_invalidate_last (SWFDEC_MOVIE (movie));
      swfdec_sandbox_use (instance->sandbox);
      swfdec_as_object_set_constructor_by_name (swfdec_as_relay_get_as_object (
	    SWFDEC_AS_RELAY (movie)), SWFDEC_AS_STR_MovieClip, NULL);
      swfdec_sandbox_unuse (instance->sandbox);
      if (swfdec_resource_is_root (instance)) {
	swfdec_player_start_ticking (player);
	swfdec_movie_initialize (SWFDEC_MOVIE (movie));
	swfdec_player_perform_actions (player);
      }
    } else {
      SWFDEC_FIXME ("cannot continue loading %s, invalid rights", 
	  swfdec_url_get_url (swfdec_loader_get_url (instance->loader)));
      swfdec_stream_set_target (SWFDEC_STREAM (instance->loader), NULL);
      instance->sandbox = old_sandbox;
      /* FIXME: anyting else on the movie we need to clear? */
    }
  } else {
    g_assert_not_reached ();
  }
}

/* NB: name must be GC'ed */
static void
swfdec_resource_emit_signal (SwfdecResource *resource, const char *name, gboolean progress, 
    SwfdecAsValue *args, guint n_args)
{
  SwfdecAsContext *cx;
  SwfdecMovie *movie;
  guint skip = progress ? 4 : 2;
  SwfdecAsValue vals[n_args + skip];

  if (resource->clip_loader == NULL)
    return;
  cx = swfdec_gc_object_get_context (resource->clip_loader);

  SWFDEC_AS_VALUE_SET_STRING (&vals[0], name);
  if (SWFDEC_AS_VALUE_IS_MOVIE (resource->movie)) {
    movie = SWFDEC_AS_VALUE_GET_MOVIE (resource->movie);
    if (movie == NULL) {
      SWFDEC_DEBUG ("no movie, not emitting signal");
      return;
    }
    if (name == SWFDEC_AS_STR_onLoadInit &&
	movie->as_value != SWFDEC_AS_VALUE_GET_VALUE (resource->movie)) {
      SWFDEC_INFO ("not emitting onLoadInit - the movie is different");
      return;
    }
    SWFDEC_AS_VALUE_SET_MOVIE (&vals[1], movie);
  } else {
    SWFDEC_AS_VALUE_SET_UNDEFINED (&vals[1]);
    movie = NULL;
  }
  if (progress) {
    SwfdecResource *res;
    
    if (SWFDEC_IS_MOVIE (movie))
      res = swfdec_movie_get_own_resource (SWFDEC_MOVIE (movie));
    else
      res = NULL;
    if (res && res->decoder) {
      SwfdecDecoder *dec = res->decoder;
      vals[2] = swfdec_as_value_from_integer (cx, dec->bytes_loaded);
      vals[3] = swfdec_as_value_from_integer (cx, dec->bytes_total);
    } else {
      vals[2] = swfdec_as_value_from_integer (cx, 0);
      vals[3] = swfdec_as_value_from_integer (cx, 0);
    }
  }
  if (n_args)
    memcpy (&vals[skip], args, sizeof (SwfdecAsValue) * n_args);
  /* FIXME: what's the correct sandbox here? */
  swfdec_sandbox_use (resource->clip_loader_sandbox);
  swfdec_as_relay_call (SWFDEC_AS_RELAY (resource->clip_loader), SWFDEC_AS_STR_broadcastMessage, 
      n_args + skip, vals, NULL);
  swfdec_sandbox_unuse (resource->clip_loader_sandbox);
}

static void
swfdec_resource_emit_error (SwfdecResource *resource, const char *message)
{
  SwfdecAsValue vals[2];

  SWFDEC_AS_VALUE_SET_STRING (&vals[0], message);
  vals[1] = swfdec_as_value_from_integer (swfdec_gc_object_get_context (resource), 0);

  swfdec_resource_emit_signal (resource, SWFDEC_AS_STR_onLoadError, FALSE, vals, 2);
}

static SwfdecSpriteMovie *
swfdec_resource_replace_movie (SwfdecSpriteMovie *movie, SwfdecResource *resource)
{
  /* can't use swfdec_movie_duplicate() here, we copy to same depth */
  SwfdecMovie *mov = SWFDEC_MOVIE (movie);
  SwfdecMovie *copy;
  
  copy = swfdec_movie_new (SWFDEC_PLAYER (swfdec_gc_object_get_context (movie)), 
      mov->depth, mov->parent, resource, NULL, mov->name);
  swfdec_movie_begin_update_matrix (copy);
  copy->matrix = mov->matrix;
  copy->modified = mov->modified;
  copy->xscale = mov->xscale;
  copy->yscale = mov->yscale;
  copy->rotation = mov->rotation;
  copy->lockroot = mov->lockroot;
  swfdec_movie_end_update_matrix (copy);
  /* FIXME: are events copied? If so, wouldn't that be a security issue? */
  swfdec_movie_set_static_properties (copy, &mov->original_transform,
      &mov->color_transform, mov->original_ratio, mov->clip_depth, 
      mov->blend_mode, NULL);
  swfdec_movie_remove (mov);
  return SWFDEC_SPRITE_MOVIE (copy);
}

static void
swfdec_resource_stream_target_open (SwfdecStreamTarget *target, SwfdecStream *stream)
{
  SwfdecLoader *loader = SWFDEC_LOADER (stream);
  SwfdecResource *instance = SWFDEC_RESOURCE (target);
  SwfdecAsObject *object;
  SwfdecMovie *movie;
  const char *query;

  g_assert (SWFDEC_AS_VALUE_IS_MOVIE (instance->movie));
  movie = SWFDEC_AS_VALUE_GET_MOVIE (instance->movie);
  if (movie == NULL) {
    SWFDEC_FIXME ("no movie, what now?");
    return;
  }
  object = swfdec_as_relay_get_as_object (SWFDEC_AS_RELAY (movie));
  query = swfdec_url_get_query (swfdec_loader_get_url (loader));
  if (query) {
    SWFDEC_INFO ("set url query movie variables: %s", query);
    swfdec_as_object_decode (object, query);
  }
  if (instance->variables) {
    SWFDEC_INFO ("set manual movie variables: %s", instance->variables);
    swfdec_as_object_decode (object, instance->variables);
  }
  swfdec_resource_emit_signal (instance, SWFDEC_AS_STR_onLoadStart, FALSE, NULL, 0);
  instance->state = SWFDEC_RESOURCE_OPENED;
}

static gboolean
swfdec_resource_stream_target_parse (SwfdecStreamTarget *target, SwfdecStream *stream)
{
  SwfdecLoader *loader = SWFDEC_LOADER (stream);
  SwfdecResource *resource = SWFDEC_RESOURCE (target);
  SwfdecBufferQueue *queue;
  SwfdecBuffer *buffer;
  SwfdecDecoder *dec = resource->decoder;
  SwfdecStatus status;
  guint parsed;

  queue = swfdec_stream_get_queue (stream);
  if (dec == NULL && swfdec_buffer_queue_get_offset (queue) == 0) {
    if (swfdec_buffer_queue_get_depth (queue) < SWFDEC_DECODER_DETECT_LENGTH)
      return FALSE;
    buffer = swfdec_buffer_queue_peek (queue, 4);
    dec = swfdec_decoder_new (buffer);
    swfdec_buffer_unref (buffer);
    if (dec == NULL) {
      SWFDEC_ERROR ("no decoder found for format");
    } else {
      glong total;
      resource->decoder = dec;
      g_signal_connect_swapped (dec, "missing-plugin", 
	  G_CALLBACK (swfdec_player_add_missing_plugin), swfdec_gc_object_get_context (resource));
      total = swfdec_loader_get_size (loader);
      if (total >= 0)
	dec->bytes_total = total;
    }
  }
  while (swfdec_buffer_queue_get_depth (queue)) {
    parsed = 0;
    status = 0;
    do {
      buffer = swfdec_buffer_queue_peek_buffer (queue);
      if (buffer == NULL)
	break;
      if (parsed + buffer->length <= 65536) {
	swfdec_buffer_unref (buffer);
	buffer = swfdec_buffer_queue_pull_buffer (queue);
      } else {
	swfdec_buffer_unref (buffer);
	buffer = swfdec_buffer_queue_pull (queue, 65536 - parsed);
      }
      parsed += buffer->length;
      if (dec) {
	status |= swfdec_decoder_parse (dec, buffer);
      } else {
	swfdec_buffer_unref (buffer);
      }
    } while (parsed < 65536 && (status & (SWFDEC_STATUS_ERROR | SWFDEC_STATUS_EOF)) == 0);
    if (status & SWFDEC_STATUS_ERROR) {
      SWFDEC_ERROR ("parsing error");
      swfdec_stream_set_target (SWFDEC_STREAM (loader), NULL);
      return FALSE;
    }
    if ((status & SWFDEC_STATUS_INIT)) {
      if (SWFDEC_IS_SWF_DECODER (dec))
	resource->version = SWFDEC_SWF_DECODER (dec)->version;
      if (swfdec_resource_is_root (resource)) {
	swfdec_player_initialize (SWFDEC_PLAYER (swfdec_gc_object_get_context (resource)),
	    dec->rate, dec->width, dec->height);
      }
    }
    if (status & SWFDEC_STATUS_IMAGE)
      swfdec_resource_stream_target_image (resource);
    swfdec_resource_emit_signal (resource, SWFDEC_AS_STR_onLoadProgress, TRUE, NULL, 0);
    if (status & SWFDEC_STATUS_EOF)
      return FALSE;
  }
  return FALSE;
}

static gboolean
swfdec_resource_abort_if_not_initialized (SwfdecResource *resource)
{
  if (resource->sandbox)
    return FALSE;

  swfdec_as_context_abort (swfdec_gc_object_get_context (resource),
      "This is not a Flash file");
  return TRUE;
}

static void
swfdec_resource_stream_target_close (SwfdecStreamTarget *target, SwfdecStream *stream)
{
  SwfdecResource *resource = SWFDEC_RESOURCE (target);
  SwfdecAsValue val;

  swfdec_resource_emit_signal (resource, SWFDEC_AS_STR_onLoadProgress, TRUE, NULL, 0);
  if (resource->decoder) {
    SwfdecDecoder *dec = resource->decoder;
    swfdec_decoder_eof (dec);
    if (dec->data_type != SWFDEC_LOADER_DATA_UNKNOWN)
      swfdec_loader_set_data_type (SWFDEC_LOADER (stream), dec->data_type);
  }

  val = swfdec_as_value_from_integer (swfdec_gc_object_get_context (resource), 0); /* FIXME */
  swfdec_resource_emit_signal (resource, SWFDEC_AS_STR_onLoadComplete, FALSE, &val, 1);
  resource->state = SWFDEC_RESOURCE_COMPLETE;
  if (swfdec_resource_abort_if_not_initialized (resource))
    return;

  if (SWFDEC_AS_VALUE_IS_MOVIE (resource->movie)) {
    SwfdecMovie *movie = SWFDEC_AS_VALUE_GET_MOVIE (resource->movie);
    if (movie)
      swfdec_actor_queue_script (SWFDEC_ACTOR (movie), SWFDEC_EVENT_LOAD);
  }
}

static void
swfdec_resource_stream_target_error (SwfdecStreamTarget *target, SwfdecStream *stream)
{
  SwfdecResource *resource = SWFDEC_RESOURCE (target);
  const char *message;

  switch (resource->state) {
    case SWFDEC_RESOURCE_REQUESTED:
      message = SWFDEC_AS_STR_URLNotFound;
      break;
    case SWFDEC_RESOURCE_OPENED:
      message = SWFDEC_AS_STR_LoadNeverCompleted;
      break;
    case SWFDEC_RESOURCE_NEW:
    case SWFDEC_RESOURCE_COMPLETE:
    case SWFDEC_RESOURCE_DONE:
    default:
      g_assert_not_reached ();
      message = SWFDEC_AS_STR_EMPTY;
      break;
  }
  swfdec_resource_emit_error (resource, message);
  swfdec_resource_abort_if_not_initialized (resource);
}

static void
swfdec_resource_stream_target_init (SwfdecStreamTargetInterface *iface)
{
  iface->get_player = swfdec_resource_stream_target_get_player;
  iface->open = swfdec_resource_stream_target_open;
  iface->parse = swfdec_resource_stream_target_parse;
  iface->error = swfdec_resource_stream_target_error;
  iface->close = swfdec_resource_stream_target_close;
}

static void
swfdec_resource_mark (SwfdecGcObject *object)
{
  SwfdecResource *resource = SWFDEC_RESOURCE (object);

  if (resource->clip_loader) {
    swfdec_gc_object_mark (resource->clip_loader);
    swfdec_gc_object_mark (resource->clip_loader_sandbox);
  }
  if (resource->sandbox)
    swfdec_gc_object_mark (resource->sandbox);
  if (resource->target)
    swfdec_gc_object_mark (resource->target);
  swfdec_as_value_mark (&resource->movie);

  SWFDEC_GC_OBJECT_CLASS (swfdec_resource_parent_class)->mark (object);
}

static void
swfdec_resource_dispose (GObject *object)
{
  SwfdecResource *resource = SWFDEC_RESOURCE (object);

  if (resource->loader) {
    swfdec_stream_set_target (SWFDEC_STREAM (resource->loader), NULL);
    g_object_unref (resource->loader);
    resource->loader = NULL;
  }
  if (resource->decoder) {
    g_signal_handlers_disconnect_by_func (resource->decoder,
	  swfdec_player_add_missing_plugin, swfdec_gc_object_get_context (resource));
    g_object_unref (resource->decoder);
    resource->decoder = NULL;
  }
  g_free (resource->variables);
  g_hash_table_destroy (resource->exports);
  g_hash_table_destroy (resource->export_names);

  G_OBJECT_CLASS (swfdec_resource_parent_class)->dispose (object);
}

static void
swfdec_resource_class_init (SwfdecResourceClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  SwfdecGcObjectClass *gc_class = SWFDEC_GC_OBJECT_CLASS (klass);

  object_class->dispose = swfdec_resource_dispose;

  gc_class->mark = swfdec_resource_mark;
}

static void
swfdec_resource_init (SwfdecResource *instance)
{
  instance->exports = g_hash_table_new_full (swfdec_str_case_hash, 
      swfdec_str_case_equal, g_free, g_object_unref);
  instance->export_names = g_hash_table_new_full (g_direct_hash, g_direct_equal, 
      g_object_unref, g_free);
}

static void
swfdec_resource_set_loader (SwfdecResource *resource, SwfdecLoader *loader)
{
  g_return_if_fail (SWFDEC_IS_RESOURCE (resource));
  g_return_if_fail (SWFDEC_IS_LOADER (loader));
  g_return_if_fail (resource->loader == NULL);

  resource->loader = g_object_ref (loader);
  swfdec_stream_set_target (SWFDEC_STREAM (resource->loader), SWFDEC_STREAM_TARGET (resource));
  resource->state = SWFDEC_RESOURCE_REQUESTED;
}

SwfdecResource *
swfdec_resource_new (SwfdecPlayer *player, SwfdecLoader *loader, const char *variables)
{
  SwfdecResource *resource;

  g_return_val_if_fail (SWFDEC_IS_PLAYER (player), NULL);
  g_return_val_if_fail (SWFDEC_IS_LOADER (loader), NULL);

  resource = g_object_new (SWFDEC_TYPE_RESOURCE, "context", player, NULL);
  resource->version = 8;
  resource->variables = g_strdup (variables);
  swfdec_resource_set_loader (resource, loader);

  return resource;
}

gpointer
swfdec_resource_get_export (SwfdecResource *instance, const char *name)
{
  g_return_val_if_fail (SWFDEC_IS_RESOURCE (instance), NULL);
  g_return_val_if_fail (name != NULL, NULL);

  return g_hash_table_lookup (instance->exports, name);
}

const char *
swfdec_resource_get_export_name (SwfdecResource *instance, SwfdecCharacter *character)
{
  g_return_val_if_fail (SWFDEC_IS_RESOURCE (instance), NULL);
  g_return_val_if_fail (SWFDEC_IS_CHARACTER (character), NULL);

  return g_hash_table_lookup (instance->export_names, character);
}

void
swfdec_resource_add_export (SwfdecResource *instance, SwfdecCharacter *character, const char *name)
{
  g_return_if_fail (SWFDEC_IS_RESOURCE (instance));
  g_return_if_fail (SWFDEC_IS_CHARACTER (character));
  g_return_if_fail (name != NULL);

  g_hash_table_insert (instance->exports, g_strdup (name), g_object_ref (character));
  g_hash_table_insert (instance->export_names, g_object_ref (character), g_strdup (name));
}

/*** RESOURC LOAD ***/

typedef struct _SwfdecResourceLoad SwfdecResourceLoad;
struct _SwfdecResourceLoad {
  SwfdecSandbox *		sandbox;
  SwfdecAsValue			target;
  char *			url;
  SwfdecBuffer *		buffer;
  SwfdecMovieClipLoader *	loader;
};

static void
swfdec_resource_load_free (gpointer loadp)
{
  SwfdecResourceLoad *load = loadp;

  g_free (load->url);
  if (load->buffer)
    swfdec_buffer_unref (load->buffer);
  g_slice_free (SwfdecResourceLoad, load);
}

static void
swfdec_resource_load_mark (gpointer loadp, gpointer playerp)
{
  SwfdecResourceLoad *load = loadp;

  swfdec_gc_object_mark (load->sandbox);
  if (load->loader)
    swfdec_gc_object_mark (load->loader);
  swfdec_as_value_mark (&load->target);
}

static gboolean
swfdec_resource_create_movie (SwfdecResource *resource, SwfdecResourceLoad *load)
{
  SwfdecPlayer *player;
  SwfdecSpriteMovie *movie;

  if (SWFDEC_AS_VALUE_IS_MOVIE (resource->movie))
    return TRUE;
  player = SWFDEC_PLAYER (swfdec_gc_object_get_context (resource));
  if (SWFDEC_AS_VALUE_IS_MOVIE (load->target)) {
    movie = (SwfdecSpriteMovie *) SWFDEC_AS_VALUE_GET_MOVIE (load->target);
    if (SWFDEC_IS_SPRITE_MOVIE (movie))
      movie = swfdec_resource_replace_movie (movie, resource);
    else
      movie = NULL;
  } else if (SWFDEC_AS_VALUE_IS_STRING (load->target)) {
    int level = swfdec_player_get_level (player, SWFDEC_AS_VALUE_GET_STRING (load->target), 7);
    if (level >= 0) {
      movie = swfdec_player_get_movie_at_level (player, level);
      if (movie)
	swfdec_movie_remove (SWFDEC_MOVIE (movie));
      movie = swfdec_player_create_movie_at_level (player, resource, level);
    } else {
      movie = NULL;
    }
  } else {
    /* we only set target to string or movie values */
    g_assert_not_reached ();
  }
  if (movie == NULL) {
    SWFDEC_WARNING ("target does not reference a movie, not loading %s", load->url);
    return FALSE;
  }
  /* FIXME: does this belong here? */
  SWFDEC_ACTOR (movie)->focusrect = SWFDEC_FLASH_YES;
  return TRUE;
}

static void
swfdec_resource_do_load (SwfdecPlayer *player, gboolean allowed, gpointer loadp)
{
  SwfdecResourceLoad *load = loadp;
  SwfdecResource *resource;
  SwfdecLoader *loader;
  
  resource = g_object_new (SWFDEC_TYPE_RESOURCE, "context", player, NULL);
  resource->version = 8;
  if (load->loader) {
    resource->clip_loader = load->loader;
    resource->clip_loader_sandbox = load->sandbox;
  }
  resource->sandbox = load->sandbox;
  if (!allowed) {
    SWFDEC_WARNING ("SECURITY: no access to %s from %s",
	load->url, swfdec_url_get_url (load->sandbox->url));
    /* FIXME: is replacing correct? */
    if (SWFDEC_AS_VALUE_IS_MOVIE (load->target)) {
      if (SWFDEC_AS_VALUE_IS_MOVIE (load->target) &&
	  SWFDEC_IS_SPRITE_MOVIE (SWFDEC_AS_VALUE_GET_MOVIE (load->target)))
	resource->movie = load->target;
      swfdec_resource_emit_error (resource, SWFDEC_AS_STR_IllegalRequest);
    }
    swfdec_player_unroot (player, load);
    return;
  }

  /* FIXME: load nonetheless, even if there's no movie? */
  if (swfdec_resource_create_movie (resource, load)) {
    loader = swfdec_player_load (player, load->url, load->buffer);
    swfdec_resource_set_loader (resource, loader);
    g_object_unref (loader);
  }
  swfdec_player_unroot (player, load);
}

static const SwfdecAccessMatrix swfdec_resource_matrix = {
  { SWFDEC_ACCESS_NO,  SWFDEC_ACCESS_NO,  SWFDEC_ACCESS_NO },
  { SWFDEC_ACCESS_NO,  SWFDEC_ACCESS_YES, SWFDEC_ACCESS_YES },
  { SWFDEC_ACCESS_YES, SWFDEC_ACCESS_NO,  SWFDEC_ACCESS_NO },
  { SWFDEC_ACCESS_YES, SWFDEC_ACCESS_NO,  SWFDEC_ACCESS_YES },
  { SWFDEC_ACCESS_YES, SWFDEC_ACCESS_NO,  SWFDEC_ACCESS_YES }
};

static void
swfdec_resource_load_request (gpointer loadp, gpointer playerp)
{
  SwfdecResourceLoad *load = loadp;
  SwfdecPlayer *player = playerp;

  /* empty URL means unload (yay!) */
  if (load->url[0] == '\0') {
    SwfdecSpriteMovie *movie;
      
    if (SWFDEC_AS_VALUE_IS_MOVIE (load->target)) {
      movie = (SwfdecSpriteMovie *) SWFDEC_AS_VALUE_GET_MOVIE (load->target);
    } else {
      movie = NULL;
    }
    if (SWFDEC_IS_SPRITE_MOVIE (movie)) {
      swfdec_resource_replace_movie (movie, SWFDEC_MOVIE (movie)->resource);
    } else {
      SWFDEC_DEBUG ("no movie, not unloading");
    }
    swfdec_player_unroot (player, load);
    return;
  }

  /* fscommand? */
  if (g_ascii_strncasecmp (load->url, "FSCommand:", 10) == 0) {
    char *command = load->url + 10;
    const char *target = swfdec_as_value_to_string (SWFDEC_AS_CONTEXT (player), load->target);
    if (SWFDEC_AS_VALUE_IS_MOVIE (load->target)) {
      SWFDEC_FIXME ("Adobe 9.0.124.0 and later don't emit fscommands here. "
	  "We just do for compatibility reasons with the testsuite.");
    }
    g_signal_emit_by_name (player, "fscommand", command, target);
    swfdec_player_unroot (player, load);
    return;
  }

  /* LAUNCH command (aka getURL) */
  if (SWFDEC_AS_VALUE_IS_STRING (load->target) && swfdec_player_get_level (player, 
	SWFDEC_AS_VALUE_GET_STRING (load->target), 7) < 0) {
    swfdec_player_launch (player, load->url, SWFDEC_AS_VALUE_GET_STRING (load->target), load->buffer);
    swfdec_player_unroot (player, load);
    return;
  }

  swfdec_player_allow_by_matrix (player, load->sandbox, load->url,
      swfdec_resource_matrix, swfdec_resource_do_load, load);
}

/* NB: must be called from a script */
/* FIXME: 5 arguments?! */
static void
swfdec_resource_load_internal (SwfdecPlayer *player, const SwfdecAsValue *val,
    const char *url, SwfdecBuffer *buffer, SwfdecMovieClipLoader *loader)
{
  SwfdecResourceLoad *load;

  g_assert (SWFDEC_AS_CONTEXT (player)->frame != NULL);
  load = g_slice_new (SwfdecResourceLoad);

  load->sandbox = swfdec_sandbox_get (player);
  load->url = g_strdup (url);
  load->target = *val;
  load->buffer = buffer;
  load->loader = loader;

  swfdec_player_root_full (player, load, swfdec_resource_load_mark, swfdec_resource_load_free);
  swfdec_player_request_resource (player, swfdec_resource_load_request, load, NULL);
}

gboolean
swfdec_resource_load_movie (SwfdecPlayer *player, const SwfdecAsValue *target, 
    const char *url, SwfdecBuffer *buffer, SwfdecMovieClipLoader *loader)
{
  SwfdecMovie *movie;
  SwfdecAsValue val;
  const char *s;

  g_return_val_if_fail (SWFDEC_IS_PLAYER (player), FALSE);
  g_return_val_if_fail (target != NULL, FALSE);
  g_return_val_if_fail (url != NULL, FALSE);
  g_return_val_if_fail (loader == NULL || SWFDEC_IS_MOVIE_CLIP_LOADER (loader), FALSE);

  if (SWFDEC_AS_VALUE_IS_MOVIE (*target)) {
    movie = SWFDEC_AS_VALUE_GET_MOVIE (*target);
    if (SWFDEC_IS_SPRITE_MOVIE (movie)) {
      swfdec_resource_load_internal (player, target, url, buffer, loader);
      return TRUE;
    }
  }

  if (loader) {
    if (SWFDEC_AS_VALUE_IS_NUMBER (*target)) {
      int i = swfdec_as_double_to_integer (SWFDEC_AS_VALUE_GET_NUMBER (*target));
      if (i < 0)
	return FALSE;
      SWFDEC_AS_VALUE_SET_STRING (&val, swfdec_as_context_give_string (
	    SWFDEC_AS_CONTEXT (player), g_strdup_printf ("_level%d", i)));
      swfdec_resource_load_internal (player, &val, url, buffer, loader);
      return TRUE;
    } else if (SWFDEC_AS_VALUE_IS_STRING (*target) || SWFDEC_AS_VALUE_IS_MOVIE (*target)) {
      s = swfdec_as_value_to_string (SWFDEC_AS_CONTEXT (player), *target);
    } else {
      SWFDEC_WARNING ("target does not reference a movie, not loading %s", url);
      return FALSE;
    }
  } else {
    s = swfdec_as_value_to_string (SWFDEC_AS_CONTEXT (player), *target);
  }
  if (swfdec_player_get_level (player, s, SWFDEC_AS_CONTEXT (player)->version) >= 0) {
    /* lowercase the string, so we can do case insensitive level lookups later on */
    char *tmp = g_ascii_strdown (s, -1);
    SWFDEC_AS_VALUE_SET_STRING (&val, swfdec_as_context_give_string (
	  SWFDEC_AS_CONTEXT (player), tmp));
    swfdec_resource_load_internal (player, &val, url, buffer, NULL);
    return TRUE;
  }
  movie = swfdec_player_get_movie_from_string (player, s);
  if (SWFDEC_IS_SPRITE_MOVIE (movie)) {
    SWFDEC_AS_VALUE_SET_MOVIE (&val, movie);
    swfdec_resource_load_internal (player, &val, url, buffer, loader);
    return TRUE;
  }
  SWFDEC_WARNING ("%s does not reference a movie, not loading %s", s, url);
  return FALSE;
}

void
swfdec_resource_load (SwfdecPlayer *player, const char *target, 
    const char *url, SwfdecBuffer *buffer)
{
  SwfdecAsValue val;

  g_return_if_fail (SWFDEC_IS_PLAYER (player));
  g_return_if_fail (target != NULL);
  g_return_if_fail (url != NULL);

  SWFDEC_AS_VALUE_SET_STRING (&val, target);
  swfdec_resource_load_internal (player, &val, url, buffer, NULL);
}

gboolean
swfdec_resource_emit_on_load_init (SwfdecResource *resource)
{
  g_return_val_if_fail (SWFDEC_IS_RESOURCE (resource), FALSE);

  if (resource->state != SWFDEC_RESOURCE_COMPLETE)
    return FALSE;

  if (SWFDEC_AS_VALUE_IS_MOVIE (resource->movie) && SWFDEC_IS_IMAGE_DECODER (resource->decoder)) {
    SwfdecImage *image = SWFDEC_IMAGE_DECODER (resource->decoder)->image;
    SwfdecMovie *movie = SWFDEC_AS_VALUE_GET_MOVIE (resource->movie);
    if (image && movie) {
      swfdec_movie_invalidate_next (movie);
      swfdec_movie_queue_update (movie, SWFDEC_MOVIE_INVALID_EXTENTS);
      movie->image = g_object_ref (image);
    }
  }
  swfdec_resource_emit_signal (resource, SWFDEC_AS_STR_onLoadInit, FALSE, NULL, 0);
  resource->state = SWFDEC_RESOURCE_DONE;
  /* free now unneeded resources */
  resource->clip_loader = NULL;
  resource->clip_loader_sandbox = NULL;
  return TRUE;
}
