/*******************************************************************************
 * 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
 *
 *******************************************************************************/

#include <iostream>
#include <sstream>
#include <limits.h>

#include "MemberAccess.hh"
#include "Variable.hh"
#include "AltaricaState.hh"
#include "Assertion.hh"
#include "Node.hh"
#include "domains/Domains.hh"
#include "Values.hh"
#include "LandmarkState.hh"
#include "AltaricaException.hh"

namespace epoch {
  namespace altarica {

    Variable::Variable(std::string name) {
      ma_= new MemberAccess(name);
      domain_ = NULL;
      asserts_ = NULL;
      init_ = NULL;
      loop_status_ = 0;      
      aliases_ = new  std::vector<std::pair<const Node*, const MemberAccess*>*>();
      index_ = UINT_MAX;
    }

    Variable::Variable(MemberAccess *ma) {
      ma_= ma;
      domain_ = NULL;
      asserts_ = NULL;
      init_ = NULL;
      loop_status_ = 0;      
      aliases_ = new  std::vector<std::pair<const Node*, const MemberAccess*>*>();
      index_ = UINT_MAX;
    }


    Variable::Variable(std::string name, Domain* d) {
      domain_ = d;
      ma_=new MemberAccess(name);
      asserts_ = NULL;
      init_ = NULL;
      loop_status_ = 0;      
      aliases_ = new  std::vector<std::pair<const Node*, const MemberAccess*>*>();
      index_ = UINT_MAX;
    }

    Variable::Variable(MemberAccess *ma, Domain* d) {
      domain_ = d;
      ma_=ma;
      asserts_ = NULL;
      loop_status_ = 0;      
      init_ = NULL;
      aliases_ = new  std::vector<std::pair<const Node*, const MemberAccess*>*>();
      index_ = UINT_MAX;
    }


    Variable::Variable(std::string name, Domain* d, bool isState) {
      domain_ = d;
      ma_ = new MemberAccess(name);
      state_ = isState;
      asserts_ = NULL;
      loop_status_ = 0;      
      init_ = NULL;
      aliases_ = new  std::vector<std::pair<const Node*, const MemberAccess*>*>();
      index_ = UINT_MAX;
    }

    Variable::Variable(MemberAccess *ma, Domain* d, bool isState) {
      domain_ = d;
      ma_ = ma;
      state_ = isState;
      loop_status_ = 0;      
      asserts_ = NULL;
      init_ = NULL;
      aliases_ = new  std::vector<std::pair<const Node*, const MemberAccess*>*>();
      index_ = UINT_MAX;
    }


    Variable::Variable(std::string name,  bool isState) {
      state_ = isState;
      ma_ = new MemberAccess(name);
      asserts_ = NULL;
      loop_status_ = 0;      
      init_ = NULL;
      aliases_ = new  std::vector<std::pair<const Node*, const MemberAccess*>*>();
      index_ = UINT_MAX;
    }

    Variable::Variable(MemberAccess *ma,  bool isState) {
      state_ = isState;
      ma_ = ma;
      asserts_ = NULL;
      init_ = NULL;
      aliases_ = new  std::vector<std::pair<const Node*, const MemberAccess*>*>();
      loop_status_ = 0;      
      index_ = UINT_MAX;
    }

    Domain * Variable::getDomain() const {
      return domain_;
    }

    void Variable::setDomain(Domain *d) {
      domain_ = d;
    }

    void Variable::setState(bool s) {
      state_ = s;
    }

    void Variable::setState() {
      state_ = true;
    }

    void Variable::setFlow() {
      state_ = false;
    }


    void Variable::addAttribute(const std::string a) {
      attributes_.push_back(std::string(a));
    }

    void Variable::addAttributes(const std::vector<std::string> *va) {
      if (va != NULL)
        for (std::vector<std::string>::const_iterator si = va->begin(); si != va->end(); ++si) {
          addAttribute(*si);
        }
    }


    void Variable::setMemberAccess(MemberAccess *ma) {
      ma_ = ma;
    }

