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

// ============================================================================
//                                   Includes
// ============================================================================
#include <getopt.h>
#include <assert.h>

#include <fstream>
#include <list>
#include <sstream>
#include <string>

#include "aevol.h"
#include "IndivAnalysis.h"

using namespace aevol;

using std::string;

// Helper functions
void interpret_cmd_line_options(int argc, char* argv[]);
void print_help(char* prog_path);
string format_sequence(const IndivAnalysis& indiv);

// Command-line option variables
static char* lineage_file_name = nullptr;
static string output_file_name;
static int32_t begin = 0; //< First generation to analyse
static int32_t end = -1; //< Last generation to analyse
static int32_t period = 1; //< Period of analysis
static int32_t final_indiv_index = -1;

int main(int argc, char* argv[]) {
  interpret_cmd_line_options(argc, argv);

  // =======================
  //  Open the lineage file
  // =======================
  gzFile lineage_file = gzopen(lineage_file_name, "r");
  if (lineage_file == Z_NULL) {
    ExitWithUsrMsg(std::string("Could not read lineage file ") +
                   lineage_file_name + "\n");
  }

  // Open the output file
  std::ofstream output_file(output_file_name);
  if (not output_file) {
    ExitWithUsrMsg(std::string("Could not open file ") +
                   output_file_name + "\n");
  }

  int64_t t0 = 0;
  int64_t t_end = 0;

  gzread(lineage_file, &t0, sizeof(t0));
  gzread(lineage_file, &t_end, sizeof(t_end));
  gzread(lineage_file,&final_indiv_index,sizeof(final_indiv_index));
  auto phenotypic_target_handler = std::make_shared<LegacyPhenotypicTargetHandler>(lineage_file);

  // =============================
  //  Open the experience manager
  // =============================
  exp_manager = new ExpManager();
  exp_manager->load(t0, false);

  // The current version doesn't allow for phenotypic variation nor for
  // different phenotypic targets among the grid
  if (not exp_manager->world()->phenotypic_target_shared()) {
    ExitWithUsrMsg("sorry, ancestor stats has not yet been implemented "
                   "for per grid-cell phenotypic target\n");
  }
  auto phenotypicTargetHandler =
      exp_manager->world()->phenotypic_target_handler();
  if (phenotypicTargetHandler->var_method() != NO_VAR) {
    ExitWithUsrMsg("sorry, ancestor stats has not yet been implemented "
                   "for variable phenotypic targets\n");
  }


  std::shared_ptr <JumpingMT> prng = std::make_shared<JumpingMT>(9695);

  // ==============================
  //  Prepare the initial ancestor
  // ============================
  // ==
  LegacyGridCell* grid_cell = nullptr;
  IndivAnalysis indiv(*(grid_cell->individual()));

  if (begin == 0) {
    output_file << format_sequence(indiv);
  }

  // ==========================================================================
  //  Replay the mutations to get the successive ancestors and analyze them
  // ==========================================================================
  ReplicationReport* rep = nullptr;

  aevol::AeTime::plusplus();
  while ((time() <= t_end) && (((time() < end) || (end == -1)))) {
    rep = new ReplicationReport(lineage_file);

    indiv.set_id(rep->id());

    // 2) Replay replication (create current individual's child)
    GeneticUnit& gen_unit = indiv.genetic_unit_nonconst(0);


    // For each genetic unit, replay the replication (undergo all mutations)
    // TODO <david.parsons@inria.fr> disabled for multiple GUs
    const auto& dnarep = rep->dna_replic_report();


    dnarep.iter_muts([&](const auto& mut) {
      gen_unit.dna()->undergo_this_mutation(*mut);

      if (period == -1) {
        // TODO<dpa> Document meaning of period == -1
        output_file << format_sequence(indiv);
      }
    });

    if (period != -1) {
      if ((time() >= begin) && ((time() < end) || (end == -1)) &&
          (((time() - begin) % period) == 0)) {
        output_file << format_sequence(indiv);
      }
    }

    delete rep;

    aevol::AeTime::plusplus();
  }

  output_file.close();
  std::cout << "output written in " << output_file_name << std::endl;

  gzclose(lineage_file);
  delete [] lineage_file_name;
  delete exp_manager;
  return EXIT_SUCCESS;
}

