/* Copyright (c) 2003-2006 Adrian Pop [adrpo@ida.liu.se], 
 * Programming Environments Laboratory
 * Linkoping University Sweden, All Rights Reserved
*******************************************************************************
  File        : "modelicaXML.cpp"
  Author      : Adrian Pop 2003-10-25
  Description : Antlr Parser caller and XML serializer
-------------------------------------------------------------------------------
  Source:
  Revision:
  Author:	Adrian Pop
  Date:		2004-05-12 
  modification: 2004-11-01 - added directory walking, ModelicaXML class
  modification: 2006-03-12 - updated to Modelica 2.2 and better command line parameters
  modification  2006-11-29 - fix a Linux version
*******************************************************************************
*/


//**  HEADERS ************************************************************

// standard include files
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <fstream>

// ModelicaParser related include file 
// the following header files are automatically generated by the antlr parser (Do not modify them)
#include "modelica_lexer.hpp"
#include "modelica_parser.hpp"
#include "modelica_tree_parser.hpp"
#include "modelica_parserTokenTypes.hpp"
#include "modelica_tree_parserTokenTypes.hpp"
#include "parse_tree_dumper.hpp"

// antlr related include files
#include "antlr/AST.hpp"
#include "antlr/CommonAST.hpp"
#include "antlr/ASTFactory.hpp"
#include "MyAST.h"
#include "dirwalk.h"

using namespace std;

#define modelicaDTDUrl "http://www.ida.liu.se/~adrpo/modelica/xml/modelicaxml-v2.dtd"

class ModelicaXML
{
  typedef std::string mstring;
private:
  /* some xml helpers declarations */
  mstring *dtdUrl;
  DOMImplementation* pDOMImpl;
  DOMDocumentType* pDoctype;
  DOMDocument* pModelicaXMLDoc;
  DOMElement* pRootElementModelica;
  DOMWriter* domWriter;
  int serializeXMLDocumentToFile(mstring xmlFileName);
  DOMElement* createModelicaXMLDOMElement(const char *fileName);
  l_list &sortMoFiles(l_list &fileList);

public:
  // constructor
  ModelicaXML(char* dtdURL);
  // destructor
  ~ModelicaXML();
  void usageModelicaXML();
  int serializeMoFileToXML(char* fileName);
  int serializeEachMoFileInDirectory(char *directoryName);
  int serializeAllMoFilesInDirectoryInXMLFile(char *directoryName, char *fileName);
};

int usageModelicaXML(void)
{
  cout << "Usage:" << "ModelicaXML [-dtd dtdURL] [-f file1.mo file2.mo ... fileN.mo]" 
       << "[-d directory] [-o file]" << endl;
  cout << "-dtd dtdURL\n\t sets the dtdURL as the dtd for the xml file:" 
       << "c:\\path\\to\\modelica.dtd OR /home/path/to/modelica.dtd OR http://host.to/modelica.dtd" 
       << "\n\t if none given, we use the default: " << modelicaDTDUrl << endl;
  cout << "-f file1.mo file2.mo ... fileN.mo\n\t transforms the file1-N.mo into xml version of " 
       << "the files, named file1-N.mo.xml" << endl;
  cout << "-d directory\n\t transforms each file.mo files from the directory" 
       << "\n\t AND subdirectories into xml version of files, named file.mo.xml" << endl;
  cout << "-d dir -o outputFile\n\t transforms ALL file.mo files from the directory" 
       << "\n\t AND subdirectories one big xml file called outputFile.xml writen in dir" << endl;
  return 1;
}

