/*
 Copyright (C) 2000-2004

 Code contributed by Greg Collecutt, Joseph Hope and Paul Cochrane

 This file is part of xmds.
 
 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.
*/

/*
  $Id: xmdsutils.cc,v 1.14 2004/07/13 05:29:38 paultcochrane Exp $
*/

/*! @file xmdsutils.cc
  @brief Utility classes and methods

  More detailed explanation...
*/

#include<xmlbasics.h>
#include<dom3.h>
#include<xmdsutils.h>
#include<string>

extern bool debugFlag;

//#define DEBUG 0

// ******************************************************************************
// ******************************************************************************
//                              xmdsException
// ******************************************************************************
// ******************************************************************************

// ******************************************************************************
xmdsException::xmdsException() :
  myNode(0),
  myErrorMessage("") {
};

// ******************************************************************************
xmdsException::xmdsException(
			     const char *const yourErrorMessage) :
  myNode(0),
  myErrorMessage(yourErrorMessage) {
};

// ******************************************************************************
xmdsException::xmdsException(
			     const Node *const yourNode,
			     const char *const yourErrorMessage) :
  myNode(yourNode),
  myErrorMessage(yourErrorMessage) {
};

// ******************************************************************************
const char* xmdsException::getError() {

  char s2[256];

  s[0]=0;

  const Node* nextNode=myNode;

  if(nextNode != 0) {
    sprintf(s2,"In element '%s',\n",nextNode->nodeName()->c_str());
    strcat(s,s2);
    nextNode=nextNode->parentNode();
  }

  while(nextNode != 0) {
    sprintf(s2," which is within element '%s',\n",nextNode->nodeName()->c_str());
    strcat(s,s2);
    nextNode=nextNode->parentNode();
  }

  sprintf(s2,"\nthe following error occurred:\n  %s\n",myErrorMessage);

  strcat(s,s2);

  return s;
};

// ******************************************************************************
// ******************************************************************************
//                              xmdsUtility public
// ******************************************************************************
// ******************************************************************************

long nxmdsUtilitys=0;  //!< Number of xmds utility objects

// ******************************************************************************
xmdsUtility::xmdsUtility() {
  if(debugFlag) {
    nxmdsUtilitys++;
    printf("xmdsUtility::xmdsUtility\n");
    printf("nxmdsUtilitys=%li\n",nxmdsUtilitys);
  }
};

// ******************************************************************************
xmdsUtility::~xmdsUtility() {
  if(debugFlag) {
    nxmdsUtilitys--;
    printf("xmdsUtility::~xmdsUtility\n");
    printf("nxmdsUtilitys=%li\n",nxmdsUtilitys);
  }
};

// ******************************************************************************
// ******************************************************************************
//                              xmdsUtility protected
// ******************************************************************************
// ******************************************************************************

// ******************************************************************************
char* xmdsUtility::errorMessage() const {
  return myErrorMessage;
};

// ******************************************************************************
void xmdsUtility::getAssignmentStrings(
				       const Element *const inElement,
				       const XMLString& ofName,
				       const bool& required,
				       const unsigned long& n2get,
				       list<XMLString>&  outList) {
  if(debugFlag) {
    printf("xmdsUtility::getAssignmentStrings\n");
  }

  outList.clear();

  // note that if an element is found, then the routine will throw an exception
  // if n2get items aren't found within, except that passing n2get=0 will escape
  // this behaviour

  const NodeList* candidateElements = inElement->getElementsByTagName(ofName,0);
  
  if(candidateElements->length()==0) {
    if(required) {
      sprintf(myErrorMessage,"Element '%s' not found",ofName.c_str());
      throw xmdsException(inElement,myErrorMessage);
    }
    else {
      return;
    }
  }

  if(candidateElements->length()==1) {

    const Node* nextNode = candidateElements->item(0);

    parseXMLString(nextNode,candidateElements->item(0)->textContent(0),outList);

    if(n2get!=0) {
      // check that numbers of requested items match
      if(outList.size()!=n2get) {
	sprintf(myErrorMessage,	"%li strings wanted but %li found",n2get,(long)outList.size());
	throw xmdsException(nextNode,myErrorMessage);
      }
    }

  }
  else {
    sprintf(myErrorMessage,"Multiple Elements '%s' found",ofName.c_str());
    throw xmdsException(inElement,myErrorMessage);
  }
};

