#include <stdio.h>
#include <string.h>
/*#include <ctype.h>*/
#include <stdlib.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
/* for g_utf8_strlen */
/*#include <glib.h>*/

#include "fb2parser.h"


static void parseP (xmlDocPtr doc, xmlNodePtr cur, int add_tab, FB2Content *fb);
static void parseAuthor      (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parseAnnotation  (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parseLink        (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parseStanza      (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parseSection     (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parseTitleInfo   (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parseTitle       (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parseDescription (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parsePoem        (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parseCite        (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parseStyle       (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parseEpigraph    (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parseBody        (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parseBinary      (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);
static void parseImage       (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb);

static void bufferAppend(char *data, int len, FB2Content *fb);
static void addMark (int start, int end, int type, char *id, FB2Content *fb);

/*static int removeSpaces (char *buffer, int length);*/

static FB2Mark *getLink (xmlNodePtr cur, FB2Content *fb);


/**********************************************************************/

static const char utf8_skip_data[256] = {
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
  3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
};

static int
utf8_strlen (const char *p)
{
  int len = 0;

  while (*p) {
    p = p + utf8_skip_data[*(unsigned char*)p];
    ++len;
  }

  return len;
}


/* добавить текст в буффер */
static void
bufferAppend(char *data, int len, FB2Content *fb) /*, int remove_spaces)*/
{
  char **buffer;
  size_t *buffer_size;
  int *current_index;
  /*int new_len;*/

  if (!data) return;

  /* добавляем в текущий буффер */
  if (fb->current_buffer == DESCRIPTION_BUFFER) {
    buffer = &(fb->description);
    buffer_size = &(fb->description_buffer_size);
    current_index = &(fb->description_current_index);
  } else if (fb->current_buffer == TEXT_BUFFER) {
    buffer = &(fb->text);
    buffer_size = &(fb->text_buffer_size);
    current_index = &(fb->text_current_index);
  } else if (fb->current_buffer == BINARY_BUFFER) {
    buffer = &(fb->binaries[fb->num_binaries]->buffer);
    buffer_size = &(fb->binaries[fb->num_binaries]->buffer_size);
    current_index = &(fb->binaries[fb->num_binaries]->current_index);
  }

  /* remove spaces */
/*
  if (remove_spaces) {
    new_len = removeSpaces(data, len);
    len = new_len;
  }
*/
  /* realloc buffer */
  if (*current_index+len >= *buffer_size) {
    while (1) {
      *buffer_size = *buffer_size*2;
      *buffer = (char *) realloc(*buffer, *buffer_size);
      if (*current_index+len < *buffer_size) {
	break;
      }
    }
  }

  strncpy((*buffer) + *current_index, data, len);
  *current_index += len;

  if (fb->current_buffer == TEXT_BUFFER) {
    int u_len;
    u_len = utf8_strlen(data);
    fb->utf8_current_index += u_len;
  }

  return;
}

/* добавить закладку */
static void
addMark (int start, int end, int type, char *id, FB2Content *fb)
{
  FB2Mark *m;
  int i;

  if (fb->current_buffer == TEXT_BUFFER) {
    for (i=0; i<BUF_SIZE-1; i++) {
      if (fb->marks[i] == NULL) {
	m = (FB2Mark *) malloc(sizeof(FB2Mark));
	m->start = start;
	m->end = end;
	m->type = type;
	if (id)
	  strncpy(m->id, id, BUF_SIZE);
	fb->marks[i] = m;
	fb->marks[i+1] = NULL;
	fb->num_marks++;
	break;
      }
    }
  }
  return;
}


/*
static int
removeSpaces (char *buffer, int length)
{
  int i, new_length;
  i = 0;
  new_length = length;
  while (i < new_length) {

    if (isspace(buffer[i])) {
      int j;
      buffer[i] = ' ';
      for (j = i+1; j < new_length; j++)
	if (!isspace(buffer[j])) break;
      if (i+1 != j) {
	memmove(buffer+i+1, buffer+j, length-j);
	new_length -= j-(i+1);
	i = j;
      }
    }
    i++;
  }
  return new_length;
}
*/


static FB2Mark *
getLink (xmlNodePtr cur, FB2Content *fb)
{
  FB2Mark *mark = NULL;
  xmlChar *prop;

  /* id */
  prop = xmlGetProp(cur, (const xmlChar *)"id");
  if (prop) {
    int i;

    for (i=0; i<BUF_SIZE-1; i++) {
      FB2Mark *m;

      m = fb->marks[i];
      if (m == NULL) {
	break;
      }
      if (m->type == LINK_TYPE || m->type == NOTE_TYPE) {

	if (!xmlStrcmp((const xmlChar *)m->id+1, prop)) {
	  mark = m;
	  mark->link_start = fb->utf8_current_index;
	}
      }
    }

    xmlFree(prop);

  }
  return mark;
}


static void
parseAuthor (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{

  xmlChar *content;
  cur = cur->children;
  while (cur != NULL) {

    if (    !xmlStrcmp(cur->name, (const xmlChar *)"first-name")
	 || !xmlStrcmp(cur->name, (const xmlChar *)"middle-name")
	 || !xmlStrcmp(cur->name, (const xmlChar *)"last-name")
	 || !xmlStrcmp(cur->name, (const xmlChar *)"nickname")) {

      content = xmlNodeGetContent(cur->children);
      if (content) {
	int len = strlen(fb->author);
	if (len > 0) {
	  strncat(fb->author, " ", BUF_SIZE-len);
	  len++;
	}
	strncat(fb->author, content, BUF_SIZE-len);
      }

      xmlFree(content);
    }

    cur = cur->next;
  }
  return;
}


static void
parseAnnotation (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{
  cur = cur->children;

  while (cur != NULL) {

    if (!xmlStrcmp(cur->name, (const xmlChar *)"p")) {
      parseP(doc, cur, 1, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"empty-line")) {
      bufferAppend("\n", 1, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"poem")) {
      parsePoem(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"cite")) {
      parseCite(doc, cur, fb);

    }

    cur = cur->next;
  }
  return;
}


static void
parseTitleInfo (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{

  xmlChar *content;
  cur = cur->children;
  while (cur != NULL) {

    if (!xmlStrcmp(cur->name, (const xmlChar *)"p")) {
      parseP(doc, cur, 1, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"empty-line")) {
      bufferAppend("\n", 1, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"genre")) {
      content = xmlNodeGetContent(cur->children);

      if(content) {
	/* add genre */
	int i;
	for (i=0; i<BUF_SIZE-1; i++) {
	  if (fb->genres[i] == NULL) {
	    fb->genres[i] = content;
	    fb->genres[i+1] = NULL;
	    fb->num_genres++;
	    break;
	  }
	}
      }

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"book-title")) {
      content = xmlNodeGetContent(cur->children);
      if (content)
	strncpy(fb->name, content, BUF_SIZE);
      xmlFree(content);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"author")) {
      parseAuthor(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"annotation")) {
      parseAnnotation(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"coverpage")) {
      xmlNodePtr c;
      c = cur->children;
      while (c) {
        if (!xmlStrcmp(c->name, (const xmlChar *)"image")) {
          xmlChar *cover_href;
          cover_href = xmlGetProp(c, (const xmlChar *)"href");
          strncpy(fb->cover_href, cover_href, BUF_SIZE);
          xmlFree (cover_href);
          break;
        }
        c = c->next;
      }
      /*
      content = xmlNodeGetContent(cur->children);
      if (content)
	strncpy(fb->cover_href, content, BUF_SIZE);
      */
      //if (cover_href)
      /*xmlFree(content);*/

    }

    cur = cur->next;
  }
  return;
}


static void
parseTitle (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{
  int start;

  bufferAppend("\n", 1, fb);

  start = fb->utf8_current_index;

  cur = cur->children;

  while (cur != NULL) {

    if (!xmlStrcmp(cur->name, (const xmlChar *)"p")) {
      parseP(doc, cur, 0, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"empty-line")) {
      bufferAppend("\n", 1, fb);

    }
    cur = cur->next;
  }

  addMark(start, fb->utf8_current_index-1, BOOKMARK_TYPE, NULL, fb);

  bufferAppend("\n", 1, fb);

  return;
}


static void
parseDescription (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{
  cur = cur->children;
  while (cur != NULL) {

    if (!xmlStrcmp(cur->name, (const xmlChar *)"title-info")) {
      parseTitleInfo(doc, cur, fb);

    }
    cur = cur->next;
  }
  return;
}


static void
parseEpigraph (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{
  xmlChar *content;

  cur = cur->children;
  while (cur != NULL) {

    if (xmlNodeIsText(cur)) {
      /*
      content = xmlNodeGetContent(cur);
      bufferAppend(content, xmlStrlen(content), fb);
      xmlFree(content);
      */
    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"cite")) {
      parseCite(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"poem")) {
      parsePoem(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"p")) {
      parseP(doc, cur, 1, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"empty-line")) {
      bufferAppend("\n", 1, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"text-author")) {

      content = xmlNodeGetContent(cur->children);
      if (content) {
	bufferAppend("\t", 1, fb);
	bufferAppend(content, xmlStrlen(content), fb);
	bufferAppend("\n", 1, fb);
      }
      xmlFree(content);

    }
    cur = cur->next;
  }
  return;
}


static void
parseP (xmlDocPtr doc, xmlNodePtr cur, int add_tab, FB2Content *fb)
{
  xmlChar *content;
  FB2Mark *mark;

  mark = getLink(cur, fb);

  cur = cur->children;

  if (add_tab)
    bufferAppend("\t", 1, fb);

  while (cur != NULL) {

    if (xmlNodeIsText(cur)) {
      content = xmlNodeGetContent(cur);
      if (content) {
	/*
	int len;
	len = removeSpaces(content, xmlStrlen(content));
	bufferAppend(content, len, fb);
	*/
	bufferAppend(content, xmlStrlen(content), fb);
      }
      xmlFree(content);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"style")) {
      parseStyle(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"strong")) {
      content = xmlNodeGetContent(cur->children);
      if (content) {
	int start = fb->utf8_current_index;
	bufferAppend(content, xmlStrlen(content), fb);
	addMark(start, fb->utf8_current_index, STRONG_TYPE, NULL, fb);
      }
      xmlFree(content);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"emphasis")) {
      content = xmlNodeGetContent(cur->children);
      if (content) {
	int start = fb->utf8_current_index;
	bufferAppend(content, xmlStrlen(content), fb);
	addMark(start, fb->utf8_current_index, EMPHASIS_TYPE, NULL, fb);
      }
      xmlFree(content);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"a")) {
      parseLink(doc, cur, fb);

    }

    cur = cur->next;
  }

  bufferAppend("\n", 1, fb);

  if (mark)
    mark->link_end = fb->text_current_index;

  return;
}


static void
parseLink (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{
  xmlChar *content;
  xmlChar *href_prop, *note_prop;
  int start = fb->utf8_current_index;

  /* id */
  href_prop = xmlGetProp(cur, (const xmlChar *)"href");
  /* type="note" */
  note_prop = xmlGetProp(cur, (const xmlChar *)"type");
  if (note_prop && (!xmlStrcmp(note_prop, (const xmlChar *)"note")))
    bufferAppend("[", 1, fb);

  cur = cur->children;

  /*bufferAppend("[", 1, fb);*/

  while (cur != NULL) {

    if (xmlNodeIsText(cur)) {
      content = xmlNodeGetContent(cur);
      if (content)
	bufferAppend(content, xmlStrlen(content), fb);
      xmlFree(content);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"style")) {
      parseStyle(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"strong")) {
      content = xmlNodeGetContent(cur->children);
      if (content) {
	int st = fb->utf8_current_index;
	bufferAppend(content, xmlStrlen(content), fb);
	addMark(st, fb->utf8_current_index, STRONG_TYPE, NULL, fb);
      }
      xmlFree(content);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"emphasis")) {
      content = xmlNodeGetContent(cur->children);
      if (content) {
	int st = fb->utf8_current_index;
	bufferAppend(content, xmlStrlen(content), fb);
	addMark(st, fb->utf8_current_index, EMPHASIS_TYPE, NULL, fb);
      }
      xmlFree(content);

    }

    cur = cur->next;
  }

  if (href_prop) {
    if (note_prop && (!xmlStrcmp(note_prop, (const xmlChar *)"note"))) {
      bufferAppend("]", 1, fb);
      addMark(start, fb->utf8_current_index, NOTE_TYPE, href_prop, fb);
      xmlFree(note_prop);
    } else {
      addMark(start, fb->utf8_current_index, LINK_TYPE, href_prop, fb);
    }
    xmlFree(href_prop);
  }

  return;
}


static void
parsePoem (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{
  cur = cur->children;
  while (cur != NULL) {

    if (!xmlStrcmp(cur->name, (const xmlChar *)"title")) {
      parseTitle(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"epigraph")) {
      parseEpigraph(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"p")) {
      parseP(doc, cur, 1, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"empty-line")) {
      bufferAppend("\n", 1, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"stanza")) {
      bufferAppend("\n", 1, fb);
      parseStanza(doc, cur, fb);
    }

    cur = cur->next;
  }
  bufferAppend("\n", 1, fb);
  return;
}


static void
parseStanza (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{
  /*xmlChar *content;*/
  cur = cur->children;

  while (cur != NULL) {

    if (!xmlStrcmp(cur->name, (const xmlChar *)"v")) {
      parseP(doc, cur, 1, fb);
      /*
      content = xmlNodeGetContent(cur->children);
      if (content) {
	bufferAppend("\t", 1, fb);
	bufferAppend(content, xmlStrlen(content), fb);
	bufferAppend("\n", 1, fb);
      }
      xmlFree(content);
      */
    }
    cur = cur->next;
  }
  return;
}


static void
parseCite (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{
  xmlChar *content;
  cur = cur->children;

  while (cur != NULL) {

    if (!xmlStrcmp(cur->name, (const xmlChar *)"poem")) {
      parsePoem(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"p") ) {
      parseP(doc, cur, 1, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"empty-line")) {
      bufferAppend("\n", 1, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"text-author")) {
      content = xmlNodeGetContent(cur->children);
      if (content) {
	bufferAppend("\t", 1, fb);
	bufferAppend(content, xmlStrlen(content), fb);
	bufferAppend("\n", 1, fb);
      }
      xmlFree(content);

    }

    cur = cur->next;
  }
  return;
}


static void
parseStyle (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{
  xmlChar *content;
  xmlChar *prop;
  int start = fb->utf8_current_index;

  prop = xmlGetProp(cur, (const xmlChar *)"name");

  cur = cur->children;

  while (cur != NULL) {

    if (xmlNodeIsText(cur)) {
      content = xmlNodeGetContent(cur);
      if (content)
	bufferAppend(content, xmlStrlen(content), fb);
      xmlFree(content);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"style")) {
      parseStyle(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"strong")) {
      content = xmlNodeGetContent(cur->children);
      if (content) {
	int st = fb->utf8_current_index;
	bufferAppend(content, xmlStrlen(content), fb);
	addMark(st, fb->utf8_current_index, STRONG_TYPE, NULL, fb);
      }
      xmlFree(content);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"emphasis")) {
      content = xmlNodeGetContent(cur->children);
      if (content) {
	int st = fb->utf8_current_index;
	bufferAppend(content, xmlStrlen(content), fb);
	addMark(st, fb->utf8_current_index, EMPHASIS_TYPE, NULL, fb);
      }
      xmlFree(content);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"a")) {
      parseLink(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"image")) {
      parseImage(doc, cur, fb);

    }

    cur = cur->next;
  }

  if (prop && (!xmlStrcmp(prop, (const xmlChar *)"italic"))) {
    addMark(start, fb->utf8_current_index, EMPHASIS_TYPE, NULL, fb);
    xmlFree(prop);
  }

  return;
}

static void
parseImage (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{
  xmlChar *prop;

  prop = xmlGetProp(cur, (const xmlChar *)"href");
  if (prop) {

    bufferAppend("\n\n", 2, fb);

    addMark(fb->utf8_current_index-1, fb->utf8_current_index,
	    IMAGE_TYPE, prop, fb);

  }
  xmlFree(prop);

  return;
}


static void
parseSection (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{
  FB2Mark *mark;

  mark = getLink(cur, fb);

  cur = cur->children;
  while (cur != NULL) {

    if (!xmlStrcmp(cur->name, (const xmlChar *)"title")) {
      parseTitle(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"section")) {
      parseSection(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"poem")) {
      parsePoem(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"cite")) {
      parseCite(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"epigraph")) {
      parseEpigraph(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"p")) {
      parseP(doc, cur, 1, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"subtitle")) {
      int start = fb->utf8_current_index;
      parseP(doc, cur, 0, fb);
      addMark(start, fb->utf8_current_index, BOOKMARK_TYPE, NULL, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"empty-line")) {
      bufferAppend("\n", 1, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"image")) {
      parseImage(doc, cur, fb);
    }

    cur = cur->next;
  }

  if (mark)
    mark->link_end = fb->text_current_index;

  return;
}


static void
parseBody (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{
  xmlChar *prop;

  prop = xmlGetProp(cur, (const xmlChar *)"name");
  if (prop) {
    int start = fb->utf8_current_index;
    bufferAppend("\n\n", 2, fb);
    bufferAppend(prop, xmlStrlen(prop), fb);
    bufferAppend("\n\n", 2, fb);
    addMark(start, fb->utf8_current_index, BOOKMARK_TYPE, NULL, fb);
    xmlFree(prop);
  }


  cur = cur->children;
  while (cur != NULL) {

    if (!xmlStrcmp(cur->name, (const xmlChar *)"section")) {
      parseSection(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"title")) {
      parseTitle(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"image")) {
      parseImage(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"epigraph")) {
      parseEpigraph(doc, cur, fb);

    }


    cur = cur->next;
  }
  return;
}


static void
parseBinary (xmlDocPtr doc, xmlNodePtr cur, FB2Content *fb)
{

  xmlChar *content;
  xmlChar *prop;

  if(fb->num_binaries == BUF_SIZE-1) return;

  /* initialize new binary buffer */
  fb->binaries[fb->num_binaries] = (FB2Binary *) malloc(sizeof(FB2Binary));
  fb->binaries[fb->num_binaries]->buffer = (char *) malloc(BUF_SIZE);
  fb->binaries[fb->num_binaries]->buffer_size = BUF_SIZE;
  fb->binaries[fb->num_binaries+1] = NULL;
  fb->binaries[fb->num_binaries]->current_index = 0;
  fb->binaries[fb->num_binaries]->id[0] = '\0';
  fb->binaries[fb->num_binaries]->content_type[0] = '\0';

  /* id */
  prop = xmlGetProp(cur, (const xmlChar *)"id");
  if (prop)
    strncpy(fb->binaries[fb->num_binaries]->id, prop, BUF_SIZE);
  xmlFree(prop);

  /* content-type */
  prop = xmlGetProp(cur, (const xmlChar *)"content-type");
  if (prop)
    strncpy(fb->binaries[fb->num_binaries]->content_type, prop, BUF_SIZE);
  xmlFree(prop);

  cur = cur->children;

  while (cur != NULL) {

    if (xmlNodeIsText(cur)) {
      content = xmlNodeGetContent(cur);

      if (content) {
	bufferAppend(content, xmlStrlen(content), fb);
      }
      xmlFree(content);
    }

    cur = cur->next;
  }

  bufferAppend("\0", 1, fb);

  fb->num_binaries++;

  return;
}


FB2Content *
parseFile(char *filename)
{

  xmlDocPtr doc;
  xmlNodePtr cur;
  FB2Content *fb;

  doc = xmlParseFile(filename);

  if (doc == NULL ) {
    fprintf(stderr, "Document not parsed successfully. \n");
    return NULL;
  }

  cur = xmlDocGetRootElement(doc);

  if (cur == NULL) {
    fprintf(stderr, "empty document\n");
    xmlFreeDoc(doc);
    return NULL;
  }

  if (xmlStrcmp(cur->name, (const xmlChar *) "FictionBook")) {
    fprintf(stderr, "document of the wrong type, root node != FictionBook\n");
    xmlFreeDoc(doc);
    return NULL;
  }

  fb = (FB2Content *) malloc(sizeof(FB2Content));

  /* initialize buffers */
  fb->text = (char *) malloc(BUF_SIZE);
  fb->text_buffer_size = BUF_SIZE;
  fb->text_current_index = 0;
  fb->utf8_current_index = 0;
  fb->description = (char *) malloc(BUF_SIZE);
  fb->description_buffer_size = BUF_SIZE;
  fb->description_current_index = 0;

  *fb->author = '\0';
  *fb->name = '\0';
  *fb->cover_href = '\0';

  fb->genres[0] = NULL;
  fb->num_genres = 0;

  fb->marks[0] = NULL;
  fb->num_marks = 0;

  fb->binaries[0] = NULL;
  fb->num_binaries = 0;

  //fb->links[0] = NULL;
  //fb->num_links = 0;

  cur = cur->children;
  while (cur != NULL) {

    if (!xmlStrcmp(cur->name, (const xmlChar *)"description")) {
      fb->current_buffer = DESCRIPTION_BUFFER;
      parseDescription(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"body")) {
      fb->current_buffer = TEXT_BUFFER;
      parseBody(doc, cur, fb);

    } else if (!xmlStrcmp(cur->name, (const xmlChar *)"binary")) {
      fb->current_buffer = BINARY_BUFFER;
      parseBinary(doc, cur, fb);

    }

    cur = cur->next;
  }
  xmlFreeDoc(doc);

  fb->current_buffer = DESCRIPTION_BUFFER;
  bufferAppend("\0", 1, fb);

  fb->current_buffer = TEXT_BUFFER;
  bufferAppend("\0", 1, fb);

  return fb;
}

void
freeFB2 (FB2Content *fb)
{
  int i;

  for (i=0; i<BUF_SIZE-1; i++) {
    if (fb->genres[i] == NULL)
      break;
    xmlFree(fb->genres[i]);
  }

  for (i=0; i<BUF_SIZE-1; i++) {
    FB2Mark *m = fb->marks[i];
    if (m == NULL)
      break;
    free(m);
  }

  for (i=0; i<BUF_SIZE-1; i++) {
    FB2Binary *bin = fb->binaries[i];
    if (bin == NULL)
      break;
    free(bin->buffer);
    free(bin);
  }

  free(fb->description);
  free(fb->text);

  return;
}


/******** test ********/
int
main(int argc, char **argv)
{
  char *docname;
  FB2Content *fb;
  int i;

  if (argc <= 1) {
    printf("Usage: %s docname\n", argv[0]);
    return 1;
  }

  docname = argv[1];
  fb = parseFile(docname);

  /*
  printf("author: %s\n", fb->author);
  printf("name: %s\n", fb->name);

  printf("genres:\n");
  for (i=0; i<BUF_SIZE-1; i++) {
    if (fb->genres[i] == NULL) {
      break;
    }
    printf("[%d] %s\n", i, fb->genres[i]);
  }
  */
  /*
  printf("-- marks --\n");
  for (i=0; i<BUF_SIZE-1; i++) {
    FB2Mark *m;
    int j;
    m = fb->marks[i];
    if (m == NULL) {
      break;
    }
    if (m->type == IMAGE_TYPE) {
      printf ("image: %s, offset=%d\n", m->id, m->start);

      //for (j = m->start; j < m->end; j++)
      //printf("%c", fb->text[j]);
      //printf("\n");

    }
  }
  */

  printf("-- links --\n");
  for (i=0; i<BUF_SIZE-1; i++) {
    FB2Mark *m;

    m = fb->marks[i];
    if (m == NULL) {
      break;
    }
    if (m->type == LINK_TYPE) {
      int j;

      printf ("link: %s, start=%d, end=%d, link_start=%d, link_end=%d\n",
	      m->id, m->start, m->end, m->link_start, m->link_end);

      for (j = m->start; j < m->end; j++)
	printf("%c", fb->text[j]);
      printf("\n");

      printf("[[");
      for (j = m->link_start; j < m->link_end; j++)
	printf("%c", fb->text[j]);
      printf("]]\n");

    }
  }

  /*
  printf("-- description --\n");
  printf("%s\n", fb->description);
  */
  //printf("-- text --\n");
  //printf("%s\n", fb->text);

  /*
  printf("num of binaries: %d\n", fb->num_binaries);

  for(i=0; i<BUF_SIZE-1; i++) {
    FB2Binary *bin;

    bin = fb->binaries[i];

    if (bin == NULL) {
      break;
    }

    printf("binary %d: id: \"%s\"\n", i, bin->id);
    printf("binary %d: content-type: \"%s\"\n", i, bin->content_type);
  }
  */

  freeFB2(fb);

  return 0;
}


