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

#include <cassert>
#include <cstdlib>
#include <cstring>

#include <algorithm>
#include <filesystem>
#include <format>
#include <fstream>
#include <list>
#include <sstream>
#include <stdexcept>
#include <string>
#include <tuple>

#include "aevol.h"

using namespace aevol;

// Helper functions
void print_help(std::filesystem::path prog_path);
auto interpret_cmd_line_args(int argc, char* argv[]) -> std::tuple<std::filesystem::path, std::filesystem::path>;

int main(int argc, char* argv[]) {
  const auto [param_file_name, fasta_file_name] = interpret_cmd_line_args(argc, argv);

  // Print the hash and date of the commit used to compile this executable
  std::cout << std::format("Running aevol version {}\n", aevol::version_string);

  // Populate parameters (default or provided)
  std::unique_ptr<ParamValues> param_values;
  if (not param_file_name.empty()) {
    // Read the parameter file
    param_values = ParamReader::read_file(param_file_name.c_str());
  }
  else {
    std::cout << "WARNING: You are creating a simulation in \"demo\" mode, with default parameters. "
              << "This is not a recommended behaviour for usual work.\n"
              << "Please create a parameter file and provide its path as a command-line argument\n"
              << "for this message to disappear." << std::endl;
    // Use default parameter values except for the environmental target which must not be empty
    param_values = std::make_unique<ParamValues>();
    param_values->std_env_gaussians.emplace_back(1.2,  0.52, 0.12);
    param_values->std_env_gaussians.emplace_back(-1.4, 0.5,  0.07);
    param_values->std_env_gaussians.emplace_back(0.3,  0.8,  0.03);
  }

  // Initialize the experiment manager from the parameter file
  auto experiment_creator = aevol::ExperimentCreator::make_from_params(*param_values);

  // Initialize the population according to the provided parameters or the provided sequences if any
  if (not fasta_file_name.empty()) {
    std::cout << "Reading sequence(s) from fasta file " << fasta_file_name << std::endl;
    try {
      auto chromosomes = read_fasta_file(fasta_file_name);
      experiment_creator->create_clonal_population_from_sequences(chromosomes);
    } catch(std::exception& e) {
      exit_with_usr_msg(e.what());
    }
  }
  else {
    auto prng = std::make_shared<JumpingMT>(param_values->seed);
    experiment_creator->create_random_clonal_population(*prng, param_values->chromosome_initial_length);
  }

  experiment_creator->write_checkpoint();
}

void print_help(std::filesystem::path prog_path) {
  auto prog_name = prog_path.filename().string();

  std::cout << "*****************************************************************************\n"
            << "*                                                                            *\n"
            << "*                        aevol - Artificial Evolution                        *\n"
            << "*                                                                            *\n"
            << "* Aevol is a simulation platform that allows one to let populations of       *\n"
            << "* digital organisms evolve in different conditions and study experimentally  *\n"
            << "* the mechanisms responsible for the structuration of the genome and the     *\n"
            << "* transcriptome.                                                             *\n"
            << "*                                                                            *\n"
            << "******************************************************************************\n"
            << "\n"
            << std::format("{}: create an experiment with setup as specified in PARAM_FILE.\n", prog_name)
            << "\n"
            << std::format("Usage : {} -h or --help\n", prog_name)
            << std::format("   or : {} -V or --version\n", prog_name)
            << std::format("   or : {} [PARAM_FILE] [--fasta SEQ_FILE]\n", prog_name)
            << "\nOptions\n"
            << "  -h, --help\n\tprint this help, then exit\n"
            << "  -V, --version\n\tprint version number, then exit\n"
            << "  --fasta SEQUENCE_FILE\n"
            << "\tload sequences from given file (in fasta format) instead of generating it\n";
}

auto interpret_cmd_line_args(int argc, char* argv[]) -> std::tuple<std::filesystem::path, std::filesystem::path> {
  // Command-line option variables
  auto param_file_name = std::filesystem::path{};
  auto fasta_file_name = std::filesystem::path{};

  // Define allowed options
  const char* options_list = "hV";
  int option_index = 0;
  enum long_opt_codes {
    FASTA = 1000
  };
  static struct option long_options_list[] = {
      {"help",       no_argument,       nullptr, 'h'},
      {"version",    no_argument,       nullptr, 'V'},
      // long-only options
      {"fasta",      required_argument, nullptr, FASTA},
      {0, 0, 0, 0}
  };

  // Get actual values of the CLI options
  int option;
  while ((option = getopt_long(argc, argv, options_list, long_options_list, &option_index)) != -1) {
    switch (option) {
      case 'h' : {
        print_help(argv[0]);
        exit(EXIT_SUCCESS);
      }
      case 'V' : {
        print_aevol_version();
        exit(EXIT_SUCCESS);
      }
      // Handle long-only options
      case FASTA: {
        fasta_file_name = optarg;
        break;
      }
      default : {
        // An error message is printed in getopt_long, we just need to exit
        exit(EXIT_FAILURE);
      }
    }
  }

  // Handle positional arguments
  if (optind < argc) {
    param_file_name = argv[optind];
  }

  return std::make_tuple(param_file_name, fasta_file_name);
}
