#include "eldbus_introspection.h"

typedef struct _Eldbus_Introspection_Element_Parse_Table Eldbus_Introspection_Element_Parse_Table;

struct _Eldbus_Introspection_Element_Parse_Table
{
   const char *name;
   void *(*parse)(Eina_Simple_XML_Node_Tag *tag);
   Eina_List **list;
};

static const char *_eldbus_introspection_attribute_value_get(Eina_Inlist *, const char *);
static void *_eldbus_introspection_parse_node(Eina_Simple_XML_Node_Tag *);
static void *_eldbus_introspection_parse_interface(Eina_Simple_XML_Node_Tag *);
static void *_eldbus_introspection_parse_method(Eina_Simple_XML_Node_Tag *);
static void *_eldbus_introspection_parse_signal(Eina_Simple_XML_Node_Tag *);
static void *_eldbus_introspection_parse_argument(Eina_Simple_XML_Node_Tag *);
static void *_eldbus_introspection_parse_property(Eina_Simple_XML_Node_Tag *);
static void *_eldbus_introspection_parse_annotation(Eina_Simple_XML_Node_Tag *);
static void _eldbus_introspection_parse_children(Eina_Inlist *, const Eldbus_Introspection_Element_Parse_Table[]);
static void _eldbus_introspection_interface_free(Eldbus_Introspection_Interface *);
static void _eldbus_introspection_method_free(Eldbus_Introspection_Method *);
static void _eldbus_introspection_signal_free(Eldbus_Introspection_Signal *);
static void _eldbus_introspection_argument_free(Eldbus_Introspection_Argument *);
static void _eldbus_introspection_property_free(Eldbus_Introspection_Property *);
static void _eldbus_introspection_annotation_free(Eldbus_Introspection_Annotation *);

EAPI Eldbus_Introspection_Node *
eldbus_introspection_parse(const char *xml)
{
   Eldbus_Introspection_Node *node;
   Eina_Simple_XML_Node_Root *xml_root;
   Eina_Simple_XML_Node *xml_node;
   Eina_Inlist *last = NULL;

   EINA_SAFETY_ON_NULL_RETURN_VAL(xml, NULL);

   node = NULL;
   xml_root = eina_simple_xml_node_load(xml, strlen(xml), EINA_TRUE);
   if (xml_root && xml_root->children) last = xml_root->children->last;
   xml_node = (Eina_Simple_XML_Node *)last;
   if (!xml_node) goto free_root;
   EINA_SAFETY_ON_FALSE_GOTO(EINA_SIMPLE_XML_NODE_TAG == xml_node->type, free_root);

   node = (Eldbus_Introspection_Node*)_eldbus_introspection_parse_node((Eina_Simple_XML_Node_Tag*)xml_node);

 free_root:
   eina_simple_xml_node_root_free(xml_root);

   return node;
}

static void *
_eldbus_introspection_parse_node(Eina_Simple_XML_Node_Tag *tag)
{
   Eldbus_Introspection_Node *node;
   Eldbus_Introspection_Element_Parse_Table table[] = {
     { "node", _eldbus_introspection_parse_node, NULL },
     { "interface", _eldbus_introspection_parse_interface, NULL },
     { NULL, NULL, NULL }
   };
   const char *name;

   node = calloc(1, sizeof(Eldbus_Introspection_Node));
   EINA_SAFETY_ON_NULL_RETURN_VAL(node, NULL);

   name = _eldbus_introspection_attribute_value_get(tag->attributes, "name");
   node->name = eina_stringshare_add(name);
   table[0].list = &node->nodes;
   table[1].list = &node->interfaces;

   _eldbus_introspection_parse_children(tag->children, table);

   return node;
}

EAPI void
eldbus_introspection_node_free(Eldbus_Introspection_Node *node)
{
   Eldbus_Introspection_Node *child_node;
   Eldbus_Introspection_Interface *interface;

   EINA_SAFETY_ON_NULL_RETURN(node);

   EINA_LIST_FREE(node->nodes, child_node)
     eldbus_introspection_node_free(child_node);

   EINA_LIST_FREE(node->interfaces, interface)
     _eldbus_introspection_interface_free(interface);

   eina_stringshare_del(node->name);
   free(node);
}