// ******************************************************************************
void xmdsUtility::getAssignmentBools(
				     const Element *const inElement,
				     const XMLString& ofName,
				     const bool& required,
				     const unsigned long& n2get,
				     list<bool>& outList) {
  if(debugFlag) {
    printf("xmdsUtility::getAssignmentBools\n");
  }

  // note that if an element is found, then the routine will throw an exception
  // if n2get items aren't found withn, except that passing n2get=0 will escape
  // this behaviour

  outList.clear();

  const NodeList* candidateElements = inElement->getElementsByTagName(ofName,0);

  if(candidateElements->length()==0) {
    if(required) {
      sprintf(myErrorMessage,"Element '%s' not found",ofName.c_str());
      throw xmdsException(inElement,myErrorMessage);
    }
    else {
      return;
    }
  }

  if(candidateElements->length()==1) {

    list<XMLString> myOutList;

    const Node* nextNode = candidateElements->item(0);

    parseXMLString(nextNode,candidateElements->item(0)->textContent(0),myOutList);

    for(list<XMLString>::const_iterator pXMLString = myOutList.begin(); pXMLString != myOutList.end(); pXMLString++) {
      if(*pXMLString=="yes") {
	outList.push_back(1);
      }
      else if(*pXMLString=="no") {
	outList.push_back(0);
      }
      else {
	throw xmdsException(nextNode,"'yes' or 'no' expected");
      }
    }

    if(n2get!=0) {
      // check that numbers of requested items match
      if(outList.size()!=n2get) {
	sprintf(myErrorMessage,"%li 'yes'/'no' wanted but %li found",n2get,(long)outList.size());
	throw xmdsException(nextNode,myErrorMessage);
      }
    }
  }
  else {
    sprintf(myErrorMessage,"Multiple Elements '%s' found",ofName.c_str());
    throw xmdsException(inElement,myErrorMessage);
  }
};

// ******************************************************************************
void xmdsUtility::getAssignmentULongs(
				      const Element *const inElement,
				      const XMLString& ofName,
				      const bool& required,
				      const unsigned long& n2get,
				      list<unsigned long>& outList) {
  if(debugFlag) {
    printf("xmdsUtility::getAssignmentLongs\n");
  }

  // note that if an element is found, then the routine will throw an exception
  // if n2get items aren't found withn, except that passing n2get=0 will escape
  // this behaviour

  outList.clear();

  const NodeList* candidateElements = inElement->getElementsByTagName(ofName,0);

  if(candidateElements->length()==0) {
    if(required) {
      sprintf(myErrorMessage,"Element '%s' not found",ofName.c_str());
      throw xmdsException(inElement,myErrorMessage);
    }
    else {
      return;
    }
  }

  if(candidateElements->length()==1) {

    list<XMLString> myOutList;

    const Node* nextNode = candidateElements->item(0);

    parseXMLString(nextNode,candidateElements->item(0)->textContent(0),myOutList);

    for(list<XMLString>::const_iterator pXMLString = myOutList.begin(); pXMLString != myOutList.end(); pXMLString++) {
      unsigned long nextULong;
      if(pXMLString->asULong(nextULong)) {
	outList.push_back(nextULong);
      }
      else {
	throw xmdsException(nextNode,"invalid positive integer");
      }
    }

    if(n2get!=0) {
      // check that numbers of requested items match
      if(outList.size()!=n2get) {
	sprintf(myErrorMessage,"%li integers wanted but %li found",n2get,(long)outList.size());
	throw xmdsException(nextNode,myErrorMessage);
      }
    }
  }
  else {
    sprintf(myErrorMessage,"Multiple Elements '%s' found",ofName.c_str());
    throw xmdsException(inElement,myErrorMessage);
  }
};

