/*******************************************************************\

              Copyright (C) 2003 Joseph Coffland

    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., 59 Temple Place - Suite 330, Boston, MA
                           02111-1307, USA.

            For information regarding this software email
                   jcofflan@users.sourceforge.net

\*******************************************************************/


#include "XMLPullParserImpl.h"
#include "XMLSerializable.h"

#include <XercesUtils/StreamInputSource.h>
#include <XercesUtils/XercesStr.h>

#include <BasicUtils/BasicException.h>

#ifdef THROW
#undef THROW
#define XML_LOCATION (currentEvent ? getLocation() : FILE_LOCATION)

#define THROW(msg) throw BasicException((msg), XML_LOCATION);
#endif

#include <BasicUtils/Zap.h>

#include <ctype.h>

#include <xercesc/sax2/SAX2XMLReader.hpp>
#include <xercesc/sax2/XMLReaderFactory.hpp>
#include <xercesc/sax/InputSource.hpp>
#include <xercesc/framework/LocalFileInputSource.hpp>
#include <xercesc/sax/SAXParseException.hpp>
#include <xercesc/validators/common/Grammar.hpp>
XERCES_CPP_NAMESPACE_USE;

using namespace std;

const string getQNamePrefix(const string &qname) {
  string::size_type pos = qname.find(":");
  if (pos != string::npos)
    return qname.substr(0, pos);

  return "";
}


XMLPullParserImpl::XMLPullParserImpl() {
  locator = NULL;

  depth = 0;
  inParse = false;
  currentEvent = NULL;
  globalMask = DEFAULT_MASK;

  createParser();
}

XMLPullParserImpl::~XMLPullParserImpl() {
  clearEvents();
  zap(parser);
}

void XMLPullParserImpl::createParser() {
  parser = XMLReaderFactory::createXMLReader();
  parser->setFeature(XMLUni::fgSAX2CoreNameSpaces, true);
  parser->setFeature(XMLUni::fgSAX2CoreValidation, true);
  parser->setFeature(XMLUni::fgXercesDynamic, true);
  parser->setFeature(XMLUni::fgXercesSchemaFullChecking, true);
  parser->setFeature(XMLUni::fgXercesUseCachedGrammarInParse, true);

  parser->setErrorHandler(&errorHandler);
  parser->setContentHandler(this);
}

void XMLPullParserImpl::clearEvents() {
  zap(currentEvent);

  while (!eventQueue.empty()) {
    delete eventQueue.front();
    eventQueue.pop_front();
  }  
}

void XMLPullParserImpl::pumpEvents() {
  if (!parser->parseNext(token))
    THROW("pumpEvents() Invalid XML document!");
}


void XMLPullParserImpl::initParse(const InputSource &in) {
  resetParser();

  inParse = true;

  if (!parser->parseFirst(in, token)) {
    inParse = false;
    THROW("initParse() Invalid or empty XML document!");
  }

  // Load first event
  next();
}


// Begin XMLPullParser interface
void XMLPullParserImpl::deserialize(const string targetNamespace,
				    const string topElementName,
				    XMLSerializable &serializable) {
  setEventMask(DEFAULT_MASK);

  event_t e = next(-TEXT);

  bool start = false;
  if (e == START_DOCUMENT) {
    e = next(-TEXT);
    start = true;
  }

  assertName(topElementName);
  assertNamespace(targetNamespace);
  match(START_ELEMENT);

  serializable.readXML(*this);
  
  match(END_ELEMENT,
	"deserialize() XMLSerializable did not parse all it's data!");
  
  if (start) match(END_DOCUMENT);
}

BasicFileLocation XMLPullParserImpl::getLocation() {
  return BasicFileLocation((locator && locator->getSystemId()) ?
			   XStrL(locator->getSystemId()) :
			   "Unknown File",
			   getLineNumber(), getColumnNumber());
}

int XMLPullParserImpl::getLineNumber() {
  if (!currentEvent) return -1;
  return currentEvent->line;
}

int XMLPullParserImpl::getColumnNumber() {
  if (!currentEvent) return -1;
  return currentEvent->col;
}

unsigned int XMLPullParserImpl::getNumAttributes() {
  ASSERT_OR_THROW("getAttributeCount() Not a START_ELEMENT event!",
		  getEventType() == START_ELEMENT);
  
  return currentEvent->numAttribs;
}

const XMLAttrib &XMLPullParserImpl::getAttribute(const unsigned int index) {
  ASSERT_OR_THROW("getAttribute() Not a START_ELEMENT event!",
		  getEventType() == START_ELEMENT);
  
  ASSERT_OR_THROW("getAttribute() index out of range!",
		  0 <= index && index <= currentEvent->numAttribs);

  return currentEvent->attribs[index];
}

const XMLAttrib &XMLPullParserImpl::getAttribute(const string name,
						 const string uri) {
  int id = findAttribute(name, uri);
  if (id != -1) return currentEvent->attribs[id];

  THROW(string("Attribute '") + name + "' not found in element '" + 
	getName() + "'!");
}

