#include <string.h>
#include "egg-markup.h"
#include "egg-toolbar.h"
#include "intl.h"

typedef enum {
  STATE_START,
  STATE_ROOT,
  STATE_MENU,
  STATE_TOOLBAR,
  STATE_POPUPS,
  STATE_ITEM,
  STATE_END
} ParseState;

typedef struct _ParseContext ParseContext;
struct _ParseContext
{
  /* parser state information */
  ParseState state;
  ParseState prev_state;

  /* function to call when we finish off a toplevel widget */
  EggWidgetFunc widget_func;
  gpointer user_data;

  /* GdkAccelGroup to use for menus */
  GtkAccelGroup *accel_group;

  /* info about the widget we are constructing at the moment */
  GtkWidget *top;
  gchar *type;
  gchar *name;

  /* the current container we are working on */
  GtkWidget *current;

  /* the ActionGroup used to create menu items */
  EggActionGroup *action_group;
};

static void
start_element_handler (GMarkupParseContext *context,
		       const gchar         *element_name,
		       const gchar        **attribute_names,
		       const gchar        **attribute_values,
		       gpointer             user_data,
		       GError             **error)
{
  ParseContext *ctx = user_data;
  gboolean raise_error = TRUE;
  gchar *error_attr = NULL;

  switch (element_name[0])
    {
    case 'R':
      if (ctx->state == STATE_START && !strcmp(element_name, "Root"))
	{
	  ctx->state = STATE_ROOT;
	  raise_error = FALSE;
	}
      break;
    case 'm':
      if (ctx->state == STATE_ROOT && !strcmp(element_name, "menu"))
	{
	  ctx->state = STATE_MENU;

	  ctx->top = ctx->current = gtk_menu_bar_new();
	  ctx->type = "menu";
	  ctx->name = NULL;

	  raise_error = FALSE;
	}
      else if (ctx->state == STATE_MENU && !strcmp(element_name, "menuitem"))
	{
	  gint i;
	  const gchar *action_name = NULL;
	  EggAction *action = NULL;

	  ctx->state = STATE_ITEM;

	  for (i = 0; attribute_names[i] != NULL; i++)
	    {
	      if (!strcmp(attribute_names[i], "verb"))
		{
		  action_name = attribute_values[i];
		  action = egg_action_group_get_action(ctx->action_group,
							 action_name);
		}
	    }

	  if (action)
	    {
	      GtkWidget *widget = egg_action_create_menu_item(action);

	      gtk_container_add(GTK_CONTAINER(ctx->current), widget);
	      gtk_widget_show(widget);
	    }
	  else
	    {
	      g_warning("could not find action '%s'",
			action_name ? action_name : "(null)");
	    }

	  raise_error = FALSE;
	}
      break;
    case 'd':
      if (ctx->state == STATE_ROOT && !strcmp(element_name, "dockitem"))
	{
	  gint i;

	  ctx->state = STATE_TOOLBAR;

	  ctx->top = ctx->current = egg_toolbar_new();
	  ctx->type = "toolbar";
	  for (i = 0; attribute_names[i] != NULL; i++)
	    {
	      if (!strcmp(attribute_names[i], "name"))
		ctx->name = g_strdup(attribute_values[i]);
	    }

	  raise_error = FALSE;
	}
      break;
    case 'p':
      if (ctx->state == STATE_ROOT && !strcmp(element_name, "popups"))
	{
	  ctx->state = STATE_POPUPS;
	  raise_error = FALSE;
	}
      else if (ctx->state == STATE_POPUPS &&!strcmp(element_name, "popup"))
	{
	  gint i;

	  ctx->state = STATE_MENU;

	  ctx->top = ctx->current = gtk_menu_new();
	  gtk_menu_set_accel_group(GTK_MENU(ctx->current), ctx->accel_group);
	  ctx->type = "popup";
	  for (i = 0; attribute_names[i] != NULL; i++)
	    {
	      if (!strcmp(attribute_names[i], "name"))
		{
		  ctx->name = g_strdup(attribute_values[i]);
		  gtk_menu_set_title(GTK_MENU(ctx->current), ctx->name);
		}
	      else if (!strcmp(attribute_names[i], "tearoff"))
		{
		  GtkWidget *tearoff = gtk_tearoff_menu_item_new();

		  gtk_container_add(GTK_CONTAINER(ctx->current), tearoff);
		  gtk_widget_show(tearoff);
		}
	    }

	  raise_error = FALSE;
	}
      break;
    case 's':
      if (ctx->state == STATE_MENU && !strcmp(element_name, "submenu"))
	{
	  gint i;
	  const gchar *label = NULL;
	  gboolean tearoff = FALSE;
	  GtkWidget *widget;

	  ctx->state = STATE_MENU;
	  for (i = 0; attribute_names[i] != NULL; i++)
	    {
	      if (!strcmp(attribute_names[i], "label"))
		label = g_strdup(attribute_values[i]);
	      else if (!strcmp(attribute_names[i], "tearoff"))
		tearoff = TRUE;
	    }
	  widget = gtk_menu_item_new_with_label(label);
	  gtk_label_set_use_underline(GTK_LABEL(GTK_BIN(widget)->child), TRUE);
	  gtk_container_add(GTK_CONTAINER(ctx->current), widget);
	  gtk_widget_show(widget);

	  ctx->current = gtk_menu_new();
	  gtk_menu_set_accel_group(GTK_MENU(ctx->current), ctx->accel_group);
	  gtk_menu_set_title(GTK_MENU(ctx->current), label);
	  gtk_menu_item_set_submenu(GTK_MENU_ITEM(widget), ctx->current);

	  if (tearoff)
	    {
	      GtkWidget *tearoff = gtk_tearoff_menu_item_new();

	      gtk_container_add(GTK_CONTAINER(ctx->current), tearoff);
	      gtk_widget_show(tearoff);
	    }
	  
	  raise_error = FALSE;
	}
      else if ((ctx->state == STATE_MENU || ctx->state == STATE_TOOLBAR) &&
	       !strcmp(element_name, "separator"))
	{
	  ctx->state = STATE_ITEM;

	  if (GTK_IS_MENU_SHELL(ctx->current))
	    {
	      GtkWidget *widget = gtk_separator_menu_item_new();
	      gtk_container_add(GTK_CONTAINER(ctx->current), widget);
	      gtk_widget_show(widget);
	    }
	  else /* toolbar */
	    {
	      EggToolItem *item = egg_tool_item_new ();
	      egg_toolbar_insert_tool_item (EGG_TOOLBAR(ctx->current), item, -1);
	      gtk_widget_show (GTK_WIDGET (item));
	    }

	  raise_error = FALSE;
	}
      break;
    case 't':
      if (ctx->state == STATE_TOOLBAR && !strcmp(element_name, "toolitem"))
	{
	  gint i;
	  const gchar *action_name = NULL;
	  EggAction *action = NULL;

	  ctx->state = STATE_ITEM;

	  for (i = 0; attribute_names[i] != NULL; i++)
	    {
	      if (!strcmp(attribute_names[i], "verb"))
		{
		  action_name = attribute_values[i];
		  action = egg_action_group_get_action(ctx->action_group,
							 action_name);
		}
	    }

	  if (action)
	    {
	      GtkWidget *widget = egg_action_create_tool_item (action);

	      gtk_container_add (GTK_CONTAINER (ctx->current), widget);
	    }
	  else
	    {
	      g_warning("could not find action '%s'",
			action_name ? action_name : "(null)");
	    }

	  raise_error = FALSE;
	}
      break;
    };

  if (raise_error)
    {
      gint line_number, char_number;

      g_markup_parse_context_get_position (context,
                                           &line_number, &char_number);

      if (error_attr)
	g_set_error (error,
		     G_MARKUP_ERROR,
		     G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
		     _("Unknown attribute '%s' on line %d char %d"),
		     error_attr,
		     line_number, char_number);
      else
	g_set_error (error,
		     G_MARKUP_ERROR,
		     G_MARKUP_ERROR_UNKNOWN_ELEMENT,
		     _("Unknown tag '%s' on line %d char %d"),
		     element_name,
		     line_number, char_number);
    }
}

