/*  cxxsim:  creates a C++ simulation from MTT elementary system equations
 *  Copyright (C) 2000,2002  Geraint Paul Bevan
 *
 *  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
 */

#include <fstream>
#include <list>
#include <map>
#include <string>

namespace cxxsim {

  class cr {
    
  public:

    cr		(std::string system_name);
    
    void read	(std::ifstream &file);
    void write	(std::ofstream &file);
   
    friend std::ifstream &operator>> (std::ifstream &file, cr &c);
    friend std::ofstream &operator<< (std::ofstream &file, cr &c);
    
  private:

    std::string			name;

    std::list<std::string>	L;
  };

  class equations {

  public:

    equations	(std::string system_name);    

    void read	(std::ifstream &file);
    void write	(std::ofstream &file);
   
    friend std::ifstream &operator>> (std::ifstream &file, equations &e);
    friend std::ofstream &operator<< (std::ofstream &file, equations &e);

  private:
    
    void descend	(std::string s);
    void parse		(void);

    std::string				name; // system name

    // list of statements collected by reader
    std::list<std::string>		statements;

    std::list<std::string>		leqn; // final expressions
    std::list<std::string>		lvar; // intermediate expressions
    std::list<std::string>		loop; // algebraic loops

    // table of final and intermediate expressions
    std::map<std::string, std::string>	meqn; // lhs, rhs mapping
    std::map<std::string, std::string>	mvar;

    // tag is symbol denoting an intermediate variable
    std::string				tag; // denotes intermediates

    // token delimiters (not ([{.`'" or alphanumeric)
    std::string				delim;
  };

  class parameter {

  public:

    parameter	(std::string system_name);

    void read	(std::ifstream &file);
    void write	(std::ofstream &file);

    friend std::ifstream &operator>> (std::ifstream &file, parameter &p);
    friend std::ofstream &operator<< (std::ofstream &file, parameter &p);

  private:

    std::string		name;

    struct record {
      std::string	variable;
      std::string	component;
    };
    
    typedef struct record record_t;
    std::list<record_t>			L;
  };

  class structure {
    
  public:

    structure		(std::string system_name);

    void read		(std::ifstream &file);
    void write		(std::ofstream &file);

    friend std::ifstream &operator>> (std::ifstream &file, structure &s);
    friend std::ofstream &operator<< (std::ofstream &file, structure &s);

  private:

    void write_declare	(std::ofstream &file);
    void write_input	(std::ofstream &file);
    void write_logic	(std::ofstream &file);
    void write_state	(std::ofstream &file);

    std::string		name;

    struct record {
      std::string	vec;	// vector: u, x, y, yz
      unsigned int	num;	// position of element in vector
      std::string	cmp;	// component
      std::string	fnm;	// full name
      unsigned int	rpt;	// number of repetitions
    };

    typedef struct record record_t;
    std::list<record_t>			Lu;
    std::list<record_t>			Lx;
    std::list<record_t>			Ly;
    std::list<record_t>			Lyz;
    std::list<record_t>::iterator	i;    
  };

  cr::cr (std::string system_name){
    name = system_name;
  }

  void
  cr::read (std::ifstream &file){
    std::string r;
    while (file >> r){
      L.push_back(r);
    }
  }

  void
  cr::write (std::ofstream &file){
    file << "enum causality { effort, flow, state };" << std::endl
	 << "typedef enum causality causality_t;" << std::endl;
    std::list<std::string>::iterator i;
    for (i = L.begin(); i != L.end(); i++){
      if ((*i != "SS") && (*i != "ISW")){
	file << "#include <" << *i << ".hh>" << std::endl;
      }
    }
    file << std::endl;
  }

  std::ifstream &operator>> (std::ifstream &file, cr &c){
    c.read(file);
    return file;
  }
  
  std::ofstream &operator<< (std::ofstream &file, cr &c){
    c.write(file);
    return file;
  }

