#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "libparsifal/parsifal.h"
#include "libparsifal/dtdvalid.h"

#define ASSERT_MEM_ABORT(p) \
  if (!(p)) { printf("Out of memory! Line: %d\n", __LINE__); return XML_ABORT; }
#define STORYP ((STORYPARSER*)v->UserData)

int StartElement(void *UserData, const XMLCH *uri, const XMLCH *localName, const XMLCH *qName, LPXMLVECTOR atts);
int EndElement(void *UserData, const XMLCH *uri, const XMLCH *localName, const XMLCH *qName);
int Characters(void *UserData, const XMLCH *Chars, int cbChars);
void ErrorHandler(LPXMLPARSER parser);
int cstream(BYTE *buf, int cBytes, int *cBytesActual, void *inputData);

typedef struct tagZENSTORY {
  char *title;
  char *author;
  char *body;
} ZENSTORY;

typedef struct tagSTORYPARSER {
  LPXMLPARSER parser;
  XMLSTRINGBUF textBuf;
  LPXMLVECTOR stories;
  ZENSTORY *curStory;
  char **curStr;
} STORYPARSER;

int StartElement(void *UserData, const XMLCH *uri, const XMLCH *localName, const XMLCH *qName, LPXMLVECTOR atts)
{
  LPXMLDTDVALIDATOR v = (LPXMLDTDVALIDATOR)UserData;
  /* initialize new story: */
  if (!strcmp(qName, "story")) {
    STORYP->curStory = XMLVector_Append(STORYP->stories, NULL);
    ASSERT_MEM_ABORT(STORYP->curStory);
    STORYP->curStory->title = (char*)NULL;
    STORYP->curStory->author = (char*)NULL;
    STORYP->curStory->body = (char*)NULL;
    STORYP->curStr = NULL;
  }
  else if (!strcmp(qName, "title"))
    STORYP->curStr = &STORYP->curStory->title;
  else if (!strcmp(qName, "author"))
    STORYP->curStr = &STORYP->curStory->author;
  else if (!strcmp(qName, "body"))
    STORYP->curStr = &STORYP->curStory->body;
  return XML_OK;
}

int EndElement(void *UserData, const XMLCH *uri, const XMLCH *localName, const XMLCH *qName)
{
  LPXMLDTDVALIDATOR v = (LPXMLDTDVALIDATOR)UserData;
  if (STORYP->curStr) {
    if (!strcmp(qName, "b"))
      return XML_OK; /* handles mixed content elements simply here */

    *STORYP->curStr = strdup(XMLStringbuf_ToString(&STORYP->textBuf));
    ASSERT_MEM_ABORT(*STORYP->curStr);
    ASSERT_MEM_ABORT(XMLStringbuf_SetLength(&STORYP->textBuf, 0));
    STORYP->curStr = NULL;
  }
  return XML_OK;
}

int Characters(void *UserData, const XMLCH *Chars, int cbChars)
{
  LPXMLDTDVALIDATOR v = (LPXMLDTDVALIDATOR)UserData;
  ASSERT_MEM_ABORT(XMLStringbuf_Append(&STORYP->textBuf, (XMLCH*)Chars, cbChars));
  return XML_OK;
}

void ErrorHandler(LPXMLPARSER parser)
{
  if (parser->ErrorCode == ERR_XMLP_VALIDATION) {
    LPXMLDTDVALIDATOR vp = (LPXMLDTDVALIDATOR)parser->UserData;
    printf("Validation Error: %s\nErrorLine: %d ErrorColumn: %d\n",
      vp->ErrorString, vp->ErrorLine, vp->ErrorColumn);
  }
  else {
    printf("Parsing Error: %s\nErrorLine: %d ErrorColumn: %d\n",
      parser->ErrorString, parser->ErrorLine, parser->ErrorColumn);
  }
}

int cstream(BYTE *buf, int cBytes, int *cBytesActual, void *inputData)
{
  *cBytesActual = fread(buf, 1, cBytes, (FILE*)inputData);
  return (*cBytesActual < cBytes);
}

int main(int argc, char* argv[])
{
  STORYPARSER sparser;
  LPXMLPARSER parser;
  LPXMLDTDVALIDATOR vp;

  if (!XMLParser_Create(&parser)) {
    puts("Error creating parser!");
    return 1;
  }

  vp = XMLParser_CreateDTDValidator();
  if (!vp) {
    puts("Error creating DTDValidator in main()");
    return 1;
  }

  if (!XMLVector_Create(&sparser.stories, 6, sizeof(ZENSTORY))) {
    puts("Error creating stories vector in main()");
    return 1;
  }

  /* init Stringbuf: blockSize 256, no pre-allocation: */
  XMLStringbuf_Init(&sparser.textBuf, 256, 0);

  sparser.parser = parser;
  sparser.curStr = NULL;
  vp->UserData = &sparser;

  parser->errorHandler = ErrorHandler;
  parser->startElementHandler = StartElement;
  parser->endElementHandler = EndElement;
  parser->charactersHandler = Characters;

  if (XMLParser_ParseValidateDTD(vp, parser, cstream, stdin, 0)) {

    /* present the stories (we'll free the strings in the same loop) : */
    int i;
    ZENSTORY *story;
    for (i=0; i<sparser.stories->length; i++) {
      story = XMLVector_Get(sparser.stories, i);
      if (story) {
        if (story->title) {
          printf("title: %s\n", story->title);
          free(story->title);
        }
        if (story->author) {
          printf("author: %s\n", story->author);
          free(story->author);
        }
        if (story->body) {
          printf("story: %s\n",story->body);
          free(story->body);
        }
      }
    }
  }

  XMLParser_FreeDTDValidator(vp);
  XMLParser_Free(parser);
  XMLStringbuf_Free(&sparser.textBuf);
  XMLVector_Free(sparser.stories);
  return 0;
}