int XMLPullParserImpl::findAttribute(const string name,
				     const string uri) {
  ASSERT_OR_THROW("findAttribute() Not a START_ELEMENT event!",
		  getEventType() == START_ELEMENT);  
  
  for (unsigned int i = 0; i < currentEvent->numAttribs; i++) {
    if (currentEvent->attribs[i].name == name &&
	currentEvent->attribs[i].uri == uri)
      return i;
  }

  return -1;
}

string XMLPullParserImpl::getName() {
  ASSERT_OR_THROW("getName() Invalid event type!",
		  (getEventType() & 
		   (START_ELEMENT | END_ELEMENT |
		    PROCESSING_INSTRUCTION)) != 0);

  return currentEvent->name;
}

string XMLPullParserImpl::getNamespace() {
  ASSERT_OR_THROW("getNamespace() Invalid event type!",
		  (getEventType() & 
		   (START_ELEMENT | END_ELEMENT)) != 0);

  return currentEvent->uri;
}

string XMLPullParserImpl::getPrefix() {
  ASSERT_OR_THROW("getPrefix() Invalid event type!",
		  (getEventType() & 
		   (START_ELEMENT | END_ELEMENT)) != 0);

  return currentEvent->prefix;
 }

string XMLPullParserImpl::getText() {
  ASSERT_OR_THROW("getText() Invalid event type!",
		  (getEventType() & (TEXT | PROCESSING_INSTRUCTION)) != 0);

  return currentEvent->text;
}

void XMLPullParserImpl::assertName(const string name) {
  ASSERT_OR_THROW(string("Excpected name '") + name + "' found '" +
		  getName() + "'!", getName() == name);
}

void XMLPullParserImpl::assertNamespace(const string uri) {
  ASSERT_OR_THROW(string("Excpected namespace '") + uri + "' found '" +
		  getNamespace() + "'!", getNamespace() == uri);
}

bool XMLPullParserImpl::isWhitespace() {
  ASSERT_OR_THROW("getText() Invalid event type!",
		  getEventType() == TEXT);

  return currentEvent->isWhitespace;
}

bool XMLPullParserImpl::isEmptyElementTag() {
  ASSERT_OR_THROW("isEmptyElementTag() Not a START_ELEMENT event!",
		  getEventType() == START_ELEMENT);

  while (eventQueue.empty()) pumpEvents();

  if (eventQueue.front()->event == END_ELEMENT)
    return true;
  
  return false;
}

void XMLPullParserImpl::skip(event_t mask) {
  ASSERT_OR_THROW("Parse is not initialized!", inParse);

  mask = globalMask & ~mask;

  if ((currentEvent->event & mask) == 0) next(mask);
}

XMLPullParser::event_t XMLPullParserImpl::next() {
  return next(globalMask);
}

XMLPullParser::event_t XMLPullParserImpl::next(event_t mask) {
  ASSERT_OR_THROW("Parse is not initialized!", inParse);

  if (((int)mask) < 0) mask = globalMask & ~(-mask);

  event_t lastEvent = NULL_EVENT;

  // Delete last event
  if (currentEvent) {
    lastEvent = currentEvent->event;
    delete currentEvent;
    currentEvent = NULL;
  }

  // Check for end of document
  if (lastEvent == END_DOCUMENT) {
    inParse = false;
    return NULL_EVENT;

  } else { // Get the next event

    // pumpEvents() will throw on error or end of file.
    while (eventQueue.empty()) pumpEvents();

    currentEvent = eventQueue.front();
    eventQueue.pop_front();
  }

  // Update depth
  if (currentEvent->event == START_ELEMENT) depth++;
  if (lastEvent == END_ELEMENT) depth--;

  // Skip this event if it is not in the event mask.
  if ((currentEvent->event & mask) == 0)
    return next(mask);


  return currentEvent->event;
}

XMLPullParser::event_t XMLPullParserImpl::getEventType() {
  if (!currentEvent) return NULL_EVENT;
  return currentEvent->event;
}

void XMLPullParserImpl::setFeature(const string name, bool state) {
  XMLCh *xName = XMLString::transcode(name.c_str());
  parser->setFeature(xName, state);
  XMLString::release(&xName);
}

bool XMLPullParserImpl::getFeature(const string name) {
  XMLCh *xName = XMLString::transcode(name.c_str());
  bool value = parser->getFeature(xName);
  XMLString::release(&xName);

  return value;
}

void *XMLPullParserImpl::getProperty(const string name) {
  XMLCh *xName = XMLString::transcode(name.c_str());
  void *value = parser->getProperty(xName);
  XMLString::release(&xName);
  
  return value;
}

void XMLPullParserImpl::setProperty(const string name, void *value) {
  XMLCh *xName = XMLString::transcode(name.c_str());
  parser->setProperty(xName, value);
  XMLString::release(&xName);
}

