#include "ruby_libxml.h"
#include "ruby_xml_schema_type.h"
#include "ruby_xml_schema_element.h"
#include "ruby_xml_schema_attribute.h"
#include "ruby_xml_schema_facet.h"

#define UNBOUNDED 1 << 30
#define FREE_AND_NULL(str) if ((str) != NULL) { xmlFree((xmlChar *) (str)); str = NULL; }

/**
 * xmlSchemaTreeItem:
 *
 * The abstract base type for tree-like structured schema components.
 * (Extends xmlSchemaAnnotItem)
 */
typedef struct _xmlSchemaTreeItem xmlSchemaTreeItem;
typedef xmlSchemaTreeItem *xmlSchemaTreeItemPtr;
struct _xmlSchemaTreeItem {
    xmlSchemaTypeType type;
    xmlSchemaAnnotPtr annot;
    xmlSchemaTreeItemPtr next;
    xmlSchemaTreeItemPtr children;
};

 /**
 * xmlSchemaParticle:
 *
 * A particle component.
 * (Extends xmlSchemaTreeItem)
 */
typedef struct _xmlSchemaParticle xmlSchemaParticle;
typedef xmlSchemaParticle *xmlSchemaParticlePtr;
struct _xmlSchemaParticle {
    xmlSchemaTypeType type;
    xmlSchemaAnnotPtr annot;
    xmlSchemaTreeItemPtr next;
    /* next particle */
    xmlSchemaTreeItemPtr children;
    /* the "term" (e.g. a model group,
a group definition, a XML_SCHEMA_EXTRA_QNAMEREF (if a reference),
   etc.) */
    int minOccurs;
    int maxOccurs;
    xmlNodePtr node;
};

VALUE cXMLSchemaType;

static const rb_data_type_t rxml_schema_type_type = {
  "XML::Schema::Type",
  {NULL, NULL, NULL},
  NULL, NULL, 0
};

VALUE rxml_wrap_schema_type(xmlSchemaTypePtr xtype)
{
  VALUE result;

  if (!xtype)
    rb_raise(rb_eArgError, "XML::Schema::Type required!");

  result = TypedData_Wrap_Struct(cXMLSchemaType, &rxml_schema_type_type, xtype);

  rb_iv_set(result, "@name", QNIL_OR_STRING(xtype->name));
  rb_iv_set(result, "@namespace", QNIL_OR_STRING(xtype->targetNamespace));
  rb_iv_set(result, "@kind", INT2NUM(xtype->type));

  return result;
}

static VALUE rxml_schema_type_base(VALUE self)
{
  xmlSchemaTypePtr xtype;

  TypedData_Get_Struct(self, xmlSchemaType, &rxml_schema_type_type, xtype);

  return (xtype->baseType != xtype) ? rxml_wrap_schema_type(xtype->baseType) : Qnil;
}

static VALUE rxml_schema_type_node(VALUE self)
{
  xmlSchemaTypePtr xtype;

  TypedData_Get_Struct(self, xmlSchemaType, &rxml_schema_type_type, xtype);

  return (xtype->node != NULL) ? rxml_node_wrap(xtype->node) : Qnil;
}

static VALUE rxml_schema_type_facets(VALUE self)
{
  xmlSchemaTypePtr xtype;
  xmlSchemaFacetPtr xfacet;
  VALUE result = rb_ary_new();
  VALUE facet;

  TypedData_Get_Struct(self, xmlSchemaType, &rxml_schema_type_type, xtype);

  xfacet = xtype->facets;

  while (xfacet != NULL)
  {
    facet = rxml_wrap_schema_facet((xmlSchemaFacetPtr)xfacet);
    rb_ary_push(result, facet);
    xfacet = xfacet->next;
  }

  return result;
}

static VALUE rxml_schema_type_annot(VALUE self)
{
  VALUE result = Qnil;
  xmlSchemaTypePtr xtype;

  TypedData_Get_Struct(self, xmlSchemaType, &rxml_schema_type_type, xtype);

  if(xtype != NULL && xtype->annot != NULL && xtype->annot->content != NULL)
  {
    xmlChar *content = xmlNodeGetContent(xtype->annot->content);
	if (content)
	{
	  result = rxml_new_cstr(content, NULL);
	  xmlFree(content);
    }
  }
  return result;
}

