/*
 * Copyright (c) 2002, 2003, 2004 Jean-Yves Lefort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Jean-Yves Lefort nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "sg-parser.h"
#include "st-handler.h"
#include "st-handlers.h"
#include "st-settings.h"
#include "st-stream-bag.h"
#include "sg-util.h"
#include "st-category-store.h"
#include "st-stream-store.h"
#include "st-handler-field.h"
#include "st-util.h"

/*** type definitions ********************************************************/

enum {
  STATEMENT_HANDLER = 1,

  STATEMENT_HANDLER_CATEGORY,
  STATEMENT_HANDLER_CATEGORY_FLAGS,
  STATEMENT_HANDLER_CATEGORY_PARENT,
  STATEMENT_HANDLER_CATEGORY_LABEL,
  STATEMENT_HANDLER_CATEGORY_URL_POSTFIX,
  
  STATEMENT_HANDLER_CATEGORY_STREAM,			/* deprecated */
  STATEMENT_HANDLER_CATEGORY_STREAM_FIELDS,		/* deprecated */

  STATEMENT_HANDLER_CATEGORY_STREAMS,
  STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM,
  STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS,
  STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS_ARRAY,
  STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS_ARRAY_ARRAY
};

typedef struct
{
  SGParser		*parser;		/* the SGParser object */
  SGParserStatement	*statement;		/* the current statement */

  /* currently parsed handler */

  STHandler		*handler;
  STCategoryStore	*handler_categories;
  GHashTable		*handler_parents;	/* temporary hash used to store category parents */
  
  /* currently parsed category */

  STCategoryBag		*category_bag;
  char			*category_parent_name;
  STStreamStore		*category_streams;

  /* currently parsed stream */

  STStreamBag		*stream_bag;
  GSList		*stream_field_iter;
  STHandlerField	*stream_field;

  /* currently parsed value arrays */

  GValueArray		*value_array;
  GValueArray		*child_value_array;
} LoadInfo;
  
/*** constant definitions ****************************************************/

static SGParserDefinition cache_definitions[] = {
  {
    0,
    STATEMENT_HANDLER,
    "handler",		TRUE,		G_TYPE_STRING
  },
  {
    STATEMENT_HANDLER,
    STATEMENT_HANDLER_CATEGORY,
    "category",		TRUE,		G_TYPE_STRING
  },
  {
    STATEMENT_HANDLER_CATEGORY,
    STATEMENT_HANDLER_CATEGORY_FLAGS,
    "flags",		FALSE,		G_TYPE_UINT
  },
  {
    STATEMENT_HANDLER_CATEGORY,
    STATEMENT_HANDLER_CATEGORY_PARENT,
    "parent",		FALSE,		G_TYPE_STRING
  },
  {
    STATEMENT_HANDLER_CATEGORY,
    STATEMENT_HANDLER_CATEGORY_LABEL,
    "label",		FALSE,		G_TYPE_STRING
  },
  {
    STATEMENT_HANDLER_CATEGORY,
    STATEMENT_HANDLER_CATEGORY_URL_POSTFIX,
    "url_postfix",	FALSE,		G_TYPE_STRING
  },
  {				/* deprecated */
    STATEMENT_HANDLER_CATEGORY,
    STATEMENT_HANDLER_CATEGORY_STREAM,
    "stream",		TRUE,		G_TYPE_STRING
  },
  {				/* deprecated */
    STATEMENT_HANDLER_CATEGORY_STREAM,
    STATEMENT_HANDLER_CATEGORY_STREAM_FIELDS,
    "fields",		TRUE,		G_TYPE_NONE
  },
  {
    STATEMENT_HANDLER_CATEGORY,
    STATEMENT_HANDLER_CATEGORY_STREAMS,
    "streams",		TRUE,		G_TYPE_NONE
  },
  {
    STATEMENT_HANDLER_CATEGORY_STREAMS,
    STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM,
    "stream",		TRUE,		G_TYPE_STRING
  },
  {
    STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM,
    STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS,
    "fields",		TRUE,		G_TYPE_NONE
  },
  {
    STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS,
    STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS_ARRAY,
    "array",		TRUE,		G_TYPE_NONE
  },
  {
    STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS_ARRAY,
    STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS_ARRAY_ARRAY,
    "array",		TRUE,		G_TYPE_NONE
  },
  { 0, 0, NULL, 0, 0 }
};