void XMLPullParserImpl::assertEvent(event_t event, const string msg) {
  ASSERT_OR_THROW(string("Expected event '") +
		  string(XMLParseEvent::getEventName(event)) +
		  string("' received '") +
		  string(XMLParseEvent::getEventName(getEventType())) +
		  string("': " + msg),
		  check(event));  
}

void XMLPullParserImpl::match(event_t event, string msg) {
  match(event, globalMask, msg);
}

void XMLPullParserImpl::match(event_t event, event_t mask, string msg) {
  assertEvent(event, msg);
  next(mask);
}

string XMLPullParserImpl::matchSimple() {
  match(START_ELEMENT);

  string text;
  while (check(TEXT)) {
    text += getText();
    next();
  }

  match(END_ELEMENT);

  return text;
}

void XMLPullParserImpl::initParse(istream &in) {
  StreamInputSource input(in);
  initParse(input);
}

void XMLPullParserImpl::initParse(const std::string filename) {
  try {
    LocalFileInputSource input(XStrG(filename.c_str()));
    initParse(input);
  } catch (XMLException &e) {
    throw BasicException(XStrL(e.getMessage()));
  }
}

void XMLPullParserImpl::resetParser() {
  clearEvents();
  locator = NULL;
  depth = 0;
  if (inParse) parser->parseReset(token);
  inParse = false;
}

void XMLPullParserImpl::preparseSchema(std::istream &schema) {
  ASSERT_OR_THROW("Cannot preparse schema while in parse!",
		  !inParse);

  // Turn off dynamic validation to force use of preparsed grammars.
  parser->setFeature(XMLUni::fgXercesDynamic, false);

  StreamInputSource *schemaInput = new StreamInputSource(schema);

  if (!parser->loadGrammar(*schemaInput, Grammar::SchemaGrammarType, true))
    THROW("setSchemaGrammar() Error loading schema grammar!");
}
// End XMLPullParser interface


// Start Xerces ContentHandler interface
void XMLPullParserImpl::characters(const XMLCh *const chars,
				   const unsigned int length) {
  XMLParseEvent *newEvent = new XMLParseEvent(TEXT, locator);
  newEvent->text = XStrL(chars);

  if (newEvent->text.find_first_not_of(" \t\r\n") == string::npos)
    newEvent->isWhitespace = true;

  pushEvent(newEvent);
}

void XMLPullParserImpl::endDocument() {
  pushEvent(new XMLParseEvent(END_DOCUMENT, locator));
}

void XMLPullParserImpl::endElement(const XMLCh *const uri,
				   const XMLCh *const localname,
				   const XMLCh *const qname) {
  XMLParseEvent *newEvent = new XMLParseEvent(END_ELEMENT, locator);
  newEvent->name = XStrL(localname);
  newEvent->uri = XStrL(uri);

  string cppQName = XStrL(qname);
  newEvent->prefix = getQNamePrefix(cppQName);

  pushEvent(newEvent);
}

string upperCase(const string &str) {
  string result = str;
  
  for (unsigned int i = 0; i < str.size(); i++)
    result[i] = toupper(result[i]);

  return result;
}

void XMLPullParserImpl::processingInstruction(const XMLCh *const target,
					      const XMLCh *const data) {
  XMLParseEvent *newEvent = new XMLParseEvent(PROCESSING_INSTRUCTION, locator);

  newEvent->name = XStrL(target);
  newEvent->text = XStrL(data);

  pushEvent(newEvent);
}

void XMLPullParserImpl::startDocument() {
  pushEvent(new XMLParseEvent(START_DOCUMENT, locator));
}

void XMLPullParserImpl::startElement(const XMLCh *const uri,
				     const XMLCh *const localname,
				     const XMLCh *const qname,
				     const Attributes &attrs) {
  XMLParseEvent *newEvent = new XMLParseEvent(START_ELEMENT, locator);

  newEvent->name = XStrL(localname);
  newEvent->uri = XStrL(uri);

  string cppQName = XStrL(qname);
  newEvent->prefix = getQNamePrefix(cppQName);

  // Attributes
  newEvent->numAttribs = attrs.getLength();
  newEvent->attribs = new XMLAttrib[newEvent->numAttribs];

  for (unsigned int i = 0; i < newEvent->numAttribs; i++) {
    newEvent->attribs[i].name = XStrL(attrs.getLocalName(i));
    newEvent->attribs[i].uri = XStrL(attrs.getURI(i));
    newEvent->attribs[i].value = XStrL(attrs.getValue(i));
    newEvent->attribs[i].type = XStrL(attrs.getType(i));

    cppQName = XStrL(attrs.getQName(i));
    newEvent->attribs[i].prefix = getQNamePrefix(cppQName);
  }

  pushEvent(newEvent);
}

void XMLPullParserImpl::startPrefixMapping(const XMLCh *const prefix,
					   const XMLCh *const uri) {
  // Not needed
}

void XMLPullParserImpl::endPrefixMapping(const XMLCh *const prefix) {
  // Not needed
}

void XMLPullParserImpl::skippedEntity(const XMLCh *const name) {
  // ignore: This is not called by Xerces anyway
}
// End Xerces ContentHandler interface