static const char *
_eldbus_introspection_attribute_value_get(Eina_Inlist *attributes, const char *key)
{
   Eina_Simple_XML_Attribute *attribute;

   EINA_INLIST_FOREACH(attributes, attribute)
     {
        if (strcmp(attribute->key, key) == 0)
          return attribute->value;
     }

  return NULL;
}

static void *
_eldbus_introspection_parse_interface(Eina_Simple_XML_Node_Tag *tag)
{
   Eldbus_Introspection_Interface *interface;
   Eldbus_Introspection_Element_Parse_Table table[] = {
     { "method", _eldbus_introspection_parse_method, NULL },
     { "signal", _eldbus_introspection_parse_signal, NULL },
     { "property", _eldbus_introspection_parse_property, NULL },
     { "annotation", _eldbus_introspection_parse_annotation, NULL },
     { NULL, NULL, NULL }
   };
   const char *name;

   interface = calloc(1, sizeof(Eldbus_Introspection_Interface));
   EINA_SAFETY_ON_NULL_RETURN_VAL(interface, NULL);

   name = _eldbus_introspection_attribute_value_get(tag->attributes, "name");
   if (!name) goto error;
   interface->name = eina_stringshare_add(name);
   table[0].list = &interface->methods;
   table[1].list = &interface->signals;
   table[2].list = &interface->properties;
   table[3].list = &interface->annotations;

   _eldbus_introspection_parse_children(tag->children, table);

   return interface;

 error:
   _eldbus_introspection_interface_free(interface);
   return NULL;
}

static void
_eldbus_introspection_interface_free(Eldbus_Introspection_Interface *interface)
{
   Eldbus_Introspection_Method *method;
   Eldbus_Introspection_Signal *signal;
   Eldbus_Introspection_Property *property;
   Eldbus_Introspection_Annotation *annotation;

   EINA_SAFETY_ON_NULL_RETURN(interface);

   EINA_LIST_FREE(interface->methods, method)
     _eldbus_introspection_method_free(method);

   EINA_LIST_FREE(interface->signals, signal)
     _eldbus_introspection_signal_free(signal);

   EINA_LIST_FREE(interface->properties, property)
     _eldbus_introspection_property_free(property);

   EINA_LIST_FREE(interface->annotations, annotation)
     _eldbus_introspection_annotation_free(annotation);

   eina_stringshare_del(interface->name);
   free(interface);
}

static void *
_eldbus_introspection_parse_method(Eina_Simple_XML_Node_Tag *tag)
{
   Eldbus_Introspection_Method *method;
   Eldbus_Introspection_Element_Parse_Table table[] = {
     { "arg", _eldbus_introspection_parse_argument, NULL },
     { "annotation", _eldbus_introspection_parse_annotation, NULL },
     { NULL, NULL, NULL }
   };
   const char *name;

   method = calloc(1, sizeof(Eldbus_Introspection_Method));
   EINA_SAFETY_ON_NULL_RETURN_VAL(method, NULL);

   name = _eldbus_introspection_attribute_value_get(tag->attributes, "name");
   if (!name) goto error;
   method->name = eina_stringshare_add(name);
   table[0].list = &method->arguments;
   table[1].list = &method->annotations;

   _eldbus_introspection_parse_children(tag->children, table);

   return method;

 error:
   _eldbus_introspection_method_free(method);
   return NULL;
}

static void
_eldbus_introspection_method_free(Eldbus_Introspection_Method *method)
{
   Eldbus_Introspection_Argument *argument;
   Eldbus_Introspection_Annotation *annotation;

   EINA_SAFETY_ON_NULL_RETURN(method);

   EINA_LIST_FREE(method->arguments, argument)
     _eldbus_introspection_argument_free(argument);

   EINA_LIST_FREE(method->annotations, annotation)
     _eldbus_introspection_annotation_free(annotation);

   eina_stringshare_del(method->name);
   free(method);
}