  equations::equations (std::string system_name){
    name = system_name;
    tag = system_name;
    delim = "~!@#$%^&*)-+=]}\\|;:,<>/?";
  };

  void
  equations::read (std::ifstream &file){
    
    char				c;
    std::string				s;
    std::string				t;

    unsigned int			i;

    //    file.unsetf(ios::skipws);
    file.setf(ios::skipws);

    // read lines from file (no max length, unlike std::getline)
    while (file >> c){
      s = "";
      while (c != ';' && file){
	if (c == '%'){
	  file.unsetf(ios::skipws);
	  while ((c != '\n') && file){
	    file >> c;			     // eat comment
	  }
	  file.setf(ios::skipws);
	} else {
	  if ((c != ' ') && (c != '\t')){    // strip whitespace
	    s += c;
	  }
	  file >> c;
	}
      }
      
      // fix vector references
      t = "MTTu(";
      while ((i = s.find(t)) < s.length()){
	s.replace(i, 5, "mttu[");
	i += s.substr(i, s.length()).find(",1)");
	s.replace(i, 3, "]");
      }
      t = "MTTx(";
      while ((i = s.find(t)) < s.length()){
	s.replace(i, 5, "mttx[");
	i += s.substr(i, s.length()).find(",1)");
	s.replace(i, 3, "]");
      }
      t = "MTTdX(";
      while ((i = s.find(t)) < s.length()){
	s.replace(i, 6, "mttdx[");
	i += s.substr(i, s.length()).find(",1)");
	s.replace(i, 3, "]");
      }
      t = "MTTy(";
      while ((i = s.find(t)) < s.length()){
	s.replace(i, 5, "mtty[");
	i += s.substr(i, s.length()).find(",1)");
	s.replace(i, 3, "]");
      }
      
      // strip newlines
      while ((i = s.find('\n')) < s.length()){
	s.replace(i, 1, "");
      }

      if ((s != "") && (s != "END")){
	// add statement to list
	statements.push_back(s);
      }
    }

    // finished reading, time to comprehend ...
    this->parse();
  }
  
  void
  equations::write (std::ofstream &file){

    std::list<std::string>::iterator	eqn;

    file << "void"					<< std::endl
	 << this->name << "_ode(const double t,"	<< std::endl
	 << "\tdouble *mttu,"				<< std::endl
	 << "\tdouble *mttx,"				<< std::endl
	 << "\tdouble *mttdx,"				<< std::endl
	 << "\tdouble *mtty)"				<< std::endl
	 << "{"						<< std::endl;
    for (eqn = leqn.begin(); eqn != leqn.end(); eqn++){
      file << *eqn << " = " << meqn[*eqn] << ";" << std::endl;
    }
    file << "} // " << this->name << "_ode"		<< std::endl;
  };

  void
  equations::descend (std::string s){

    char		c;
    unsigned int	i;
    bool		cont;

    std::list<std::string>::iterator	var;

    loop.push_back(s);

    // find algebraic loops
    for (var = loop.begin(); var != loop.end(); var++){
      if ((i = mvar[s].find(*var)) < mvar[s].length()){
	c = mvar[s].c_str()[i + var->length()];
	if (delim.find(c) < delim.length()){
	  std::cerr << std::endl
		    << "warning: algebraic loop" << std::endl
		    << '\t' << s << " = " << mvar[s].substr(0, 60);
	  if (mvar[s].length() > 60){
	    std::cerr << " ...";
	  }
	  std::cerr << std::endl << std::endl;
	  mvar[s] = s + "____loop";
	}
      }
    }

    // substitutes intermediate variables for their expansion
    do {
      cont = false;
      for (var = lvar.begin(); var != lvar.end(); var++){
	// check for token match
	while ((i = mvar[s].find(*var)) < mvar[s].length()){
	  // ensure exact match
	  c = mvar[s].c_str()[i + var->length()];
	  if (delim.find(c) < delim.length()){
	    // expand expression recursively
	    this->descend(*var);
	    // check match again, map may have changed
	    if ((i = mvar[s].find(*var)) < mvar[s].length()){
	      c = mvar[s].c_str()[i + var->length()];
	      if (delim.find(c) < delim.length()){
	    	// substitute expression
		mvar[s].replace(i, var->length(), mvar[*var]);
		// continue
		cont = true;
	      }
	    }	    
	  }
	}
      }
    } while (cont);
    loop.pop_back();
  }
  