    Variable * Variable::clone() const {
      Variable * n = new Variable(ma_->clone(), domain_, state_);
      // if (ma_!=NULL)
      //  n->setMemberAccess(ma_->clone());
      n->addAttributes(&attributes_);
      n->setNode(node_);
      for (std::vector<std::pair<const Node*, const MemberAccess*>*>::iterator i =
             aliases_->begin(); i!=aliases_->end(); ++i)
        n->addAlias((*i)->first, (*i)->second->clone());
      //      if (init_ != NULL)
      n->setInit(init_);
      return n;
    }

    // bool Variable::isState() const {
    //     return state_;
    // }


    // bool Variable::isFlow() const {
    //     return !state_;
    // }

    void Variable::setNode(const Node*n) {
      node_ = n;
    }

    const Node * Variable::getNode() const{
      return node_;
    }

    const std::vector<Node *> * Variable::getNodes() const {
      std::vector<Node*>* ret = new std::vector<Node*>();
      ret->push_back((Node*)node_);
      for (std::vector<std::pair<const Node*, const MemberAccess*>*>::iterator i =
             aliases_->begin(); i!=aliases_->end(); ++i) {
        ret->push_back((Node*)(*i)->first);
      }
      return ret;
    }

    std::vector<const Node *>* Variable::getPath() const {
      std::vector<const Node*>* ret = node_->getPath();
      ret->push_back(node_);
      return ret;
    }

    std::string Variable::getFullName() const {
      std::ostringstream ss;
      ss << node_->getFullName()
         << "."
         << getMA()->toString();
      return ss.str();
    }

    void Variable::setInit(Assignment* n) {
      init_ = n;
    }

    const Assignment * Variable::getInit() {
      return init_;
    }


    const MemberAccess * Variable::getMA() const{
      return ma_;
    }

    std::vector<Variable*> * Variable::flattenDomain() {
      return domain_->flatten(this);
    }

    void Variable::addToMASecond(int i) {
      ma_->addIndexSecond(i);
    }


    void Variable::addToMASecond(std::string s) {
      ma_->addIdSecond(s);
    }

    void Variable::addAssert(Assertion* a) {
      if (asserts_ == NULL)
        asserts_ = new std::vector<Assertion*>();
      asserts_->push_back(a);
    }

    void Variable::addAsserts(const std::vector<Assertion*> * a) {
      if (a == NULL)
        return;
      for (std::vector<Assertion*>::const_iterator i = a->begin(); i!= a->end(); ++i)
        addAssert(*i);
    }

    const  std::vector<Assertion*> * Variable::getAsserts() const {
      return asserts_;
    }



    // unsigned int Variable::nbUnComputedOpen(std::vector<Variable*> * vv, AltaricaState *s) {
    //     unsigned int c  = 0;
    //     for (std::vector<Variable*>::iterator i = vv->begin(); i!= vv->end(); ++i) {
    //  if (!s->hasValue(*i))
    //      ++c;
    //     }
    //     return c;
    // }

    void Variable::forceEvaluate(const AltaricaModel *am, AltaricaState *s) const {
      s->setValue(this, getDomain()->toValue());
      s->getValue(this)->unConstrain();

      // if (asserts_->size() > 1) {
      //  std::cerr << "more than one assert possible to compute" << std::endl;
      // }
      if (asserts_->size() == 0) {
        std::string s = "cannot compute: " + toString();
        std::cerr << "\nError " << s << std::endl;        
        throw new AltaricaException(s, "Variable::evaluate");        
      }
      
      for (std::vector<Assertion*>::iterator ai = asserts_->begin(); ai != asserts_->end(); ++ai) {
        (*ai)->evaluate(am, s);
      }
      s->getValue(this)->constrain();
    }
    


