/* GObject introspection: gen-introspect
 *
 * Copyright (C) 2007  Jürg Billeter
 * Copyright (C) 2007  Johan Dahlin
 *
 * 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., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Author:
 * 	Jürg Billeter <j@bitron.ch>
 */

#include <stdio.h>
#include <glib.h>
#include "scanner.h"
#include "gidlnode.h"

typedef struct {
  int indent;
  FILE *output;
} GIdlWriter;

static void node_generate (GIdlWriter * writer, GIdlNode * node);

static void
g_writer_write_inline (GIdlWriter * writer, const char *s)
{
  fprintf (writer->output, "%s", s);
}

static void
g_writer_write (GIdlWriter * writer, const char *s)
{
  int i;
  for (i = 0; i < writer->indent; i++)
    {
      fprintf (writer->output, "\t");
    }

  g_writer_write_inline (writer, s);
}

static void
g_writer_write_indent (GIdlWriter * writer, const char *s)
{
  g_writer_write (writer, s);
  writer->indent++;
}

static void
g_writer_write_unindent (GIdlWriter * writer, const char *s)
{
  writer->indent--;
  g_writer_write (writer, s);
}

static void
field_generate (GIdlWriter * writer, GIdlNodeField * node)
{
  char *markup =
    g_markup_printf_escaped ("<field name=\"%s\" type=\"%s\"/>\n",
			     node->node.name, node->type->unparsed);
  g_writer_write (writer, markup);
  g_free (markup);
}

static void
value_generate (GIdlWriter * writer, GIdlNodeValue * node)
{
  char *markup =
    g_markup_printf_escaped ("<member name=\"%s\" value=\"%d\"/>\n",
			     node->node.name, node->value);
  g_writer_write (writer, markup);
  g_free (markup);
}

static void
constant_generate (GIdlWriter * writer, GIdlNodeConstant * node)
{
  char *markup =
    g_markup_printf_escaped
    ("<constant name=\"%s\" type=\"%s\" value=\"%s\"/>\n", node->node.name,
     node->type->unparsed, node->value);
  g_writer_write (writer, markup);
  g_free (markup);
}

static void
property_generate (GIdlWriter * writer, GIdlNodeProperty * node)
{
  char *markup =
    g_markup_printf_escaped ("<property name=\"%s\" "
			     "type=\"%s\" "
			     "readable=\"%s\" "
			     "writable=\"%s\" "
			     "construct=\"%s\" "
			     "construct-only=\"%s\"/>\n",
			     node->node.name,
			     node->type->unparsed,
			     node->readable ? "1" : "0",
			     node->writable ? "1" : "0",
			     node->construct ? "1" : "0",
			     node->construct_only ? "1" : "0");
  g_writer_write (writer, markup);
  g_free (markup);
}