//-----------------------------------------------------------------------------
// func         : main function for the modelica XML serializer
// author       : Adrian Pop 2003-10-25
// organization : Linkoping University Sweden, Programming Environment Laboratory
//-----------------------------------------------------------------------------
// the function accept as argument a flattened modelica file ad will generate as output the corresponding C files 
// for the Modelica Simulator
int main( int argc, char* argv[] )
{
	// check if the modelica file is present in the argument list
    if (argc < 2)
    {
      return usageModelicaXML();
    }

    if (strcmp(argv[1],"--help") == 0)
    {
      return usageModelicaXML();
    }

    list<string*> argvList;
    cout << "Calling: " << argv[0] << " with arguments:\n\t "; 
    for (int i = 1; i < argc; i++) 
    {
      cout << argv[i] << " ";
      argvList.push_back(new string(argv[i]));
    }
    cout << endl << endl;

    list<string*> filesToConvert;
    string* directoryToConvert = NULL;
    string* serializedFile = NULL;
    string* dtdUrl = NULL;
    string* front = NULL;
    while (true)
    {
      if (!argvList.size()) break;
      front = argvList.front();
      argvList.pop_front();
      if (*front == "-f")
      {
	string* file = NULL;
	while (true)
	{
	  if (!argvList.size()) break;
	  file = argvList.front();
	  if (!file) 
	  {
	    if (filesToConvert.size() == 0) return usageModelicaXML();
	    break;
	  }
	  if (*file == "-f" || *file == "--help" || 
	      *file == "-d" || *file == "-dtd"   ||
	      *file == "-o") 
	  {
	    if (filesToConvert.size() == 0) return usageModelicaXML();
	    break;
	  }
	  argvList.pop_front();
	  filesToConvert.push_back(file);
	}
      }
      else 
	if (*front == "-d")
	{
	  if (!argvList.size()) break;
	  string *directory = argvList.front();
	  if (!directory || /* if is empty or if is command line parameter */
	      (directory && (*directory == "-f" || *directory == "--help" || 
			     *directory == "-d" || *directory == "-dtd"   ||
			     *directory == "-o"))) return usageModelicaXML();
	  /* else just collect the directory to convert */
	  directoryToConvert = directory;
	  argvList.pop_front();
	}
	else 
	  if (*front == "-dtd")
	  {
	    if (!argvList.size()) break;
	    string *url = argvList.front();
	    if (!url || /* if is empty or if is command line parameter */
		(url && (*url == "-f" || *url == "--help" || 
			 *url == "-d" || *url == "-dtd"   ||
			 *url == "-o"))) return usageModelicaXML();
	    /* else just collect the dtd url */
	    dtdUrl = url;
	    argvList.pop_front();
	  }
	  else
	    if (*front == "--help") return usageModelicaXML();
	    else
	      if (*front == "-o")
	      {
		if (!argvList.size()) break;
		string *f = argvList.front();
		if (!f || /* if is empty or if is command line parameter */
		    (f && (*f == "-f" || *f == "--help" || 
			   *f == "-d" || *f == "-dtd"   ||
			   *f == "-o"))) return usageModelicaXML();
		/* else just collect the output file */
		serializedFile = f;
		argvList.pop_front();
	      }
	      else return usageModelicaXML();
    }
    /* see what we have */
    ModelicaXML* pModelicaXML = NULL;
    if (dtdUrl) pModelicaXML = new ModelicaXML((char*)dtdUrl->c_str());
    else pModelicaXML = new ModelicaXML(NULL);
    
    bool somethingToDo = false;

    try
    {
      for (unsigned int i = 0; i < filesToConvert.size(); i++)
      {
	pModelicaXML->serializeMoFileToXML((char*)filesToConvert.front()->c_str());
	filesToConvert.pop_front();
	somethingToDo = true;
      }
      if (!serializedFile && directoryToConvert)
      {
	pModelicaXML->serializeEachMoFileInDirectory((char*)directoryToConvert->c_str());
	somethingToDo = true;
      }
      if (serializedFile && directoryToConvert)
      {
	pModelicaXML->serializeAllMoFilesInDirectoryInXMLFile(
							      (char*)directoryToConvert->c_str(), 
							      (char*)serializedFile->c_str());
	somethingToDo = true;
      }
      if (!somethingToDo) usageModelicaXML();
    }
    catch(XMLException& e)
    {
      std::cerr << "XMLException: " << XMLString::transcode(e.getMessage()) << std::endl; exit (1);
    }
    catch(DOMException& e)
    {
      std::cerr << "DOMException: Code: " << e.code << " Msg: " << e.msg << std::endl; exit (1);
    }
    catch(std::exception& e) 
    {
      std::cerr << "STDException: " << e.what() << std::endl; exit (1);
    }
    return 0;
}