  void
  equations::parse (void){
    
    char		c;
    unsigned int	i;

    std::string		lhs;
    std::string		rhs;
    
    std::list<std::string>::iterator	s;

    std::list<std::string>::iterator	eqn;
    std::list<std::string>::iterator	var;

    for (s = statements.begin(); s != statements.end(); s++){
      // parse statement
      if ((i = s->find(":=")) < s->length()){
	lhs = s->substr(0, i);
	rhs = s->substr(i+2, s->length());
	
	// separate intermediate and final variables
	if (s->substr(0, tag.length()) == tag){
	  // add to table of intermediate variables
	  lvar.push_back(lhs);
	  mvar[lhs] = "(" + rhs + ")";
	} else {
	  // add to table of final variables
	  leqn.push_back(lhs);
	  meqn[lhs] = rhs + ";";
	}
      } else {
	std::cerr << std::endl
		  << "warning: non-assignment -- ignoring"
		  << std::endl
		  << '\t' << *s << std::endl
		  << std::endl;
      }
    }
      
    // expand rhs of intermediate variables
    for (var = lvar.begin(); var != lvar.end(); var++){
      loop.clear();
      descend(*var);
    }
    
    // substitute expanded expressions into final equations
    for (eqn = leqn.begin(); eqn != leqn.end(); eqn++){
      for (var = lvar.begin(); var != lvar.end(); var++){
	while ((i = meqn[*eqn].find(*var)) < meqn[*eqn].length()){
	  c = meqn[*eqn].c_str()[i + var->length()];
	  if (delim.find(c) < delim.length()){
	    meqn[*eqn].replace(i, var->length(), mvar[*var]);
	  }
	}
      }
    }
  }

    std::ifstream &operator>> (std::ifstream &file, equations &e){
    e.read(file);
    return file;
  }
  
  std::ofstream &operator<< (std::ofstream &file, equations &e){
    e.write(file);
    return file;
  }

  parameter::parameter (std::string system_name){
    name = system_name;
  };

  void
  parameter::read (std::ifstream &file){
    record_t r;
    while (file >> r.variable >> r.component){
      if (r.variable.find("#") == 0){
	file.unsetf(ios::skipws);
	char c = '\0';
	while (c != '\n'){
	  file >> c;
	}
	file.setf(ios::skipws);
      } else {
	L.push_back(r);
      }
    }
  }

  void
  parameter::write (std::ofstream &file){
    std::list<record_t>::iterator i;
    std::string type;
    std::string value;
    for (i = L.begin(); i != L.end(); i++){
      if (i->variable.substr(0, 6) == "bool__"){
	type = "bool";
	value = "false";
      } else if (i->variable.substr(0, 6) == "char__"){
	type = "char";
	value = "\0";
      } else if (i->variable.substr(0, 8) == "double__"){
	type = "double";
	value = "1.0";
      } else if (i->variable.substr(0, 7) == "float__"){
	type = "float";
	value = "1.0";
      } else if (i->variable.substr(0, 5) == "int__"){
	type = "int";
	value = "1";
      } else if (i->variable.substr(0, 7) == "string__"){
	type = "std::string";
	value = "hello world!";
      } else {
	type = "double";
	value = "1.0";
      }
      file << "static " << type << "\t" << i->variable << " = " << value
	   << ";\t// " << i->component << std::endl;
    }
    file << std::endl;
  }

  std::ifstream &operator>> (std::ifstream &file, parameter &p){
    p.read(file);
    return file;
  }

