// ****************************************************************************
//
//          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/>.
//
// ****************************************************************************

#include "Selection.h"

namespace aevol {

// NB: functions global_selection and local_selection are defined in selection-type specific files

auto collect_fitness_values_global(const Population& population) -> std::tuple<std::vector<double>, double> {
  auto fitnesses = std::vector<double>(population.size());
  auto sum_fitnesses = 0.0;

  for (auto indiv_id = indiv_id_type{0}; indiv_id < population.size(); ++indiv_id) {
    fitnesses[indiv_id] = population[indiv_id].fitness();
    sum_fitnesses += fitnesses[indiv_id];
  }

  return {fitnesses, sum_fitnesses};
}

auto collect_fitness_values_local(const Population& population, const Grid& grid, indiv_id_type indiv_id)
    -> std::tuple<std::vector<double>, double> {
  auto selection_patch_size = exp_setup->selection().selection_patch_size();
  auto neighborhood_size = selection_patch_size * selection_patch_size;
  auto fitnesses         = std::vector<double>(neighborhood_size);
  auto sum_fitnesses     = 0.0;

  auto count = size_t{0};
  for (auto i = -(selection_patch_size / 2); i <= (selection_patch_size / 2); i++) {
    for (auto j = -(selection_patch_size / 2); j <= (selection_patch_size / 2); j++) {
      fitnesses[count] = population[grid.apply_offset(indiv_id, i, j)].fitness();
      sum_fitnesses += fitnesses[count];
      ++count;
    }
  }

  return {fitnesses, sum_fitnesses};
}

auto compute_reproduction_probabilities(size_t patch_size, std::vector<double> fitnesses, double sum_fitnesses)
    -> std::vector<double> {
  auto probs = std::vector<double>(patch_size);
  if (sum_fitnesses == 0.) {
    std::ranges::fill(probs, 1. / probs.size());
  } else {
    for (auto indiv_id = indiv_id_type{0}; indiv_id < patch_size; ++indiv_id) {
      probs[indiv_id] = fitnesses[indiv_id] / sum_fitnesses;
    }
  }

  return probs;
}

auto local_index_to_offset(indiv_id_type local_index, size_t patch_size) -> std::array<grid_xy_difference_type, 2> {
  return {grid_xy_difference_type((local_index / patch_size) - patch_size / 2),
          grid_xy_difference_type((local_index % patch_size) - patch_size / 2)};
}

auto to_global_index(indiv_id_type reference_index, const Grid& grid, indiv_id_type local_index, size_t patch_size)
    -> indiv_id_type {
  auto [x_offset, y_offset] = local_index_to_offset(local_index, patch_size);
  return grid.apply_offset(reference_index, x_offset, y_offset);
}

}  // namespace aevol