// ******************************************************************************
void xmdsUtility::getAssignmentDoubles(
				       const Element *const inElement,
				       const XMLString& ofName,
				       const bool& required,
				       const unsigned long& n2get,
				       list<double>& outList) {
  if(debugFlag) {
    printf("xmdsUtility::getAssignmentDoubles\n");
  }

  // note that if an element is found, then the routine will throw an exception
  // if n2get items aren't found withn, except that passing n2get=0 will escape
  // this behaviour

  outList.clear();

  const NodeList* candidateElements = inElement->getElementsByTagName(ofName,0);

  if(candidateElements->length()==0) {
    if(required) {
      sprintf(myErrorMessage,"Element '%s' not found",ofName.c_str());
      throw xmdsException(inElement,myErrorMessage);
    }
    else {
      return;
    }
  }

  if(candidateElements->length()==1) {

    list<XMLString> myOutList;

    const Node* nextNode = candidateElements->item(0);

    parseXMLString(nextNode,candidateElements->item(0)->textContent(0),myOutList);

    for(list<XMLString>::const_iterator pXMLString = myOutList.begin(); pXMLString != myOutList.end(); pXMLString++) {
      double nextdouble;
      if(pXMLString->asDouble(nextdouble)) {
	outList.push_back(nextdouble);
      }
      else {
	throw xmdsException(nextNode,"invalid floating point format");
      }
    }

    if(n2get!=0) {
      // check that numbers of requested items match
      if(outList.size()!=n2get) {
	sprintf(myErrorMessage,"%li reals wanted but %li found",n2get,(long)outList.size());
	throw xmdsException(nextNode,myErrorMessage);
      }
    }
  }
  else {
    sprintf(myErrorMessage,"Multiple Elements '%s' found",ofName.c_str());
    throw xmdsException(inElement,myErrorMessage);
  }
};

// ******************************************************************************
void xmdsUtility::getAssignmentDomains(
				       const Element *const inElement,
				       const XMLString& ofName,
				       const bool& required,
				       const unsigned long& n2get,
				       list<domainStruct>& outList) {
  if(debugFlag) {
    printf("xmdsUtility::getAssignmentDomains\n");
  }

  // note that if an element is found, then the routine will throw an exception
  // if n2get items aren't found withn, except that passing n2get=0 will escape
  // this behaviour

  outList.clear();

  const NodeList* candidateElements = inElement->getElementsByTagName(ofName,0);

  if(candidateElements->length()==0) {
    if(required) {
      sprintf(myErrorMessage,"Element '%s' not found",ofName.c_str());
      throw xmdsException(inElement,myErrorMessage);
    }
    else {
      return;
    }
  }

  if(candidateElements->length()==1) {

    list<XMLString> myPairsList;

    const Node* nextNode = candidateElements->item(0);

    parseXMLString(nextNode,candidateElements->item(0)->textContent(0),myPairsList);

    if(n2get!=0) {
      // check that numbers of requested items match
      if(myPairsList.size() != n2get) {
	sprintf(myErrorMessage,"%li domain pairs wanted but %li found",n2get,(long)myPairsList.size());
	throw xmdsException(nextNode,myErrorMessage);
      }
    }
    for(list<XMLString>::const_iterator pXMLString = myPairsList.begin(); pXMLString != myPairsList.end(); pXMLString++) {

      list<XMLString> myDoublesList;

      parseXMLString(nextNode,&*pXMLString,myDoublesList);

      if(myDoublesList.size() != 2) {
	throw xmdsException(nextNode,"bracketed pairs of reals expected");
      }

      domainStruct nextDomain;

      list<XMLString>::const_iterator pXMLString2;

      pXMLString2 = myDoublesList.begin();

      if(!pXMLString2->asDouble(nextDomain.begin)) {
	throw xmdsException(nextNode,"invalid floating point format");
      }

      pXMLString2++;
      if(!pXMLString2->asDouble(nextDomain.end)) {
	throw xmdsException(nextNode,"invalid floating point format");
      }

      if(nextDomain.end<=nextDomain.begin) {
	throw xmdsException(nextNode,"domain ends must be > begins");
      }

      outList.push_back(nextDomain);
    }

  }
  else {
    sprintf(myErrorMessage,"Multiple Elements '%s' found",ofName.c_str());
    throw xmdsException(inElement,myErrorMessage);
  }
};

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