    void Variable::evaluate(const AltaricaModel *am, AltaricaState *s) const {
      // if (index_ == 717 || index_ == 1411 || index_ == 2125 || index_ == 2841) {        
      //   std::cout << "blah\n";
      //   std::cout << s->toString(true,true,false,false) << std::endl;        
      // }

      

      
      if (s->getFlowValue(index_) != NULL)       {
        loop_status_ = 0;        
        return;      
      }
      

      switch (loop_status_) {
      case 0: loop_status_ = 1;
        break;
      case 1: loop_status_ = 2;        
        std::cerr << ">>>>>>>>>>>>>>>>>>>>>>>>> loop detected, into loop for debug\n";
        std::cerr << ">>>>>>>>>>>>>>>>>>>>>>>>> evaluating var: " << this->toString() << " \n";        
        break;
      case 2:
        std::cerr << "<<<<<<<<<<<<<<<<<<<<<<<<  end of loop, assuming all possible values, you should check your model\n";
        s->setValue(this, getDomain()->toValue());
        s->getValue(this)->unConstrain();
//        loop_status_ = 0;        
        return;
      }
      
        
          




      
      s->setValue(this, NULL);
      
      // s->setValue(this, getDomain()->toValue(), false);
//      s->getValue(this)->unConstrain();
      if ((asserts_ == NULL) || (asserts_->size() == 0)) {
        std::string s = "cannot compute: " + toString();
        std::cerr << "\nError " << s << std::endl;        
        throw new AltaricaException(s, "Variable::evaluate");        
      }
      
      for (std::vector<Assertion*>::iterator ai = asserts_->begin(); ai != asserts_->end(); ++ai) {
        if (loop_status_ == 2)
          std::cerr << ">>>>>>>>>>>>>>>>>>> using assertion\n" << (*ai)->getExpr()->toString() << "\n";
        
        (*ai)->evaluate(am, s);
      }
      s->getValue(this)->constrain();
      loop_status_ = 0;      
      
      
    }

    void Variable::evaluateLM(const AltaricaModel *am, LandmarkState *lms) const {
      if (lms->getState()->hasValue(this))
        return;
      lms->getState()->setValue(this, getDomain()->toValue());
      lms->getState()->getValue(this)->unConstrain();

      // int bidon = this->toString().find("MMEL_status^applicable");
      // if (bidon!=-1) {
      //  std::cerr << "blah";
      // }

      if (asserts_->size() > 1) {
        std::cerr << "more than one assert possible to compute" << std::endl;
      }

      for (std::vector<Assertion*>::iterator ai = asserts_->begin(); ai != asserts_->end(); ++ai) {
        (*ai)->evaluateLM(am, lms);
      }
      lms->getState()->getValue(this)->constrain();
    }



    std::string Variable::toString() const {
	    std::stringstream ss;

	    ss << index_ << ": " << node_->toString() << "."+ma_->toString();
	    for (std::vector<std::pair<const Node*, const MemberAccess*>*>::iterator i = aliases_->begin();
			    i!= aliases_->end(); ++i)
		    ss << " / " << (*i)->first->toString() << "." << (*i)->second->toString();
	    return ss.str();
    }

    std::string Variable::toFiacre(const bool print_head) const {
	    std::ostringstream ss;

	    if (print_head) {
		    if (node_->findStateVariableByName(ma_->toString()))
			    ss << "y.";
		    if (node_->findFlowVariableByName(ma_->toString()))
			    ss << "x.";
	    }
	    ss << node_->toFiacre() << "_" << ma_->toString();

	    // for (std::vector<std::pair<const Node*, const MemberAccess*>*>::iterator i = aliases_->begin(); i!= aliases_->end(); ++i)
	    //	 ss << " / " << (*i)->first->toString() << "_" << (*i)->second->toString();
	    return ss.str();
    }

    bool Variable::equals(std::vector<Variable*> *v1, std::vector<Variable*> *v2) {
    	if (v1->size() != v2->size())
    		return false;
    	for (std::vector<Variable*>::iterator v1it = v1->begin(); v1it != v1->end(); ++v1it) {
    		bool isIn = false;
    		for (std::vector<Variable*>::iterator v2it = v2->begin();
    				v2it != v2->end() && ! isIn; ++v2it) {
    			if (*v2it == *v1it)
    				isIn = true;
    		}
    		if (!isIn)
    			return false;
    	}
    	return true;
    }