void interpret_cmd_line_options(int argc, char* argv[]) {
  const char* short_options = "hVb:e:P:";
  static struct option long_options[] = {
      {"help",        no_argument,       nullptr, 'h'},
      {"version",     no_argument,       nullptr, 'V'},
      {"begin",       required_argument, nullptr, 'b'},
      {"end",         required_argument, nullptr, 'e'},
      {"period",      required_argument, nullptr, 'P'},
      {0, 0, 0, 0}
  };

  int option;
  while ((option = getopt_long(argc, argv, short_options, long_options,
                               nullptr)) != -1) {
    switch (option) {
    case 'h' :
      print_help(argv[0]);
      exit(EXIT_SUCCESS);
    case 'V' :
      PrintAevolVersion();
      exit(EXIT_SUCCESS);
    case 'b' :
      begin = atol(optarg);
      break;
    case 'e' :
      end = atol(optarg);
      break;
    case 'P' :
      period = atol(optarg);
      break;
    default:
      // An error message is printed in getopt_long, we just need to exit
      exit(EXIT_FAILURE);
    }
  }

  // There should be only one remaining arg: the lineage file
  if (optind != argc - 2) {
    ExitWithUsrMsg("please specify a lineage file and an output file");
  }

  assert(period > 0 ||period == -1);

  lineage_file_name = new char[strlen(argv[optind]) + 1];
  strcpy(lineage_file_name, argv[optind]);

  ++optind;
  std::ostringstream ofnstream;
  ofnstream << argv[optind];
  output_file_name = ofnstream.str();
}

void print_help(char* prog_path) {
  // Get the program file-name in prog_name (strip prog_path of the path)
  char* prog_name; // No new, it will point to somewhere inside prog_path
  if ((prog_name = strrchr(prog_path, '/'))) {
    prog_name++;
  }
  else {
    prog_name = prog_path;
  }

  printf("******************************************************************************\n");
  printf("*                                                                            *\n");
  printf("*                        aevol - Artificial Evolution                        *\n");
  printf("*                                                                            *\n");
  printf("* Aevol is a simulation platform that allows one to let populations of       *\n");
  printf("* digital organisms evolve in different conditions and study experimentally  *\n");
  printf("* the mechanisms responsible for the structuration of the genome and the     *\n");
  printf("* transcriptome.                                                             *\n");
  printf("*                                                                            *\n");
  printf("******************************************************************************\n");
  printf("\n");
  printf("%s: extract genomes from the provided lineage.\n",
         prog_name);
  printf("\n");
  printf("Usage : %s -h or --help\n", prog_name);
  printf("   or : %s -V or --version\n", prog_name);
  printf("   or : %s LINEAGE_FILE OUTPUT_FILE [-b TIMESTEP] [-e TIMESTEP] [-P PERIOD]\n",
         prog_name);
  printf("\nOptions\n");
  printf("  -h, --help\n\tprint this help, then exit\n");
  printf("  -V, --version\n\tprint version number, then exit\n");
  printf("  -b, --begin TIMESTEP\n");
  printf("\ttimestep at which to start the extraction\n");
  printf("  -e, --end TIMESTEP\n");
  printf("\ttimestep at which to stop the extraction (not included in output)\n");
  printf("  -P, --period\n");
  printf("\tperiod with which to perform the extraction\n");
}

string format_sequence(const IndivAnalysis& indiv) {
  std::ostringstream str;
  str << ">" << "gen_" << time() << "_id_" << indiv.id() << " [organism=Aevol]" << " [strain=" << indiv.strain_name() << "]" << std::endl;
  str << indiv.genetic_unit_sequence(0) << std::endl;
  return str.str();
}