static void
function_generate (GIdlWriter * writer, GIdlNodeFunction * node)
{
  const char *tag_name;
  GString *markup_s;
  gchar *markup;

  if (node->node.type == G_IR_NODE_CALLBACK)
    tag_name = "callback";
  else if (node->is_constructor)
    tag_name = "constructor";
  else if (node->is_method)
    tag_name = "method";
  else
    tag_name = "function";

  markup_s = g_string_new ("<");
  g_string_append_printf (markup_s,
			  "%s name=\"%s\"",
			  tag_name, node->node.name);

  if (node->node.type != G_IR_NODE_CALLBACK)
    g_string_append_printf (markup_s,
			    g_markup_printf_escaped (" symbol=\"%s\"", node->symbol));

  if (node->deprecated)
    g_string_append_printf (markup_s, " deprecated=\"1\"");

  g_string_append (markup_s, ">\n");

  g_writer_write_indent (writer, markup_s->str);
  g_string_free (markup_s, TRUE);

  markup_s =
    g_string_new (g_markup_printf_escaped ("<return-type type=\"%s\"",
			     node->result->type->unparsed));

  if (node->result->transfer)
    g_string_append (markup_s, g_markup_printf_escaped (" transfer=\"full\"/>\n"));
  else
    g_string_append (markup_s, "/>\n");

  g_writer_write (writer, markup_s->str);
  g_string_free (markup_s, TRUE);

  if (node->parameters != NULL)
    {
      GList *l;
      g_writer_write_indent (writer, "<parameters>\n");
      for (l = node->parameters; l != NULL; l = l->next)
	{
	  GIdlNodeParam *param = l->data;
	  const gchar *direction = g_idl_node_param_direction_string (param);
	  
	  markup_s = g_string_new ("<parameter");

	  g_string_append_printf (markup_s, " name=\"%s\"", param->node.name);

	  g_string_append (markup_s,
			   g_markup_printf_escaped (" type=\"%s\"",
						    param->type->unparsed));

	  if (param->transfer)
	    g_string_append (markup_s,
			   g_markup_printf_escaped (" transfer=\"full\""));

	  if (param->allow_none)
	    g_string_append (markup_s,
			     g_markup_printf_escaped (" allow-none=\"1\""));
	  
	  if (strcmp (direction, "in") != 0)
	    g_string_append (markup_s,
			     g_markup_printf_escaped (" direction=\"%s\"",
						      direction));

	  g_string_append (markup_s, "/>\n");

	  g_writer_write (writer, markup_s->str);
	  g_string_free (markup_s, TRUE);
	}
      g_writer_write_unindent (writer, "</parameters>\n");
    }
  markup = g_strdup_printf ("</%s>\n", tag_name);
  g_writer_write_unindent (writer, markup);
  g_free (markup);
}

static void
vfunc_generate (GIdlWriter * writer, GIdlNodeVFunc * node)
{
  char *markup =
    g_markup_printf_escaped ("<vfunc name=\"%s\">\n", node->node.name);
  g_writer_write_indent (writer, markup);
  g_free (markup);
  markup =
    g_markup_printf_escaped ("<return-type type=\"%s\"/>\n",
			     node->result->type->unparsed);
  g_writer_write (writer, markup);
  g_free (markup);
  if (node->parameters != NULL)
    {
      GList *l;
      g_writer_write_indent (writer, "<parameters>\n");
      for (l = node->parameters; l != NULL; l = l->next)
	{
	  GIdlNodeParam *param = l->data;
	  markup =
	    g_markup_printf_escaped ("<parameter name=\"%s\" type=\"%s\"/>\n",
				     param->node.name, param->type->unparsed);
	  g_writer_write (writer, markup);
	  g_free (markup);
	}
      g_writer_write_unindent (writer, "</parameters>\n");
    }
  g_writer_write_unindent (writer, "</vfunc>\n");
}

static void
signal_generate (GIdlWriter * writer, GIdlNodeSignal * node)
{
  char *markup;
  const char *when = "LAST";
  if (node->run_first)
    {
      when = "FIRST";
    }
  else if (node->run_cleanup)
    {
      when = "CLEANUP";
    }
  markup =
    g_markup_printf_escaped ("<signal name=\"%s\" when=\"%s\">\n",
			     node->node.name, when);
  g_writer_write_indent (writer, markup);
  g_free (markup);
  markup =
    g_markup_printf_escaped ("<return-type type=\"%s\"/>\n",
			     node->result->type->unparsed);
  g_writer_write (writer, markup);
  g_free (markup);
  if (node->parameters != NULL)
    {
      GList *l;
      g_writer_write_indent (writer, "<parameters>\n");
      for (l = node->parameters; l != NULL; l = l->next)
	{
	  GIdlNodeParam *param = l->data;
	  markup =
	    g_markup_printf_escaped ("<parameter name=\"%s\" type=\"%s\"/>\n",
				     param->node.name, param->type->unparsed);
	  g_writer_write (writer, markup);
	  g_free (markup);
	}
      g_writer_write_unindent (writer, "</parameters>\n");
    }
  g_writer_write_unindent (writer, "</signal>\n");
}

