/*******************************************************************************
 * Copyright (c) 2015-2016 ONERA.
 * Ce logiciel est la propriété de l’ONERA (the French Aerospace Lab).
 * 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).
 * 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
 *
 *******************************************************************************/

#include "Synchronisation.hh"
#include "Node.hh"
#include <limits.h>
#include <iostream>
#include "AltaricaModel.hh"
#include "MemberAccess.hh"
#include "Event.hh"
#include "MetaEvent.hh"
#include <sstream>
#include <limits>



namespace epoch {
  namespace altarica {
    Synchronisation::Synchronisation() {
      max_ = 0;
      ma_ = NULL;
      numerical_constant_ = -1;
    };


    Synchronisation::Synchronisation(MemberAccess *ma) {
      max_ = 0;
      ma_ = ma;
      numerical_constant_ = -1;
    };

    void Synchronisation::addEvent(MemberAccess*e, bool optional) {
      broadcastList_.push_back(e);
      optionalBcast_.push_back(optional);
    }

    void Synchronisation::setConstraintType(short ct) {
      constraint_type_ = ct;
    }

    void Synchronisation::setNumConstant(int n) {
      numerical_constant_ = n;
    }

    void Synchronisation::setMax() {
      max_ = 1;
    }

    void Synchronisation::setMin() {
      max_ = -1;
    }


    Synchronisation * Synchronisation::clone() {
      Synchronisation * n;
      if (ma_ != NULL)
        n = new Synchronisation(ma_->clone());
      else
        n = new Synchronisation();
      n->setConstraintType(constraint_type_);
      n->setNumConstant(numerical_constant_);
      if (max_ == 1)
        n->setMax();
      else if (max_ == -1)
        n->setMin();
      std::vector<MemberAccess*>::iterator itma = broadcastList_.begin();
      std::vector<bool>::iterator ito = optionalBcast_.begin();
      for (; itma != broadcastList_.end(); ++itma) {
        n->addEvent((*itma)->clone(), *ito);
        ++ito;
      }
      return n;
    }

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

    void Synchronisation::matchEvents() {
      broadcastListOptionalEvents_.clear();
      broadcastListMandatoryEvents_.clear();
      for (unsigned int i = 0; i < broadcastList_.size(); ++i) {
        Event * e = node_->findEventByName(broadcastList_[i]);
        if(e == NULL)
          std::cerr << "\nSync failed, unknown event : "
                    << broadcastList_[i]->toString()
                    << std::endl;
        else {
          e->addSync(this);
          if (optionalBcast_[i])
            broadcastListOptionalEvents_.push_back(e);
          else
            broadcastListMandatoryEvents_.push_back(e);
        }
      }
    }


    const std::vector<Event*> * Synchronisation::getOptionalEvents() {
      if (broadcastListOptionalEvents_.size() == 0 && broadcastListMandatoryEvents_.size() ==0)
        matchEvents();
      return &broadcastListOptionalEvents_;
    }


    const std::vector<Event*> * Synchronisation::getMandatoryEvents() {
      if (broadcastListOptionalEvents_.size() == 0 && broadcastListMandatoryEvents_.size() ==0)
        matchEvents();
      return &broadcastListMandatoryEvents_;
    }

    std::string Synchronisation::toString() const {
      std::stringstream ss;
      ss << "blist: ";
      for (std::vector<MemberAccess*>::const_iterator i = broadcastList_.begin(); i!= broadcastList_.end(); ++i) {
        ss << (*i)->toString() << ",";
      }
      ss << "\n mandatoryEv: ";
      for (std::vector<Event*>::const_iterator i = broadcastListMandatoryEvents_.begin(); i!= broadcastListMandatoryEvents_.end(); ++i) {
        ss << (*i)->toString() << ",";
      }
      ss << "\n optionalEv: ";
      for (std::vector<Event*>::const_iterator i = broadcastListOptionalEvents_.begin(); i!= broadcastListOptionalEvents_.end(); ++i) {
        ss << (*i)->toString() << ",";
      }
      return ss.str();

    }



