/*
  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: xsilfield.cc,v 1.22 2005/05/19 03:42:00 joehope Exp $
*/

/*! @file xsilfield.cc
  @brief XSIL format parsing classes and methods

  More detailed explanation...
*/

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

#define DEBUG 0

// ******************************************************************************
// ******************************************************************************
//                              xsilField public
// ******************************************************************************
// ******************************************************************************

long nxsilFields=0; //!< The number of xsil fields

// ******************************************************************************
xsilField::xsilField() {
  if(DEBUG) {
    nxsilFields++;
    printf("xsilField::xsilField\n");
    printf("nxsilFields=%li\n",nxsilFields);
  }
};

// ******************************************************************************
xsilField::~xsilField() {
  if(DEBUG) {
    nxsilFields--;
    printf("xsilField::~xsilField\n");
    printf("nxsilFields=%li\n",nxsilFields);
  }
};

// ******************************************************************************
void xsilField::processElement(
							   const Element *const yourElement) {
	if(DEBUG) {
		printf("xsilField::processElement\n");
	}
	
	fieldName="";
	nIndependentVariables=0;
	variableNamesList.clear();
	latticeList.clear();
	
	const NodeList* candidateElements;
	const NamedNodeMap* elementAttributes;
	const Node* attributeNode;
	list<XMLString> anXMLStringList;
	
	// ************************************
	// find name
	
	elementAttributes = yourElement->attributes();
	
	attributeNode = elementAttributes->getNamedItem("Name");
	
	if(attributeNode != 0) {
		fieldName = *attributeNode->nodeValue();
	}
	else  {
		throw xmdsException(yourElement,"Where is my Name='...' attribute?");
	}
	
	// ************************************
	// find n_independent Param assignment
	
	candidateElements = yourElement->getElementsByTagName("Param",0);
	
	if(candidateElements->length()>1) {
		throw xmdsException(yourElement,"Only one <Param> element expected ");
	}
	
	elementAttributes = candidateElements->item(0)->attributes();
	
	attributeNode = elementAttributes->getNamedItem("Name");
	
	if(attributeNode != 0) {
		if(*attributeNode->nodeValue() == "n_independent") {
			if(!candidateElements->item(0)->textContent(0)->asULong(nIndependentVariables)) {
				throw xmdsException(candidateElements->item(0),"Invalid positive integer format");
			}
		}
		else {
			throw xmdsException(candidateElements->item(0),"Unknown <Param> element");
		}
	}
	else {
		throw xmdsException(candidateElements->item(0),"Where is my Name='...' attribute?");
	}
	
	// ************************************
	// find Arrays
	
	candidateElements = yourElement->getElementsByTagName("Array",0);
	
	if(candidateElements->length() != 2) {
		throw xmdsException(yourElement,"Exactly two <Array> elements expected ");
	}
	
	// ************************************
	// find variables Array
	
	elementAttributes = candidateElements->item(0)->attributes();
	
	attributeNode = elementAttributes->getNamedItem("Name");
	
	if(attributeNode != 0) {
		if(*attributeNode->nodeValue() != "variables") {
			sprintf(errorMessage(),"Unknown <Array> element '%s'",attributeNode->nodeValue()->c_str());
			throw xmdsException(yourElement,errorMessage());
		}
	}
	else
		throw xmdsException(candidateElements->item(0),"Where is my Name='...' attribute?");
	
	const Element* myVariablesArrayElement = dynamic_cast<const Element*>(candidateElements->item(0));
	
	// ************************************
	// find data Array
	
	elementAttributes = candidateElements->item(1)->attributes();
	
	attributeNode = elementAttributes->getNamedItem("Name");
	
	if(attributeNode != 0) {
		if(*attributeNode->nodeValue() != "data") {
			sprintf(errorMessage(),"Unknown <Array> element '%s'",attributeNode->nodeValue()->c_str());
			throw xmdsException(yourElement,errorMessage());
		}
	}
	else {
		throw xmdsException(candidateElements->item(1),"Where is my Name='...' attribute?");
	}
	
	const Element* myDataArrayElement = dynamic_cast<const Element*>(candidateElements->item(1));
	
	// ************************************
	// process variables Array
	
	unsigned long nVariables;
	
	candidateElements = myVariablesArrayElement->getElementsByTagName("Dim",0);
	
	if(candidateElements->length()>1) {
		throw xmdsException(myVariablesArrayElement,"Only one <Dim> element expected ");
	}
	
	if(!candidateElements->item(0)->textContent(0)->asULong(nVariables)) {
		throw xmdsException(candidateElements->item(0),"Invalid positive integer format");
	}
	
	getAssignmentStrings(myVariablesArrayElement,"Stream",1,nVariables,variableNamesList);
	
	
	// ************************************
	// process data Array
	
	candidateElements = myDataArrayElement->getElementsByTagName("Dim",0);
	
	if(candidateElements->length() != nIndependentVariables + 1) {
		sprintf(errorMessage(),"Exactly %li <Dim> elements expected",nIndependentVariables + 1);
		throw xmdsException(myDataArrayElement,errorMessage());
	}
	
	for(unsigned long i=0 ; i<candidateElements->length(); i++) {
		
		unsigned long nextDim;
		
		if(!candidateElements->item(i)->textContent(0)->asULong(nextDim)) {
			throw xmdsException(candidateElements->item(i),"Invalid positive integer format");
		}
		
		latticeList.push_back(nextDim);
	}
	
	candidateElements = myDataArrayElement->getElementsByTagName("Stream",0);
	
	if(candidateElements->length() != 1) {
		throw xmdsException(myDataArrayElement,"A <Stream> element expected");
	}
	
	const Element* myStreamElement = dynamic_cast<const Element*>(candidateElements->item(0));
	
	// get the Metalink tags
	candidateElements = myDataArrayElement->getElementsByTagName("Metalink",1);
	
	if (candidateElements->length() != 1) {
		throw xmdsException(myDataArrayElement,"<Metalink> element expected");
	}
	
	// get the Format attribute
	elementAttributes = candidateElements->item(0)->attributes();
	
	attributeNode = elementAttributes->getNamedItem("Format");
	
	if(attributeNode != 0) {
		if(*attributeNode->nodeValue() != "Text" && *attributeNode->nodeValue() != "Binary") {
			sprintf(errorMessage(),"Unknown <Metalink> attribute '%s'",attributeNode->nodeValue()->c_str());
			throw xmdsException(yourElement,errorMessage());
		}
	}
	else {
		throw xmdsException(candidateElements->item(0),"Where is my Format='...' attribute?");
	}
	
	//  determine the stream format and work out where the data is
	streamFormat = *attributeNode->nodeValue();
	if(streamFormat == "Text") {
		// ok then, the data is ascii, go get it tiger!
		data = *myStreamElement->textContent(0);
	}
	else if (streamFormat == "Binary") {
		// data is binary, the textContent of the Stream element is the datafile filename
		binDatFname = *myStreamElement->textContent(0);
		
		// try to remove whitespace chars
		std::string tmp = binDatFname.c_str();
		std::string tmp2;
		for (unsigned long int i=0; i<tmp.length(); i++) {
			if (!isspace(tmp[i])) {
				tmp2 += tmp[i];
			}
		}
		
		// biff the temporary variable into the binary data filename
		binDatFname = tmp2.c_str();
		
		// need to check what encoding the data is in
		// is it big or little endian??
		attributeNode = elementAttributes->getNamedItem("Encoding");
		if (attributeNode != 0) {
			if (*attributeNode->nodeValue() != "LittleEndian" && *attributeNode->nodeValue() != "BigEndian") {
				sprintf(errorMessage(),"Unknown <Metalink> attribute '%s'", attributeNode->nodeValue()->c_str());
				throw xmdsException(yourElement,errorMessage());
			}
		}
		else {
			throw xmdsException(candidateElements->item(0),"Where is my Encoding='...' attribute?");
		}
		
		// the binary encoding of the data file
		binEncoding = *attributeNode->nodeValue();
		
		// get the precision attribute
		elementAttributes = candidateElements->item(0)->attributes();
		
		attributeNode = elementAttributes->getNamedItem("precision");
		
		if(attributeNode != 0) {
			if(*attributeNode->nodeValue() != "single" && *attributeNode->nodeValue() != "double") {
				sprintf(errorMessage(),"Unknown <Metalink> attribute '%s'",attributeNode->nodeValue()->c_str());
				throw xmdsException(yourElement,errorMessage());
			}
		}
		else {
			throw xmdsException(candidateElements->item(0),"Where is my precison='...' attribute?");
		}
		
		// the precison of the binary data
		binPrecision = *attributeNode->nodeValue();
		
		// get the UnsignedLong attribute
		elementAttributes = candidateElements->item(0)->attributes();
		
		attributeNode = elementAttributes->getNamedItem("UnsignedLong");
		
		if(attributeNode != 0) {
			if(*attributeNode->nodeValue() != "ulong" && *attributeNode->nodeValue() != "uint32" && *attributeNode->nodeValue() != "uint64") {
				sprintf(errorMessage(),"Unknown <Metalink> attribute '%s'",attributeNode->nodeValue()->c_str());
				throw xmdsException(yourElement,errorMessage());
			}
			ulongType = *attributeNode->nodeValue();
		}
		else {
			ulongType = "ulong";
			printf("Defaulting to ulong\n");
		}
	}
};

