#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.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 ASSERT_SCANF_ABORT(r, items, field) \
  if (((r)==EOF || (r)!=(items))) { printf("Illegal value for %s\n", (field)); return XML_ABORT; }
#define SAFE_FREE(p) if(p!=NULL) free(p);
#define BOOKP ((BOOKPARSER*)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 tagBOOK {
  int id;
  char *author, *title, *genre, *publish_date, *description;
  float price;
} BOOK;

typedef struct tagBOOKPARSER {
  XMLSTRINGBUF textBuf;
  LPXMLVECTOR books;
  BOOK *curBook;
  int fld;
} BOOKPARSER;

int StartElement(void *UserData, const XMLCH *uri, const XMLCH *localName, const XMLCH *qName, LPXMLVECTOR atts)
{
  LPXMLDTDVALIDATOR v = (LPXMLDTDVALIDATOR)UserData;

  if (!strcmp(qName, "book")) {
    int ret;
    LPXMLRUNTIMEATT a;
    /* append and initialize new book: */
    BOOKP->curBook = XMLVector_Append(BOOKP->books, NULL);
    ASSERT_MEM_ABORT(BOOKP->curBook);
    memset(BOOKP->curBook, 0, sizeof(struct tagBOOK));
    BOOKP->fld = -1;
    /* get book id: */
    a = XMLParser_GetNamedItem(v->parser, "id");
    /* a == NULL doesn't happen 'cos id is #REQUIRED */
    ret = sscanf(a->value, "bk%d", &BOOKP->curBook->id);
    ASSERT_SCANF_ABORT(ret, 1, a->qname);
  }
  else if (!strcmp(qName, "author")) BOOKP->fld = offsetof(BOOK, author);
  else if (!strcmp(qName, "title")) BOOKP->fld = offsetof(BOOK, title);
  else if (!strcmp(qName, "genre")) BOOKP->fld = offsetof(BOOK, genre);
  else if (!strcmp(qName, "price")) BOOKP->fld = offsetof(BOOK, price);
  else if (!strcmp(qName, "publish_date")) BOOKP->fld = offsetof(BOOK, publish_date);
  else if (!strcmp(qName, "description")) BOOKP->fld = offsetof(BOOK, description);
  return XML_OK;
}

int EndElement(void *UserData, const XMLCH *uri, const XMLCH *localName, const XMLCH *qName)
{
  LPXMLDTDVALIDATOR v = (LPXMLDTDVALIDATOR)UserData;
  if (BOOKP->fld != -1) {
    char *s = XMLStringbuf_ToString(&BOOKP->textBuf);
    ASSERT_MEM_ABORT(s);
    if (BOOKP->fld == offsetof(BOOK, price)) {
      int ret = sscanf(s, "%f", &BOOKP->curBook->price);
      ASSERT_SCANF_ABORT(ret, 1, qName);
    }
    else {
      char **sp = (char**)((char*)BOOKP->curBook+BOOKP->fld);
      *sp = strdup(s);
      ASSERT_MEM_ABORT(*sp);
    }
    ASSERT_MEM_ABORT(XMLStringbuf_SetLength(&BOOKP->textBuf, 0));
    BOOKP->fld = -1;
  }
  return XML_OK;
}

int Characters(void *UserData, const XMLCH *Chars, int cbChars)
{
  LPXMLDTDVALIDATOR v = (LPXMLDTDVALIDATOR)UserData;
  ASSERT_MEM_ABORT(XMLStringbuf_Append(&BOOKP->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[])
{
  BOOKPARSER bparser;
  BOOK *b;
  LPXMLPARSER parser;
  LPXMLDTDVALIDATOR vp;
  int i, success;

  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(&bparser.books, 6, sizeof(BOOK))) {
    puts("Error creating books vector in main()");
    return 1;
  }

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

  vp->UserData = &bparser;

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

  success = XMLParser_ParseValidateDTD(vp, parser, cstream, stdin, 0);

  for (i=0; i<bparser.books->length; i++) {
    b = XMLVector_Get(bparser.books, i);
    if (success)
      printf("id: %d\n"
         "author: %s\n"
         "title: %s\n"
         "genre: %s\n"
         "price: %f\n"
         "publish_date: %s\n"
         "description: %s\n\n",
         b->id, b->author, b->title, b->genre,
         b->price, b->publish_date, b->description
      );
    SAFE_FREE(b->author);
    SAFE_FREE(b->title);
    SAFE_FREE(b->genre);
    SAFE_FREE(b->publish_date);
    SAFE_FREE(b->description);
  }

  XMLParser_FreeDTDValidator(vp);
  XMLParser_Free(parser);
  XMLStringbuf_Free(&bparser.textBuf);
  XMLVector_Free(bparser.books);
  return 0;
}