    std::vector<MetaEvent*> * Synchronisation::buildSynchronisedEvents(const AltaricaModel *am, AltaricaState *s) {
      // if (ma_ != NULL)
      //  std::cerr << "building sync events for sync "<< *(ma_->toString()) << " of node: " << *(node_->getMA()->toString()) << std::endl;
      // else
      //  std::cerr << "building sync events for  node: " << *(node_->getMA()->toString()) << std::endl;

      std::vector<MetaEvent*> * ret = NULL;

      for (std::vector<Event*>::iterator j = broadcastListMandatoryEvents_.begin();
           j!=broadcastListMandatoryEvents_.end() ; ++j)
        if (!(*j)->isEnabled(am, s)) {
          return NULL;
        }

      ret = new std::vector<MetaEvent*>();

      MetaEvent * me = new MetaEvent();
      for (std::vector<Event*>::iterator j = broadcastListMandatoryEvents_.begin();   j!=broadcastListMandatoryEvents_.end(); ++j)
        me->push_back(*j);
      ret->push_back(me);

      for ( std::vector<Event*>::iterator i = broadcastListOptionalEvents_.begin();
            i!= broadcastListOptionalEvents_.end(); ++i) {
        if ((*i)->isEnabled(am, s)) {
          int nvec = ret->size();
          std::vector<MetaEvent*> *temp = new std::vector<MetaEvent*>();
          for (std::vector<MetaEvent*>::iterator k = ret->begin(); k!=ret->end(); ++k)
            temp->push_back((*k)->clone());
          ret->insert(ret->begin(), temp->begin(), temp->end());
          delete temp;
          for (int j = 0; j<nvec; ++j) {
            (*ret)[j]->push_back(*i);
          }
        }
      }

      // now apply constraints

      std::vector<MetaEvent*>::iterator iret;
      if (numerical_constant_ != -1) {
        iret = ret->begin();
        while (iret != ret->end()) {
          int s = (*iret)->size();
          bool removed = false;
          switch (constraint_type_) {
          case -2: // INF
            if (s >= numerical_constant_) {
              iret = ret->erase(iret);
              removed = true;
            }
            break;
          case -1: // INFEQ
            if (s > numerical_constant_) {
              iret = ret->erase(iret);
              removed = true;
            }
            break;
          case 0: // EQ
            if (s != numerical_constant_) {
              iret = ret->erase(iret);
              removed = true;
            }
            break;
          case 1: // SUPEQ
            if (s < numerical_constant_) {
              iret = ret->erase(iret);
              removed = true;
            }
            break;
          case 2: // SUP
            if (s <= numerical_constant_) {
              iret = ret->erase(iret);
              removed = true;
            }
            break;
          }
          if (!removed)
            ++iret;
        }
      }

      // if min then select only minimal size among remaining
      if (max_ == -1) {
        unsigned int min_size = UINT_MAX;
        for (iret = ret->begin(); iret != ret->end(); ++iret) {
          if ((*iret)->size()<min_size)
            min_size = (*iret)->size();
        }
        iret = ret->begin();
        while (iret != ret->end()) {
          if ((*iret)->size() != min_size) {
            iret = ret->erase(iret);
          } else
            ++iret;
        }
      } else if (max_ == 1) {  // if max select only max size among remaining
        unsigned int max_size = std::numeric_limits<unsigned int>::min();
        for (iret = ret->begin(); iret != ret->end(); ++iret) {
          if ((*iret)->size()>max_size)
            max_size = (*iret)->size();
        }
        iret = ret->begin();
        while (iret != ret->end()) {
          if ((*iret)->size() != max_size) {
            iret = ret->erase(iret);
          } else
            ++iret;
        }        
      }
      

      // finally send stuff
      return ret;
    }

    Synchronisation::~Synchronisation() {
      for (std::vector<MemberAccess*>::iterator i = broadcastList_.begin();
           i!= broadcastList_.end(); ++i)
        delete *i;
      delete ma_;
    }
  }
}
