
/*
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation;
   version 2 of the License.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

   © 2020 Thales group

    Authors:
        Johann Dreo <johann.dreo@thalesgroup.com>
*/

#ifndef _eoAlgoFoundryFastGA_H_
#define _eoAlgoFoundryFastGA_H_

#include <array>
#include <tuple>
#include <limits>

/** A class that assemble an eoFastGA on the fly, given a combination of available operators.
 *
 * The foundry should first be set up with sets of operators
 * for the main modules of a FastGA:
 * continuators, crossovers, mutations, selections, replacement operators, etc.
 *
 * This is done through public member variable's `add` method,
 * which takes the class name as template and its constructor's parameters
 * as arguments. For example:
 * @code
 * foundry.selectors.add< eoRandomSelect<EOT> >();
 * @endcode
 *
 * @warning If the constructor takes a reference YOU SHOULD ABSOLUTELY wrap it
 * in a `std::ref`, or it will silently be passed as a copy,
 * which would effectively disable any link with other operator(s).
 *
 * In a second step, the operators to be used should be selected
 * by indicating their index, passing an array of 10 elements:
 * @code
 * foundry.select({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
 * @endcode
 *
 * @note: by default, the firsts of the 10 operators are selected.
 *
 * If you don't (want to) recall the order of the operators in the encoding,
 * you can use the `index()` member, for example:
 * @code
 * foundry.at(foundry.continuators.index()) = 2; // select the third continuator
 * @endcode
 *
 * Now, you can call the foundry just like any eoAlgo, by passing it an eoPop:
 * @code
 * foundry(pop);
 * @encode
 * It will instantiate the needed operators (only) and the algorithm itself on-the-fly,
 * and then run it.
 *
 * @note: Thanks to the underlying eoOperatorFoundry, not all the added operators are instantiated.
 * Every instantiation is deferred upon actual use. That way, you can still reconfigure them
 * at any time with `eoForgeOperator::setup`, for example:
 * @code
 * foundry.selector.at(0).setup(0.5); // Will call constructor's arguments
 * @endcode
 *
 * @ingroup Foundry
 * @ingroup Algorithms
 */
template<class EOT>
class eoAlgoFoundryFastGA : public eoAlgoFoundry<EOT>
{
    public:
        /** The constructon only take an eval, because all other operators
         * are stored in the public containers.
         */
        eoAlgoFoundryFastGA(
                eoInit<EOT> & init,
                eoEvalFunc<EOT>& eval,
                const size_t max_evals = 10000,
                const size_t max_restarts = std::numeric_limits<size_t>::max()
            ) :
            eoAlgoFoundry<EOT>(10),

            crossover_rates(0, 0.0, 1.0),
            crossover_selectors(1, false),
            crossovers(2, false),
            aftercross_selectors(3, false),

            mutation_rates(4, 0.0, 1.0),
            mutation_selectors(5, false),
            mutations(6, false),

            replacements(7, false),
            continuators(8, true), // Always re-instantiate continuators, because they hold a state.
            offspring_sizes(9, 0, std::numeric_limits<size_t>::max()),
            _eval(eval),
            _init(init),
            _max_evals(max_evals),
            _max_restarts(max_restarts)
        { }

    public:

        /* Operators containers @{ */
        eoParameterFoundry< double             > crossover_rates;
        eoOperatorFoundry< eoSelectOne<EOT>   > crossover_selectors;
        eoOperatorFoundry< eoQuadOp<EOT>      > crossovers;
        eoOperatorFoundry< eoSelectOne<EOT>   > aftercross_selectors;

        eoParameterFoundry< double             > mutation_rates;
        eoOperatorFoundry< eoSelectOne<EOT>   > mutation_selectors;
        eoOperatorFoundry< eoMonOp<EOT>       > mutations;

        eoOperatorFoundry< eoReplacement<EOT> > replacements;
        eoOperatorFoundry< eoContinue<EOT>    > continuators;
        eoParameterFoundry< size_t             > offspring_sizes;
        /* @} */