void xmdsUtility::getAttributeStrings(
				      const Element *const inElement,
				      const XMLString& ofElementName,
				      const XMLString& ofAttrName,
				      const bool& required,
				      XMLString&  outString) {
  if(debugFlag) {
    printf("xmdsUtility::getAttributeStrings\n");
  }

  outString = EMPTY_STRING;

  const NodeList* candidateElements = inElement->getElementsByTagName(ofElementName,0);
  
  // this code is a bit weird as it was grabbed from getAssignmentStrings, and so
  // don't really need this next test
  // @todo Need to clean up this function so that required boolean means the right thing
  //       Namely that the attribute is required, atm we're checking if the element (tag)
  //       is required
  // Really not happy with how the attribute parsing code is going
  // this looks like it needs a bit of work to knock it into some proper shape,
  // what we've got here is too much of a hack on top of getAssignmentStrings and
  // a decent amount of thought needs to go into the implementation
  if(candidateElements->length()==0) {
    if(required) {
      sprintf(myErrorMessage,"Element '%s' not found",ofElementName.c_str());
      throw xmdsException(inElement,myErrorMessage);
    }
    else {
      return;
    }
  }

  if(candidateElements->length()==1) {

    const Node* nextNode = candidateElements->item(0);
    const NamedNodeMap* attrNodeMap = nextNode->attributes();
    if (attrNodeMap->length() > 0) {
      const Node* attrNode = attrNodeMap->getNamedItem(ofAttrName);
      if (attrNode != 0) {
	outString = attrNode->nodeValue()->c_str();
      }
      // if the attribute isn't found, and it's required, barf
      else if ((attrNode == 0) && required) {
	sprintf(myErrorMessage,"Attribute '%s' not found",ofAttrName.c_str());
	throw xmdsException(inElement,myErrorMessage);
      }      
      else {
	outString = EMPTY_STRING;
      }
    }
  }
  else {
    sprintf(myErrorMessage,"Multiple Elements '%s' found",ofElementName.c_str());
    throw xmdsException(inElement,myErrorMessage);
  }
};

// ******************************************************************************
void xmdsUtility::getAttributeBools(
				     const Element *const inElement,
				     const XMLString& ofElementName,
				     const XMLString& ofAttrName,
				     const bool& required,
				     bool& outBool) {
  if(debugFlag) {
    printf("xmdsUtility::getAttributeBools\n");
  }

  outBool = false;

  const NodeList* candidateElements = inElement->getElementsByTagName(ofElementName,0);

  if (debugFlag) {
    printf("xmdsUtility::getAttributeBools : candidateElements->length = %ld\n",candidateElements->length());
  }

  if(candidateElements->length()==0) {
    if(required) {
      sprintf(myErrorMessage,"Element '%s' not found",ofElementName.c_str());
      throw xmdsException(inElement,myErrorMessage);
    }
    else {
      return;
    }
  }

  if(candidateElements->length()==1) {  // we only want one such tag

    const Node* nextNode = candidateElements->item(0);  // grab the tag's node
    const NamedNodeMap* attrNodeMap = nextNode->attributes();  // grab the attributes of the tag as a map
    if (debugFlag) {
      printf("xmdsUtility::getAttributeBools : attrNodeMap->length = %ld\n",attrNodeMap->length());
    }
    if (attrNodeMap->length() > 0) {
      const Node* attrNode = attrNodeMap->getNamedItem(ofAttrName);  // get the node with the name ofAttrName
      if (attrNode != 0) {
	// then compare the value with 'yes' and 'no'
	const string boolString = static_cast <string>(attrNode->nodeValue()->c_str());
	if (debugFlag) {
	  printf("xmdsUtility::getAttributeBools : boolString = %s\n", boolString.c_str());
	}
	if (boolString == "yes") {
	  outBool = true;
	}
	else if (boolString == "no") {
	  outBool = false;
	}
	else {
	  throw xmdsException(nextNode, "'yes' or 'no' expected");
	}
      }
    }
  }
  else {
    sprintf(myErrorMessage,"Multiple Elements '%s' found",ofElementName.c_str());
    throw xmdsException(inElement,myErrorMessage);
  }
  if (debugFlag) {
    printf("xmdsUtility::getAttributeBools : outBool = %d\n",outBool);
  }

};

