/*******************************************************************************
 * Copyright (c) 2017 IRT AESE (IRT Saint Exupéry).
 * Ce logiciel est la propriété de l'IRT AESE (IRT Saint Exupéry).
 * Tous droits réservés.
 *
 * Ce programme et les éléments qui l'accompagnent sont mis à disposition
 * aux conditions définies par le contrat de licence logiciel CeCILL-C soumise
 * au droit français et respectant les principes de diffusion des logiciels libres.
 * Vous pouvez utiliser, modifier et/ou redistribuer ce programme
 * sous les conditions de la licence CeCILL-C (http://www.cecill.info).

 * This software is property of the IRT AESE (IRT Saint Exupéry).
 * All rights reserved.
 *
 * This program and the accompanying materials are made available under
 * the terms of the CeCILL-C license under French law and
 * abiding by the rules of distribution of free software.
 * You can  use, modify and/ or redistribute the software under the terms of
 * the CeCILL-C license (http://www.cecill.info).
 *
 * Contributeurs/contributors:
 *     Alexandre Albore (IRT Saint-Exupéry) - Altarica/Fiacre translation
 *
 *******************************************************************************/

/*
 * FiacreTranslator.cc
 *
 *  Created on: 27 oct. 2016
 *      Author: alexandre.albore
 *      This file is part of the IRT MOISE project
 */
#include <algorithm>
#include "FiacreTranslator.hh"
#include "Variable.hh"
#include "Constant.hh"
#include "expressions/Expressions.hh"
#include "Assignment.hh"
#include "AltaricaGenerator.hh"
#include "Event.hh"
#include "MemberAccess.hh"
#include "Synchronisation.hh"
#include "Transition.hh"
#include "Node.hh"
#include "Assertion.hh"
#include "EventLaw.hh"