static void rxmlSchemaCollectElements(xmlSchemaParticlePtr xparticle, VALUE hash)
{
  VALUE element;
  xmlSchemaTreeItemPtr xterm;

  if (xparticle == NULL)
    return;

  xterm = xparticle->children;

  if (xterm != NULL)
  {
    switch (xterm->type)
	{
      case XML_SCHEMA_TYPE_ELEMENT:
        element = rxml_wrap_schema_element((xmlSchemaElementPtr)xterm);
        rb_iv_set(element, "@min", INT2NUM(xparticle->minOccurs));

        if (xparticle->maxOccurs >= UNBOUNDED)
          rb_iv_set(element, "@max", rb_const_get(rb_path2class("Float"), rb_intern("INFINITY")));
        else
          rb_iv_set(element, "@max", INT2NUM(xparticle->maxOccurs));

        if (xparticle->annot != NULL)
        {
          xmlChar *content = xmlNodeGetContent(xparticle->annot->content);

          if (content != NULL)
          {
            rb_iv_set(element, "@annotation", rb_str_new2((const char *) content));
            xmlFree(content);
          }
        }

        rb_hash_aset(hash, rb_str_new2((const char *) ((xmlSchemaElementPtr)xterm)->name), element);
        break;

      case XML_SCHEMA_TYPE_SEQUENCE:
        break;

      case XML_SCHEMA_TYPE_CHOICE:
        break;

      case XML_SCHEMA_TYPE_ALL:
        break;

      case XML_SCHEMA_TYPE_ANY:
        break;

      default:
        return;
    }
  }

  if (xterm &&
      ((xterm->type == XML_SCHEMA_TYPE_SEQUENCE) || (xterm->type == XML_SCHEMA_TYPE_CHOICE) || (xterm->type == XML_SCHEMA_TYPE_ALL)) &&
      (xterm->children != NULL))
  {
    rxmlSchemaCollectElements((xmlSchemaParticlePtr)xterm->children, hash);
  }

  if (xparticle->next != NULL)
  {
	rxmlSchemaCollectElements((xmlSchemaParticlePtr)xparticle->next, hash);
  }
}

static VALUE rxml_schema_type_elements(VALUE self)
{
  VALUE result = rb_hash_new();
  xmlSchemaTypePtr xtype;

  TypedData_Get_Struct(self, xmlSchemaType, &rxml_schema_type_type, xtype);
  rxmlSchemaCollectElements((xmlSchemaParticlePtr) xtype->subtypes, result);

  return result;
}

static VALUE rxml_schema_type_attributes(VALUE self)
{
  VALUE result = rb_ary_new();
  xmlSchemaTypePtr xtype;
  xmlSchemaAttributeUsePtr xuse;
  xmlSchemaItemListPtr xuses;
  int i;

  TypedData_Get_Struct(self, xmlSchemaType, &rxml_schema_type_type, xtype);
  xuses = xtype->attrUses;

  if (xuses != NULL)
  {
    for (i = 0; i < xuses->nbItems; i++)
	{
      xuse = (xmlSchemaAttributeUsePtr)xuses->items[i];
      rb_ary_push(result, rxml_wrap_schema_attribute(xuse));
    }
  }

  return result;
}

void rxml_init_schema_type(void)
{
  cXMLSchemaType = rb_define_class_under(cXMLSchema, "Type", rb_cObject);
  rb_undef_alloc_func(cXMLSchemaType);

  rb_define_attr(cXMLSchemaType, "namespace", 1, 0);
  rb_define_attr(cXMLSchemaType, "name", 1, 0);
  rb_define_attr(cXMLSchemaType, "kind", 1, 0);

  rb_define_method(cXMLSchemaType, "base", rxml_schema_type_base, 0);
  rb_define_method(cXMLSchemaType, "node", rxml_schema_type_node, 0);
  rb_define_method(cXMLSchemaType, "elements", rxml_schema_type_elements, 0);
  rb_define_method(cXMLSchemaType, "attributes", rxml_schema_type_attributes, 0);
  rb_define_method(cXMLSchemaType, "facets", rxml_schema_type_facets, 0);
  rb_define_method(cXMLSchemaType, "annotation", rxml_schema_type_annot, 0);
}
