/*******************************************************************************
 * 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 "AltaricaState.hh"
#include "Assignment.hh"
#include <sstream>
#include <boost/functional/hash.hpp>
#include "AltaricaModel.hh"
#include <iostream>
#include "Values.hh"
#include "Variable.hh"
#include "AltaricaInitInfo.hh"
#include <cassert>

using namespace epoch::altarica;


AltaricaState::AltaricaState(bool initial,  const AltaricaModel *amodel, AltaricaInitInfo *initInfo) : amodel_(amodel), cache(NULL) {
  unsigned int ns = amodel_->getNStateVars();
  unsigned int nf = amodel_->getNFlowVars();
  stateVars_.resize(ns,NULL);
  flowVars_.resize(nf,NULL);
  
  if (initial) {
    for (unsigned int i = 0; i<ns; ++i)  {
	    Value * v = NULL;
	    // if we have something to init in initFile, take value here
	    Value *iv = NULL;
	    if (initInfo != NULL)
        iv = initInfo->getInitValue(i);
	    if (iv != NULL) {
        v = iv;
	    } else {
        const Assignment * init = amodel_->getStateVar(i)->getInit();
        if (init != NULL) {
          v = amodel_->getStateVar(i)->getInit()->evaluate(amodel, this,  amodel_->getStateVar(i)->getNode());
        } 	
	    }    
	    if (v == NULL) {
        std::string b = "cannot init variable: " + amodel_->getStateVar(i)->toString();
        BOOST_THROW_EXCEPTION(std::runtime_error(b));
        exit(-1);
	    }
	    if (v->isSymbol()) {
        EnumDomain * d = (EnumDomain*) amodel_->getStateVar(i)->getDomain();
        ValueSymbol* vs = (ValueSymbol*) v;        
        if (!d->contains(vs->toSymbol())) 
        {
          std::string s = "unknown value: ";
          s+= vs->toSymbol();
          s+= "     for variable " ;
          s+= amodel_->getStateVar(i)->toString();
          s+= "  should be in ";
          s+= amodel_->getStateVar(i)->getDomain()->toString();
            throw AltaricaException(s, "AltaricaState constructor of initial state");
        }
        
        vs->setDomain((EnumDomain*)amodel_->getStateVar(i)->getDomain());
	    }
	    stateVars_[i] = v;
	    v->own();	    
    }
    rehash_();
  }
}

AltaricaState::AltaricaState(const std::vector<Value*> &mstate,  const AltaricaModel *amodel) {    
    amodel_ = amodel;
    cache = NULL;
    stateVars_.assign(mstate.begin(),mstate.end());
    for (unsigned int i = 0; i< stateVars_.size(); ++i) 
      if (stateVars_[i] != NULL)
        stateVars_[i]->own();
    flowVars_.resize(amodel_->getNFlowVars(),NULL);
}

void AltaricaState::addValue(const Variable* var, Value*v) {
    if (var->isState()) 
	addStateValue(var->getIndex(), v);
    else
	addFlowValue(var->getIndex(), v);
}

bool AltaricaState::isEmpty() const {
    for (unsigned int i=0; i< stateVars_.size(); ++i)
	if (stateVars_[i] != NULL)
	    return false;
    return true;
}


void AltaricaState::setValue(const Variable*var, Value *val) {
  
    if (var->isState()) {
	if (val->isSymbol()) {
	    ValueSymbol* vs = (ValueSymbol*) val;
	    vs->setDomain((EnumDomain*)var->getDomain());
	}    
	setStateValue(var->getIndex(),val);
    } else 
	setFlowValue(var->getIndex(),val);
}

void AltaricaState::addStateValue(unsigned int i, Value*val) {
    if (val == NULL)
	return;
    if (stateVars_[i] == NULL) {
      stateVars_[i] = val;
    } else {
      if (stateVars_[i]->ro())
        stateVars_[i] = stateVars_[i]->clone();
      stateVars_[i]->merge(val);
    }
    stateVars_[i]->own();
    Value::deleteIfPossible(val);
}


void AltaricaState::setStateValue(unsigned int i, Value*val) {
    if (val == NULL)
	return;
    Value::release(stateVars_[i]);
    val->own();
    stateVars_[i] = val;
}

void AltaricaState::addFlowValue(unsigned int i, Value*val) {
    if (val == NULL) 
	return;
    if (flowVars_[i] == NULL) {
      flowVars_[i] = val;
    } else {
      if (flowVars_[i]->ro())
        flowVars_[i] = flowVars_[i]->clone();
      flowVars_[i]->merge(val);
    }
    flowVars_[i]->own();
    Value::deleteIfPossible(val);
}


void AltaricaState::setFlowValue(unsigned int i, Value*val) {
  if (val != NULL)
    val->own();
  Value::release(flowVars_[i]);
  flowVars_[i] = val;
}



// Value * AltaricaState::getValue(const Variable *v) const{
//     if (hasValue(v))
// 	return (*vars_)[v];
//     else
// 	return NULL;
// }


bool AltaricaState::hasStateValue(unsigned int i ) const {
    	if (stateVars_[i] == NULL)
	    return false;    
	return true;
}


bool AltaricaState::hasValue(const Variable *  v) const {
    if (v->isState()) {
	if (stateVars_[v->getIndex()] == NULL)
	    return false;    
	return true;
    } else {
	if (flowVars_[v->getIndex()] == NULL)	
	    return false;    
	return true;
    }
}

Value * AltaricaState::getValue(const Variable *v) const {
  if (v->isState()) {
    return stateVars_[v->getIndex()];
  } else {
    return flowVars_[v->getIndex()];	
  }
}

Value * AltaricaState::getStateValue(unsigned int i) const {
    return stateVars_[i];
}

Value * AltaricaState::getFlowValue(unsigned int i) const {
  return flowVars_[i];
}

void AltaricaState::removeValue(const Variable *v) {
  if (v->isState()) {
    Value * vv = stateVars_[v->getIndex()];
    Value::release(vv);
    stateVars_[v->getIndex()] = NULL;
  } else {
    Value * vv = flowVars_[v->getIndex()];
    if (vv != NULL)
      Value::release(vv);
    flowVars_[v->getIndex()] = NULL;
  }
}

void AltaricaState::releaseFlowVars() const {
    for (unsigned int i = 0; i<flowVars_.size();  ++i) {
      Value::release(flowVars_[i]);
      flowVars_[i]= NULL;
		}
}

void AltaricaState::deleteFlowValues() {
    for (unsigned int i = 0; i<flowVars_.size();  ++i) {
      Value::release(flowVars_[i]);
      flowVars_[i]= NULL;
    }
}





void AltaricaState::mergeToState(AltaricaState*s) const {
    unsigned int i = 0;
    for (std::vector<Value*>::const_iterator vi = stateVars_.begin(); vi!= stateVars_.end(); ++vi)
    	s->addStateValue(i++, *vi);
    i = 0;
    for (std::vector<Value*>::const_iterator vi = flowVars_.begin(); vi!= flowVars_.end(); ++vi)
	    s->addFlowValue(i++, *vi);
}

void AltaricaState::intersectState(const AltaricaState *s) {
    if (s == NULL)
	return;
    for (unsigned int i = 0; i< amodel_->getNStateVars(); ++i)
	removeValue(i, s->getStateValue(i));
}

void AltaricaState::mergeIntersectState  (const AltaricaState *s) {
    if (s == NULL)
	return;
    for (unsigned int i = 0; i< amodel_->getNStateVars(); ++i) {
	if (hasStateValue(i)) {
	    if (s->hasStateValue(i))
		getStateValue(i)->intersectValue(s->getStateValue(i));
	} else {
	    setStateValue(i, s->getStateValue(i));
	}
    }
    
    
}

void AltaricaState::removeValue(const Variable *v, const Value*vv) {
    removeValue(v->getIndex(), vv);
}

void AltaricaState::removeValue(unsigned int i, const Value*vv) {
    Value *v = stateVars_[i];    
    if (v != NULL) {
      if (v->ro())
        v = v->clone();
      v->removeValue(vv);
      if (v->card() == 0) {
        Value::deleteIfPossible(v);
        v = NULL;
      } else 
        v->own();
      stateVars_[i] = v;
    }
}



AltaricaState::~AltaricaState() {
    for (std::vector<Value*>::iterator i = stateVars_.begin(); i!= stateVars_.end(); ++i)  {
	Value::release(*i);
    }
    releaseFlowVars();
    Value::release(cache);
}

AltaricaState * AltaricaState::clone() const {
    AltaricaState * n = new AltaricaState(stateVars_,amodel_);    
    n->setHash(hash_);
    return n;
    
}

// AltaricaState * AltaricaState::getMacroState() const {
//     AltaricaState *n = new AltaricaState((*stateVars_),amodel_,true);    
//     n->rehash_();
//     return n;
// }

// unsigned int AltaricaState::size() {
//     return vars_->size();
// }

void AltaricaState::setHash(size_t h) {
    hash_ = h;
}

bool AltaricaState::equals(AltaricaState *s) {
    // if (this == s)
    //   return true;
    unsigned int j = 0;    
    for (std::vector<Value*>::iterator i = stateVars_.begin(); i!= stateVars_.end(); ++i) {
	if ( ((*i) != s->getStateValue(j)) && !(*i)->equals(s->getStateValue(j)) )
	    return false;
	++j;
    }
    return true;
}

std::string AltaricaState::toString(bool state, bool flow, bool printNULL, bool printDomain) {
    std::stringstream ss;
    //    std::string s;
    if (state) {
	if (flow)
	    ss  << "STATE VARS:"<<std::endl;
	unsigned int j = 0;
	for (std::vector<Value*>::iterator i = stateVars_.begin();   i!= stateVars_.end(); ++i) {	
	    if (printNULL ||(*i) != NULL ) {
		Variable * v = amodel_->getStateVar(j);
		ss << v->toString();
		if ((*i) != NULL) {
		    ss << " : " << (*i)->toString();// << "(" << *i << ")" ;
		    if (printDomain)
			ss << " in " << v->getDomain()->toString();
		    ss <<";" ;
		} else
		    ss<< " : NULL;";
		ss<< std::endl;
	    }
	    j++;
	}
    }
    if (flow) {
      if (state)
        ss<< "FLOW VARS:\n";
      unsigned int j = 0;
      for (std::vector<Value*>::iterator i = flowVars_.begin();    i!= flowVars_.end(); ++i) {	
        Variable *v = amodel_->getFlowVar(j++);
        ss << v->toString();
        if ((*i) != NULL)
          ss<< " : " << (*i)->toString() << " in " << v->getDomain()->toString() <<";" ;	   
        ss<< std::endl;
      }
    }
    
    return ss.str();
}


void AltaricaState::rehash_() {
    hash_ = 0;
    boost::hash<int> int_hash;
    for (std::vector<Value*>::iterator i = stateVars_.begin(); i!= stateVars_.end(); ++i) {
      int h = (*i)->toHash();
      boost::hash_combine(hash_, int_hash(h));
    }
}


std::size_t AltaricaState::hash() {
    return hash_;
}

bool AltaricaState::operator<(const AltaricaState&o) const {
  unsigned int j = 0;
  for (std::vector<Value*>::const_iterator i = stateVars_.begin(); i!= stateVars_.end(); ++i) {
    Value* ov = o.getStateValue(j++);
    
    if ((*i)->less(ov))
	    return true;
  }
  return false;
}