    std::vector<Variable*> *Variable::removeFromVec(std::vector<Variable*> *toRemove, std::vector<Variable*> *all) {
      for (std::vector<Variable*>::iterator i = toRemove->begin(); i!=toRemove->end(); ++i) {
        bool removed = false;
        for (std::vector<Variable*>::iterator j = all->begin(); j!=all->end() && !removed; ++j) {
          if (*i == *j) {
            all->erase(j);
            removed = true;
          }
        }
      }
      return all;
    }

    Variable::~Variable() {
      delete ma_;
      attributes_.clear();
      delete asserts_;
      for (std::vector<std::pair<const Node*, const MemberAccess*>*>::iterator i =aliases_->begin();
           i!= aliases_->end(); ++i) {
        delete (*i)->second;
        delete *i;
      }
      delete aliases_;
    }

    void Variable::addAlias(const Node *n, const MemberAccess*ma) {
      aliases_->push_back(new std::pair<const Node*, const MemberAccess*>(n,ma));
    }

    bool Variable::hasName(const Node* n, const MemberAccess* ma) const {
      if (node_ == n && ma_->equals(ma))
        return true;
      for (std::vector<std::pair<const Node*,const MemberAccess*>*>::const_iterator i = aliases_->begin();
           i!= aliases_->end(); ++i)
        if ((*i)->first == n && (*i)->second->equals(ma))
          return true;
      return false;
    }

    void Variable::mergeVariable(const Variable *v) {
	    std::cerr << "Merging variable " << getFullName() << " : " << getAsserts()->size() << " with " << v->getFullName() << " : " << v->getAsserts()->size() << std::endl;

      if (state_ || v->isState())
        BOOST_THROW_EXCEPTION(std::runtime_error(std::string("trying to merge varibles that are not flow vars!!!!!!!")));
      addAttributes(v->getAttributes());
      addAsserts(v->getAsserts());
      addAlias(v->getNode(), v->getMA()->clone());
      const  std::vector<std::pair<const Node*, const MemberAccess*>*> * valiases = v->getAliases();
      for (std::vector<std::pair<const Node*, const MemberAccess*>*>::const_iterator i = valiases->begin();
           i!= valiases->end(); ++i) {
        addAlias((*i)->first, (*i)->second->clone());
      }

      std::cerr << "   merged variable " << getFullName() << " : " << getAsserts()->size()<< std::endl;
    }

    const  std::vector<std::pair<const Node*, const MemberAccess*>*> * Variable::getAliases() const {
      return aliases_;
    }

    const std::vector<std::string> *Variable::getAttributes() const {
      return &attributes_;
    }

    void Variable::removeAssert(Assertion*a) {
      for (std::vector<Assertion*>::iterator i = asserts_->begin(); i != asserts_->end(); ++i)
        if (*i == a) {
          asserts_->erase(i);
          return;
        }

    }

    bool Variable::removeOwner(Node *n) {
      if (node_ == n) {
        node_ = NULL;
        if (aliases_->size() == 0)
          return true;
      } else {
        std::vector<std::pair<const Node*, const MemberAccess*>*>::iterator i;
        for (i = aliases_->begin(); i!= aliases_->end() && (*i)->first!=n; ++i);
        if (i == aliases_->end())
          BOOST_THROW_EXCEPTION(std::runtime_error(std::string("unable to find owner!!!!!!!")));
        delete (*i)->second;
        delete *i;
        aliases_->erase(i);
        if (aliases_->size() == 0 && node_ == NULL)
          return true;
      }
      return false;
    }


    std::vector<Variable*> * Variable::infoVars(const AltaricaModel *am, const Node *n, bool flow, bool computed) {
      if (state_)
        return NULL;
      std::vector<Variable*>* vv = new std::vector<Variable*>();
      for (std::vector<Assertion*>::iterator i = asserts_->begin(); i!= asserts_->end(); ++i) {
        std::vector<Variable*> *vvv = (*i)->infoVars(am, flow, computed);
        vv->insert(vv->end(), vvv->begin(), vvv->end());
        delete vvv;
      }
      return vv;
    }


  }
}
