/*******************************************************************************
 * Copyright (c) 2015-2017 ONERA and IRT AESE (IRT Saint Exupéry).
 * Ce logiciel est la propriété de l’ONERA (the French Aerospace Lab) et 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 ONERA (the French Aerospace Lab) and 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:
 *     Guillaume Infantes (ONERA - Centre de Toulouse) - initial API and implementation
 *     Alexandre Albore (IRT Saint-Exupéry) - Altarica/Fiacre translation
 *
 *******************************************************************************/

#ifndef ALTARICA_MODEL_HH
#define ALTARICA_MODEL_HH

#include <vector>
#include <string>
#include <list>
#include <stddef.h>
#include <cstddef>

#ifndef NDEBUG_SEMANTICS
#define DEBUG_SEMANTICS
#endif

namespace epoch {
  namespace altarica {


    class Variable;
    class Node;
    class AltaricaState;
    class AltaricaModel;
    class Constant;
    class Domain;
    class Event;
    class Synchronisation;
    class Value;
    class AltaricaInitInfo;
    class AltaricaInitInfoXML;
    class AltaricaJson;
    class MetaEvent;
    class Assertion;

    //! The whole model itself, including functions to simulate it
    /*! AltaricaModel contains all information needed to simulate an AltaricaModel
      and functions to generate (meta) events at a given state, then following states
    */
    class AltaricaModel {

    private:
      bool hasDirac_;
      bool discrete_;

      void (*probaComputer)(std::vector<MetaEvent*>* mev, bool mayHaveDirac, AltaricaModel *am, AltaricaState *as);
      std::vector<MetaEvent*> * (*metaEventOrderer)(std::vector<MetaEvent*> *mev);
      std::vector<MetaEvent*> * (AltaricaModel::*getEnabledMetaEventsMostPrioOnlyFunctor)(AltaricaState *state);
      std::vector<MetaEvent*> * (AltaricaModel::*getEnabledMetaEventsAllFunctor)(AltaricaState *state);
      std::vector<MetaEvent*> * (AltaricaModel::*getEnabledMetaEventsDiracFunctor)(AltaricaState *state);

      std::string varStringCache;
      Variable * varCache;

      AltaricaInitInfo * initInfo;

      //! is the model ready to use?
      bool finalized_;

      //! constants of the .alt file
      std::vector<epoch::altarica::Constant*> constants;

      //! declared domains
      /*! declared domains of the .alt file
        does not contain anonymous domains !!!!
        (of the type flow    A:[1,10];)
      */
      std::vector<epoch::altarica::Domain*> domains;
      //! all declared nodes
      /*! declared nodes. each nodes contains a full living copy of its subnodes;
        so the model of the system is containdes in "main" node only, other
        are used for cloning during model building phase
      */
      std::vector<epoch::altarica::Node*> nodes;

      //! all state variables
      std::vector<epoch::altarica::Variable*> stateVars;

      //! all different flow variables
      /*! all _different_ flow vars: if flow vars (in the sense of altarica
        have always the same value, only one flowvar appears in the list
      */
      std::vector<epoch::altarica::Variable*> flowVars;

      //! all atomic events
      /*! atomic events inside main node and subnodes */
      std::vector<epoch::altarica::Event*> atomicEvents;

      std::vector<epoch::altarica::Event*> atomicDiracEvents;

      //! all synchronisations
      /*! synchronisations inside main node and subnodes */
      std::vector<epoch::altarica::Synchronisation*> syncs;

      //! all metaEvents
      /*! metaevents, used only if !onTheFly */
      std::vector<epoch::altarica::MetaEvent*> * metaEvents_;

      //! all metaEvents, sorted by priority
      /*! all metaevents, sorted, used only if !onTheFly */
      std::vector<std::vector<epoch::altarica::MetaEvent*> *> *metaEventsByPrio_;

      //! list of all asserts: unused
	std::list<epoch::altarica::Assertion*> * asserts_;

      //! the main node (default: "main")
      epoch::altarica::Node * mainNode_;

      //! helper to build global var list
      void addVars(epoch::altarica::Node* n);

      //! helper to build global event list
      void addEvents(epoch::altarica::Node* n);

      //! helper to build global sync list
      void addSyncs(epoch::altarica::Node* n);

      //! helper to build global assert list
      void addAsserts(epoch::altarica::Node* n);

      //! helper to build global  lists
      void addGlobalDataRec(epoch::altarica::Node *n);

      //! fusion/elimination of simple affectations
      void compactSimpleAssertsRec(epoch::altarica::Node *n);

      //! compute metaevents
      /*! compute all metaevents
        if no state (NULL) no checking of preconditions -> use only with !onTheFly
      */
      std::vector<epoch::altarica::MetaEvent*> *  computeMetaEventsNoDirac(epoch::altarica::AltaricaState *s);
      std::vector<epoch::altarica::MetaEvent*> * computeDiracMetaEvents();

