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

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

#include <glib.h>

#include "clutter-actor.h"
#include "clutter-behaviour.h"
#include "clutter-container.h"

#include "clutter-script.h"
#include "clutter-script-private.h"
#include "clutter-scriptable.h"

#include "clutter-debug.h"
#include "clutter-private.h"

GType
clutter_script_get_type_from_symbol (const gchar *symbol)
{
  static GModule *module = NULL;
  GTypeGetFunc func;
  GType gtype = G_TYPE_INVALID;

  if (!module)
    module = g_module_open (NULL, G_MODULE_BIND_LAZY);
  
  if (g_module_symbol (module, symbol, (gpointer)&func))
    gtype = func ();
  
  return gtype;
}

GType
clutter_script_get_type_from_class (const gchar *name)
{
  static GModule *module = NULL;
  GTypeGetFunc func;
  GString *symbol_name = g_string_new ("");
  char c, *symbol;
  int i;
  GType gtype = G_TYPE_INVALID;

  if (!module)
    module = g_module_open (NULL, 0);
  
  for (i = 0; name[i] != '\0'; i++)
    {
      c = name[i];
      /* skip if uppercase, first or previous is uppercase */
      if ((c == g_ascii_toupper (c) &&
           i > 0 && name[i-1] != g_ascii_toupper (name[i-1])) ||
          (i > 2 && name[i]   == g_ascii_toupper (name[i]) &&
           name[i-1] == g_ascii_toupper (name[i-1]) &&
           name[i-2] == g_ascii_toupper (name[i-2])))
        g_string_append_c (symbol_name, '_');
      g_string_append_c (symbol_name, g_ascii_tolower (c));
    }
  g_string_append (symbol_name, "_get_type");
  
  symbol = g_string_free (symbol_name, FALSE);

  if (g_module_symbol (module, symbol, (gpointer)&func))
    {
      CLUTTER_NOTE (SCRIPT, "Type function: %s", symbol);
      gtype = func ();
    }
  
  g_free (symbol);

  return gtype;
}

gboolean
clutter_script_enum_from_string (GType        type, 
                                 const gchar *string,
                                 gint        *enum_value)
{
  GEnumClass *eclass;
  GEnumValue *ev;
  gchar *endptr;
  gint value;
  gboolean retval = TRUE;
  
  g_return_val_if_fail (G_TYPE_IS_ENUM (type), 0);
  g_return_val_if_fail (string != NULL, 0);
  
  value = strtoul (string, &endptr, 0);
  if (endptr != string) /* parsed a number */
    *enum_value = value;
  else
    {
      eclass = g_type_class_ref (type);
      ev = g_enum_get_value_by_name (eclass, string);
      if (!ev)
	ev = g_enum_get_value_by_nick (eclass, string);

      if (ev)
	*enum_value = ev->value;
      else
        retval = FALSE;
      
      g_type_class_unref (eclass);
    }

  return retval;
}

gboolean
clutter_script_flags_from_string (GType        type, 
                                  const gchar *string,
                                  gint        *flags_value)
{
  GFlagsClass *fclass;
  gchar *endptr, *prevptr;
  guint i, j, ret, value;
  gchar *flagstr;
  GFlagsValue *fv;
  const gchar *flag;
  gunichar ch;
  gboolean eos;

  g_return_val_if_fail (G_TYPE_IS_FLAGS (type), 0);
  g_return_val_if_fail (string != 0, 0);

  ret = TRUE;
  
  value = strtoul (string, &endptr, 0);
  if (endptr != string) /* parsed a number */
    *flags_value = value;
  else
    {
      fclass = g_type_class_ref (type);

      flagstr = g_strdup (string);
      for (value = i = j = 0; ; i++)
	{
	  
	  eos = flagstr[i] == '\0';
	  
	  if (!eos && flagstr[i] != '|')
	    continue;
	  
	  flag = &flagstr[j];
	  endptr = &flagstr[i];
	  
	  if (!eos)
	    {
	      flagstr[i++] = '\0';
	      j = i;
	    }
	  
	  /* trim spaces */
	  for (;;)
	    {
	      ch = g_utf8_get_char (flag);
	      if (!g_unichar_isspace (ch))
		break;
	      flag = g_utf8_next_char (flag);
	    }
	  
	  while (endptr > flag)
	    {
	      prevptr = g_utf8_prev_char (endptr);
	      ch = g_utf8_get_char (prevptr);
	      if (!g_unichar_isspace (ch))
		break;
	      endptr = prevptr;
	    }
	  
	  if (endptr > flag)
	    {
	      *endptr = '\0';
	      fv = g_flags_get_value_by_name (fclass, flag);
	      
	      if (!fv)
		fv = g_flags_get_value_by_nick (fclass, flag);
	      
	      if (fv)
		value |= fv->value;
	      else
		{
		  ret = FALSE;
		  break;
		}
	    }
	  
	  if (eos)
	    {
	      *flags_value = value;
	      break;
	    }
	}
      
      g_free (flagstr);
      
      g_type_class_unref (fclass);
    }

  return ret;
}

