//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Job/MinimizerItem.h
//! @brief     Defines MinimizerItem class
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#ifndef BORNAGAIN_GUI_MODEL_JOB_MINIMIZERITEM_H
#define BORNAGAIN_GUI_MODEL_JOB_MINIMIZERITEM_H

#include "GUI/Model/Data/ComboProperty.h"

class IMinimizer;
class ObjectiveMetric;

enum class MinimizerType { Minuit2, GSLMultiMin, GSLLMA, GSLSimAn, Genetic };

//! Returns a string representation of the minimizer type
QString minimizerTypeToName(MinimizerType type);

//! Base class to store minimizer settings.

class MinimizerItem {
public:
    virtual ~MinimizerItem() = default;

    virtual std::unique_ptr<IMinimizer> createMinimizer() const = 0;

    virtual void writeTo(QXmlStreamWriter* w) const = 0;
    virtual void readFrom(QXmlStreamReader* r) = 0;
};

//! Holds collection of minimizers and allows switching between them.

class GSLLMAMinimizerItem;
class GSLMultiMinimizerItem;
class GeneticMinimizerItem;
class MinuitMinimizerItem;
class SimAnMinimizerItem;

class MinimizerContainerItem : public MinimizerItem {
public:
    MinimizerContainerItem();
    ~MinimizerContainerItem() override;

    MinuitMinimizerItem* minimizerItemMinuit() const;
    GSLMultiMinimizerItem* minimizerItemGSLMulti() const;
    GeneticMinimizerItem* minimizerItemGenetic() const;
    SimAnMinimizerItem* minimizerItemSimAn() const;
    GSLLMAMinimizerItem* minimizerItemGSLLMA() const;

    MinimizerItem* currentMinimizerItem() const;

    QString currentMinimizer() const;
    void setCurrentMinimizer(const QString& name);

    bool algorithmHasMinimizer(const QString& name);
    void setCurrentCommonAlgorithm(const QString& name);
    ComboProperty commonAlgorithmCombo() const;
    void applyAlgorithmToMinimizer(const QString& name);

    // Objective metric to use for estimating distance between simulated and experimental data
    QString currentObjectiveMetric() const;
    void setCurrentObjectiveMetric(const QString& name);
    ComboProperty objectiveMetricCombo() const;

    // Normalization to use for estimating distance between simulated and experimental data
    QString currentNormFunction() const;
    void setCurrentNormFunction(const QString& name);
    ComboProperty normFunctionCombo() const;

    std::unique_ptr<IMinimizer> createMinimizer() const override;
    std::unique_ptr<ObjectiveMetric> createMetric() const;

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

private:
    // hold them all for storing their internal settings.
    std::unique_ptr<MinuitMinimizerItem> m_MinuitMinimizer;
    std::unique_ptr<GSLMultiMinimizerItem> m_GSLMultiMinimizer;
    std::unique_ptr<GeneticMinimizerItem> m_GeneticMinimizer;
    std::unique_ptr<SimAnMinimizerItem> m_SimAnMinimizer;
    std::unique_ptr<GSLLMAMinimizerItem> m_GSLLMAMinimizer;

    ComboProperty m_algorithm;
    ComboProperty m_minimizer;
    ComboProperty m_metric;
    ComboProperty m_norm;
};

//! Holds settings for ROOT Minuit2 minimizer.

class MinuitMinimizerItem : public MinimizerItem {
public:
    MinuitMinimizerItem();
    ~MinuitMinimizerItem() override;

    QString currentAlgorithm() const;
    void setCurrentAlgorithm(const QString& name);

    // Minimization strategy (0-low, 1-medium, 2-high quality)
    int strategy() const;
    void setStrategy(int value);

    // Error definition factor for parameter error calculation
    double errorDefinition() const;
    void setErrorDefinition(double value);

    // Tolerance on the function value at the minimum
    double tolerance() const;
    void setTolerance(double value);

    // Relative floating point arithmetic precision
    double precision() const;
    void setPrecision(double value);

    // Maximum number of function calls
    int maxFuncCalls() const;
    void setMaxFuncCalls(int value);

    std::unique_ptr<IMinimizer> createMinimizer() const override;

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

private:
    ComboProperty m_algorithm;
    int m_strategy;
    double m_errorDef;
    double m_tolerance;
    double m_precision;
    int m_maxFuncCalls;
};

//! Holds settings for GSL MultiMin minimizer family.

class GSLMultiMinimizerItem : public MinimizerItem {
public:
    GSLMultiMinimizerItem();
    ~GSLMultiMinimizerItem() override;

    QString currentAlgorithm() const;
    void setCurrentAlgorithm(const QString& name);

    // Maximum number of iterations
    int maxIterations() const;
    void setMaxIterations(int value);

    std::unique_ptr<IMinimizer> createMinimizer() const override;

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

private:
    ComboProperty m_algorithm;
    int m_maxIterations;
};

//! Holds settings for TMVA/Genetic minimizer.

class GeneticMinimizerItem : public MinimizerItem {
public:
    GeneticMinimizerItem();
    ~GeneticMinimizerItem() override;

    // Tolerance on the function value at the minimum
    double tolerance() const;
    void setTolerance(double value);

    // Maximum number of iterations
    int maxIterations() const;
    void setMaxIterations(int value);

    // Population size
    int populationSize() const;
    void setPopulationSize(int value);

    // Random seed
    int randomSeed() const;
    void setRandomSeed(int value);

    std::unique_ptr<IMinimizer> createMinimizer() const override;

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

private:
    double m_tolerance;
    int m_maxIterations;
    int m_populationSize;
    int m_randomSeed;
};

//! Holds settings for GSL's simulated annealing minimizer.

class SimAnMinimizerItem : public MinimizerItem {
public:
    SimAnMinimizerItem();
    ~SimAnMinimizerItem() override;

    // Number of points to try for each step
    int maxIterations() const;
    void setMaxIterations(int value);

    // Number of iterations at each temperature
    int iterationsAtEachTemp() const;
    void setIterationsAtEachTemp(int value);

    // Max step size used in random walk
    double stepSize() const;
    void setStepSize(double value);

    // Boltzmann k
    double boltzmanK() const;
    void setBoltzmanK(double value);

    // Boltzmann initial temperature
    double boltzmanInitT() const;
    void setBoltzmanInitT(double value);

    // Boltzmann mu
    double boltzmanMu() const;
    void setBoltzmanMu(double value);

    // Boltzmann minimal temperature
    double boltzmanMinT() const;
    void setBoltzmanMinT(double value);

    std::unique_ptr<IMinimizer> createMinimizer() const override;

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

private:
    int m_maxIterations;
    int m_iterationsAtTemp;
    double m_stepSize;
    double m_boltzmann_K;
    double m_boltzmann_T_init;
    double m_boltzmann_Mu;
    double m_boltzmann_T_min;
};

//! Holds settings for GSL's version of Levenberg-Marquardt.

class GSLLMAMinimizerItem : public MinimizerItem {
public:
    GSLLMAMinimizerItem();
    ~GSLLMAMinimizerItem() override;

    // Tolerance on the function value at the minimum
    double tolerance() const;
    void setTolerance(double value);

    // Maximum number of iterations
    int maxIterations() const;
    void setMaxIterations(int value);

    std::unique_ptr<IMinimizer> createMinimizer() const override;

    void writeTo(QXmlStreamWriter* w) const override;
    void readFrom(QXmlStreamReader* r) override;

private:
    double m_tolerance;
    int m_maxIterations;
};

#endif // BORNAGAIN_GUI_MODEL_JOB_MINIMIZERITEM_H
