#include <stdint.h>
#include <iostream>     
#include <fstream> 
#include <cstdlib>
#include <ctime>
#include <chrono>
#include <ratio>
#include "species.h"
#include "controller.h"
#include <sys/stat.h>
#include <vector>
#include <math.h>
#include <string>
#include <stdlib.h>

using namespace std;
using namespace std::chrono;

typedef int64_t sll;


int main(int argc, char** argv)
{
    
  if((argc != 5) && (argc != 9))
  {
    cout << endl;
    cout << "Falsche Parameteranzahl! Aufruf mit:" << endl;
    cout << endl;
    cout << "-- Grid: ------------------------------------------------------------------------------------------------------" << endl; 
    cout << "          1             2    3 (10³)  4       5     6                         7     8" << endl;
    cout << "./run     Ausgabeordner seed mig_prop websize steps numberOfMutationsPerPatch nGrid lKernel" << endl;
    cout << endl;
    cout << "oder:" << endl;
    cout << endl;
    cout << "-- Speicherstand laden: ---------------------------------------------------------------------------------------" << endl; 
    cout << "          1             2             3     4" << endl;
    cout << "./run     Ausgabeordner Eingabeordner steps numberOfMutationsPerPatch" << endl;
    cout << endl;
    return 0;
  }

  high_resolution_clock::time_point start_time = high_resolution_clock::now();
  high_resolution_clock::time_point timer;
  duration<double> time_span;
  duration<double> start_time_span;
  
  bool load = (argc == 5);              //Prüft, ob ein altes Programm geladen wurde
  
  //Erstellen des Controllers
  Controller* ctrl = new Controller();

  char* path = argv[1];
  
  char* old_path;
    
  string save_dir;
  string par_out;  
  string fin_out;  
  
  sll seed = 0;
  double migration_proportion = 0.0;
  int websize = 0.0;
  int steps = 0;
  double nompp = 0;
  
  int nGrid = 0;
  int lKernel = 0;
  
  sll runtime = 0;
  sll new_runtime = 0;
    
  sll stepsize = 0;
  
  if(load)
  {
    
    old_path = argv[2];    
    
    save_dir = old_path + string("/save");
    
    struct stat statbuf;
    
    //Prüfen, ob der Eingabeordner existiert
    if (stat(old_path, &statbuf) == -1)
    {
      cout << "Kein Eingabeordner namens '" << old_path << "'!" << endl;
      cout << "Programm kann nicht geladen werden!" << endl;
      return 0;
    }
    else
    {
      if (!S_ISDIR(statbuf.st_mode))
      {
        cout << "'" << old_path << "' existiert, ist aber kein Ordner!" << endl;
        cout << "Programm kann nicht geladen werden!" << endl;
        return 0;
      }
    }
    
    //Prüfen, ob im Eingabeordner ein Ordner 'save' existiert
    if (stat(save_dir.c_str(), &statbuf) == -1)
    {
      cout << "Kein Save-Ordner unter '" << save_dir << "'!" << endl;
      cout << "Programm kann nicht geladen werden!" << endl;
      return 0;
    }
    else
    {
      if (!S_ISDIR(statbuf.st_mode))
      {
        cout << "'" << save_dir << "' existiert, aber 'save' ist kein Ordner!" << endl;
        cout << "Programm kann nicht geladen werden!" << endl;
        return 0;
      }
    }
    
        
    if(!ctrl->load_controller(path, save_dir))
    {
      cout << "Programm kann nicht geladen werden!" << endl;
      return 0;
    }
    
    
    migration_proportion = ctrl->get_migration_proportion();
    websize = ctrl->get_websize();
    steps = atoi(argv[3]);
    nompp = atof(argv[4]);    
    nGrid = ctrl->get_n();
    lKernel = ctrl->get_l();
    
  }
  else
  {
    seed = strtoul(argv[2], NULL, 10);
    migration_proportion = atof(argv[3])/1e3;
    websize = atoi(argv[4]);
    steps = atoi(argv[5]);
    nompp = atof(argv[6]);

    
    nGrid = atoi(argv[7]);
    lKernel = atoi(argv[8]);
  
    //Initialisieren des Controllers
    ctrl->set_websize(websize);                 //muss vor init gemacht werden!
    ctrl->init(path ,seed, nGrid, lKernel);
    ctrl->set_migration_proportion(migration_proportion);

    
  }
  
  //Berechnen der Laufzeit und der stepsize  
  new_runtime = round(nompp*nGrid*nGrid*(1.0 + migration_proportion));
  runtime = new_runtime;
  if(load)
    runtime += ctrl->get_ttilde();
    
  if(steps < 1)
    stepsize = -1;
  else
  {
    if(steps < runtime)
      stepsize = new_runtime/steps;
    else
      stepsize = 1;
  }   
  
  
  save_dir = path + string("/save");  

  //Ausgabe der genutzten Parameter
  cout << endl;
  cout << "=========================================================" << endl;
  if(load)
  {
    cout << "Programmfortsetzung mit Parametern:" << endl;
    cout << "old_path = " << old_path << endl;
  }
  else
    cout << "Programmstart mit Parametern:" << endl;
  
  cout << "path = " << path;
    
  //Erstellen des Ordners: Fehlermeldung, falls Ordner schon existiert
  const int dir_err = mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
  if(-1 == dir_err)
    cout << " -> Ordner existiert bereits!";
            
  //Erstellen des Save-Ordners: Fehlermeldung, falls Ordner schon existiert
  const int save_dir_err = mkdir(save_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
  if(-1 == save_dir_err)
    cout << " -> Unterordner existiert bereits!";
    
  cout << endl;
  if(load)
    cout << "seed aus alter Simulation" << endl;
  else
    cout << "seed = " << seed << endl;
  cout << "dispersal rate = " << migration_proportion << endl;
  cout << "websize = " << websize << endl;
  cout << "steps = " << steps << endl;
  cout << "number of speciations per patch = " << nompp << endl;
  cout << "runtime = " << runtime << endl;
  cout << "stepsize = " << stepsize << endl;
  cout << "============" << endl;
  cout << endl;
  cout << "nodes = " << nGrid*nGrid << endl;
  cout << "nGrid = " << abs(-nGrid) << endl;
  cout << "lKernel = " << lKernel << endl;
  if(nGrid < 0)
    cout << "open";
  else
    cout << "periodic";
  cout << " boundary conditions" << endl;
  cout << "============" << endl;
  cout << endl;
  
  
  
  //Ausgabe der Übergabeparameter
  par_out = path + string("/hm_par.out");
    
  ofstream parWriter;
  parWriter.open(par_out.c_str(), ios::out | ios::app);
  if (parWriter.is_open())
  {

    parWriter << "=================================" << endl;
    if(load)
      parWriter << "old_path = " << old_path << endl;  
    parWriter << "path = " << path;
      
    parWriter << endl;
    if(load)
      parWriter << "seed aus alter Simulation" << endl;
    else
      parWriter << "seed = " << seed << endl;
    parWriter << "dispersal rate = " << migration_proportion << endl;
    parWriter << "websize = " << websize << endl;
    parWriter << "steps = " << steps << endl;
    parWriter << "number of speciations per patch = " << nompp << endl;
    parWriter << "runtime = " << runtime << endl;
    parWriter << "stepsize = " << stepsize << endl;
    parWriter << "============" << endl;
    parWriter << endl;
    parWriter << "nodes = " << nGrid*nGrid << endl;
    parWriter << "nGrid = " << abs(-nGrid) << endl;
    parWriter << "lKernel = " << lKernel << endl;
    if(nGrid < 0)
      parWriter << "open";
    else
      parWriter << "periodic";
    parWriter << " boundary conditions" << endl;
      
    parWriter.close();
  }
  else
    cout << "Unable to open file " << par_out << endl;
  
  
  
  //möglicherweiße bestehende hm_fin.out überschreiben
  fin_out = path + string("/hm_fin.out");
    
  ofstream finWriter;
  finWriter.open(fin_out.c_str(), ios::out | ios::trunc);
  if (finWriter.is_open())
  {
  	finWriter.close();
  }
  else
    cout << "Unable to open file " << fin_out << endl;
  
  
  
  //Dinge, die immer erledigt werden müssen
  sll curr = 0;
  if(load)
    curr = 0;
   
  
  //Ausgabe der Dauer aller Berechnungen vor dem eigentlichen Programmablauf
  start_time_span = duration_cast<duration<double>>(high_resolution_clock::now() - start_time);
  cout << "Für Initialisierung benötigte Laufzeit: " << start_time_span.count() << " s" << endl;
  cout << endl;
  start_time = high_resolution_clock::now();
  timer = high_resolution_clock::now();
  
  //Programmstart
  
  while(ctrl->get_ttilde() < runtime)
  {
      
    ctrl->find_chosen_and_do_action();
        
    time_span = duration_cast<duration<double>>(high_resolution_clock::now() - timer);
      
    if(time_span.count() > 60.0)
    {
      start_time_span = duration_cast<duration<double>>(high_resolution_clock::now() - start_time);
      if(start_time_span.count() < 3660.0)
      {
        timer = high_resolution_clock::now();
        cout << endl;
        cout << "t = " << ctrl->get_ttilde() << " (" << start_time_span.count() << " s) - Fortschritt: " << round((((double)ctrl->get_ttilde()-runtime+new_runtime)/(new_runtime))*1000)/10. << "%" << endl;
      }
      else if(time_span.count() > 1800.0)
      {
        timer = high_resolution_clock::now();
        cout << endl;
        cout << "t = " << ctrl->get_ttilde() << " (" << start_time_span.count()/3600.0 << " h) - Fortschritt: " << round((((double)ctrl->get_ttilde()-runtime+new_runtime)/(new_runtime))*1000)/10. << "%" << endl;
      }
    }
    
    curr++;
    
    if(curr == stepsize)
    {
      curr = 0;
      ctrl->save_controller(save_dir);
      ctrl->print();
    }
    
  }
  
  if(curr > 0)
    ctrl->save_controller(save_dir);
    
  if(curr > stepsize/10)
    ctrl->print();
    
  ctrl->clean_up();
  
  
  sll counter_all_webs = ctrl->get_counter_all_webs();
  sll counter_inbound = ctrl->get_counter_inbound();
  int living_starters  = nGrid*nGrid*websize - ctrl->get_dead_starters();
  
  delete ctrl;
  
  start_time_span = duration_cast<duration<double>>(high_resolution_clock::now() - start_time);
  
  cout << endl;
  cout << endl;
  cout << "Programmende! Laufzeit: " << start_time_span.count() << " s" << endl;
  cout << "Mittlere Laufzeit pro 1.000.000 Zeitschritte: " << start_time_span.count()/new_runtime*1000000 << " s" << endl;
  cout << "Nach Innen fehlgeschlagene Ausbreitungen:\t" << counter_inbound << " (" << 100.*(double)((double)counter_inbound/(double)runtime*(double)(1.+migration_proportion)/(double)migration_proportion) << "%)" << endl;
  cout << "Davon durch Spezies auf allen Habitaten:\t" << counter_all_webs << " (" << 100.*(double)((double)counter_all_webs/(double)counter_inbound) << "%)" << endl;
  cout << "Verbleibende Startspezies: " << living_starters << "(" << 100.*(double)((double)living_starters/((double)(nGrid*nGrid*websize))) << "%)" << endl;
  cout << endl;
  cout << "=========================================================" << endl;
  cout << endl;


  //Datei erzeugen, die zeigt, dass Programm erfolgreich beendet wurde
  finWriter.open(fin_out.c_str(), ios::out | ios::trunc);
  if (finWriter.is_open())
  {
  	finWriter << "=========================================" << endl;
  	finWriter << "Programmende! Laufzeit: " << start_time_span.count() << " s (" << start_time_span.count()/3600.0 << " h)" << endl;	
  	finWriter << "=========================================" << endl;

  	finWriter.close();
  }
  else
    cout << "Unable to open file " << fin_out << endl;
  

  return 0;
}