        /** instantiate and call the pre-selected algorithm.
         */
        void operator()(eoPop<EOT>& pop)
        {
            assert( crossover_selectors.size() > 0); assert(this->at( crossover_selectors.index()) <  crossover_selectors.size());
            assert(          crossovers.size() > 0); assert(this->at(          crossovers.index()) <           crossovers.size());
            assert(aftercross_selectors.size() > 0); assert(this->at(aftercross_selectors.index()) < aftercross_selectors.size());
            assert(  mutation_selectors.size() > 0); assert(this->at(  mutation_selectors.index()) <   mutation_selectors.size());
            assert(           mutations.size() > 0); assert(this->at(           mutations.index()) <            mutations.size());
            assert(        replacements.size() > 0); assert(this->at(        replacements.index()) <         replacements.size());
            assert(        continuators.size() > 0); assert(this->at(        continuators.index()) <         continuators.size());

            // Objective function calls counter
            eoEvalCounterThrowException<EOT> eval(_eval, _max_evals);
            eo::log << eo::xdebug << "Evaluations: " << eval.value() << " / " << _max_evals << std::endl;
            eoPopLoopEval<EOT> pop_eval(eval);

            // Algorithm itself
            eoFastGA<EOT> algo(
                this->crossover_rate(),
                this->crossover_selector(),
                this->crossover(),
                this->aftercross_selector(),
                this->mutation_rate(),
                this->mutation_selector(),
                this->mutation(),
                pop_eval,
                this->replacement(),
                this->continuator(),
                this->offspring_size()
            );

            // Restart wrapper
            // eoAlgoPopReset<EOT> reset_pop(_init, pop_eval);
            // eoGenContinue<EOT> restart_cont(_max_restarts);
            // eoAlgoRestart<EOT> restart(eval, algo, restart_cont, reset_pop);

            try {
                // restart(pop);
                algo(pop);
            } catch(eoMaxEvalException e) {
#ifndef NDEBUG
                eo::log << eo::debug << "Reached maximum evaluations: " << eval.getValue() << " / " << _max_evals << std::endl;
#endif
                // In case some solutions were not evaluated when max eval occured.
                // FIXME can this even be considered legal?
                // eoPopLoopEval<EOT> pop_last_eval(_eval);
                // pop_last_eval(pop,pop);
            }
        }

        /** Return an approximate name of the selected algorithm.
         */
        std::string name()
        {
            std::ostringstream name;
            name << "crossover_rates: "     << this->at(     crossover_rates.index()) << " (" << this->     crossover_rate()             << ") + ";
            name << "crossover_selectors: " << this->at( crossover_selectors.index()) << " (" << this-> crossover_selector().className() << ") + ";
            name << "aftercross_selector: " << this->at(aftercross_selectors.index()) << " (" << this->aftercross_selector().className() << ") + ";
            name << "crossovers: "          << this->at(          crossovers.index()) << " (" << this->          crossover().className() << ") + ";
            name << "mutation_rates: "      << this->at(      mutation_rates.index()) << " (" << this->      mutation_rate()             << ") + ";
            name << "mutation_selectors: "  << this->at(  mutation_selectors.index()) << " (" << this->  mutation_selector().className() << ") + ";
            name << "mutations: "           << this->at(           mutations.index()) << " (" << this->           mutation().className() << ") + ";
            name << "replacements: "        << this->at(        replacements.index()) << " (" << this->        replacement().className() << ") + ";
            name << "continuators: "        << this->at(        continuators.index()) << " (" << this->        continuator().className() << ") + ";
            name << "offspring_sizes: "     << this->at(     offspring_sizes.index()) << " (" << this->     offspring_size()             << ")";
            return name.str();
        }

    protected:
        eoEvalFunc<EOT>& _eval;
        eoInit<EOT>& _init;
        const size_t _max_evals;
        const size_t _max_restarts;

    public:
        eoContinue<EOT>& continuator()
        {
            assert(this->at(continuators.index()) < continuators.size());
            return continuators.instantiate(this->at(continuators.index()));
        }

        double& crossover_rate()
        {
            return crossover_rates.instantiate(this->at(crossover_rates.index()));
        }

        eoQuadOp<EOT>& crossover()
        {
            assert(this->at(crossovers.index()) < crossovers.size());
            return crossovers.instantiate(this->at(crossovers.index()));
        }

        double& mutation_rate()
        {
            return mutation_rates.instantiate(this->at(mutation_rates.index()));
        }

        eoMonOp<EOT>& mutation()
        {
            assert(this->at(mutations.index()) < mutations.size());
            return mutations.instantiate(this->at(mutations.index()));
        }

        eoSelectOne<EOT>& crossover_selector()
        {
            assert(this->at(crossover_selectors.index()) < crossover_selectors.size());
            return crossover_selectors.instantiate(this->at(crossover_selectors.index()));
        }

        eoSelectOne<EOT>& aftercross_selector()
        {
            assert(this->at(aftercross_selectors.index()) < aftercross_selectors.size());
            return aftercross_selectors.instantiate(this->at(aftercross_selectors.index()));
        }

        eoSelectOne<EOT>& mutation_selector()
        {
            assert(this->at(mutation_selectors.index()) < mutation_selectors.size());
            return mutation_selectors.instantiate(this->at(mutation_selectors.index()));
        }

        size_t& offspring_size()
        {
            return offspring_sizes.instantiate(this->at(offspring_sizes.index()));
        }

        eoReplacement<EOT>& replacement()
        {
            assert(this->at(replacements.index()) < replacements.size());
            return replacements.instantiate(this->at(replacements.index()));
        }

};

#endif // _eoAlgoFoundryFastGA_H_
