/*
 * This file is part of the Poliqarp suite.
 * 
 * Copyright (C) 2009 by Instytut Podstaw Informatyki Polskiej
 * Akademii Nauk (IPI PAN; Institute of Computer Science, Polish
 * Academy of Sciences; cf. www.ipipan.waw.pl).  All rights reserved.
 * 
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation and appearing in the file gpl.txt included in the packaging
 * of this file.  (See http://www.gnu.org/licenses/translations.html for
 * unofficial translations.)
 * 
 * A commercial license is available from IPI PAN (contact
 * Michal.Ciesiolka@ipipan.waw.pl or ipi@ipipan.waw.pl for more
 * information).  Licensees holding a valid commercial license from IPI
 * PAN may use this file in accordance with that license.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
 * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE.
 */

#include <poliqarp-config.h>

#include <assert.h>

#include <libxml/xinclude.h>

#include <foostring/foostring.h>

#include <bp/common.h>
#include <bp/ng/document.h>
#include <bp/ng/interp.h>
#include <bp/ng/segment.h>
#include <bp/ng/xml-nkjp.h>
#include <bp/ng/xml-parser.h>

const char *xml_ns_tei = "http://www.tei-c.org/ns/1.0";
const char *xml_ns_nkjp = "http://www.nkjp.pl/ns/1.0";

static inline bool xml_has_tei_ns(xml_reader *reader)
{
   return xml_has_ns(reader, xml_ns_tei);
}

static inline bool xml_has_tei_tag(xml_reader *reader, const char *check_tag)
{
   return xml_has_tag(reader, check_tag) && xml_has_tei_ns(reader);
}

/**
 * @return a newly allocated string
 */
static inline char *xml_get_tei_attr(xml_reader *reader,
   const char *name)
{
   return xml_get_attr(reader, NULL, name);
}

static inline bool xml_has_tei_attr(xml_reader *reader, const char *name,
   const char *check_value)
{
   return xml_has_attr(reader, NULL, name, check_value);
}

/**
 * @return a newly allocated string
 */
static inline char* xml_get_nkjp_attr(xml_reader *reader, const char *name)
{
   return xml_get_attr(reader, xml_ns_nkjp, name);
}

static int skip_element(xml_reader *reader)
{
   int rc, depth = xml_get_depth(reader);
   if (xml_is_empty(reader))
      return 0;
   while ((rc = xml_read(reader)) == 1) {
      if (xml_has_end_tag(reader)) {
         if (xml_get_depth(reader) == depth) {
            rc = 0;
            break;
         }
      }
   }
   return -(rc != 0);
}

static int parse_xsd_boolean(const char *s, bool *value)
{
   /* TEI data.truthValue is equivalent to xsd:boolean, which can have the
    * following legal literals: true, false, 1, 0
    */
   if (s == NULL || s[0] == '\0')
      return -1;
   if (s[0] == '0' && s[1] == '\0')
      *value = false;
   else if (s[0] == '1' && s[1] == '\0')
      *value = true;
   else if (strcmp(s, "false"))
      *value = false;
   else if (strcmp(s, "true"))
      *value = 1;
   else
      return -1;
   return 0;
}

static int parse_binary(xml_reader *reader, bool *value)
{
   int rc, depth = xml_get_depth(reader);
   *value = false;
   if (xml_is_empty(reader))
      return -1;
   while ((rc = xml_read(reader)) == 1) {
      if (xml_has_start_tag(reader)) {
         if (xml_has_tei_tag(reader, "binary")) {
            char *s = xml_get_tei_attr(reader, "value");
            parse_xsd_boolean(s, value);
            free(s);
         }
      } else if (xml_has_end_tag(reader)) {
         if (xml_get_depth(reader) == depth) {
            rc = 0;
            break;
         }
      }
   }
   return -(rc != 0);
}

static int parse_symbol(xml_reader *reader, char **string)
{
   int rc, depth = xml_get_depth(reader);
   assert(*string == NULL);
   if (xml_is_empty(reader))
      return -1;
   while ((rc = xml_read(reader)) == 1) {
      if (xml_has_start_tag(reader)) {
         if (*string == NULL && xml_has_tei_tag(reader, "symbol")) {
            char *value = xml_get_tei_attr(reader, "value");
            if (value == NULL) {
               bp_warning("<symbol> without a value");
               continue;
            }
            *string = value;
         }
      } else if (xml_has_end_tag(reader)) {
         if (xml_get_depth(reader) == depth) {
            rc = 0;
            break;
         }
      }
   }
   return -(rc != 0);
}
static int parse_string(xml_reader *reader, char **string)
{
   int rc, depth = xml_get_depth(reader);
   assert(*string == NULL);
   if (xml_is_empty(reader))
      return -1;
   while ((rc = xml_read(reader)) == 1) {
      if (xml_has_start_tag(reader)) {
         if (*string == NULL && xml_has_tei_tag(reader, "string")) {
            xml_node *node = xml_expand(reader);
            if (node == NULL) {
               bp_warning("unable to expand a node");
               rc = -1;
               break;
            }
            *string = xml_node_as_string(node);
         }
      } else if (xml_has_end_tag(reader)) {
         if (xml_get_depth(reader) == depth) {
            rc = 0;
            break;
         }
      }
   }
   return -(rc != 0);
}

