/*
 * ALMA - Atacama Large Millimeter Array
 * (c) European Southern Observatory, 2002
 * (c) Associated Universities Inc., 2002
 * Copyright by ESO (in the framework of the ALMA collaboration),
 * Copyright by AUI (in the framework of the ALMA collaboration),
 * All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307  USA
 *
 *
 * File Misc.cpp
 */

#include <alma/ASDM/Misc.h>
 
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>

#include <algorithm> //required for std::swap
#include <iostream>
#include <sstream>

// string.h provides strcpy, strtok and strcat
#include <string.h>

#include <libxml/xmlmemory.h>
#include <libxml/debugXML.h>
#include <libxml/HTMLtree.h>
#include <libxml/xmlIO.h>
#include <libxml/parser.h>
#include <libxml/xinclude.h>
#include <libxml/catalog.h>

#include <libxslt/xslt.h>
#include <libxslt/xsltInternals.h>
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>

using namespace std;

extern int xmlLoadExtDtdDefaultValue;

#include <alma/ASDM/ASDMValuesParser.h>

namespace asdm {
  bool directoryExists(const char* dir) {
    DIR* dhandle = opendir(dir);

    if (dhandle != NULL) {
      closedir(dhandle);
      return true;
    }
    else {
      return false;
    }
  }