static void
end_element_handler (GMarkupParseContext *context,
		     const gchar         *element_name,
		     gpointer             user_data,
		     GError             **error)
{
  ParseContext *ctx = user_data;
  GtkWidget *widget;

  switch (ctx->state)
    {
    case STATE_START:
      g_warning("shouldn't get any end tags at this point");
      /* should do a GError here */
      break;
    case STATE_ROOT:
      ctx->state = STATE_END;
      break;
    case STATE_MENU:
      widget = GTK_IS_MENU(ctx->current) ?
	gtk_menu_get_attach_widget(GTK_MENU(ctx->current)) : NULL;
      if (widget) /* not back to the toplevel ... */
	{
	  ctx->current = widget->parent;
	  ctx->state = STATE_MENU;
	}
      else
	{
	  if (GTK_IS_MENU(ctx->current)) /* must be a popup */
	    ctx->state = STATE_POPUPS;
	  else
	    ctx->state = STATE_ROOT;

	  /* notify */
	  (* ctx->widget_func)(ctx->top, ctx->type, ctx->name, ctx->user_data);
	  ctx->top = NULL;
	  ctx->type = NULL;
	  g_free(ctx->name);
	  ctx->name = NULL;
	  ctx->current = NULL;
	}
      break;
    case STATE_TOOLBAR:
      ctx->state = STATE_ROOT;

      /* notify */
      (* ctx->widget_func)(ctx->top, ctx->type, ctx->name, ctx->user_data);
      ctx->top = NULL;
      ctx->type = NULL;
      g_free(ctx->name);
      ctx->name = NULL;
      ctx->current = NULL;
      break;
    case STATE_POPUPS:
      ctx->state = STATE_ROOT;
      break;
    case STATE_ITEM:
      if (GTK_IS_MENU_SHELL(ctx->current))
	ctx->state = STATE_MENU;
      else
	ctx->state = STATE_TOOLBAR;
      break;
    case STATE_END:
      g_warning("shouldn't get any end tags at this point");
      /* should do a GError here */
      break;
    }
}

