/*******************************************************************************
 * 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 "AltaricaJson.hh"
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include "Node.hh"
#include "AltaricaModel.hh"
#include "Event.hh"
#include "MetaEvent.hh"
#include "EventLaw.hh"
#include "expressions/ExpressionREAL.hh"
#include "Variable.hh"
#include "domains/Domains.hh"
#include "MemberAccess.hh"
#include "AltaricaState.hh"

using namespace epoch::altarica;

AltaricaJson::AltaricaJson(std::string jsonFile, AltaricaModel *am) : AltaricaInitInfo(am), 
                                                                      hasInit_(false), 
                                                                      hasGoal_(false), 
                                                                      hasEvents_(false),
                                                                      hasActive_(false),
                                                                      hasInactive_(false)
{
	verbose = true;
	parseJsonFile(jsonFile);
	overrideLaws();
}





void AltaricaJson::parseJsonFile(std::string jsonFile) {
	boost::property_tree::ptree pt;
	boost::property_tree::json_parser::read_json(jsonFile,pt);
	for (auto&  ii : pt.get_child("init")) {
		hasInit_ = true;
		if (verbose) std::cout << "processing init info for " << ii.first << " ";
		std::string node_name = ii.first;
		std::vector<Node*> * altNodeVector = NULL; 
    
    
    altNodeVector = getNodes(node_name);
    if (altNodeVector->size() == 0) 
    {
      std::cerr <<  "could not find any node for: " << node_name << std::endl;      
      break;
    }    
		
		
		for (auto& iii : ii.second.get_child("")) {
			
			for (auto &n : *altNodeVector) {    
				
				
				
				if (iii.first.compare("laws") == 0) {
					if (verbose) std::cout << "  applying law init info on node: " << n->getMA()->toString() << std::endl;
					for (auto& i4 : iii.second.get_child("")) {
						std::string event_name =  i4.first;
						MemberAccess * tempMA = new MemberAccess(event_name);
            
						Event * oldEvent = n->findEventByName(tempMA);
						if (oldEvent == NULL) {
							std::cerr << "     !!!! CANNOT FIND EVENT: " << event_name << " in node " << node_name << std::endl;
							break;
						}
            
						std::string law_type;
						double param = std::numeric_limits<double>::min();
						for (auto& i5:i4.second.get_child("")) {
							if (i5.first.compare("type") == 0) {
								law_type = i5.second.get_value<std::string>();
							}
							if (i5.first.compare("param") == 0) {
								param = i5.second.get_value<float>();
              }
                            
            }
            EventLaw * newLaw;
            Expression * newParam;
            if (law_type.compare("") == 0) {
              newLaw = oldEvent->getLaw()->clone();                           
            } else if (law_type.compare("exp") == 0) {
              newLaw = new EventLaw(ALTARICA_EXP);
            } else if (law_type.compare("dirac") == 0) {
              newLaw = new EventLaw(ALTARICA_DIRAC_0);
            } else if (law_type.compare("const") == 0) {
              newLaw = new EventLaw(ALTARICA_DISCRETE);
            }
            if (param == std::numeric_limits<double>::min())
              newParam = oldEvent->getLaw()->getParam();
            else
              newParam = new ExpressionREAL(param);
            newLaw->setParam(newParam);
            initLaws[oldEvent] = newLaw;                                                
          }               
        }
            
        if (iii.first.compare("values") == 0) {
          if (verbose) std::cout << "  applying values init info on node: " << n->getMA()->toString() << std::endl;
          for (auto& i4 : iii.second.get_child("")) {
            for (auto& i5 : i4.second.get_child("")) {
              std::string varName = i5.first;
              if (verbose)std::cout << "    on variable: " << varName << std::endl;              
              Variable * var = n->findStateVariableByName(varName);
              if (var == NULL) {
                std::cerr << "    !!! CANNOT FIND VARIABLE " << varName << std::endl;
                break;
              }
              Value * v = getValue(var, i5.second.get_value<std::string>());
              initValues[var->getIndex()] = v;                                                                        
            }
          }
        }
      }
    }
  }

  for (auto& ii : pt.get_child("goal"))  {    
    hasGoal_ = true;
    std::string node_name = ii.first;
    std::vector<Node*> * altNodeVector = NULL;
    if (verbose) std::cout << "processing goal info for " << node_name;

    altNodeVector = amodel->findNodesByType(node_name);
    if (altNodeVector == NULL) {
      altNodeVector = new std::vector<Node*>();
      Node *n = amodel->findNodeByFullName(node_name);
      if (n != NULL) {
        if (verbose) std::cout <<  " (instance)" << std::endl;          
        altNodeVector->push_back(n);
      }
    } else 
      if (verbose) std::cout << " (type)" << std::endl;
    if (altNodeVector->size() == 0) {
      std::cerr << "cannot find node: " << node_name << std::endl;
      break;
    }

    for (auto &n : *altNodeVector) {    
      for (auto & i3 : ii.second.get_child("")) {
        for (auto& i4 : i3.second.get_child("")) {
          std::string varName = i4.first;
          std::string varValue =  i4.second.get_value<std::string>();
          Variable * var = n->findStateVariableByName(varName);
          if (var == NULL) 
            var = n->findFlowVariableByName(varName);
          if (var == NULL) {
            if (verbose) std::cout << "cannot find variable " << varName << std::endl;
            break;
          }
          Value * v = getValue(var, varValue);                    
          goalValues[var] = v;
        }
      }   
    }
  }


  for (auto& ii : pt.get_child("events")) {
    hasEvents_ = true;
    std::vector<Event*> * newMEV = new std::vector<Event*>();
    for (auto& i3: ii.second.get_child("")) {
      std::string evName = i3.second.data();
      MemberAccess * tempMA = new MemberAccess(evName);
      tempMA->setGlobal(true);
      Event * ev = amodel->getMainNode()->findEventByName(tempMA);
      if (ev == NULL) {    
        std::cerr << "cannot find event " << evName << std::endl;
        break;
      }
      newMEV->push_back(ev);                  
    }
    testCase.push_back(newMEV);
  }

  for (auto& ii : pt.get_child("active")) {
    hasActive_ = true;
    std::vector<Event*> * newMEV = new std::vector<Event*>();
    for (auto& i3: ii.second.get_child("")) {
      std::string evName = i3.second.data();
      MemberAccess * tempMA = new MemberAccess(evName);
      tempMA->setGlobal(true);
      Event * ev = amodel->getMainNode()->findEventByName(tempMA);
      if (ev == NULL) {    
        std::cerr << "cannot find event " << evName << std::endl;
        break;
      }
      newMEV->push_back(ev);                  
    }
    activeEvents.push_back(newMEV);
  }

  for (auto& ii : pt.get_child("inactive")) {
    hasInactive_ = true;
    std::vector<Event*> * newMEV = new std::vector<Event*>();
    for (auto& i3: ii.second.get_child("")) {
      std::string evName = i3.second.data();
      MemberAccess * tempMA = new MemberAccess(evName);
      tempMA->setGlobal(true);
      Event * ev = amodel->getMainNode()->findEventByName(tempMA);
      if (ev == NULL) {    
        std::cerr << "cannot find event " << evName << std::endl;
        break;
      }
      newMEV->push_back(ev);                  
    }
    inactiveEvents.push_back(newMEV);
  }


}

std::string AltaricaJson::toString() const {
  std::string s = AltaricaInitInfo::toString();
  if (hasGoal_) {
    s+= "GOAL VALUES:\n";
    //      for (std::map<unsigned int, Value*>::const_iterator i = goalValues.begin(); i!= goalValues.end(); ++i) {
    for (auto &i:goalValues) {
      s+= i.first->toString() + " = " + i.second->toString()+"\n";
    }    
  }
  if (hasEvents_) {
    s+="EVENTS:\n";
    for (auto& mev:testCase) {
      s+=" { ";
      for (auto &e : *mev) {
        s+= e->toString();
      }
      s+= " }\n";
    }
  }
  return s;
}

std::vector<MetaEvent*> * AltaricaJson::matchMEV(std::vector<Event*> *mev, std::vector<MetaEvent*>* allmev) {
  std::vector<MetaEvent*> * matched = new std::vector<MetaEvent*>();
  for (auto& mevx:*allmev) {
    bool ok = true;
    for (auto& ev:*mev) {
      if (!mevx->containsE(ev)) {
        ok = false;
        break;
      }
    }
    if (ok)
      matched->push_back(mevx);
  }
  return matched;

}

std::vector<MetaEvent*> * AltaricaJson::selectSmaller(std::vector<MetaEvent*> *set) {
  std::vector<MetaEvent*> * smallestMEVs = new std::vector<MetaEvent*>();  
  unsigned int smaller = std::numeric_limits<unsigned int>::max();
  for (auto mev: *set) {
    if (smaller > mev->size())
      smaller = mev->size();    
  }
  for (auto mev:*set) {
    if (mev->size() == smaller)
      smallestMEVs->push_back(mev);    
  }
  delete set;
  return smallestMEVs;
}



bool AltaricaJson::runTestCase(AltaricaState *cstate, bool printFlow, bool printNULL, bool printDomain) {

  for (auto& mev: testCase) {
    std::vector<MetaEvent*> * allmev =  amodel->getEnabledMetaEventsAll(cstate);
    std::vector<MetaEvent*> * matched = matchMEV(mev, allmev);
    matched = selectSmaller(matched);    
    if (matched->size() != 1) {
      std::cerr << "cannot find corresponding MEV\n";
      std::cerr << "required: {";
      for (auto& ev:*mev)
        std::cerr << ev->toString() << " ";
      std::cerr << "}\n";
      std::cerr << "possible: \n";
      for (auto& mev: *allmev) 
        std::cerr << mev->toString();
      std::cerr << std::endl;
      return false;
    } else {
        
      std::cout << "Executing MEV: "<< (*matched)[0]->toString() << std::endl;
      std::cout << "state reached:" << std::endl;
      cstate = amodel->getNextState(cstate, (*matched)[0]);           
      if (printFlow)
        amodel->evaluateFlowVars(cstate);
      std::cout << cstate->toString(true,printFlow,printNULL,printDomain);   
    }
  }

  if (hasGoal_) {
        
    std::cout <<  "GOAL VALUES:\n";
    for (auto &i:goalValues) {
      std::cout << i.first->toString() << " = " << i.second->toString() << std::endl;
    }    
    std::cout << cstate->toString(true, printFlow, printNULL, printDomain);
        
    for (auto& gv:goalValues) {
      Value * reachedValue = cstate->getValue(gv.first);
      if (!reachedValue->contains(gv.second)) {
        std::cout << "TESTCASE FAILED for variable " << gv.first->toString();
        std::cout << "\n required value: " << gv.second->toString();
        std::cout << "\n  reached value : " << reachedValue->toString() << std::endl;
        return false;
      }
    }
//    return true;
  }

  if (hasActive_) {  
    std::vector<MetaEvent*> * allmev =  amodel->getEnabledMetaEventsAll(cstate);
    for (auto& mev: activeEvents) {
      std::vector<MetaEvent*> * matched = matchMEV(mev, allmev);
      matched = selectSmaller(matched);   
      if (matched->size() == 0)
        return false;      
    }    
  }
  
  if (hasInactive_) {
    std::vector<MetaEvent*> * allmev =  amodel->getEnabledMetaEventsAll(cstate);
    for (auto& mev: activeEvents) {
      std::vector<MetaEvent*> * matched = matchMEV(mev, allmev);
      matched = selectSmaller(matched);   
      if (matched->size() != 0)
        return false;      
    }    
  }
  


  return true;
}
