/* Copyright (C) 2013 David Vaz <davidvaz@dcc.fc.up.pt>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General  Public  License
 * as published by the Free Software Foundation; either  version
 * 2 of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of  the  GNU  General  Public
 * License along with this program; if not, write  to  the  Free
 * Software Foundation, Inc., 51 Franklin Street,  Fifth  Floor,
 * Boston, MA 02110-1301, USA.
 */
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>


#include "Yap.h"
#include "Yatom.h"

#include <libxml/parser.h>
#include <libxml/tree.h>

void libxml2_yap_init (void);

struct exo_aux {
  YAP_Functor functor;
  YAP_PredEntryPtr pred;
  size_t n;
};


static Term  read_atts(xmlAttr *att_node, sigjmp_buf *ji USES_REGS)
{
  if (att_node == NULL)
    return TermNil;
  Term ttail = read_atts(att_node->next, ji PASS_REGS);
  Term thead;
  Term tf;
    if (HR > ASP-1024)
      siglongjmp(*ji, 2);
  if (att_node->children) {
    if (att_node->children->type == XML_TEXT_NODE) {
      Term ts[2];
      ts[0] = MkAtomTerm(Yap_LookupAtom((const char *)att_node->name));
      ts[1] = MkStringTerm((const char *)att_node->children->content);
      thead = Yap_MkApplTerm(FunctorEq, 2, ts );
      tf = MkPairTerm(thead, ttail);
    } else {
      // error
      tf = TermNil;
    }
  } else {
    tf = MkAtomTerm(Yap_LookupAtom((const char *)att_node->name));
  }    
  if (att_node->ns) {
    Term ts[2];
    ts[0] = MkAtomTerm(Yap_LookupAtom((const char *)att_node->ns->prefix));
    ts[1] = tf;
    tf = Yap_MkApplTerm( FunctorModule, 2, ts );
  }
  return tf;
}

/**
 * load_element_names:
 * @a_node: the initial xml node to consider.
 *
 * Prints the names of the all the xml elements
 * that are siblings or children of a given xml node.
 */
static Term
print_element_names(xmlNode * a_node, sigjmp_buf *ji USES_REGS)
{
    xmlNode *cur_node = NULL;
    int count = 0;

    for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
        if (cur_node->type == XML_ELEMENT_NODE) {
          count++;
        }
    }
    if (HR > ASP-(1024+count))
      siglongjmp(*ji, 1);
    Atom at = Yap_LookupAtom((char *)a_node->name);
    Functor f = Yap_MkFunctor(at, count+1);
    Term t = Yap_MkNewApplTerm(f, count+1);
    CELL *s = RepAppl(t) + 2;
    s[-1]  = read_atts(a_node->properties, ji PASS_REGS);
    int i = 0;
    for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
      if (cur_node->type == XML_ELEMENT_NODE) {
        s[i++] =  print_element_names(cur_node, ji PASS_REGS);
      }
    }
    if (a_node->ns) {
      Term ts[2];
      ts[0] = MkAtomTerm(Yap_LookupAtom((const char *)a_node->ns->prefix));
      ts[1] = t;
      t = Yap_MkApplTerm( FunctorModule, 2, ts );
    }
    return t;
}

static Int
load_xml ( void )
{
  CACHE_REGS
    sigjmp_buf jmp_info;
   xmlDoc *doc = NULL;
    xmlNode *root_element = NULL;
    CELL *h0 = HR;
    
    const char *f = AtomOfTerm(Deref(ARG1))->StrOfAE;

    /*
     * this initialize the library and check potential ABI mismatches
     * between the version it was compiled for and the actual shared
     * library used.
     */
    LIBXML_TEST_VERSION

    /*parse the file and get the DOM */
    doc = xmlReadFile(f, NULL, 0);

    if (doc == NULL) {
      fprintf(stderr, "error: could not parse file %s\n", f);
      return false;
    }
    

    /*Get the root element node */
    root_element = xmlDocGetRootElement(doc);
    if (sigsetjmp(jmp_info, 0)) {
      HR = h0;
      Yap_gc(2, ENV, P);
      h0 = HR;
    }
    Term t = print_element_names(root_element, &jmp_info PASS_REGS);

    /*free the document */
    xmlFreeDoc(doc);

    /*
     *Free the global variables that may
     *have been allocated by the parser.
     */
    xmlCleanupParser();

    return Yap_unify(ARG2, t);
}

extern Int YAP_UserCPredicate(const char *, Int f(void), int arity);
 
void libxml2_yap_init (void)
{
   YAP_UserCPredicate("load_xml", load_xml, 2);
   YAP_UserCPredicate("load_xml2", load_xml, 2);
}