ModelicaXML::ModelicaXML(char* dtdURL)
{
  // initialize xml framework
  XMLPlatformUtils::Initialize();
  
  // XML DOM creation
  pDOMImpl = DOMImplementationRegistry::getDOMImplementation(X("Core"));
  
  // create the document type (according to modelica.dtd)
  pDoctype = 
    pDOMImpl->createDocumentType(
				 X("modelica"),
				 NULL,
				 (dtdURL) ? X(string(dtdURL).c_str()) : X(string(modelicaDTDUrl).c_str()));
  
  // create the <program> root element 
  pModelicaXMLDoc = pDOMImpl->createDocument(
					     0,                    // root element namespace URI.
					     X("modelica"),         // root element name
					     pDoctype);                   // document type object (DTD).
  
  pRootElementModelica = pModelicaXMLDoc->getDocumentElement();
  
  // create the writer
  domWriter = pDOMImpl->createDOMWriter();
  // set the pretty print feature
  
  if (domWriter->canSetFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true))
    domWriter->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true);
}

ModelicaXML::~ModelicaXML()
{
  domWriter->release();
  // release the document
  pModelicaXMLDoc->release();
  // terminate the XML framework
  XMLPlatformUtils::Terminate();
}


int ModelicaXML::serializeEachMoFileInDirectory(char *directoryName)
{
  typedef std::string mstring;
  mstring filename;
  l_list dirList;
  getDirectoryStructure(directoryName, dirList);
  std::list<char*>::const_iterator it;
  for(it=dirList.begin(); it!=dirList.end(); ++it)
  {
    l_list fileList;
    getFileList(*it, fileList, "*.mo");
    if (fileList.size()) fileList = sortMoFiles(fileList);
    cout << "Found: " << fileList.size() << " *.mo files in directory: " << *it  << endl;
    if (!fileList.size()) { continue; }
    std::list<char*>::const_iterator itFileList;
    for(itFileList=fileList.begin(); itFileList!=fileList.end(); ++itFileList)
    {
      filename = mstring(*itFileList);
      cout << " [" << filename << "]";
      DOMElement* pModelicaXML = createModelicaXMLDOMElement(filename.c_str());
      // if is not NULL append it to the <modelica></modelica> element
      if (pModelicaXML) pRootElementModelica->appendChild(pModelicaXML);
      // vomit the current XML Document to file
      std::string xmlFile(filename.c_str());
      xmlFile += ".xml";
      serializeXMLDocumentToFile(xmlFile);
      if (pModelicaXML) pRootElementModelica->removeChild(pModelicaXML);
    }
    if (fileList.size()) cout << endl;
  } 
  return 0;
}

int ModelicaXML::serializeAllMoFilesInDirectoryInXMLFile(char *directoryName, char *xmlFileName)
{
  int i=0;
  typedef std::string mstring;
  mstring filename;
  l_list dirList;
  getDirectoryStructure(directoryName, dirList);
  std::list<char*>::const_iterator it;
  for(it=dirList.begin(); it!=dirList.end(); ++it)
  {
    l_list fileList;
    getFileList(*it, fileList, "*.mo");
    // take care of pakage.mo first
    fileList = sortMoFiles(fileList);
    cout << "Found: " << fileList.size() << " *.mo files in directory: " << *it  << endl;
    std::list<char*>::const_iterator itFileList;
    for(itFileList=fileList.begin(); itFileList!=fileList.end(); ++itFileList)
    {
      filename = mstring(*itFileList);
      cout << " [" << *itFileList << "]";
      DOMElement* pModelicaXML = createModelicaXMLDOMElement(filename.c_str());
      // if is not NULL append it to the <modelica></modelica> element
      if (pModelicaXML) 
      {
	pRootElementModelica->appendChild(pModelicaXML);
	i++;
      }
    }
    if (fileList.size()) cout << endl;
  } 
  // vomit the current XML Document to file
  std::string xmlFile(xmlFileName);
  xmlFile = mstring(directoryName) + mstring(PATH_SEPARATOR) + xmlFile;
  xmlFile += ".xml";
  serializeXMLDocumentToFile(xmlFile);
  cout << "Found: " << i << " .mo files" << endl;
  cout << "Serialized to: " << xmlFile << endl;
  return 0;
}

