// ****************************************************************************
//
//          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 "ScoreFunction.h"

#include "utility.h"

namespace aevol {

namespace align {

auto ScoreFunction::make_from_checkpoint(std::ifstream& is, mxifstream& mxis) -> std::unique_ptr<ScoreFunction> {
  auto obj = std::make_unique<ScoreFunction>();

  obj->load_from_checkpoint(is, mxis);

  return obj;
}

auto ScoreFunction::make_linear_function(score_t min, score_t max) -> std::unique_ptr<ScoreFunction> {
  auto obj = std::make_unique<ScoreFunction>();

  obj->function_shape_ = FunctionShape::LINEAR;
  obj->linear_fn_params_ = {{min, max}};
  obj->score_function_ = make_linear_lambda(min, max);

  return obj;
}

auto ScoreFunction::make_sigmoid_function(score_t mean, double lambda) -> std::unique_ptr<ScoreFunction> {
  auto obj = std::make_unique<ScoreFunction>();

  obj->function_shape_ = FunctionShape::SIGMOID;
  obj->sigmoid_fn_params_ = {{mean, lambda}};
  obj->score_function_ = make_sigmoid_lambda(mean, lambda);

  return obj;
}

void ScoreFunction::write_to_checkpoint(std::ofstream& os, mxofstream&) const {
  os << "FunctionShape: " << to_string(function_shape_) << ' ';
  switch (function_shape_) {
    case FunctionShape::LINEAR: {
      os << std::get<0>(linear_fn_params_.value()) << ' ' << std::get<1>(linear_fn_params_.value()) << '\n';
      break;
    }
    case FunctionShape::SIGMOID: {
      os << std::get<0>(sigmoid_fn_params_.value()) << ' ' << std::get<1>(sigmoid_fn_params_.value()) << '\n';
      break;
    }
    default:
      exit_with_dev_msg("unexpectedly reached switch default label", __FILE__, __LINE__);
      exit(EXIT_FAILURE);
  }
}

void ScoreFunction::load_from_checkpoint(std::ifstream& is, mxifstream&) {
  get_expected_or_throw(is, "FunctionShape:", function_shape_);
  switch (function_shape_) {
    case FunctionShape::LINEAR: {
      auto p1 = std::tuple_element_t<0, decltype(linear_fn_params_)::value_type>{};
      auto p2 = std::tuple_element_t<1, decltype(linear_fn_params_)::value_type>{};
      is >> p1 >> p2;
      linear_fn_params_ = {{p1, p2}};
      score_function_ = make_linear_lambda(p1, p2);
      break;
    }
    case FunctionShape::SIGMOID: {
      auto p1 = std::tuple_element_t<0, decltype(linear_fn_params_)::value_type>{};
      auto p2 = std::tuple_element_t<1, decltype(linear_fn_params_)::value_type>{};
      is >> p1 >> p2;
      sigmoid_fn_params_ = {{p1, p2}};
      score_function_ = make_sigmoid_lambda(p1, p2);
      break;
    }
    default:
      exit_with_dev_msg("unexpectedly reached switch default label", __FILE__, __LINE__);
      exit(EXIT_FAILURE);
  }
}

auto ScoreFunction::make_linear_lambda(score_t min, score_t max) -> std::function<score_t (JumpingMT&)> {
  return [min, max](JumpingMT& prng){
    return static_cast<score_t>(ceil(min + prng.random() * (max - min)));
  };
}

auto ScoreFunction::make_sigmoid_lambda(score_t mean, double lambda) -> std::function<score_t (JumpingMT&)> {
  return [mean, lambda](JumpingMT& prng){
    // We want the probability of rearrangement for an alignment of score <score> to be:
    // prob = 1 / (1 + exp(-(score-mean)/lambda))
    // The score needed for a rearrangement to take place with a given random drawing is hence:
    // needed_score = ceil(-lambda * log(1/rand - 1) + mean)
    return static_cast<score_t>(std::max(ceil(-lambda * log(1 / prng.random() - 1) + mean), 0.));
  };
}

}  // namespace align

}  // namespace aevol