  std::ofstream &operator<< (std::ofstream &file, parameter &p){
    p.write(file);
    return file;
  }

  structure::structure (std::string system_name){
    name = system_name;
  };
  
  void
  structure::read (std::ifstream &file){
    std::list<record_t> *p;
    record_t r;
    if (! file){
      std::cerr << "warning: no structure data found (empty file)" << std::endl;
    }
    while (file >> r.vec >> r.num >> r.cmp >> r.fnm >> r.rpt){
      if (r.vec == "input"){
	p = &(this->Lu);
      } else if (r.vec == "state"){
	p = &(this->Lx);
      } else if (r.vec == "output"){
	p = &(this->Ly);
      } else {
	p = &(this->Lyz);
      }
      p->push_back(r);
    }
  };

  void
  structure::write (std::ofstream &file){
    this->write_declare(file);
    file << std::endl;
    this->write_input(file);
    file << std::endl;
    this->write_logic(file);
    file << std::endl;
    this->write_state(file);
    file << std::endl;
  }

  void
  structure::write_declare (std::ofstream &file){
    file << "const int mttNu\t= " << this->Lu.size() + 1 << ";"	<< std::endl
	 << "const int mttNx\t= " << this->Lx.size() + 1 << ";"	<< std::endl
	 << "const int mttNy\t= " << this->Ly.size() + 1 << ";"	<< std::endl
	 << "static double mttu[mttNu];"			<< std::endl
	 << "static double mttx[mttNx];"			<< std::endl
	 << "static double mtty[mttNy];"			<< std::endl
	 << "static double mttdx[mttNx];"			<< std::endl
	 << std::endl;
  }

  void
  structure::write_input (std::ofstream &file){
    file << "void"					<< std::endl
	 << this->name << "_input(const double t,"	<< std::endl
	 << "\tdouble *mttu,"				<< std::endl
	 << "\tdouble *mttx,"				<< std::endl
	 << "\tdouble *mtty)"				<< std::endl
	 << "{"						<< std::endl;
    for (i = Lu.begin(); i != Lu.end(); i++){
      file << "\tmttu["	<< i->num << "]\t= 1.0;"
	   << "\t// "	<< i->cmp
	   << "\t("	<< i->fnm << ")"		<< std::endl;
    }
    file << "} // " << this->name << "_input"		<< std::endl;
  }

  void
  structure::write_logic (std::ofstream &file){
    file << "void"					<< std::endl
	 << this->name << "_logic(const double t,"	<< std::endl
	 << "\tdouble *mttu,"				<< std::endl
	 << "\tdouble *mttx,"				<< std::endl
	 << "\tdouble *mttdx,"				<< std::endl
	 << "\tdouble *mtty)"				<< std::endl
	 << "{"						<< std::endl;
    for (i = Lx.begin(); i != Lx.end(); i++){
      if (i->cmp == "MTT_SWITCH"){
	file << "int " << i->fnm << " = 1;"		<< std::endl
	     << "if ((" << i->fnm << " == 0)"
	     << " || ((" << i->fnm << " < 0)"
	     << " && (mttx[" << i->num << "] < 0.0))){"	<< std::endl
	     << "\tmttx[" << i->num << "]\t = 0.0;"	<< std::endl
	     << "\tmttdx[" << i->num << "]\t = 0.0;"	<< std::endl
	     << "}"					<< std::endl;
      }
    }
    file << "} // " << this->name << "_logic"		<< std::endl;
  }

  void
  structure::write_state (std::ofstream &file){
    file << "void"					<< std::endl
	 << this->name << "_state(double *mttx)"	<< std::endl
	 << "{"						<< std::endl;
    for (i = Lx.begin(); i != Lx.end(); i++){
      file << "\tmttx["	<< i->num << "]\t= 0.0;"
	   << "\t// "	<< i->cmp
	   << "\t("	<< i->fnm << ")"		<< std::endl;
    }    
    file << "} // " << this->name << "_state"		<< std::endl;      
  }