DOMElement* ModelicaXML::createModelicaXMLDOMElement(const char* fileName)
{
  ifstream file;
  //ofstream wfile;
  
  //open the file passed as an argument
  file.open(fileName);
  
  DOMElement* pRootElementModelicaXML = NULL;
  
  //if the file cannot be opened
  if (!file)
  {
    cerr << "Could not open file: " << fileName << "\n";
    return NULL;
  }
 
  try 
  {
    antlr::ASTFactory my_factory("MyAST", MyAST::factory); 
    modelica_lexer lexer(file);
    lexer.setFilename(fileName);
    modelica_parser parser(lexer);
    parser.initializeASTFactory(my_factory);
    parser.setASTFactory(&my_factory);
    parser.stored_definition();
    //wfile.open("output.txt");
    //wfile << parser.getAST()->toStringList() << std::endl;
    antlr::RefAST ast = parser.getAST();
    //parse_tree_dumper dumper(std::cout);
    //std::cout << std::flush;
    if (ast) 
    {
      //dumper.dump(ast);
      modelica_tree_parser walker;
      walker.initializeASTFactory(my_factory);
      walker.setASTFactory(&my_factory);
      pRootElementModelicaXML = walker.stored_definition(RefMyAST(ast), fileName, pModelicaXMLDoc);
    }
    else
    {
      //wfile << std::endl << "Parse error: <NULL> AST\n";
      std::cerr << std::endl << "Parse error: <NULL> AST\n";
      file.close();
      return NULL;
    }
  }
  catch(antlr::ANTLRException& e) 
  {
    std::cerr << "Parser/Lexer/Walker Exception: " << e.toString() << std::endl;
    file.close();
    std::cerr << "ERROR! File:" << fileName << std::endl;
    return NULL;
  }
  catch(std::exception& e) 
  {
    std::cerr << "STDException: " << e.what() << std::endl;
    file.close();
    std::cerr << "ERROR! File:" << fileName << std::endl;
    return NULL;
  }
  file.close();
  //std::cout << "SUCCESS! File:" << fileName << std::endl;
  //wfile << std::endl << "SUCCESS! File:" << fileName << std::endl;
  //wfile.close();
  return pRootElementModelicaXML;
}

int ModelicaXML::serializeXMLDocumentToFile(mstring xmlFileName)
{
  // fix the file
  XMLFormatTarget *myFormatTarget = new LocalFileFormatTarget(X(xmlFileName.c_str()));
  //XMLFormatTarget *myOutFormatTarget = new StdOutFormatTarget;
  
  // serialize a DOMNode to the local file "
  domWriter->writeNode(myFormatTarget, *pModelicaXMLDoc);
  //domWriter->writeNode(myOutFormatTarget, *pModelicaXMLDoc);
  
  myFormatTarget->flush();
  //myOutFormatTarget->flush();

  delete myFormatTarget;
  // delete myOutFormatTarget;
  return 0;
}

int ModelicaXML::serializeMoFileToXML(char* fileName)
{
  // ModelicaXML filename (normal operation)
  DOMElement *pModelicaXMLElement = createModelicaXMLDOMElement(fileName);
  if (pModelicaXMLElement) pRootElementModelica->appendChild(pModelicaXMLElement);
  // vomit the current XML Document to file
  std::string xmlFile(fileName);
  xmlFile += ".xml";
  serializeXMLDocumentToFile(xmlFile);
  
  unsigned int elementCount = pModelicaXMLDoc->getElementsByTagName(X("*"))->getLength();
  std::cout << std::endl;
  std::cout << "The tree serialized contains: " << elementCount << " elements." << std::endl;
  
  return 0;
}


l_list &ModelicaXML::sortMoFiles(l_list &fileList)
{	
  bool bPackageExists = false;
  bool bExampleExists = false;
  mstring filename;
  mstring str_package;
  mstring str_example;
  std::list<char*>::iterator itFileList;
  for(itFileList=fileList.begin(); itFileList!=fileList.end(); ++itFileList)
  {
    filename = mstring(*itFileList);
    if (endsWith(filename, "package.mo")) 
    {
      bPackageExists = true;
      str_package = filename;
      fileList.erase(itFileList);
      itFileList=fileList.begin();
    }
    if (endsWith(filename, "Example.mo")) 
    {
      bExampleExists = true;
      fileList.erase(itFileList);
      str_example = filename;      
      itFileList=fileList.begin();
    }
  }
  if (bPackageExists)
  {
    fileList.push_front((char*)str_package.c_str());
  }
  if (bExampleExists)
  {
    fileList.push_back((char*)str_example.c_str());
  }
  return fileList;
}