// ******************************************************************************
void xsilField::writeAsFormat(
			      FILE *const outfile,
			      const outputFormatEnum& format,
			      const long& iD,
			      const char *datFileNameBase) {
  if(DEBUG) {
    printf("xsilField::writeAsFormat\n");
  }

  if (streamFormat == "Text") {

    // dump data as ascii file
    
    char datFileName[64];
    
    sprintf(datFileName,"%s%li.dat",datFileNameBase,iD);
    
    FILE *tempfile=fopen(datFileName,"w");
    
    // write header row (this doesn't work for matlab!)
    if(format != FORMAT_MATLAB) {
      for(list<XMLString>::const_iterator pXMLString = variableNamesList.begin(); pXMLString != variableNamesList.end(); pXMLString++) {
	fprintf(tempfile," %s ",pXMLString->c_str());
      }
    }
    
    // write data
    fprintf(tempfile,"%s",data.c_str());
    fclose(tempfile);
    
    // initialise variables
    list<XMLString>::iterator pXMLString = variableNamesList.begin();
    for(unsigned long i=0;i<variableNamesList.size();i++) {
      
      // need to format variable names to remove any non alpha-numeric characters
      pXMLString->goLatinAlphaNumeric();
      
      char tempString[64];
      sprintf(tempString,"_%li",iD);
      *pXMLString += tempString;
      
      if(nIndependentVariables==0) {
	fprintf(outfile,"%s = zeros(1,1);\n",pXMLString->c_str());
      }
      else if(nIndependentVariables==1) {
	if(i==0) {
	  fprintf(outfile,"temp_d1 = zeros(1,%li);\n",lattice(0));
	}
	else {
	  fprintf(outfile,"%s = zeros(1,%li);\n",pXMLString->c_str(),lattice(0));
	}
      }
      else {
	if(i<nIndependentVariables) {
	  fprintf(outfile,"temp_d%li = zeros(%li",i+1,lattice(nIndependentVariables-1));
	}
	else {
	  fprintf(outfile,"%s = zeros(%li",pXMLString->c_str(),lattice(nIndependentVariables-1));
	}
	
	for(unsigned long j=nIndependentVariables-1;j>0;j--) {
	  fprintf(outfile,",%li",lattice(j-1));
	}
	
	fprintf(outfile,");\n");
      }
      
      if(i<nIndependentVariables) {
	fprintf(outfile,"%s = zeros(1,%li);\n",pXMLString->c_str(),lattice(i));
      }
      
      pXMLString++;
    }
    fprintf(outfile,"\n");
    
    // load in temp file
    if(format==FORMAT_SCILAB) {
      fprintf(outfile,"%s%li = fscanfMat('%s');\n",datFileNameBase,iD,datFileName);
    }
    else {
      fprintf(outfile,"load %s -ascii\n",datFileName);
    }
    
    for(unsigned long i=0;i<nIndependentVariables;i++) {
      fprintf(outfile,"temp_d%li(:) = %s%li(:,%li);\n",i+1,datFileNameBase,iD,i+1);
    }
    
    for(unsigned long i=nIndependentVariables;i<variableNamesList.size();i++) {
      fprintf(outfile,"%s(:) = %s%li(:,%li);\n",variableName(i)->c_str(),datFileNameBase,iD,i+1);
    }
    
    // work out coordinates
    for(unsigned long i=0;i<nIndependentVariables;i++) {
      
      fprintf(outfile,"%s(:) = temp_d%li(",variableName(i)->c_str(),i+1);
      
      if(i==(nIndependentVariables-1)) {
	fprintf(outfile,":");
      }
      else {
	fprintf(outfile,"1");
      }
      
      for(unsigned long j=nIndependentVariables-1;j>0;j--) {
	if((j-1)==i) {
	  fprintf(outfile,",:");
	}
	else {
	  fprintf(outfile,",1");
	}
      }
      
      fprintf(outfile,");\n");
    }
    fprintf(outfile,"\n");
    
    // clear excess variables
    
    fprintf(outfile,"clear %s%li",datFileNameBase,iD);
    for(unsigned long i=0;i<nIndependentVariables;i++) {
      fprintf(outfile," temp_d%li",i+1);
    }
    fprintf(outfile,"\n");

    fprintf(outfile,"\n");

  }
  else if (streamFormat == "Binary") {

    if (format == FORMAT_MATLAB) {
      
      // work out how matlab will interpret endian-ness
      std::string machineFormat;
      if (binEncoding == "BigEndian") {
	machineFormat = "ieee-be";
      }
      else if (binEncoding == "LittleEndian") {
	machineFormat = "ieee-le";
      }
      else {
	machineFormat = "native";
      }

      fprintf(outfile,"fpDat = fopen('%s','r','%s');\n",binDatFname.c_str(),machineFormat.c_str());
      fprintf(outfile,"if (fpDat < 0)\n");
      fprintf(outfile,"  disp('Cannot open binary data file: %s')\n",binDatFname.c_str());
      fprintf(outfile,"  return\n");
      fprintf(outfile,"end\n");
      
      unsigned long int k = 0;
      for(list<XMLString>::iterator pXMLString = variableNamesList.begin(); pXMLString != variableNamesList.end(); pXMLString++) {
	char tempString[64];
	sprintf(tempString,"_%li",iD);
	*pXMLString += tempString;

	if (k < nIndependentVariables) {
	  fprintf(outfile,"%sLen = fread(fpDat,1,'%s');\n",pXMLString->c_str(),ulongType.c_str());
	  fprintf(outfile,"%s = zeros(1,%sLen);\n",pXMLString->c_str(),pXMLString->c_str());
	  fprintf(outfile,"%s(:) = fread(fpDat,%sLen,'%s');\n",pXMLString->c_str(),pXMLString->c_str(),binPrecision.c_str());
	}
	else if (k >= nIndependentVariables) {
	  fprintf(outfile,"%sLen = fread(fpDat,1,'%s');\n",pXMLString->c_str(),ulongType.c_str());
	  if (nIndependentVariables == 1) {
	    fprintf(outfile,"%s = fread(fpDat,%sLen,'%s');\n",pXMLString->c_str(),
		    variableNamesList.begin()->c_str(),binPrecision.c_str());
	  }
	  else if (nIndependentVariables == 2) {
	    fprintf(outfile,"%s = fread(fpDat,[%sLen,%sLen],'%s');\n",pXMLString->c_str(),
		    (++variableNamesList.begin())->c_str(),variableNamesList.begin()->c_str(),binPrecision.c_str());
	  }
	  else if (nIndependentVariables > 2) {
	    // now we need to create a multi-dimensional matrix, 
	    // and this is harder to do...
	    // we need to read in a matrix-sized (ie 2D) block at a time, 
	    // and append this to the other dimensions the number of 
	    // independent variables determines the dimensions of the 
	    // N-D matrix to produce

	    // construct the for loop to loop over the third and subsequent dimensions
 	    list<XMLString>::iterator pIndepVars = variableNamesList.begin();
	    for (unsigned long int inumIndepVars=2; inumIndepVars<nIndependentVariables; inumIndepVars++) {
	      fprintf(outfile, "for index%li = 1:%sLen\n",inumIndepVars-2,pIndepVars->c_str());
	      pIndepVars++;
	    }
	    
	    // generate the first part of the string, which is the array to be assigned into
	    fprintf(outfile, "%s(:,:,",pXMLString->c_str());
	    pIndepVars = variableNamesList.begin();
	    for (unsigned long int inumIndepVars=nIndependentVariables-1; inumIndepVars>=2; inumIndepVars--) {
	      fprintf(outfile, "index%li",inumIndepVars-2);
	      // need to append a comma if not the last index to append
	      if (inumIndepVars != 2) {
		fprintf(outfile,",");
	      }
	    }

	    // generate the fread statement
	    // to do this, I have to work out what the last and second-to-last 
	    // independent variable names are this is because, for some reason, 
	    // one can't inspect a given element of a list
	    
	    // first, the last one
 	    pIndepVars = variableNamesList.begin();
	    XMLString lastIndepVar;
	    for (unsigned long int inumIndepVars=0; inumIndepVars<nIndependentVariables; inumIndepVars++) {
	      lastIndepVar = *pIndepVars;
	      pIndepVars++;
	    }

	    // now the second to last one
	    XMLString secondLastIndepVar;
 	    pIndepVars = variableNamesList.begin();
	    for (unsigned long int inumIndepVars=1; inumIndepVars<nIndependentVariables; inumIndepVars++) {
	      secondLastIndepVar = *pIndepVars;
	      pIndepVars++;
	    }
	    
	    fprintf(outfile, ") = fread(fpDat,[%sLen,%sLen],'%s');\n",lastIndepVar.c_str(),
		    secondLastIndepVar.c_str(),binPrecision.c_str());

	    // finish off the for loop
	    for (unsigned long int inumIndepVars=2; inumIndepVars<nIndependentVariables; inumIndepVars++) {
	      fprintf(outfile,"end\n");
	    }

	  }

	}
	k++;

      }
      
      // clean up a bit
      fprintf(outfile,"fclose(fpDat);\n");
      fprintf(outfile,"clear fpDat ");

      for(list<XMLString>::iterator pXMLString = variableNamesList.begin(); pXMLString != variableNamesList.end(); pXMLString++) {
	fprintf(outfile, "%sLen ",pXMLString->c_str());
      }
      for (unsigned long int inumIndepVars=2; inumIndepVars<nIndependentVariables; inumIndepVars++) {
	fprintf(outfile, "index%li ",inumIndepVars-2);
      }
      
      fprintf(outfile, "\n");

    }
    else if (format == FORMAT_SCILAB) {
      
      // as far as I can tell, scilab can't handle binary data input,
      // so barf, and tell why, and give some alternative.
      
      printf("\nFatal error: Sorry, but at the time of this version of xmds,\n"
	     "scilab cannot handle binary input.  To be able to use scilab,\n"
	     "please change your output format to ascii (in the <output> tag).\n"
	     "Exiting...\n");
      exit(254);
    }
    else {
      throw(xmdsException("Unknown format.  I only accept Matlab or Scilab at present\n"));
    }
    
  }
  else {
    throw(xmdsException("Stream format is neither Text or Binary, something has seriously gone wrong!\n"));
  }

};

// ******************************************************************************
// ******************************************************************************
//                              xsilField private
// ******************************************************************************
// ******************************************************************************

// ******************************************************************************
unsigned long xsilField::lattice(
				 const unsigned long& index) const {
  if(DEBUG) {
    printf("xsilField::lattice\n");
  }

  if(index>=latticeList.size()) {
    throw xmdsException("Internal range error in xsilField::lattice");
  }

  list<unsigned long>::const_iterator pULong = latticeList.begin();
  for(unsigned long i=0; i<index; i++) {
    pULong++;
  }

  return *pULong;
};

// ******************************************************************************
const XMLString* xsilField::variableName(
					 const unsigned long& index) const {
  if(DEBUG) {
    printf("xsilField::varaibleName\n");
  }

  if(index>=variableNamesList.size()) {
    throw xmdsException("Internal range error in xsilField::variableName");
  }

  list<XMLString>::const_iterator pXMLString = variableNamesList.begin();
  for(unsigned long i=0; i<index; i++) {
    pXMLString++;
  }

  return &*pXMLString;
};




syntax highlighted by Code2HTML, v. 0.9.1