static void
interface_generate (GIdlWriter * writer, GIdlNodeInterface * node)
{
  GList *l;
  char *markup;
  if (node->node.type == G_IR_NODE_OBJECT)
    {
      markup =
	g_markup_printf_escaped ("<object name=\"%s\" "
				 "parent=\"%s\" "
				 "type-name=\"%s\" "
				 "get-type=\"%s\">\n",
				 node->node.name,
				 node->parent,
				 node->gtype_name,
				 node->gtype_init);
    }
  else if (node->node.type == G_IR_NODE_INTERFACE)
    {
      markup =
	g_markup_printf_escaped
	("<interface name=\"%s\" type-name=\"%s\" get-type=\"%s\">\n",
	 node->node.name, node->gtype_name, node->gtype_init);
    }

  g_writer_write_indent (writer, markup);
  g_free (markup);
  if (node->node.type == G_IR_NODE_OBJECT && node->interfaces != NULL)
    {
      GList *l;
      g_writer_write_indent (writer, "<implements>\n");
      for (l = node->interfaces; l != NULL; l = l->next)
	{
	  markup =
	    g_markup_printf_escaped ("<interface name=\"%s\"/>\n",
				     (char *) l->data);
	  g_writer_write (writer, markup);
	  g_free (markup);
	}
      g_writer_write_unindent (writer, "</implements>\n");
    }
  else if (node->node.type == G_IR_NODE_INTERFACE
	   && node->prerequisites != NULL)
    {
      GList *l;
      g_writer_write_indent (writer, "<requires>\n");
      for (l = node->prerequisites; l != NULL; l = l->next)
	{
	  markup =
	    g_markup_printf_escaped ("<interface name=\"%s\"/>\n",
				     (char *) l->data);
	  g_writer_write (writer, markup);
	  g_free (markup);
	}
      g_writer_write_unindent (writer, "</requires>\n");
    }

  for (l = node->members; l != NULL; l = l->next)
    {
      node_generate (writer, l->data);
    }

  if (node->node.type == G_IR_NODE_OBJECT)
    {
      g_writer_write_unindent (writer, "</object>\n");
    }
  else if (node->node.type == G_IR_NODE_INTERFACE)
    {
      g_writer_write_unindent (writer, "</interface>\n");
    }
}

static void
struct_generate (GIdlWriter * writer, GIdlNodeStruct * node)
{
  GList *l;
  char *markup =
    g_markup_printf_escaped ("<struct name=\"%s\">\n", node->node.name);
  g_writer_write_indent (writer, markup);
  g_free (markup);
  for (l = node->members; l != NULL; l = l->next)
    {
      node_generate (writer, l->data);
    }
  g_writer_write_unindent (writer, "</struct>\n");
}

static void
union_generate (GIdlWriter * writer, GIdlNodeUnion * node)
{
  GList *l;
  char *markup =
    g_markup_printf_escaped ("<union name=\"%s\">\n", node->node.name);
  g_writer_write_indent (writer, markup);
  g_free (markup);
  for (l = node->members; l != NULL; l = l->next)
    {
      node_generate (writer, l->data);
    }
  g_writer_write_unindent (writer, "</union>\n");
}

static void
boxed_generate (GIdlWriter * writer, GIdlNodeBoxed * node)
{
  GList *l;
  char *markup =
    g_markup_printf_escaped
    ("<boxed name=\"%s\" type-name=\"%s\" get-type=\"%s\">\n",
     node->node.name, node->gtype_name, node->gtype_init);
  g_writer_write_indent (writer, markup);
  g_free (markup);
  for (l = node->members; l != NULL; l = l->next)
    {
      node_generate (writer, l->data);
    }
  g_writer_write_unindent (writer, "</boxed>\n");
}

