// ****************************************************************************
//
//          Aevol - An in silico experimental evolution platform
//
// ****************************************************************************
//
// Copyright: See the AUTHORS file provided with the package or <www.aevol.fr>
// Web: http://www.aevol.fr/
// E-mail: See <http://www.aevol.fr/contact/>
// Original Authors : Guillaume Beslon, Carole Knibbe, David Parsons
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// ****************************************************************************

#ifndef AEVOL_STATSORCHESTRATOR_H_
#define AEVOL_STATSORCHESTRATOR_H_

#include <filesystem>
#include <memory>
#include <optional>
#include <vector>

#include "ae_types.h"
#include "stats/AbstractSingleIndividualStatsHandler.h"
#include "stats/MeanStatsHandler.h"
#include "stats/DefaultGlobalStatsData.h"
#include "stats/FitnessStatsData.h"
#include "stats/ChromosomeStatsData.h"
#include "stats/DiploidChromosomeStatsData.h"
#include "stats/DiploidNonCodingStatsData.h"

namespace aevol {

class StatsOrchestrator {
 public:
  StatsOrchestrator()                                    = delete;
  StatsOrchestrator(const StatsOrchestrator&)            = delete;
  StatsOrchestrator(StatsOrchestrator&&)                 = delete;
  StatsOrchestrator& operator=(const StatsOrchestrator&) = delete;
  StatsOrchestrator& operator=(StatsOrchestrator&&)      = delete;
  virtual ~StatsOrchestrator()                           = default;

  StatsOrchestrator(std::optional<time_type> best_indiv_stats_output_frequency,
                    std::optional<time_type> whole_pop_stats_output_frequency);

  auto best_indiv_stats_output_frequency() const { return best_indiv_stats_output_frequency_; }
  auto mean_stats_output_frequency() const { return whole_pop_stats_output_frequency_; }

  void init_stats(time_type time);
  template <std::ranges::input_range InputRange>
  void write_stats(time_type time,
                   size_t nb_indivs,
                   indiv_id_type best_indiv_idx,
                   const Individual& best_indiv,
                   InputRange&& indiv_range);

 protected:
  // Stats handlers
  std::optional<time_type> best_indiv_stats_output_frequency_ = std::nullopt;
  std::vector<std::unique_ptr<AbstractSingleIndividualStatsHandler>> best_indiv_stats_handlers_;
  std::optional<time_type> whole_pop_stats_output_frequency_ = std::nullopt;
  #ifndef DIPLOID
  std::unique_ptr<MeanStatsHandler<DefaultGlobalStatsData,
                                   FitnessStatsData,
                                   ChromosomeStatsData>> mean_stats_handler_;
  #else
  std::unique_ptr<MeanStatsHandler<DefaultGlobalStatsData,
                                   FitnessStatsData,
                                   DiploidChromosomeStatsData,
                                   DiploidNonCodingStatsData>> mean_stats_handler_;
  #endif
};

template <std::ranges::input_range InputRange>
void StatsOrchestrator::write_stats(time_type time,
                                    size_t nb_indivs,
                                    indiv_id_type best_indiv_idx,
                                    const Individual& best_indiv,
                                    InputRange&& indiv_range) {
  if (best_indiv_stats_output_frequency_ and time % best_indiv_stats_output_frequency_.value() == 0) {
    for (const auto& stats_handler: best_indiv_stats_handlers_) {
      stats_handler->write_data(time, nb_indivs, best_indiv_idx, best_indiv);
    }
  }
  if (whole_pop_stats_output_frequency_ and time % whole_pop_stats_output_frequency_.value() == 0) {
    mean_stats_handler_->write_data(time, nb_indivs, best_indiv_idx, indiv_range);
  }
}

}  // namespace aevol

#endif  // AEVOL_STATSORCHESTRATOR_H_