  bool createDirectory(const char* dir) { 
    return mkdir(dir, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0;
  }
	
  bool createPath(const char* path) {
    char localpath[256];
    strcpy(localpath, path);

    char directory[256];
    if (path[0] == '/') {
      strcpy(directory, "/");
    }
    else {
      strcpy(directory, "");
    }
    	
    char* pch = strtok(localpath, "/");
    while (pch != NULL) {
      strcat(directory, pch);
      strcat(directory, "/");
      if (!directoryExists(directory) && !createDirectory(directory)) {
	return false;
      }
      pch = strtok(NULL, "/");
    }
    return true;
  }
    
  void ByteSwap(unsigned char * b, int n) {
    int i = 0;
    int j = n-1;
    while (i<j) {
      std::swap(b[i], b[j]);
      i++, j--;
    }
  }

  const ByteOrder* ByteOrder::Little_Endian = new ByteOrder("Little_Endian");
  const ByteOrder* ByteOrder::Big_Endian = new ByteOrder("Big_Endian");
  const ByteOrder* ByteOrder::Machine_Endianity = ByteOrder::machineEndianity();

  ByteOrder::ByteOrder(const string& name):
    name_(name) {;}

  ByteOrder::~ByteOrder() {;}

  const ByteOrder* ByteOrder::machineEndianity() {
#if defined(__APPLE__)
    if (__DARWIN_BYTE_ORDER == __DARWIN_LITTLE_ENDIAN)
#else 
      if (__BYTE_ORDER == __LITTLE_ENDIAN)
#endif
	return Little_Endian;
      else
	return Big_Endian;
  }
  
  string ByteOrder::toString() const {
    return name_;
  }

  const ByteOrder* ByteOrder::fromString(const string &s) {
    if (s == "Little_Endian") return Little_Endian;
    if (s == "Big_Endian") return Big_Endian;
    return 0;
  }

  string uniqSlashes(const string & s) {
    string result;
    char c;
    bool inslash = false;
    size_t indexi=0;

    while (indexi < s.size()) {
      if ((c = s.at(indexi)) != '/') {
	inslash = false;
	result.push_back(c);
      }
      else
	if (inslash == false) {
	  result.push_back(c);
	  inslash = true;
	}
      indexi++;
    }
    return result;
  }

  ASDMUtilsException::ASDMUtilsException():message("ASDMUtilsException:") {;}

  ASDMUtilsException::ASDMUtilsException(const string & message): message("ASDMUtilsException:" + message) {;}   

  const string& ASDMUtilsException::getMessage() const { return message; }

#ifndef WITHOUT_BOOST
  ASDMUtils::DotXMLFilter::DotXMLFilter(vector<string>& filenames) {this->filenames = &filenames;}

  void ASDMUtils::DotXMLFilter::operator()(boost::filesystem::directory_entry& p) {
    if (!extension(p).compare(".xml")) {
      filenames->push_back(p.path().string());
    }
  }
#endif
 
  set<string>				ASDMUtils::evlaValidNames;
  set<string>				ASDMUtils::almaValidNames;
  map<ASDMUtils::Origin, string>	ASDMUtils::filenameOfV2V3xslTransform;
  map<string, string>			ASDMUtils::rootSubdir ;
  bool					ASDMUtils::initialized = ASDMUtils::initialize();

  bool ASDMUtils::initialize() {
    string evlaValidNames_a[] = {"EVLA"};
    evlaValidNames = set<string>(evlaValidNames_a, evlaValidNames_a + 1);

    string almaValidNames_a[] = {"ALMA", "AOS", "OSF", "IRAM_PDB"};
    almaValidNames = set<string>(almaValidNames_a, almaValidNames_a + 4);

    filenameOfV2V3xslTransform[ASDMUtils::ALMA]    = "sdm-v2v3-alma.xsl";
    filenameOfV2V3xslTransform[ASDMUtils::EVLA]    = "sdm-v2v3-evla.xsl";
    filenameOfV2V3xslTransform[ASDMUtils::UNKNOWN] = "";

    rootSubdir["INTROOT"]  = "config/";
    rootSubdir["ACSROOT"]  = "config/";

    return true;
  }

  string ASDMUtils::version(const string& asdmPath) {

    string result;

#ifndef WITHOUT_BOOST
    string ASDMPath = boost::algorithm::trim_copy(asdmPath);
    if (!boost::algorithm::ends_with(ASDMPath, "/")) ASDMPath+="/";
#else
    string ASDMPath = trim_copy(asdmPath);
    if (ASDMPath.back()!='/') ASDMPath+="/";
#endif
    ASDMPath += "ASDM.xml";

    // Does ASDMPath exist ?
#ifndef WITHOUT_BOOST
    if (!boost::filesystem::exists(boost::filesystem::path(ASDMPath))) {
#else
    if (!file_exists(ASDMPath)) {
#endif
      throw ASDMUtilsException("File not found '"+ASDMPath+"'.");
    }

    // Read and parse ASDM.xml
    xmlDocPtr ASDMDoc = xmlParseFile(ASDMPath.c_str());
  
    if (ASDMDoc == NULL ) {
      throw ASDMUtilsException("Error while parsing '"+ASDMPath+"'.");
    }
  
    /*
     * Can we find an attribute schemaVersion in the top level element ?
     */
    xmlNodePtr cur = xmlDocGetRootElement(ASDMDoc);
    xmlChar* version_ = xmlGetProp(cur, (const xmlChar *) "schemaVersion");

    // Yes ? then return its value.
    if (version_ != NULL) {
      result = string((char *) version_);
      xmlFree(version_);
      xmlFreeDoc(ASDMDoc);
      return result;
    }

    // Let's do some housecleaning
    xmlFreeDoc(ASDMDoc);

    // No ? then try another approach ... Can we find a dataUID element in the row elements of the Main table and in such a case
    // make the assumption that it's a v3 ASDM.
#ifndef WITHOUT_BOOST
    string MainPath = boost::algorithm::trim_copy(asdmPath);
    if (!boost::algorithm::ends_with(MainPath, "/")) MainPath+="/";
#else
    string MainPath = trim_copy(asdmPath);
    if (MainPath.back()!='/') MainPath+="/";
#endif
    MainPath += "Main.xml";    

    result = "UNKNOWN";
    // Does MainPath exist ?
#ifndef WITHOUT_BOOST
    if (boost::filesystem::exists(boost::filesystem::path(MainPath))) {
#else
    if (file_exists(MainPath)) {
#endif
      xmlDocPtr MainDoc =  xmlParseFile(MainPath.c_str());
  
      if (MainDoc == NULL ) {
	throw ASDMUtilsException("Error while parsing '"+MainPath+"'.");
      }

      // Here we make the reasonable assumption that there will be
      // row elements (at least one).
      //
      // Send an alarm though if no row element is found.
      xmlNodePtr cur = xmlDocGetRootElement(MainDoc)->xmlChildrenNode;
      int nRow = 0;
      while (cur != NULL) {
	if (!xmlStrcmp(cur->name, (const xmlChar*) "row")) {
	  nRow++;
	  if (hasChild (MainDoc, cur, (const xmlChar *) "dataUID")) {
	    result = "3";
	    break;
	  }

	  if (hasChild (MainDoc, cur, (const xmlChar *) "dataOid")) {
	    result = "2";
	    break;
	  }
	}
	cur = cur -> next;
      }

      xmlFreeDoc(MainDoc);      
      if (nRow == 0) {
	throw ASDMUtilsException("No 'row' elements present in '"+MainPath+"'.");
      }
    }
    
    return result;
  }

  bool ASDMUtils::hasChild(xmlDocPtr, // doc
			   xmlNodePtr node, const xmlChar* childName) {
    node = node->xmlChildrenNode;

    while (node != NULL) {
      if (!xmlStrcmp(node->name , childName))
	break; 
      node = node->next;
    }
    return (node != NULL);      
  }

  string ASDMUtils::parseRow(xmlDocPtr doc, xmlNodePtr node, const xmlChar* childName) {
    //
    // Consider the children of node (i.e. expectedly of "<row>...</row>")
    node = node->xmlChildrenNode;

    //
    // And look for childName.
    while (node != NULL) {
      if (!xmlStrcmp(node->name , childName)) {
	xmlChar* content = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
	string result((char *) content);
	xmlFree(content);
	return result;
      }
      node = node->next;
    }
    // If we are here , it's because we have not found childName as an element in node.
    throw ASDMUtilsException("Element '"+string((char *)childName)+"' not found.");
  }
 
  vector<string> ASDMUtils::telescopeNames(const string& asdmPath) {
    vector<string> result;

#ifndef WITHOUT_BOOST
    string execBlockPath = boost::algorithm::trim_copy(asdmPath);
    if (!boost::algorithm::ends_with(execBlockPath, "/")) execBlockPath+="/";
#else
    string execBlockPath = trim_copy(asdmPath);
    if (execBlockPath.back()!='/') execBlockPath+="/";
#endif
    execBlockPath += "ExecBlock.xml";

    // Does execBlockPath exist ?
#ifndef WITHOUT_BOOST
    if (!boost::filesystem::exists(boost::filesystem::path(execBlockPath))) {
#else
    if (!file_exists(execBlockPath)) {
#endif
      throw ASDMUtilsException("File not found '"+execBlockPath+"'.");
    }

    // Read and parse ExecBlock.xml
    xmlDocPtr execBlockDoc;
    xmlNodePtr cur;

    execBlockDoc = xmlParseFile(execBlockPath.c_str());
    if (execBlockDoc == NULL) {
      throw ASDMUtilsException("Error while parsing '"+execBlockPath+"'.");
    }

    cur = xmlDocGetRootElement(execBlockDoc)->xmlChildrenNode;
    while (cur != NULL) {
      if (!xmlStrcmp(cur->name, (const xmlChar*) "row")) {
#ifndef WITHOUT_BOOST
	result.push_back(boost::algorithm::trim_copy(parseRow(execBlockDoc, cur, (const xmlChar *) "telescopeName")));
#else
	result.push_back(trim_copy(parseRow(execBlockDoc, cur, (const xmlChar *) "telescopeName")));
#endif
      }
      cur = cur -> next;
    }
    xmlFreeDoc(execBlockDoc);

    return result;
  }

  ASDMUtils::Origin ASDMUtils::origin(const vector<string>& telescopeNames) {
    unsigned int numEVLA = 0;
    unsigned int numALMA = 0;
    for ( vector<string>::const_iterator iter = telescopeNames.begin(); iter != telescopeNames.end(); iter++ ) {
      if (evlaValidNames.find(*iter) != evlaValidNames.end())
	numEVLA++;
      else if (almaValidNames.find(*iter) != almaValidNames.end())
	numALMA++;
    }

    Origin autoOrigin = UNKNOWN;
    if (numEVLA == telescopeNames.size())
      autoOrigin = EVLA;
    else if (numALMA == telescopeNames.size())
      autoOrigin = ALMA;
    
    return autoOrigin;
  }

  vector<string> ASDMUtils::xmlFilenames ( const string & asdmPath  ) {
    vector<string> result;

#ifndef WITHOUT_BOOST
    boost::filesystem::path p(asdmPath);
    DotXMLFilter dotXMLFilter(result);
    std::for_each(boost::filesystem::directory_iterator(p), boost::filesystem::directory_iterator(), dotXMLFilter);
#else
    DIR *dir;
    if ((dir = opendir(asdmPath.c_str())) != NULL) {
      struct dirent *ent;
      string dirSep="/";
      if (asdmPath.back()=='/') dirSep="";

      while ((ent = readdir(dir)) != NULL) {
	string thisFile = ent->d_name;
	if (thisFile.size() > 4) {
	  if (thisFile.compare((thisFile.size()-4),4,".xml")==0) {
	    result.push_back(asdmPath+dirSep+thisFile);
	  }
	}
      }
      closedir(dir);
    } else {
      throw ASDMUtilsException("Could not open ASDM directory to retrieve xml files: "+asdmPath);
    }
#endif
    return result;
  }

  string ASDMUtils::pathToV2V3ALMAxslTransform() {return pathToxslTransform(filenameOfV2V3xslTransform[ASDMUtils::ALMA]);}
  string ASDMUtils::pathToV2V3EVLAxslTransform() {return pathToxslTransform(filenameOfV2V3xslTransform[ASDMUtils::EVLA]);}
  string ASDMUtils::nameOfV2V3xslTransform(ASDMUtils::Origin origin) {
    return filenameOfV2V3xslTransform[origin];
  }

  ASDMParseOptions::ASDMParseOptions() {
    origin_		= ASDMUtils::UNKNOWN;
    detectOrigin_       = true;
    version_    	= "UNKNOWN";
    detectVersion_      = true;
    loadTablesOnDemand_ = false;
    checkRowUniqueness_ = true;
  }

  ASDMParseOptions::ASDMParseOptions(const ASDMParseOptions& x) {
    origin_				       = x.origin_;
    detectOrigin_			       = x.detectOrigin_;
    version_				       = x.version_;
    detectVersion_			       = x.detectVersion_;
    loadTablesOnDemand_			       = x.loadTablesOnDemand_;
    checkRowUniqueness_                        = x.checkRowUniqueness_;
  }

  ASDMParseOptions::~ASDMParseOptions() {;}

  ASDMParseOptions& ASDMParseOptions::operator = (const ASDMParseOptions& rhs) {
    origin_				       = rhs.origin_;
    detectOrigin_			       = rhs.detectOrigin_;
    version_				       = rhs.version_;
    detectVersion_			       = rhs.detectVersion_;
    loadTablesOnDemand_			       = rhs.loadTablesOnDemand_;
    checkRowUniqueness_                        = rhs.checkRowUniqueness_;
    return *this;
  }
  ASDMParseOptions& ASDMParseOptions::asALMA() { origin_ = ASDMUtils::ALMA; detectOrigin_ = false; return *this; }
  ASDMParseOptions& ASDMParseOptions::asIRAM_PDB() { origin_ = ASDMUtils::ALMA; detectOrigin_ = false; return *this; }
  ASDMParseOptions& ASDMParseOptions::asEVLA() { origin_ = ASDMUtils::EVLA; detectOrigin_ = false; return *this; }
  ASDMParseOptions& ASDMParseOptions::asV2() { version_ = "2"; detectVersion_ = false; return *this; }
  ASDMParseOptions& ASDMParseOptions::asV3() { version_ = "3"; detectVersion_ = false; return *this; }
  ASDMParseOptions& ASDMParseOptions::loadTablesOnDemand(bool b) { loadTablesOnDemand_ = b;  return *this; }
  ASDMParseOptions& ASDMParseOptions::checkRowUniqueness(bool b) { checkRowUniqueness_ = b;  return *this; }
  string ASDMParseOptions::toString() const {
    ostringstream oss;
    oss << *this;
    return oss.str();
  }

  XSLTransformer::XSLTransformer() : cur(NULL) {
    xmlSubstituteEntitiesDefault(1);
    xmlLoadExtDtdDefaultValue = 1;
  }

  XSLTransformer::XSLTransformer(const string& xsltPath) {
    if (getenv("ASDM_DEBUG")) {
	cout << "XSLTransformer::XSLTransformer(const string& xsltPath) called " << endl;
	cout << "About parse the style sheet contained in " << xsltPath << endl;
    }

    xmlSubstituteEntitiesDefault(1);
    xmlLoadExtDtdDefaultValue = 1;
    
    cur = xsltParseStylesheetFile((const xmlChar*) xsltPath.c_str());
    if (cur == NULL)
      throw XSLTransformerException("Failed to  parse the XSL stylecheet contained in '" + xsltPath + "'.");
  }

  void XSLTransformer::setTransformation(const string& xsltPath) {
    if (getenv("ASDM_DEBUG")) 
      cout << "XSLTransformer::setTransformation(const string& xsltPath) called on '" << xsltPath << "'." << endl;
    
    if (cur) {
      xsltFreeStylesheet(cur);
      cur = NULL;
    }

    // cout << "About parse the style sheet contained in " << xsltPath << endl;
      
    cur = xsltParseStylesheetFile((const xmlChar*) xsltPath.c_str());
    if (cur == NULL)
      throw XSLTransformerException("Failed to parse the XSL stylecheet contained in '" + xsltPath + "'." );
  }


  XSLTransformer::~XSLTransformer() {
    // cout << "XSLTransformer::~XSLTransformer() called" << endl;
    if (cur) {
      xsltFreeStylesheet(cur);
      cur = NULL;
    }
  } 

  string XSLTransformer::operator()(const string& xmlPath){
    xmlDocPtr doc = NULL, res = NULL;
    //xsltStylesheetPtr cur;

    xmlChar* docTxtPtr = NULL;
    int docTxtLen = 0;
    
    if (getenv("ASDM_DEBUG")) cout << "About to read and parse " << xmlPath << endl;
    doc = xmlParseFile(xmlPath.c_str());
    if (doc == NULL) {
      throw XSLTransformerException("Could not parse the XML file '" + xmlPath + "'." );
    }

    if (!cur) {
      xmlDocDumpFormatMemory(doc, &docTxtPtr, &docTxtLen, 1);
    }
    else {
      res = xsltApplyStylesheet(cur, doc, NULL);
      if ( res == NULL ) {
	throw XSLTransformerException("Failed to apply the XSLT tranformation to the XML document contained in '" + xmlPath + "'.");
      }
      int status = xsltSaveResultToString(&docTxtPtr,
					  &docTxtLen,
					  res,
					  cur);
      if (status == -1) 
	throw XSLTransformerException("Could not dump the result of the XSL transformation into memory.");
    }

    if (getenv("ASDM_DEBUG")) 
      cout << "Making a string from the result of the XSL transformation" << endl;
    string docXML((char *) docTxtPtr, docTxtLen);
    // cout << "docXML = " << docXML << endl;

    xmlFree(docTxtPtr);
    if (res) xmlFreeDoc(res);
    xmlFreeDoc(doc);
    
    // cout << "All resources unneeded freed" << endl;
    return docXML;
  } 

  std::ostream& operator<<(std::ostream& output, const ASDMParseOptions& p) {
    string s;
    switch (p.origin_) {
    case ASDMUtils::UNKNOWN:
      s = "UNKNOWN";
      break;
    case ASDMUtils::ALMA:
      s = "ALMA";
      break;
    case ASDMUtils::EVLA:
      s = "EVLA";
      break;
    }
    output << "Origin=" << s << ",Version=" << p.version_ << ",LoadTablesOnDemand=" << p.loadTablesOnDemand_ << ",CheckRowUniqueness=" << p.checkRowUniqueness_;
    return output;  // for multiple << operators.
  }

  CharComparator::CharComparator(std::ifstream * is_p, off_t limit):is_p(is_p), limit(limit){asdmDebug_p = getenv("ASDM_DEBUG");}

  bool CharComparator::operator() (char cl, char cr) {
    if (asdmDebug_p) cout << "Entering CharComparator::operator()" << endl;
    if (is_p && is_p->tellg() > limit) 
      return true;
    else 
      return toupper(cl) == cr;
    if (asdmDebug_p) cout << "Exiting CharComparator::operator()" << endl;
  }

  CharCompAccumulator::CharCompAccumulator(std::string* accumulator_p, std::ifstream * is_p, off_t limit): accumulator_p(accumulator_p),
													   is_p(is_p),
													   limit(limit) {nEqualChars = 0; asdmDebug_p = getenv("ASDM_DEBUG");}
  bool CharCompAccumulator::operator()(char cl, char cr) {
    if (asdmDebug_p) cout << "Entering CharCompAccumulator::operator()" << endl;
    bool result = false;
    // Are we beyond the limit ?
    if (is_p && is_p->tellg() > limit) 
      result = true;      // Yes
    else {                // No
      if (toupper(cl) == toupper(cr)) {
	result = true;
	nEqualChars++;
      }
      else {
	if (nEqualChars > 0) {
	  accumulator_p->erase(accumulator_p->end() - nEqualChars + 1, accumulator_p->end());
	  nEqualChars = 0;
	}
	result = false;
      }
      accumulator_p->push_back(cl);
    }
    if (asdmDebug_p) cout << "Exiting CharCompAccumulator::operator()" << endl;
    return result;
  }  
  
  istringstream ASDMValuesParser::iss;
  ostringstream ASDMValuesParser::oss;

} // end namespace asdm