static int parse_f_disamb(xml_reader *reader, struct corpus_document *corpus,
   struct segment *segment)
{
   int rc, depth = xml_get_depth(reader);
   if (xml_is_empty(reader))
      return -1;
   while ((rc = xml_read(reader)) == 1) {
      if (xml_has_start_tag(reader)) {
         if (xml_has_tei_tag(reader, "f")) {
            if (xml_has_tei_attr(reader, "name", "choice")) {
               char *id = xml_get_tei_attr(reader, "fVal");
               if (id == NULL)
                  bp_warning("<f name=\"choice\"> with no fVal attribute");
               else if (id[0] != '#')
                  bp_warning("<f name=\"choice\"> with invalid fVal value");
               else {
                  struct interp *interp = segment_get_interp(segment, id + 1);
                  if (interp == NULL)
                     bp_warning("<f name=\"choice\"> points to an unknown interpretation");
                  else
                     interp->disamb = 1;
               }
               free(id);
            }
         }
      } else if (xml_has_end_tag(reader)) {
         if (xml_get_depth(reader) == depth) {
            rc = 0;
            break;
         }
      }
   }
   return -(rc != 0);
}

static int parse_f_msd(xml_reader *reader, struct corpus_document *corpus_document,
   struct segment *segment, const char *last_base, const char *last_ctag)
{
   int rc, depth = xml_get_depth(reader);
   if (xml_is_empty(reader))
      return -1;
   while ((rc = xml_read(reader)) == 1) {
      if (xml_has_start_tag(reader)) {
         if (xml_has_tei_tag(reader, "symbol")) {
            struct interp *interp;
            string_t tag;
            char *base = NULL;
            char *id, *tag_tail;
            id = xml_get_id(reader);
            if (id == NULL) {
               bp_warning("<symbol> without an id");
            }
            tag_tail = xml_get_tei_attr(reader, "value");
            if (tag_tail == NULL) {
               bp_warning("<symbol> without a value");
               free(id);
               continue;
            }
            tag = string_create();
            if (*tag_tail == '\0')
               string_append_str(tag, last_ctag);
            else {
               string_append_str(tag, last_ctag);
               string_append_char(tag, ':');
               string_append_str(tag, tag_tail);
            }
            free(tag_tail); tag_tail = NULL;
            if (base == NULL)
               base = bp_strdup(last_base);
            interp = interp_alloc();
            interp->base = base;
            interp->tag = string_free_and_get_buffer(tag);
            segment_add_interp(segment, interp, id);
            free(id);
         }
      } else if (xml_has_end_tag(reader)) {
         if (xml_get_depth(reader) == depth) {
            rc = 0;
            break;
         }
      }
   }
   return -(rc != 0);
}

static int parse_fs_lex(xml_reader *reader, struct corpus_document *corpus_document,
   struct segment *segment)
{
   int rc, depth = xml_get_depth(reader);
   char *last_base = NULL;
   char *last_ctag = NULL;
   if (xml_is_empty(reader))
      return -1;
   while ((rc = xml_read(reader)) == 1) {
      if (xml_has_start_tag(reader)) {
         if (xml_has_tei_tag(reader, "f")) {
            if (xml_has_tei_attr(reader, "name", "base")) {
               if (last_base != NULL) {
                  bp_warning("more than one base for an interpretation");
                  rc = -1;
                  break;
               }
               rc = parse_string(reader, &last_base);
               if (rc != 0)
                  break;
            }
            else if (xml_has_tei_attr(reader, "name", "ctag")) {
               if (last_ctag != NULL) {
                  bp_warning("more than one ctag for an interpretation");
                  rc = -1;
                  break;
               }
               rc = parse_symbol(reader, &last_ctag);
               if (rc != 0)
                  break;
            }
            else if (xml_has_tei_attr(reader, "name", "msd")) {
               if (last_base == NULL || last_ctag == NULL) {
                  rc = -1;
                  break;
               }
               rc = parse_f_msd(reader, corpus_document, segment, last_base, last_ctag);
               if (rc != 0)
                  break;
            }
         }
      } else if (xml_has_end_tag(reader)) {
         if (xml_get_depth(reader) == depth) {
            rc = 0;
            break;
         }
      }
   }
   free(last_base);
   free(last_ctag);
   return -(rc != 0);
}

static int parse_f_interps(xml_reader *reader,
   struct corpus_document *corpus_document, struct segment *segment)
{
   int rc, depth = xml_get_depth(reader);
   if (xml_is_empty(reader))
      return -1;
   while ((rc = xml_read(reader)) == 1) {
      if (xml_has_start_tag(reader)) {
         if (xml_has_tei_tag(reader, "fs") && xml_has_tei_attr(reader, "type", "lex")) {
            rc = parse_fs_lex(reader, corpus_document, segment);
            if (rc != 0)
               break;
         }
      } else if (xml_has_end_tag(reader)) {
         if (xml_get_depth(reader) == depth) {
            rc = 0;
            break;
         }
      }
   }
   return -(rc != 0);
}