/*** function declarations ***************************************************/

static void st_oldcache_load_handler_begin		(LoadInfo *info);
static void st_oldcache_load_handler_end		(LoadInfo *info);

static void st_oldcache_load_category_begin		(LoadInfo *info);
static void st_oldcache_load_category_end		(LoadInfo *info);

static void st_oldcache_load_stream_begin		(LoadInfo *info);
static void st_oldcache_load_stream_end			(LoadInfo *info);

static void st_oldcache_load_pop_field			(LoadInfo *info);

static void st_oldcache_load_stream_field		(LoadInfo *info);

static void st_oldcache_load_value_array_begin		(LoadInfo *info);
static void st_oldcache_load_value_array_value		(LoadInfo *info);
static void st_oldcache_load_value_array_end		(LoadInfo *info);

static void st_oldcache_load_child_value_array_begin 	(LoadInfo *info);
static void st_oldcache_load_child_value_array_value 	(LoadInfo *info);
static void st_oldcache_load_child_value_array_end   	(LoadInfo *info);

/*** implementation **********************************************************/

gboolean
st_oldcache_load (const char *filename, GError **err)
{
  LoadInfo info = { NULL, };
  
  g_return_val_if_fail(filename != NULL, FALSE);

  info.parser = sg_parser_new(filename, err);
  if (! info.parser)
    return FALSE;

  info.parser->scanner->msg_handler = st_parser_msg_cb;
  sg_parser_definev(info.parser, cache_definitions);

  while ((info.statement = sg_parser_get_statement(info.parser)))
    {
      if (SG_PARSER_STATEMENT_IS_END(info.statement))
	{
	  switch (SG_PARSER_SCOPE(info.parser))
	    {
	    case STATEMENT_HANDLER:
	      st_oldcache_load_handler_end(&info);
	      break;
	      
	    case STATEMENT_HANDLER_CATEGORY:
	      st_oldcache_load_category_end(&info);
	      break;

	    case STATEMENT_HANDLER_CATEGORY_STREAM:
	    case STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM:
	      st_oldcache_load_stream_end(&info);
	      break;

	    case STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS_ARRAY:
	      st_oldcache_load_value_array_end(&info);
	      break;

	    case STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS_ARRAY_ARRAY:
	      st_oldcache_load_child_value_array_end(&info);
	      break;
	    }
	}
      
      if (info.statement->definition)
	{
	  switch (info.statement->definition->id)
	    {
	    case STATEMENT_HANDLER:
	      st_oldcache_load_handler_begin(&info);
	      break;
	      
	    case STATEMENT_HANDLER_CATEGORY:
	      st_oldcache_load_category_begin(&info);
	      break;
	      
	    case STATEMENT_HANDLER_CATEGORY_FLAGS:
	      if (info.handler && info.category_bag)
		info.category_bag->flags = g_value_get_uint(&info.statement->value);
	      break;

	    case STATEMENT_HANDLER_CATEGORY_PARENT:
	      if (info.handler && info.category_bag)
		info.category_parent_name = g_value_dup_string(&info.statement->value);
	      break;
	      
	    case STATEMENT_HANDLER_CATEGORY_LABEL:
	      if (info.handler && info.category_bag)
		ST_CATEGORY(info.category_bag)->label = g_value_dup_string(&info.statement->value);
	      break;
	      
	    case STATEMENT_HANDLER_CATEGORY_URL_POSTFIX:
	      if (info.handler && info.category_bag)
		ST_CATEGORY(info.category_bag)->url_postfix = g_value_dup_string(&info.statement->value);
	      break;
	      
	    case STATEMENT_HANDLER_CATEGORY_STREAM:		/* deprecated */
	    case STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM:
	      st_oldcache_load_stream_begin(&info);
	      break;
	      
	    case STATEMENT_HANDLER_CATEGORY_STREAM_FIELDS:	/* deprecated */
	    case STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS:
	      if (info.handler && info.stream_bag)
		info.stream_field_iter = st_handler_get_fields(info.handler);
	      break;

	    case STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS_ARRAY:
	      st_oldcache_load_value_array_begin(&info);
	      break;

	    case STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS_ARRAY_ARRAY:
	      st_oldcache_load_child_value_array_begin(&info);
	      break;
	    }
	}
      else if (G_IS_VALUE(&info.statement->value))
	{
	  switch (SG_PARSER_SCOPE(info.parser))
	    {
	    case STATEMENT_HANDLER_CATEGORY_STREAM_FIELDS:
	    case STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS:
	      st_oldcache_load_stream_field(&info);
	      break;

	    case STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS_ARRAY:
	      st_oldcache_load_value_array_value(&info);
	      break;

	    case STATEMENT_HANDLER_CATEGORY_STREAMS_STREAM_FIELDS_ARRAY_ARRAY:
	      st_oldcache_load_child_value_array_value(&info);
	      break;

	    default:
	      g_scanner_warn(info.parser->scanner, _("unexpected value"));
	    }
	}

      sg_parser_statement_evaluate(info.statement);
      sg_parser_statement_free(info.statement);
    }

  sg_parser_free(info.parser);
  
  return TRUE;
}