      //! sub function of computeMetaEvents
      std::vector<epoch::altarica::MetaEvent*> * findAndRemoveMetas(epoch::altarica::Event*e, std::vector<epoch::altarica::MetaEvent*> * metas);

      //! prioritize metaEvents as given by computeMetaEvents
      std::vector<std::vector<epoch::altarica::MetaEvent*> *> * buildPrioritizedMetaEvents(std::vector<epoch::altarica::MetaEvent*> *);

      //! helper for buildPrioritizedMetaEvents
      std::vector<std::vector<epoch::altarica::MetaEvent*>*>* splitPrioClass(std::vector<epoch::altarica::MetaEvent*>* prioClass);

      //! helper for buildPrioritizedMetaEvents
      epoch::altarica::MetaEvent* popMorePrio(std::vector<epoch::altarica::MetaEvent*> * prioClass);

      //! helper for buildPrioritizedMetaEvents
      epoch::altarica::MetaEvent* popSamePrio(std::vector<epoch::altarica::MetaEvent*> *prioClass, epoch::altarica::MetaEvent* p);

      static void computeDiscreteProbas(std::vector<MetaEvent*>* mev, bool mayHaveDirac, AltaricaModel *am, AltaricaState *as);
      static void giveContinuousProbas(std::vector<MetaEvent*>* mev, bool mayHaveDirac, AltaricaModel *am, AltaricaState *as);
      static std::vector<MetaEvent*>* orderMetaEventMostProbableFirst(std::vector<MetaEvent*>* mev);
      static std::vector<MetaEvent*>* orderMetaEventLessProbableFirst(std::vector<MetaEvent*>* mev);      
      static std::vector<MetaEvent*>* orderMetaEventNOOP(std::vector<MetaEvent*>* mev);

      std::vector<MetaEvent*> * getEnabledMetaEventsMostPrioOnlyOnTheFly(AltaricaState *state);
      std::vector<MetaEvent*> * getEnabledMetaEventsMostPrioOnlyOffLine(AltaricaState *state);
      std::vector<MetaEvent*> * getEnabledMetaEventsAllOnTheFly(AltaricaState *state);
      std::vector<MetaEvent*> * getEnabledMetaEventsAllOffLine(AltaricaState *state);
      std::vector<MetaEvent*> * getEnabledMetaEventsDiracOnTheFly(AltaricaState *state);      
      std::vector<MetaEvent*> * getEnabledMetaEventsDiracOffLine(AltaricaState *state);

      static void purgeMetaEvents(std::vector<MetaEvent*>*);
      

      static bool noProba(std::vector<MetaEvent*>* mev);
      static bool noProba(MetaEvent* mev);
      static bool hasDirac(std::vector<MetaEvent*> * mev);
      // static bool dirac(MetaEvent* mev);
      static bool discrete(std::vector<MetaEvent*>* mev);
      bool diracEnabled(AltaricaState*s);
      std::vector<MetaEvent*> * computeDiracMetaEvents(AltaricaState *state);
      std::vector<MetaEvent*> * getEnabledMetaEventsDirac(AltaricaState *state);
      AltaricaState * forwardDirac(AltaricaState *s, std::vector<const Variable*>* = NULL);


      bool isInAssigned(Variable* v, std::vector<std::vector<const Variable*>* >& assigned);
      bool isInAlreadyAssignedInDirac(const Variable* v, std::vector<const Variable*>* assigned);
      bool isInAlreadyAssignedInDirac(std::vector<const Variable*> *vv, std::vector<const Variable*>* assigned);
      static bool debugSemantics;

    public:

      //! simple constructor
      AltaricaModel(bool continuous = false);
      //! complex    constructor :)
      ~AltaricaModel();

      void setInitInfo(std::string initFile);

      AltaricaInitInfo * getInitInfo() const {return initInfo;}

      // Functors changes

      void giveMostProbableTransitionFirst() {  metaEventOrderer = &AltaricaModel::orderMetaEventMostProbableFirst;}
      void giveLessProbableTransitionFirst() {metaEventOrderer = &AltaricaModel::orderMetaEventLessProbableFirst;}
      void setContinuous() {probaComputer = &AltaricaModel::giveContinuousProbas; discrete_ = false;}
      void setDiscrete() {probaComputer = &AltaricaModel::computeDiscreteProbas; discrete_ = true;}

      bool discrete() {return discrete_;}

      //! setter for the parser
      void addConstant(epoch::altarica::Constant* c);

      //! setter for the parser
      void addDomain(epoch::altarica::Domain* d);

      //! setter for the parser
      void addNode(epoch::altarica::Node* n);