static void *
_eldbus_introspection_parse_signal(Eina_Simple_XML_Node_Tag *tag)
{
   Eldbus_Introspection_Signal *signal;
   Eldbus_Introspection_Element_Parse_Table table[] = {
     { "arg", _eldbus_introspection_parse_argument, NULL },
     { "annotation", _eldbus_introspection_parse_annotation, NULL },
     { NULL, NULL, NULL }
   };
   const char *name;

   signal = calloc(1, sizeof(Eldbus_Introspection_Signal));
   EINA_SAFETY_ON_NULL_RETURN_VAL(signal, NULL);

   name = _eldbus_introspection_attribute_value_get(tag->attributes, "name");
   if (!name) goto error;
   signal->name = eina_stringshare_add(name);
   table[0].list = &signal->arguments;
   table[1].list = &signal->annotations;

   _eldbus_introspection_parse_children(tag->children, table);

   return signal;

 error:
   _eldbus_introspection_signal_free(signal);
   return NULL;
}

static void
_eldbus_introspection_signal_free(Eldbus_Introspection_Signal *signal)
{
   Eldbus_Introspection_Argument *argument;
   Eldbus_Introspection_Annotation *annotation;

   EINA_SAFETY_ON_NULL_RETURN(signal);

   EINA_LIST_FREE(signal->arguments, argument)
     _eldbus_introspection_argument_free(argument);

   EINA_LIST_FREE(signal->annotations, annotation)
     _eldbus_introspection_annotation_free(annotation);

   eina_stringshare_del(signal->name);
   free(signal);
}

static void *
_eldbus_introspection_parse_argument(Eina_Simple_XML_Node_Tag *tag)
{
   Eldbus_Introspection_Argument *argument;
   const char *name;
   const char *type;
   const char *direction;

   argument = calloc(1, sizeof(Eldbus_Introspection_Argument));
   EINA_SAFETY_ON_NULL_RETURN_VAL(argument, NULL);

   name = _eldbus_introspection_attribute_value_get(tag->attributes, "name");
   argument->name = eina_stringshare_add(name);

   type = _eldbus_introspection_attribute_value_get(tag->attributes, "type");
   if (!type) goto error;
   argument->type = eina_stringshare_add(type);

   direction = _eldbus_introspection_attribute_value_get(tag->attributes, "direction");
   if (direction)
     {
        if (strcmp(direction, "in") == 0)
          argument->direction = ELDBUS_INTROSPECTION_ARGUMENT_DIRECTION_IN;
        else
        if (strcmp(direction, "out") == 0)
          argument->direction = ELDBUS_INTROSPECTION_ARGUMENT_DIRECTION_OUT;
        else
          argument->direction = ELDBUS_INTROSPECTION_ARGUMENT_DIRECTION_NONE;
     }

   return argument;

 error:
   _eldbus_introspection_argument_free(argument);
   return NULL;
}

static void
_eldbus_introspection_argument_free(Eldbus_Introspection_Argument *argument)
{
   EINA_SAFETY_ON_NULL_RETURN(argument);
   eina_stringshare_del(argument->name);
   eina_stringshare_del(argument->type);
   free(argument);
}

static void *
_eldbus_introspection_parse_property(Eina_Simple_XML_Node_Tag *tag)
{
   Eldbus_Introspection_Property *property;
   Eldbus_Introspection_Element_Parse_Table table[] = {
     { "annotation", _eldbus_introspection_parse_annotation, NULL },
     { NULL, NULL, NULL }
   };
   const char *name;
   const char *type;
   const char *access;

   property = calloc(1, sizeof(Eldbus_Introspection_Property));
   EINA_SAFETY_ON_NULL_RETURN_VAL(property, NULL);

   name = _eldbus_introspection_attribute_value_get(tag->attributes, "name");
   if (!name) goto error;
   property->name = eina_stringshare_add(name);

   type = _eldbus_introspection_attribute_value_get(tag->attributes, "type");
   if (!type) goto error;
   property->type = eina_stringshare_add(type);

   access = _eldbus_introspection_attribute_value_get(tag->attributes, "access");
   if (!access) goto error;

   if (strcmp(access, "read") == 0)
     property->access = ELDBUS_INTROSPECTION_PROPERTY_ACCESS_READ;
   else if (strcmp(access, "write") == 0)
     property->access = ELDBUS_INTROSPECTION_PROPERTY_ACCESS_WRITE;
   else if (strcmp(access, "readwrite") == 0)
     property->access = ELDBUS_INTROSPECTION_PROPERTY_ACCESS_READWRITE;
   else
     EINA_SAFETY_ON_TRUE_GOTO(!!"Unknown property access", error);
   table[0].list = &property->annotations;

   _eldbus_introspection_parse_children(tag->children, table);
   return property;

 error:
   _eldbus_introspection_property_free(property);
   return NULL;
}