//#define USEUNDEF true // Used to extend the types with an undef value
// 2016031810012552 to fr
namespace epoch {
namespace altarica {

const std::string xx = std::string("x");
const std::string yy = std::string("y");
const std::pair<std::string,std::string> prefixes(xx,yy);

FiacreTranslator::FiacreTranslator(const AltaricaModel *amodel): amodel_(amodel) {

}

FiacreTranslator::~FiacreTranslator() {
	os_.clear();
}


std::string FiacreTranslator::getType(Value* dom) {
	if (dom->isBool())
		return std::string("bool");
	else if (dom->isInt())
		return std::string("int");
	else if (dom->isSymbol())
		return std::string("union");
}

/* @return a string with the union
 * Could return a list for ex.
 */
std::string FiacreTranslator::makesUnion(const std::vector<std::string>* sym) {
	std::string currentType;

	for (std::vector<std::string>::const_iterator i = sym->begin(); i!= sym->end(); ++i) {
		if (i+1 == sym->end())
			currentType += *i ;
		else
			currentType += *i + "|";
	}
	return currentType;
}

std::string FiacreTranslator::removeAmp(std::string &r) {
	size_t index = 0;
	while (true) {
		index = r.find("&", index);
		if (index == std::string::npos) break;
		r.replace(index, 1, ".");
		index += 1;
	}
	return r;
}


/// Does the product of MetaEvents
/// NB: A·B= AB (but not A, B)
std::string FiacreTranslator::print_MandatoryEvents_sync (const std::vector<epoch::altarica::Event*> *ev, int n,
		std::ostringstream * spre, std::ostringstream * spost)    {

	std::ostringstream sret;
	std::string s ;

//	if ( spre->rdbuf()->in_avail() == 0 )
//		*spre << "on (";
//	else *spre << "\t[] on (";


	if (n < 0) {
		sret << spre->str() + ");\n" + spost->str() + "\t\tx := assertions(y";

                // listing the state vars
/*		for(auto &s : model().getStateVars()) {
                sret << s->toFiacre();
                if (&s != &model().getStateVars().back())
                sret << ", " ;
		} */
		sret << ");" << std::endl << "\t\tto s0"<< std::endl;
		return sret.str();
	}

	// unsigned j = 0;
	if (ev->at(n)->getTrans()->size() != 0)
	for (auto &ti : *(ev->at(n)->getTrans()) ) {
		std::ostringstream s1, s2;
		std::string ti_str =  ti->getPrecond()->toString( ti->getNode(), &prefixes, model());
		s1 <<  spre->str() << removeAmp(ti_str);
		if ( n != 0 )
			s1 << " and ";
		//s1 <<  sout->str() << "\tE" << n << "T" << j++;

		const std::vector<epoch::altarica::Assignment*>* effects = ti->getAssigns();
		if(effects->size() > 0) {
			s2 << spost->str();

			for(auto &a : *effects) {
				std::string eff_str = "\t\t"
						+ a->getVar()->toFiacre()
						+ " := "
						+ a->getExpr()->toString( a->getNode(), &prefixes, model() )
						+ ";\n";
				s2 << removeAmp(eff_str);
			}
		}
		sret << print_MandatoryEvents_sync( ev,  n-1, &s1, &s2);
	}
	else { // null transition
		std::ostringstream s1, s2;
		s1 <<  spre->str();
		s2 << spost->str();
		sret << print_MandatoryEvents_sync( ev,  n-1, &s1, &s2);
	}

	return sret.str();
}


/// Does the optional syncs
/// NB: A: a -> a' ; B: b -> b'
/// A·B: a|b -> (if a then a') &  (if b then b')
std::string FiacreTranslator::print_OptionalEvents_sync (const std::vector<epoch::altarica::Event*> *ev, int n,
		std::ostringstream * spre, std::ostringstream * spost)    {

	std::ostringstream sret;
	std::string s ;

	if (n < 0) {
		sret << spre->str() + ");\n" + spost->str() + "\t\tx := assertions(y";
//		for(auto &s : model().getStateVars()) {
//			sret << s->toFiacre();
//			if (&s != &model().getStateVars().back())
//				sret << ", " ;
//		}
		sret << ");" << std::endl << "\t\tto s0"<< std::endl;
		return sret.str();
	}
	///
//	const std::string ff = std::string("f");
//	const std::string gg = std::string("g");
//	const std::pair<std::string,std::string> prefixes(ff,gg);
//	std::string assertion = "    " + ai->getExpr()->toString((ai->getNode()), &prefixes, model()) + ";\n";//
//			size_t index = 0;
//			while (true) {
//				index = assertion.find(".", index);
//				if (index == std::string::npos) break;
//				assertion.replace(index, 1, "_");
//				index += 1;
//			}
//			index = 0;
//			while (true) { // NB: Transitions already use ':='
//				index = assertion.find("=", index);
//				size_t indexif = assertion.find("if", 0);
//				if (index == std::string::npos) break;
//				if (indexif == std::string::npos) // does not replace for if conditions (the rest of expr is taken care by the Expressions)
//					assertion.replace(index, 1, ":=");
//				break;
//			}
	///

	if (ev->at(n)->getTrans()->size() != 0)
	for (auto &ti : *(ev->at(n)->getTrans()) ) {  // HERE: do it in reverse order (?)
		std::ostringstream s1, s2;
		std::string ti_str =  ti->getPrecond()->toString( ti->getNode(), &prefixes, model());
		s1 <<  spre->str() << removeAmp(ti_str);
		if ( n != 0 )
			s1 << " or ";

		const std::vector<epoch::altarica::Assignment*>* effects = ti->getAssigns();
		if(effects->size() > 0) {
			s2 << spost->str();
			for(auto &a : *effects) {
						std::string eff_str = "\t\tif ("
								+ ti->getPrecond()->toString( ti->getNode(), &prefixes, model() )
								+ ") then "
								+ a->getVar()->toFiacre()
								+ " := "
								+ a->getExpr()->toString( a->getNode(), &prefixes, model() )
								+ "\n\t\tend;\n";
						s2 << removeAmp(eff_str);
					}
		}
		sret << print_OptionalEvents_sync( ev,  n-1, &s1, &s2);
	}
	else { // null transition
		std::ostringstream s1, s2;
		s1 <<  spre->str();
		s2 << spost->str();
		sret << print_OptionalEvents_sync( ev,  n-1, &s1, &s2);
	}

	return sret.str();
}


std::string FiacreTranslator::print_assertions_function (std::vector<std::string> &vecStateVars, std::vector<std::string> &vecFlowVars) {

	std::ostringstream ss;

	const std::string ff = std::string("f");
	const std::string gg = std::string("g");
	const std::pair<std::string,std::string> prefixes(ff,gg);

	// Updates the assertions
		ss << std::endl << "function assertions (";
		ss << "g : StateVars) : FlowVars is" << std::endl << "    var f : FlowVars";

//Produces the list of state variables
//		for (int t=0;t<vecStateVars.size();++t) {
//			ss << vecStateVars.at(t);
//
//			if (t+1 == vecStateVars.size()) {
//				ss << ") : FlowVars is" << std::endl << "    var f : FlowVars";

//
//
//	#ifdef USEUNDEF
//				ss << ":= {" << std::endl;
//
//				for(auto &v : model().getFlowVars()) {
//					os_<< "\t" << v->toFiacre();
//
//					ss << "=" << UNDEF;
//
//					if (&v != &model().getFlowVars().back())
//						ss<< "," << std::endl;
//					else
//						os_ << "\n    }";
//				}
//	#endif
//
				ss << std::endl << "    begin"<< std::endl;
//			} else
//				ss << ", ";
//		}

		for(auto &ai : model().getAsserts()) {
					std::string assertion = "    " + ai->getExpr()->toString((ai->getNode()), &prefixes, model()) + ";\n";//
					size_t index = 0;
					while (true) {
						index = assertion.find(".", index);
						if (index == std::string::npos) break;
						assertion.replace(index, 1, "_");
						index += 1;
					}
					index = 0;
					while (true) { // NB: Transitions already use ':='
						index = assertion.find("=", index);
						size_t indexif = assertion.find("if", 0);
						if (index == std::string::npos) break;
						if (indexif == std::string::npos) // does not replace for if conditions (the rest of expr is taken care by the Expressions)
							assertion.replace(index, 1, ":=");
						break;
					}
					index = 0;
					ss << removeAmp(assertion);
		}
//				}
//		}

		ss << "    return f"<< std::endl<< std::endl;
		ss << "    end"<< std::endl;

		return ss.str();

}


std::string FiacreTranslator::print_metaEvents(const epoch::altarica::MetaEvent& e) {
	std::ostringstream sret;
	size_t index = 0;

	if (e.getEvents()->size() != 0) {
		std::ostringstream s1;
		std::ostringstream s2;
		std::string label;

		label = ((e.getEvents()->size() == 1)? "" : "meta_event_");

		double minTime, maxTime = -100000.0;
		bool has_Tlaw = false;

		s1 << "on (";
		for (std::vector<epoch::altarica::Event*>::iterator i = e.getEvents()->begin();
				i!=e.getEvents()->end(); ++i) {
			for (auto &t : *(*i)->getTrans()) {

                        /* Prints preconditions = guards */
				std::string r(t->getPrecond()->toString( t->getNode(), &prefixes, model() ));
				s1 <<  removeAmp(r);
				if ((i+1) != e.getEvents()->end()) s1 << " and ";
				else if (*(&t+1) != *(*i)->getTrans()->end())  s1 << " and ";

				 /* Gets the transition law */
				const EventLaw *law = (*i)->getLaw();
				const Node * ne = (*i)->getNode();
				if ( law && law->isTimed())  {
					has_Tlaw = true;
					double minT = std::atof(law->getStartParam()->toString( ne , model() ).c_str()) ;
					double maxT = std::atof(law->getEndParam()->toString(ne, model() ).c_str());

					// Note to self: faire une metaEvent law.
					minTime = std::min( minTime, minT );
					maxTime = maxT < 0 ?  maxTime : std::max(maxT, maxTime);
				}

				/* Prints effects */
				if(t->getAssigns()->size() > 0) {
					for(auto &a : *(t->getAssigns())) {
						std::string rr = "\t\t" + a->getVar()->toFiacre()
										+ " := "
										+ a->getExpr()->toString( a->getNode(), &prefixes, model())
										+ ";\n";
						s2 << removeAmp(rr);
					}
				}
			}
			label +=  (*i)->getFullName() ;
		}

		sret << s1.str() << ");" << std::endl;

		if (has_Tlaw) {
			sret  << "\t\twait";
		if (minTime < 0)
			sret << "]w,";
		else sret << "[" << minTime << ",";
		if (maxTime < 0)
			sret << "w[;";
		else sret << maxTime << "];" ;
		sret << std::endl;
		}

		sret << s2.str();

		sret << "\t\t/* flow resolution */"<< std::endl;
		sret << "\t\tx := assertions(y);" << std::endl;
		/* Prints transition label */
		{
			size_t index = 0;

			while (true) {
				index = label.find(".", index);
				if (index == std::string::npos) break;
				label.replace(index, 1, "_");
				index += 1;
			}
			sret << "\t\t#" << label << ";" << std::endl;
		}

		sret << "\t\tto s0"<< std::endl;
	}
	else
		std::cerr << " NO EVENT";

	return sret.str();
}

std::string FiacreTranslator::print_transitions (Event &e,std::string &sconnect) {
	std::ostringstream sret;
	size_t index = 0;

	if(e.getTrans()->size() > 0) {

		if (sconnect.empty())
			sret <<"\t  select"<< std::endl << "\t\t";

		for(auto &t : *e.getTrans()) {
			std::string r = "on (" + t->getPrecond()->toString( t->getNode(), &prefixes, model() ) + ");\n";

			sret << sconnect << removeAmp(r);

			const EventLaw * law = e.getLaw();
			const Node * ne = e.getNode();
			if ( law && law->isTimed())
				sret << "\t\twait" << (std::atof(law->getStartParam()->toString( ne , model() ).c_str()) < 0 ? "]w"
								: "[" + law->getStartParam()->toString( ne, model() )) << ","
				<< (std::atof(law->getEndParam()->toString(ne, model() ).c_str()) < 0 ? "w[;"
						: law->getEndParam()->toString(ne, model() ) + "];") << std::endl;

			if(t->getAssigns()->size() > 0) {
				for(auto &a : *t->getAssigns()) {
					std::string rr =  "\t\t" // NB: tail is always a state variable
							+ a->getVar()->toFiacre()
							+ " := "
							+ a->getExpr()->toString( a->getNode(), &prefixes, model() )
							+ ";\n";
					sret << removeAmp(rr);
				}
			}
			sret << "\t\t/* flow resolution */"<< std::endl;
			sret << "\t\tx := assertions(y);" << std::endl;

			/* Prints transition label */
			{
				std::string s = e.getFullName();
				size_t index = 0;

				while (true) {
					index = s.find(".", index);
					if (index == std::string::npos) break;
					s.replace(index, 1, "_");
					index += 1;
				}
				sret << "\t\t#" << s << ";" << std::endl;
			}

			sret << "\t\tto s0"<< std::endl;
			if (sconnect.empty())
				sconnect += "\t\t[] ";
		}
	}
	return sret.str();
}

void FiacreTranslator::printFiacre() {

	// Gets types and state variables
	std::ostringstream  osStateVars, osStateVars2, osTransitions, liftedTypes;
	std::vector<std::string> vecFlowVars, vecStateVars;

	os_ << "/* Types */"<<std::endl<<std::endl;
	liftedTypes << "type flowtype is union" << std::endl << "\t";


	if (model().getNStateVars()) osStateVars <<"\tvar ";
	for (unsigned int j = 0; j != model().getNStateVars(); ) {

		// State Vars

		Variable * v = model().getStateVar(j++);
		Value* dom = v->getDomain()->toValue();

		// gets the type
		// NB: works only if they are always present in the same order in the Altarica file, otherwise use a list (more costly)

		if (dom->isSymbol()) {
			const std::vector<std::string>* sym = ((ValueSymbol*)dom)->toSymbols();
			std::string currentType = makesUnion(sym);
			std::string typeName = ( v->getDomain()->get_name() == "" ?
					std::string("type").append( std::to_string(types_.size())) :
					v->getDomain()->get_name() );
			// Hashes the type
			if (types_.emplace(currentType, typeName).second ) {
				// prints the type
				os_ << "type " << types_[currentType];// state var v->toFiacre();
				os_ << " is union " << currentType << " end" << std::endl;// getType(dom);
			}

			vecStateVars.push_back( v->toFiacre(false) + " : " + types_[currentType] );
			osStateVars <<"\t" << v->toFiacre(false) << " : " << types_[currentType];
                        
		} // is symbol
		else  { // is basic type
			vecStateVars.push_back( v->toFiacre(false) + " : " + getType(dom) );
			osStateVars <<"\t" << v->toFiacre(false) << " : " << getType(dom);
		}
		osStateVars << " := " << v->getInit()->getExpr()->toString() << ","  << std::endl;
                if (j == 1)
                        osStateVars2 << "\t" << v->toFiacre(false) << " = " << v->getInit()->getExpr()->toString() ;
                else
                        osStateVars2 << ","  << std::endl <<"\t" << v->toFiacre(false) << " = " << v->getInit()->getExpr()->toString() ;
	}
	os_ << std::endl;

	// Flow Vars
	for(auto &v : model().getFlowVars()) {

		std::string theType;
		Value* dom = v->getDomain()->toValue();

		// gets the type

		if (dom->isSymbol())  {  // is symbol
			const std::vector<std::string>* sym = ((ValueSymbol*)dom)->toSymbols();
			std::string currentType = makesUnion(sym);
			std::string typeName = ( v->getDomain()->get_name() == "" ?
					std::string("type").append( std::to_string(types_.size())) :
					v->getDomain()->get_name() );

			// Hashes the type
#ifdef USEUNDEF
			if (std::find(flowtypes_.begin(), flowtypes_.end(), typeName) == flowtypes_.end())
			{
				std::string tn= typeName;
				flowtypes_.push_back(tn);
				transform(tn.begin(), tn.end(), tn.begin(), toupper);
				liftedTypes  << tn << " of " << typeName << "|\n\t";
			}
#endif

			if (types_.emplace(currentType, typeName).second ) {
				// prints the type
				os_ << "type " << types_[currentType];// state var v->toFiacre();
				os_ << " is union " << currentType << " end" << std::endl;// getType(dom);
			}

			theType = types_[currentType];
		} // end is symbol
		else {
#ifdef USEUNDEF
			if (std::find(flowtypes_.begin(), flowtypes_.end(), getType(dom)) == flowtypes_.end()) { // is basic type

				std::string tn = getType(dom);

				flowtypes_.push_back(tn);
				transform(tn.begin(), tn.end(), tn.begin(), toupper);
				liftedTypes  << tn << " of " << getType(dom) << "|\n\t";


#endif

				theType = getType(dom);
			}// end is basic type


#ifdef USEUNDEF
		vecFlowVars.push_back(v->toFiacre(false) + " : flowtype"); // + types_[currentType]);

	} // end for

	liftedTypes << UNDEF << " end" << std::endl;
	os_ << liftedTypes.str();
#else

	vecFlowVars.push_back(v->toFiacre(false) + " : " + theType);
	// Gets the aliases
//	for (std::vector<std::pair<const Node*, const MemberAccess*>*>::const_iterator i = (v->getAliases())->begin(); i!= v->getAliases()->end(); ++i) {
//
//
//		{
//			// Get the variable
//			std::ostringstream oss;
//
//			oss.str("");
//			std::string nodename = (*i)->first->getFullName();
//			if (nodename != "main")
//				oss << nodename.substr(5, std::string::npos) << ".";
//			oss << (*i)->second->toString();
//			std::string aliasname = oss.str();
//			std::cout << "looking for " << v->getFullName() << std::endl;
//			//epoch::altarica::Variable* av = v->getNode()->findVariableByName(aliasname);
//			//if (av == NULL)
//			//	throw new std::out_of_range("Cannot find Variable: " + nodename +
//			//			" in node " + nodename + "\n");
//			//vecFlowVars.push_back( av->toFiacre() + " : " + theType );
//
//
//			  MemberAccess *ma = new MemberAccess(aliasname);
//			  //      ma->setGlobal(true);
//			        Variable * av = model().getMainNode()->findVariableByName(ma);
//			        std::cout << " - " << av->getFullName() << std::endl;
//			//        model().existsVar();
//
//		}

	//	vecFlowVars.push_back( (*i)->first->toFiacre() + "_" + (*i)->second->toString() + " : " +  theType );
//	}
	} // end for
#endif

	os_ << "type FlowVars is record\n";
	for (int t=0;t<vecFlowVars.size();++t) {
		os_ << "\t" << vecFlowVars.at(t);
		if (t+1 < vecFlowVars.size())
			os_<< ",\n";
		else
			os_ << " end" << std::endl;
	}
        
	os_ << "type StateVars is record\n";
	for (int t=0;t<vecStateVars.size();++t) {
		os_ << "\t" << vecStateVars.at(t);
		if (t+1 < vecStateVars.size())
			os_<< ",\n";
		else
			os_ << " end" << std::endl;
	}

	os_ <<std::endl<< "/* Constants */"<<std::endl;

	std::vector<epoch::altarica::Constant*> constants = model().getConstants();
	for (std::vector<epoch::altarica::Constant*>::const_iterator c = constants.begin(); c!= constants.end(); ++c) {
		std::string t = std::string("type");
		const Expression* expr = (*c)->getExpression();

		if (const ExpressionUNSIGNEDINT* vu = dynamic_cast<const ExpressionUNSIGNEDINT*>(expr))
			t = std::string("nat");
		else if (const ExpressionBOOLCONSTANT* vu = dynamic_cast<const ExpressionBOOLCONSTANT*>(expr))
			t = std::string("bool");
		//		for (auto &n : model().getNodes())
		//			if (n->getMA()->isGlobal() || n->getParent() != NULL) // << n->getMA()->toString() << "_"
		os_ << "const "  << (*c)->getName() << " : "
				<< t << " is " << (*c)->getExpression()->toString()<< std::endl;
	}


	os_ << print_assertions_function(vecStateVars, vecFlowVars);


	os_<<std::endl << "process mainprocess (&x : FlowVars, &y: StateVars) is\n\tstates s0"<<std::endl;


//	for (int t=0;t<vecFlowVars.size();++t)
//		os_ << "," << std::endl <<"\t" << vecFlowVars.at(t); /* NB: FLOW VARS have no Init value  << " := " << v->getInit()->getExpr()->toString() */
//	os_ << std::endl;

	/*********** Transitions ********/

	osTransitions << std::endl <<"\tfrom s0"<<std::endl;

	std::string sconnect("");
	for(auto &e : model().getAtomicEvents()) {
//		osTransitions << print_transitions(*e, sconnect);
	}

	//	osTransitions << "\t\t/* Syncs */"<<std::endl;
	//	for(auto &sy : model().getSyncs()) {
	//		if (sy->getMandatoryEvents()->size() || sy->getOptionalEvents()->size()) {
	//			std::ostringstream s1("\t\t[] on (");
	//			std::ostringstream s2;
	//			int num = sy->getMandatoryEvents()->size()-1;
	//
	//			if (sy->getMandatoryEvents()->size() > 1)
	//				osTransitions << print_MandatoryEvents_sync(sy->getMandatoryEvents(), num, &s1, &s2);
	//
	//			num = sy->getOptionalEvents()->size()-1;
	//			if (sy->getOptionalEvents()->size() > 1)
	//				osTransitions << print_OptionalEvents_sync(sy->getOptionalEvents(), num, &s1, &s2);
	//		}
	//	}

	osTransitions << "/*  Syncs  */" << std::endl;
	osTransitions <<"\t  select"<< std::endl << "\t\t";
	bool firstME = true;

	for(auto &sy :model().getMetaEvents()) {
		if (firstME)
			firstME=false;
		else 		osTransitions << "\t\t[]";

	   osTransitions << print_metaEvents(*sy);
	}

	os_ << osTransitions.str();
	os_ << "\t  end" << std::endl;

	std::cout << os_.str();


	std::cout << "component Main is" << std::endl;

	// Removing the last comma
	osStateVars.seekp(osStateVars.str().length()-1);
	std::cout << "\t/* State Variables */\n\tvar y : StateVars := {"<< std::endl;
	std::cout << osStateVars2.str() << " }," << std::endl;
	std::cout << "\tx : FlowVars"<< std::endl << std::endl;

	std::cout << "\tpar * in\n\t\tmainprocess (&x, &y)\n\tend"  << std::endl << std::endl << "Main"<< std::endl;

}

} // end namespace
}