      //! after parsing, finlizing the model is mandatory
      void finalize(std::string mainNodeName = "main", bool onTheFlyMeta = true);
      void finalizeRec(epoch::altarica::Node *n);

      static void setDebugSemantics(bool d) { AltaricaModel::debugSemantics = d;}
      static bool getDebugSemantics() { return AltaricaModel::debugSemantics; }
      //! find a node in the node list
      /*! mainly used for parsing */
      epoch::altarica::Node * findNodeByName(const std::string& s);
      const epoch::altarica::Node * findNodeByFullName(const std::string& s) const;
      epoch::altarica::Node* findNodeByFullName(const std::string& s) {
        return const_cast<Node*>(
          const_cast<const AltaricaModel*>(this)
          ->findNodeByFullName(s));
      }
      std::vector<epoch::altarica::Node*> * findNodesByType(const std::string& s);
      //! find a constant in the constant list
      /*! used only by parsing */
       epoch::altarica::Constant * findConstantByName(const std::string& s) const;
      //! find a domain in the domain list
      /*! used only by parsing */
      epoch::altarica::Domain * findDomainByName(const std::string& s);

      const epoch::altarica::Node * getMainNode() const {return mainNode_;}

      //! build initial state
      /*! as stated by init declarations of the .alt file*/
      epoch::altarica::AltaricaState * initialState();

      //! Orders the assertions in dataflow
      void orders_assertions();

      //! give possible metaEvents for a given state of higher priority (useless for probabilistic MC)
      std::vector<epoch::altarica::MetaEvent*> * getEnabledMetaEventsMostPrioOnly(epoch::altarica::AltaricaState *s); 

      //! give possible metaEvents for a given state
      std::vector<epoch::altarica::MetaEvent*> * getEnabledMetaEventsAll(epoch::altarica::AltaricaState *s);

      //! generate a new state from a state and a metaevent
      /*! if metaEvent is not enabled (does not come from getEnabledMetaEvents) -> trouble ahead */
      // void getNextState(epoch::altarica::AltaricaState *s, epoch::altarica::MetaEvent* me,
      //            epoch::altarica::AltaricaState *ns);
      //! get next state, + allocation of a new state

      epoch::altarica::AltaricaState * getNextState(epoch::altarica::AltaricaState *s, epoch::altarica::MetaEvent* me);
      // std::vector<epoch::altarica::AltaricaState*> *getNextStatesFull(epoch::altarica::AltaricaState *s);
      // std::vector<epoch::altarica::AltaricaState*> *getNextStatesPartial(epoch::altarica::AltaricaState *s);

      //! helper for dealing with labri-style priorities in syncs
      static bool hasEventDAG(std::vector<epoch::altarica::MetaEvent*>* prioClass);

      //! print all flow variables and values at a given state
      void evaluateFlowVars(epoch::altarica::AltaricaState *s) const;

      //! evalute a flow var
      Value * getFlowVarValueAndRelease(AltaricaState *s, std::string n);
      //! reads a state var
      Value * getStateVarValue(AltaricaState *s, std::string n);
      //! gives a var value
      Value * getVarValueAndRelease(AltaricaState *s, std::string n);

      unsigned int getNFlowVars() const {return flowVars.size();}
      unsigned int getNStateVars() const {return stateVars.size();}
      unsigned int getNNodes() const {return nodes.size();}
      unsigned int getNEvents() const {return atomicEvents.size();}

      Variable* getStateVar(unsigned int i) const;
      Variable* getFlowVar(unsigned int i) const;

      bool existsVar(std::string n);

      void setInitInfoXML(std::string initFile);
      AltaricaJson *  setInitInfoJSON(std::string initFile);
        

      const std::vector<epoch::altarica::Event*>& getAtomicDiracEvents() const {
        return atomicDiracEvents;
      }

      const std::vector<epoch::altarica::Event*>& getAtomicEvents() const {
        return atomicEvents;
      }

      const std::vector<epoch::altarica::Constant*>& getConstants() const {
        return constants;
      }

      const std::vector<epoch::altarica::MetaEvent*>& getMetaEvents() const {
    	return *metaEvents_;
      }

      std::list<epoch::altarica::Assertion*>& getAsserts() const {
    	return *asserts_;
      }

      const std::vector<epoch::altarica::Domain*>& getDomains() const {
        return domains;
      }

      const std::vector<epoch::altarica::Variable*>& getFlowVars() const {
        return flowVars;
      }

      const std::vector<epoch::altarica::Node*>& getNodes() const {
        return nodes;
      }

      const std::vector<epoch::altarica::Variable*>& getStateVars() const {
        return stateVars;
      }

      const std::vector<epoch::altarica::Synchronisation*>& getSyncs() const {
        return syncs;
      }
    };

  }
}
#endif