static void
enum_generate (GIdlWriter * writer, GIdlNodeEnum * node)
{
  GList *l;
  GString *markup_s;
  char *markup;
  const char *tag_name = NULL;

  if (node->node.type == G_IR_NODE_ENUM)
    {
      tag_name = "enum";
    }
  else if (node->node.type == G_IR_NODE_FLAGS)
    {
      tag_name = "flags";
    }

  markup_s = g_string_new ("<");
  g_string_append_printf (markup_s,
			  "%s name=\"%s\"",
			  tag_name, node->node.name);

  if (node->gtype_name != NULL)
    g_string_append_printf (markup_s,
			    g_markup_printf_escaped (" type-name=\"%s\"", node->gtype_name));

  if (node->gtype_init != NULL)
    g_string_append_printf (markup_s,
			    g_markup_printf_escaped (" get-type=\"%s\"", node->gtype_init));

  if (node->deprecated)
    g_string_append_printf (markup_s, " deprecated=\"1\"");

  g_string_append (markup_s, ">\n");

  g_writer_write_indent (writer, markup_s->str);
  g_string_free (markup_s, TRUE);

  for (l = node->values; l != NULL; l = l->next)
    {
      node_generate (writer, l->data);
    }

  markup = g_strdup_printf ("</%s>\n", tag_name);
  g_writer_write_unindent (writer, markup);
  g_free (markup);
}

static void
node_generate (GIdlWriter * writer, GIdlNode * node)
{
  switch (node->type)
    {
    case G_IR_NODE_FUNCTION:
    case G_IR_NODE_CALLBACK:
      function_generate (writer, (GIdlNodeFunction *) node);
      break;
    case G_IR_NODE_VFUNC:
      vfunc_generate (writer, (GIdlNodeVFunc *) node);
      break;
    case G_IR_NODE_OBJECT:
    case G_IR_NODE_INTERFACE:
      interface_generate (writer, (GIdlNodeInterface *) node);
      break;
    case G_IR_NODE_STRUCT:
      struct_generate (writer, (GIdlNodeStruct *) node);
      break;
    case G_IR_NODE_UNION:
      union_generate (writer, (GIdlNodeUnion *) node);
      break;
    case G_IR_NODE_BOXED:
      boxed_generate (writer, (GIdlNodeBoxed *) node);
      break;
    case G_IR_NODE_ENUM:
    case G_IR_NODE_FLAGS:
      enum_generate (writer, (GIdlNodeEnum *) node);
      break;
    case G_IR_NODE_PROPERTY:
      property_generate (writer, (GIdlNodeProperty *) node);
      break;
    case G_IR_NODE_FIELD:
      field_generate (writer, (GIdlNodeField *) node);
      break;
    case G_IR_NODE_SIGNAL:
      signal_generate (writer, (GIdlNodeSignal *) node);
      break;
    case G_IR_NODE_VALUE:
      value_generate (writer, (GIdlNodeValue *) node);
      break;
    case G_IR_NODE_CONSTANT:
      constant_generate (writer, (GIdlNodeConstant *) node);
      break;
    default:
      g_assert_not_reached ();
    }
}

static void
g_writer_write_module (GIdlWriter * writer, GIdlModule * module)
{
  GList *l;
  char *markup =
    g_markup_printf_escaped ("<namespace name=\"%s\">\n", module->name);
  g_writer_write_indent (writer, markup);
  g_free (markup);
  for (l = module->entries; l != NULL; l = l->next)
    {
      node_generate (writer, l->data);
    }
  g_writer_write_unindent (writer, "</namespace>\n");
}

void
g_idl_writer_save_file (GIdlModule *module,
			const gchar *filename)
{
  GIdlWriter *writer;

  writer = g_new0 (GIdlWriter, 1);

  if (!filename)
    writer->output = stdout;
  else
    writer->output = fopen (filename, "w");

  g_writer_write (writer, "<?xml version=\"1.0\"?>\n");
  g_writer_write_indent (writer, "<repository version=\"1.0\""
			 "xmlns=\"http://www.gtk.org/introspection/core/1.0\""
			 "xmlns:c=\"http://www.gtk.org/introspection/c/1.0\""
			 "xmlns:glib=\"http://www.gtk.org/introspection/glib/1.0\">");
  g_writer_write_module (writer, module);
  g_writer_write_unindent (writer, "</repository>\n");

  if (filename)
    fclose (writer->output);
}