static void
_eldbus_introspection_property_free(Eldbus_Introspection_Property *property)
{
   Eldbus_Introspection_Annotation *annotation;

   EINA_SAFETY_ON_NULL_RETURN(property);

   EINA_LIST_FREE(property->annotations, annotation)
     _eldbus_introspection_annotation_free(annotation);

   eina_stringshare_del(property->name);
   eina_stringshare_del(property->type);
   free(property);
}

static void *
_eldbus_introspection_parse_annotation(Eina_Simple_XML_Node_Tag *tag)
{
   Eldbus_Introspection_Annotation *annotation;
   const char *name;
   const char *value;

   annotation = calloc(1, sizeof(Eldbus_Introspection_Annotation));
   EINA_SAFETY_ON_NULL_RETURN_VAL(annotation, NULL);

   name = _eldbus_introspection_attribute_value_get(tag->attributes, "name");
   if (!name) goto error;
   annotation->name = eina_stringshare_add(name);

   value = _eldbus_introspection_attribute_value_get(tag->attributes, "value");
   if (!value) goto error;
   annotation->value = eina_stringshare_add(value);

   return annotation;

 error:
   _eldbus_introspection_annotation_free(annotation);
   return NULL;
}

static void
_eldbus_introspection_annotation_free(Eldbus_Introspection_Annotation *annotation)
{
   EINA_SAFETY_ON_NULL_RETURN(annotation);
   eina_stringshare_del(annotation->name);
   eina_stringshare_del(annotation->value);
   free(annotation);
}

static void
_eldbus_introspection_parse_children(Eina_Inlist *children, const Eldbus_Introspection_Element_Parse_Table table[])
{
   Eina_Simple_XML_Node *child;

   EINA_INLIST_FOREACH(children, child)
     {
        const Eldbus_Introspection_Element_Parse_Table *it;
        Eina_Simple_XML_Node_Tag *tag;
        void *item;

        if (EINA_SIMPLE_XML_NODE_TAG != child->type)
          continue;

        tag = (Eina_Simple_XML_Node_Tag*) child;

        for (it = table; it->name; ++it)
          {
             if (strcmp(tag->name, it->name) != 0)
               continue;

             item = it->parse(tag);
             if (item)
               *it->list = eina_list_append(*it->list, item);
          }
     }
}

EAPI Eldbus_Introspection_Interface *
eldbus_introspection_interface_find(Eina_List *interfaces, const char *name)
{
   Eina_List *it;
   Eldbus_Introspection_Interface *interface;

   EINA_LIST_FOREACH(interfaces, it, interface)
     {
        if (strcmp(interface->name, name) == 0)
          return interface;
     }

   return NULL;
}

EAPI Eldbus_Introspection_Property *
eldbus_introspection_property_find(Eina_List *properties, const char *name)
{
   Eina_List *it;
   Eldbus_Introspection_Property *property;

   EINA_LIST_FOREACH(properties, it, property)
     {
        if (strcmp(property->name, name) == 0)
          return property;
     }

   return NULL;
}

EAPI Eldbus_Introspection_Argument *
eldbus_introspection_argument_find(Eina_List *arguments, const char *name)
{
   Eina_List *it;
   Eldbus_Introspection_Argument *argument;

   EINA_LIST_FOREACH(arguments, it, argument)
     {
        if (strcmp(argument->name, name) == 0)
          return argument;
     }

   return NULL;
}