static void
st_oldcache_load_handler_begin (LoadInfo *info)
{
  const char *name;

  name = g_value_get_string(&info->statement->value);

  if (! strcmp(name, "live365")
      || ! strcmp(name, "local")
      || ! strcmp(name, "xiph"))
    {
      g_scanner_warn(info->parser->scanner, _("obsolete cache format, ignoring handler"));
      return;
    }

  info->handler = st_handlers_find_by_name(name);

  if (info->handler)
    {
      info->handler_parents = g_hash_table_new(g_str_hash, g_str_equal);
      info->handler_categories = st_handler_get_categories(info->handler);
    }
  else
    g_scanner_warn(info->parser->scanner, _("%s: no such handler"), name);
}

static void
st_oldcache_load_handler_end (LoadInfo *info)
{
  if (! info->handler)
    return;

  g_hash_table_destroy(info->handler_parents);
  g_object_unref(info->handler_categories);
  
  info->handler = NULL;
  info->handler_categories = NULL;
  info->handler_parents = NULL;
}

static void
st_oldcache_load_category_begin (LoadInfo *info)
{
  const char *category_name;

  if (! info->handler)
    return;

  category_name = g_value_get_string(&info->statement->value);
  if (ST_CATEGORY_BAG_NAME_IS_STOCK(category_name))
    {
      info->category_bag = st_handler_get_stock_category(info->handler, category_name);
      if (! info->category_bag)
	g_scanner_warn(info->parser->scanner, _("%s: no such stock category"), category_name);
    }
  else
    {
      info->category_bag = st_category_bag_new(info->handler);
      ST_CATEGORY(info->category_bag)->name = g_strdup(category_name);
    }
}

static void
st_oldcache_load_category_end (LoadInfo *info)
{
  if (! (info->handler && info->category_bag))
    return;

  if (! ST_CATEGORY_BAG_IS_STOCK(info->category_bag))
    {
      STCategoryBag *parent = NULL;

      if (info->category_parent_name)
	{
	  parent = g_hash_table_lookup(info->handler_parents,
					       info->category_parent_name);
	  if (! parent)
	    g_scanner_warn(info->parser->scanner,
			   _("parent category \"%s\" not found"),
			   info->category_parent_name);
	}

      g_hash_table_insert(info->handler_parents,
			  ST_CATEGORY(info->category_bag)->name,
			  info->category_bag);

      st_category_store_append(info->handler_categories,
			       info->category_bag,
			       parent);
      g_object_unref(info->category_bag);
    }
  
  if (info->category_streams)
    {
      st_handler_set_streams(info->handler,
			     ST_CATEGORY(info->category_bag)->name,
			     info->category_streams);
      g_object_unref(info->category_streams);
    }
  
  g_free(info->category_parent_name);

  info->category_bag = NULL;
  info->category_parent_name = NULL;
  info->category_streams = NULL;
}

static void
st_oldcache_load_stream_begin (LoadInfo *info)
{
  if (! (info->handler && info->category_bag))
    return;

  info->stream_bag = st_stream_bag_new(info->handler);
  ST_STREAM(info->stream_bag)->name = g_value_dup_string(&info->statement->value);
}