  std::ifstream &operator>> (std::ifstream &file, structure &s){
    s.read(file);
    return file;
  }

  std::ofstream &operator<< (std::ofstream &file, structure &s){
    s.write(file);
    return file;
  }

};

void
usage(const char *program){
  std::cerr << std::endl
	    << "usage: " << program << " system" << std::endl
	    << std::endl;
}

int main(int argc, char *argv[])
{
  // check usage
  if (argc != 2){
    usage(argv[0]);
    return 1;
  }
  std::string system_name = argv[1];
  
  // open files for reading and writing
  std::string cr_filename		= system_name + "_cr.txt";
  std::string parameter_filename	= system_name + "_sympar.txt";
  std::string structure_filename	= system_name + "_struc.txt";
  std::string equations_filename	= system_name + "_ese.r";
  std::string cxxsim_filename		= system_name + "_cxxsim.cc";

  std::ifstream cr_file	      (cr_filename.c_str());
  std::ifstream parameter_file(parameter_filename.c_str());
  std::ifstream structure_file(structure_filename.c_str());
  std::ifstream equations_file(equations_filename.c_str());
  std::ofstream cxxsim_file(cxxsim_filename.c_str());

  // announce transformation
  std::clog << "Creating " << cxxsim_filename << std::endl;
  
  // do transformations (abracadabra!)
  cxxsim::cr	    system_cr	    (system_name);
  cxxsim::parameter system_parameter(system_name);
  cxxsim::structure system_structure(system_name);
  cxxsim::equations system_equations(system_name);

  cr_file	 >> system_cr;
  parameter_file >> system_parameter;
  structure_file >> system_structure;
  equations_file >> system_equations;
  
  
  cxxsim_file << system_cr
	      << system_parameter
	      << system_structure 
	      << system_equations
	      << std::endl;

  cxxsim_file << std::endl
	      << "int"								<< std::endl
	      << "main(void){"							<< std::endl
	      << std::endl
	      << "  double mttdt = 0.1; // integration time step"		<< std::endl
	      << "  double mttt;"						<< std::endl
	      << "  int i;"							<< std::endl
	      << std::endl
	      << "  for (mttt = 0.0; mttt <= 10.0; mttt += mttdt){"		<< std::endl
	      << std::endl
	      << "    // get inputs and rates"					<< std::endl
	      << "    " << system_name << "_input(mttt,mttu,mttx,mtty);"	<< std::endl
	      << "    " << system_name << "_ode(mttt,mttu,mttx,mttdx,mtty);"	<< std::endl
	      << std::endl
	      << "    // integrate states (euler)"				<< std::endl
	      << "    for (i = 1; i < mttNx; i++){"				<< std::endl
	      << "      mttx[i] += mttdx[i] * mttdt;"				<< std::endl
	      << "    }"							<< std::endl
	      << std::endl
	      << "    // overwrite switch states"				<< std::endl
	      << "    " << system_name << "_logic(mttt,mttu,mttx,mttdx,mtty);"	<< std::endl
	      << std::endl
	      << "    // write: time outputs time states"			<< std::endl
	      << "    std::cout << mttt << '\\t';"				<< std::endl
	      << "    for (i = 1; i < mttNy; i++){"				<< std::endl
	      << "      std::cout << mtty[i] << ' ';"				<< std::endl 
	      << "    }"							<< std::endl
	      << "    std::cout << '\\t' << mttt;"				<< std::endl
	      << "    for (i = 1; i < mttNx; i++){"				<< std::endl
	      << "      std::cout << ' ' << mttx[i];"				<< std::endl 
	      << "    }"							<< std::endl
	      << "    std::cout << std::endl;"					<< std::endl
	      << "  }"								<< std::endl
	      << "  return 0;"							<< std::endl
	      << "}"								<< std::endl;

  // close files
  cr_file.close();
  parameter_file.close();
  structure_file.close();
  equations_file.close();
  cxxsim_file.close();

  return 0;
}