// ******************************************************************************
unsigned long xmdsUtility::spaceList2ULong(
					   const list<bool>& spaceList) const {
  if(debugFlag) {
    printf("xmdsUtility::spaceList2ULong\n");
  }

  unsigned long space=0;
  unsigned long two2n=1;
  for(list<bool>::const_iterator pBool = spaceList.begin(); pBool != spaceList.end(); pBool++) {
    if(*pBool) {
      space += two2n;
    }
    two2n *= 2;
  }

  return space;
};

// ******************************************************************************
// ******************************************************************************
//                              xmdsUtility private
// ******************************************************************************
// ******************************************************************************

// ******************************************************************************
void xmdsUtility::parseXMLString(
				 const Node *const inNode,
				 const XMLString *const inString,
				 list<XMLString>& outXMLStringList) {
  if(debugFlag) {
    printf("xmdsUtility::parseXMLString\n");
  }

  outXMLStringList.clear();

  if(inString->length()==0) {
    return;
  }

  XMLString copyInString = *inString;

  // begin by replacing all commas and semicolons with spaces
  unsigned long i=0;
  while(i<copyInString.length()) {
    if((copyInString.data(i)==',')|(copyInString.data(i)==';')) {
      copyInString.replaceData(i,1," ");
    }
    i++;
  }

  // now mark all begins and ends
  list<unsigned long> begins;
  list<unsigned long> ends;
  long bracketLevel=0;
  i=0;

  char lastChar=0x20;
  while(i<copyInString.length()) {
    if(copyInString.data(i)=='(') {
      if(bracketLevel==0) {
	begins.push_back(i+1);
      }
      bracketLevel++;
    }
    else if(copyInString.data(i)==')') {
      bracketLevel--;
      if(bracketLevel==0) {
	ends.push_back(i);
      }
    }
    else if(bracketLevel==0) {
      if(!XMLChar::isWhiteSpace(copyInString.data(i))&(XMLChar::isWhiteSpace(lastChar)|(lastChar==')'))) {
	begins.push_back(i);
      }
      else if(XMLChar::isWhiteSpace(copyInString.data(i))&!(XMLChar::isWhiteSpace(lastChar)|(lastChar==')'))) {
	ends.push_back(i);
      }
    }
    lastChar=copyInString.data(i);
    i++;
  }

  if(bracketLevel !=0 ) {
    throw xmdsException(inNode,"Imbalanced bracketing");
  }

  if(!(XMLChar::isWhiteSpace(lastChar)|(lastChar==')'))) {
    ends.push_back(i);
  }

  list<unsigned long>::const_iterator pbegin = begins.begin();
  list<unsigned long>::const_iterator pend = ends.begin();

  while(pbegin != begins.end()) {
    XMLString subString;
    copyInString.subString(subString,*pbegin,*pend);
    outXMLStringList.push_back(subString);
    pbegin++;
    pend++;
  }
};


syntax highlighted by Code2HTML, v. 0.9.1