static void
st_oldcache_load_stream_end (LoadInfo *info)
{
  if (! (info->handler && info->stream_bag))
    return;

  if (! info->category_streams)
    {
      info->category_streams = st_stream_store_new(info->handler);
      st_stream_store_touch(info->category_streams);
    }

  st_stream_store_append(info->category_streams, info->stream_bag);
  g_object_unref(info->stream_bag);

  info->stream_bag = NULL;
  info->stream_field_iter = NULL;
  info->stream_field = NULL;
}

static void
st_oldcache_load_pop_field (LoadInfo *info)
{
  while (info->stream_field_iter)
    {
      info->stream_field = info->stream_field_iter->data;
      info->stream_field_iter = info->stream_field_iter->next;

      if (! ST_HANDLER_FIELD_IS_VOLATILE(info->stream_field))
	return;
    }

  info->stream_field = NULL;	/* not found */
  g_scanner_warn(info->parser->scanner, _("too many fields"));
}

static void
st_oldcache_load_stream_field (LoadInfo *info)
{
  if (! (info->handler && info->stream_bag))
    return;

  st_oldcache_load_pop_field(info);
  if (info->stream_field)
    {
      if (st_handler_field_get_type(info->stream_field) == GDK_TYPE_PIXBUF && G_VALUE_HOLDS_STRING(&info->statement->value))
	{
	  const char *filename;

	  filename = g_value_get_string(&info->statement->value);
	  if (filename)
	    {
	      GdkPixbuf *pixbuf;
	      GError *err = NULL;
	      
	      pixbuf = gdk_pixbuf_new_from_file(filename, &err);
	      if (pixbuf)
		{
		  GValue value = { 0, };
		  
		  g_value_init(&value, GDK_TYPE_PIXBUF);
		  g_value_set_object(&value, pixbuf);
		  g_object_unref(pixbuf);

		  st_stream_bag_set_field(info->stream_bag, info->stream_field, &value);
		  g_value_unset(&value);
		}
	      else
		{
		  g_scanner_warn(info->parser->scanner, "%s", err->message);
		  g_error_free(err);
		}
	    }
	}
      else if (G_VALUE_HOLDS(&info->statement->value, st_handler_field_get_type(info->stream_field)))
	st_stream_bag_set_field(info->stream_bag, info->stream_field, &info->statement->value);
      else
	g_scanner_warn(info->parser->scanner, _("wrong field type"));
    }
}

static void
st_oldcache_load_value_array_begin (LoadInfo *info)
{
  if (! (info->handler && info->stream_bag))
    return;

  st_oldcache_load_pop_field(info);
  if (info->stream_field)
    {
      if (st_handler_field_get_type(info->stream_field) == G_TYPE_VALUE_ARRAY)
	info->value_array = g_value_array_new(0);
      else
	g_scanner_warn(info->parser->scanner, _("wrong field type"));
    }
}

static void
st_oldcache_load_value_array_value (LoadInfo *info)
{
  if (! (info->handler && info->value_array))
    return;

  g_value_array_append(info->value_array, &info->statement->value);
}

static void
st_oldcache_load_value_array_end (LoadInfo *info)
{
  GValue value = { 0, };
  
  if (! (info->handler && info->value_array))
    return;

  g_value_init(&value, G_TYPE_VALUE_ARRAY);
  g_value_take_boxed(&value, info->value_array);

  st_stream_bag_set_field(info->stream_bag, info->stream_field, &value);
  g_value_unset(&value);

  info->value_array = NULL;
}

static void
st_oldcache_load_child_value_array_begin (LoadInfo *info)
{
  if (! (info->handler && info->value_array))
    return;

  info->child_value_array = g_value_array_new(0);
}

static void
st_oldcache_load_child_value_array_value (LoadInfo *info)
{
  if (! (info->handler && info->child_value_array))
    return;

  g_value_array_append(info->child_value_array, &info->statement->value);
}

static void
st_oldcache_load_child_value_array_end (LoadInfo *info)
{
  GValue value = { 0, };
  
  if (! (info->handler && info->child_value_array))
    return;

  g_value_init(&value, G_TYPE_VALUE_ARRAY);
  g_value_take_boxed(&value, info->child_value_array);

  st_stream_bag_set_field(info->stream_bag, info->stream_field, &value);
  g_value_array_append(info->value_array, &value);
  g_value_unset(&value);

  info->child_value_array = NULL;
}