static void
cleanup (GMarkupParseContext *context,
	 GError              *error,
	 gpointer             user_data)
{
  ParseContext *ctx = user_data;

  gtk_widget_destroy(ctx->top);
  ctx->top = NULL;
  ctx->type = NULL;
  g_free(ctx->name);
  ctx->name = NULL;
  ctx->current = NULL;
}


static GMarkupParser ui_parser = {
  start_element_handler,
  end_element_handler,
  NULL,
  NULL,
  cleanup
};


gboolean
egg_create_from_string (EggActionGroup *action_group,
			EggWidgetFunc widget_func, gpointer user_data,
			GtkAccelGroup *accel_group,
			const gchar *buffer, guint length,
			GError **error)
{
  ParseContext ctx = { 0 };
  GMarkupParseContext *context;
  gboolean res = TRUE;

  g_return_val_if_fail(EGG_IS_ACTION_GROUP(action_group), FALSE);
  g_return_val_if_fail(widget_func != NULL, FALSE);
  g_return_val_if_fail(GTK_IS_ACCEL_GROUP(accel_group), FALSE);
  g_return_val_if_fail(buffer != NULL, FALSE);

  ctx.state = STATE_START;
  ctx.widget_func = widget_func;
  ctx.user_data = user_data;
  ctx.accel_group = accel_group;
  ctx.top = NULL;
  ctx.type = NULL;
  ctx.name = NULL;
  ctx.current = NULL;
  ctx.action_group = action_group;

  context = g_markup_parse_context_new(&ui_parser, 0, &ctx, NULL);
  if (length < 0)
    length = strlen(buffer);

  if (g_markup_parse_context_parse(context, buffer, length, error))
    {
      if (!g_markup_parse_context_end_parse(context, error))
	res = FALSE;
    }
  else
    res = FALSE;

  g_markup_parse_context_free (context);

  return res;
}

gboolean
egg_create_from_file (EggActionGroup *action_group,
		      EggWidgetFunc widget_func,
		      gpointer user_data,
		      GtkAccelGroup *accel_group,
		      const gchar *filename,
		      GError **error)
{
  gchar *buffer;
  gsize length;
  gboolean res;

  if (!g_file_get_contents (filename, &buffer, &length, error))
    return FALSE;

  res = egg_create_from_string(action_group, widget_func, user_data,
			       accel_group, buffer, length, error);
  g_free(buffer);

  return res;
}