static gboolean
parse_knot_from_array (JsonArray   *array,
                       ClutterKnot *knot)
{
  JsonNode *val;

  if (json_array_get_length (array) < 2)
    return FALSE;

  val = json_array_get_element (array, 0);
  if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE)
    knot->x = json_node_get_int (val);

  val = json_array_get_element (array, 1);
  if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE)
    knot->y = json_node_get_int (val);

  return TRUE;
}

static gboolean
parse_knot_from_object (JsonObject  *object,
                        ClutterKnot *knot)
{
  JsonNode *val;

  if (json_object_get_size (object) < 2)
    return FALSE;

  val = json_object_get_member (object, "x");
  if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE)
    knot->x = json_node_get_int (val);

  val = json_object_get_member (object, "y");
  if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE)
    knot->y = json_node_get_int (val);

  return TRUE;
}

gboolean
clutter_script_parse_knot (ClutterScript *script,
                           JsonNode      *node,
                           ClutterKnot   *knot)
{
  g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), FALSE);
  g_return_val_if_fail (node != NULL, FALSE);
  g_return_val_if_fail (knot != NULL, FALSE);

  switch (JSON_NODE_TYPE (node))
    {
    case JSON_NODE_ARRAY:
      return parse_knot_from_array (json_node_get_array (node), knot);

    case JSON_NODE_OBJECT:
      return parse_knot_from_object (json_node_get_object (node), knot);

    default:
      break;
    }

  return FALSE;
}

static gboolean
parse_geometry_from_array (JsonArray       *array,
                           ClutterGeometry *geometry)
{
  JsonNode *val;

  if (json_array_get_length (array) < 4)
    return FALSE;

  val = json_array_get_element (array, 0);
  if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE)
    geometry->x = json_node_get_int (val);

  val = json_array_get_element (array, 1);
  if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE)
    geometry->y = json_node_get_int (val);

  val = json_array_get_element (array, 2);
  if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE)
    geometry->width = json_node_get_int (val);

  val = json_array_get_element (array, 3);
  if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE)
    geometry->height = json_node_get_int (val);

  return TRUE;
}

static gboolean
parse_geometry_from_object (JsonObject      *object,
                            ClutterGeometry *geometry)
{
  JsonNode *val;

  if (json_object_get_size (object) < 4)
    return FALSE;

  val = json_object_get_member (object, "x");
  if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE)
    geometry->x = json_node_get_int (val);

  val = json_object_get_member (object, "y");
  if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE)
    geometry->y = json_node_get_int (val);

  val = json_object_get_member (object, "width");
  if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE)
    geometry->width = json_node_get_int (val);

  val = json_object_get_member (object, "height");
  if (JSON_NODE_TYPE (val) == JSON_NODE_VALUE)
    geometry->height = json_node_get_int (val);

  return TRUE;
}

gboolean
clutter_script_parse_geometry (ClutterScript   *script,
                               JsonNode        *node,
                               ClutterGeometry *geometry)
{
  g_return_val_if_fail (CLUTTER_IS_SCRIPT (script), FALSE);
  g_return_val_if_fail (node != NULL, FALSE);
  g_return_val_if_fail (geometry != NULL, FALSE);

  switch (JSON_NODE_TYPE (node))
    {
    case JSON_NODE_ARRAY:
      return parse_geometry_from_array (json_node_get_array (node), geometry);

    case JSON_NODE_OBJECT:
      return parse_geometry_from_object (json_node_get_object (node), geometry);

    default:
      break;
    }

  return FALSE;
}