//| This file is a part of the sferes2 framework.
//| Copyright 2009, ISIR / Universite Pierre et Marie Curie (UPMC)
//| Main contributor(s): Jean-Baptiste Mouret, mouret@isir.fr
//|
//| This software is a computer program whose purpose is to facilitate
//| experiments in evolutionary computation and evolutionary robotics.
//|
//| This software is governed by the CeCILL 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 license as circulated by CEA, CNRS and INRIA at the
//| following URL "http://www.cecill.info".
//|
//| As a counterpart to the access to the source code and rights to
//| copy, modify and redistribute granted by the license, users are
//| provided only with a limited warranty and the software's author,
//| the holder of the economic rights, and the successive licensors
//| have only limited liability.
//|
//| In this respect, the user's attention is drawn to the risks
//| associated with loading, using, modifying and/or developing or
//| reproducing the software by the user in light of its specific
//| status of free software, that may mean that it is complicated to
//| manipulate, and that also therefore means that it is reserved for
//| developers and experienced professionals having in-depth computer
//| knowledge. Users are therefore encouraged to load and test the
//| software's suitability as regards their requirements in conditions
//| enabling the security of their systems and/or data to be ensured
//| and, more generally, to use and operate it in the same conditions
//| as regards security.
//|
//| The fact that you are presently reading this means that you have
//| had knowledge of the CeCILL license and that you accept its terms.




#ifndef FITNESS_HPP_
#define FITNESS_HPP_

#include <iostream>
#include <cmath>
#include <vector>
#include <cassert>
#include <boost/serialization/nvp.hpp>
#include <boost/shared_ptr.hpp>
#include <sferes/stc.hpp>
#include <sferes/dbg/dbg.hpp>


#define SFERES_FITNESS SFERES_CLASS_D

namespace sferes {
  namespace fit {

    namespace mode {
      enum mode_t { eval = 0, view, usr1, usr2, usr3, usr4, usr5 };
    }
    SFERES_CLASS(Fitness) {
    public:
      Fitness() : _value(0), _mode(mode::eval) {}
      float value() const {
        return _value;
      }
      const std::vector<float>& objs() const {
        return _objs;
      }
      void add_obj() {
        _objs.resize(_objs.size() + 1);
      }
      float obj(size_t i) const {
        assert(i < _objs.size());
        assert(!std::isnan(_objs[i]));
        return _objs[i];
      }
      void set_obj(size_t i, float v) {
        assert(i < _objs.size());
        assert(!std::isnan(v));
        _objs[i] = v;
      }
      template<typename Indiv>
      void eval(Indiv& i) {
        stc::exact(this)->eval(i);
      }
      template<class Archive>
      void serialize(Archive & ar, const unsigned int version) {
        dbg::trace trace("fit", DBG_HERE);
        ar & BOOST_SERIALIZATION_NVP(_value);
        ar & BOOST_SERIALIZATION_NVP(_objs);
      }
      void set_mode(mode::mode_t m) {
        _mode = m;
      }
      mode::mode_t mode() const {
        return _mode;
      }
    protected:
      float _value;
      std::vector<float> _objs;
      mode::mode_t _mode;
    };

    /// this comparator is to use with sort, NOT with std::max_element
    // Binary function that accepts two elements in the range as arguments,
    // and returns a value convertible to bool. The value returned indicates
    // whether the element passed as first argument is considered to go before
    // the second in the specific strict weak ordering it defines.
    // see: http://www.cplusplus.com/reference/algorithm/sort/
    struct compare {
      template<typename I>
      bool operator()(const boost::shared_ptr<I> i1, const boost::shared_ptr<I> i2) const {
        return i1->fit().value() > i2->fit().value();
      }
    };

    // comparator for max_element. This is not for std::sort! (see above)
    // comparison function object (i.e. an object that satisfies the
    // requirements of Compare) which returns ​true if the first argument
    // is less than the second.
    // see: http://en.cppreference.com/w/cpp/algorithm/max_element
    struct compare_max {
      template<typename I>
      bool operator()(const boost::shared_ptr<I> i1, const boost::shared_ptr<I> i2) const {
        return i1->fit().value() < i2->fit().value();
      }
    };

    struct compare_obj {
      compare_obj(unsigned i) : _i(i) {}
      template<typename I>
      bool operator()(const boost::shared_ptr<I> i1, const boost::shared_ptr<I> i2) const {
        assert(_i < i1->fit().objs().size());
        assert(_i < i2->fit().objs().size());
        assert(i1->fit().objs().size());
        assert(i2->fit().objs().size());
        return i1->fit().obj(_i) > i2->fit().obj(_i);
      }
      size_t _i;
    };
    // lexical order
    struct compare_objs_lex {
      compare_objs_lex() {}
      template<typename I>
      bool operator()(const boost::shared_ptr<I> i1, const boost::shared_ptr<I> i2) const {
        assert(i1->fit().objs().size() == i2->fit().objs().size());
        assert(i1->fit().objs().size());
        assert(i2->fit().objs().size());
        for (size_t i = 0; i < i1->fit().objs().size(); ++i)
          if (i1->fit().obj(i) > i2->fit().obj(i))
            return true;
          else if (i1->fit().obj(i) < i2->fit().obj(i))
            return false;
        return false;
      }

    };

    // returns :
    //  1 if i1 dominates i2
    // -1 if i2 dominates i1
    // 0 if both a and b are non-dominated
    template<typename I1, typename I2>
    inline int dominate_flag(const boost::shared_ptr<I1> i1, const boost::shared_ptr<I2> i2) {
      assert(i1->fit().objs().size());
      assert(i2->fit().objs().size());
      if (i1->fit().objs().size() != i2->fit().objs().size()){
        std::cout<<i1->fit().objs().size()<<" vs "<<i2->fit().objs().size()<<std::endl;
      	throw "Objectives unequal!";
      }
      assert(i1->fit().objs().size() == i2->fit().objs().size());

      size_t nb_objs = i1->fit().objs().size();
      assert(nb_objs);

      bool flag1 = false, flag2 = false;
      for (unsigned i = 0; i < nb_objs; ++i) {
        float fi1 = i1->fit().obj(i);
        float fi2 = i2->fit().obj(i);
        if (fi1 > fi2)
          flag1 = true;
        else if (fi2 > fi1)
          flag2 = true;
      }
      if (flag1 && !flag2)
        return 1;
      else if (!flag1 && flag2)
        return -1;
      else
        return 0;
    }

    // true if i1 dominate i2
    template<typename I1, typename I2>
    inline bool dominate(const boost::shared_ptr<I1> i1, const boost::shared_ptr<I2> i2) {
      return (dominate_flag(i1, i2) == 1);
    }


    struct compare_pareto {
      template<typename I1, typename I2>
      int operator()(const boost::shared_ptr<I1> i1, const boost::shared_ptr<I2> i2) const {
        return dominate_flag(i1, i2);
      }
      template<typename I>
      bool eq(const boost::shared_ptr<I> i1, const boost::shared_ptr<I> i2) {
        bool c1 = operator()(i1, i2);
        bool c2 = operator()(i2, i1);
        return !c1 && !c2;
      }
    };

    SFERES_CLASS_D(FitDummy, Fitness) {
    public:
      template<typename Indiv>
      void eval(Indiv& i) {
      }
    };
  }
}



#endif