static int parse_fs_morph(xml_reader *reader, struct corpus_document *corpus_document, struct segment *segment)
{
   int rc, depth = xml_get_depth(reader);
   if (xml_is_empty(reader))
      return -1;
   while ((rc = xml_read(reader)) == 1) {
      if (xml_has_start_tag(reader)) {
         if (xml_has_tei_tag(reader, "f")) {
            if (xml_has_tei_attr(reader, "name", "orth")) {
               if (segment->orth != NULL) {
                  bp_warning("more than one orth for a segment");
               } else {
                  rc = parse_string(reader, &segment->orth);
                  if (rc != 0)
                     break;
               }
            } else if (xml_has_tei_attr(reader, "name", "nps")) {
               bool nospace;
               rc = parse_binary(reader, &nospace);
               if (rc != 0)
                  break;
               segment->space = nospace;
            } else if (xml_has_tei_attr(reader, "name", "interps")) {
               rc = parse_f_interps(reader, corpus_document, segment);
               if (rc != 0)
                  break;
            }
            else if (xml_has_tei_attr(reader, "name", "disamb")) {
               rc = parse_f_disamb(reader, corpus_document, segment);
               if (rc != 0)
                  break;
            }
         }
      } else if (xml_has_end_tag(reader)) {
         if (xml_get_depth(reader) == depth) {
            rc = 0;
            break;
         }
      }
   }
   return -(rc != 0);
}

static int parse_seg(xml_reader *reader, struct corpus_document *corpus)
{
   int rc, depth = xml_get_depth(reader);
   struct segment segment;
   if (xml_is_empty(reader))
      return -1;
   segment_create(&segment, true);
   while ((rc = xml_read(reader)) == 1) {
      if (xml_has_start_tag(reader)) {
         if (xml_has_tei_tag(reader, "fs") && xml_has_tei_attr(reader, "type", "morph")) {
            rc = parse_fs_morph(reader, corpus, &segment);
            if (rc != 0)
               break;
         }
      } else if (xml_has_end_tag(reader)) {
         if (xml_get_depth(reader) == depth) {
            rc = 0;
            break;
         }
      }
   }
   if (rc == 0)
      rc = corpus_add_segment(corpus, &segment);
   segment_destroy(&segment);
   return -(rc != 0);
}

static int skip_seg(xml_reader *reader, struct corpus_document *corpus)
{
   return skip_element(reader);
}

int xml_parse_morph_nkjp(xml_reader *reader, struct document *document,
   struct corpus *corpus)
{
   char *document_id = NULL;
   struct corpus_document *corpus_document = NULL;
   int rc, depth = xml_get_depth(reader);
   if (xml_is_empty(reader))
      return -1;
   assert(reader != NULL);
   while ((rc = xml_read(reader)) == 1) {
      if (!xml_has_tei_ns(reader))
         continue;
      if (xml_has_start_tag(reader)) {
         if (xml_has_tag(reader, "p")) {
            if (corpus_document == NULL) {
               bp_warning("unexpected <p> element");
               continue;
            }
            corpus_push_subdocument(corpus_document, "p");
         }
         else if (xml_has_tag(reader, "s")) {
            if (corpus_document == NULL) {
               bp_warning("unexpected <s> element");
               continue;
            }
            corpus_push_subdocument(corpus_document, "s");
         } else if (xml_has_tag(reader, "seg")) {
            if (corpus_document == NULL) {
               bp_warning("unexpected <seg> element");
               continue;
            }
            bool bad = false;
            const char *bad_str = xml_get_nkjp_attr(reader, "bad");
            parse_xsd_boolean(bad_str, &bad);
            if (bad)
               rc = skip_seg(reader, corpus_document);
            else
               rc = parse_seg(reader, corpus_document);
            if (rc != 0)
               break;
         } else if (xml_has_tag(reader, "text")) {
            if (corpus_document != NULL)
               corpus_end_document(corpus_document, 0);
            free(document_id);
            document_id = xml_get_id(reader);
            if (document_id == NULL)
               document_id = bp_strdup("");
            corpus_document = corpus_start_document(corpus);
            document_collect_metadata(document, corpus_document, document_id);
         }
      }
      else if (xml_has_end_tag(reader)) {
         if (xml_has_tag(reader, "p") || xml_has_tag(reader, "s")) {
            assert(corpus_document != NULL);
            corpus_pop_subdocument(corpus_document);
         }
         else if (xml_has_tag(reader, "text")) {
            if (corpus_document != NULL)
               corpus_end_document(corpus_document, 1);
            corpus_document = NULL;
         }
         if (xml_get_depth(reader) == depth)
            return 0;
      }
   }
   if (corpus_document != NULL)
      corpus_end_document(corpus_document, rc == 0);
   free(document_id);
   return -(rc != 0);
}

bool xml_has_nkjp_root(xml_reader *reader)
{
   assert(reader != NULL);
   return xml_has_tei_tag(reader, "teiCorpus");
}

