#include "SecGame.hh"
#include "Registry.hh"

#include <iomanip>
#include <pwd.h>
#include <dlfcn.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>


using namespace std;



/**
 * Creates a temporal directory and returns its name.
 */
string make_tmp () {
  char s[] = "/tmp/game-XXXXXX";
  char* p = mkdtemp(s);
  if (not p) {
    cerr << "error: problems with in mkdtemp()." << endl;
    exit(EXIT_FAILURE);
  }
  return string(s);
}


/**
 * Creates a fifo with error control.
 */
void mymkfifo (string filename) {
  int r1 = mkfifo(filename.c_str(), O_CREAT);
  if (r1 != 0) {
    cerr << "error: problems with mkfifo()." << endl;
    exit(EXIT_FAILURE);
  }
  int r2 = chmod(filename.c_str(), 0600);      // the 0 before 600 is important, man! it's octal.
  if (r2 == -1) {
    cerr << "error: problems with chmod()." << endl;
    exit(EXIT_FAILURE);
  }
}


/**
 * Like system() but for strings and with error control.
 */
void mysystem (string command) {
  int r1 = system(command.c_str());
  if (r1 == -1) {
    cerr << "error: problems with system()." << endl;
    exit(EXIT_FAILURE);
  }
}


/**
 * Struct with the infor contained in /proc/self/stat.
 * or /proc/pid/stat.
 *
 * do "man proc" for documentation.
 */

struct Stat {
  int pid;
  string comm;
  char state;
  int ppid;
  int pgrp;
  int session;
  int tty_nr;
  int tpgid;
  unsigned flags;
  long unsigned minflt;
  long unsigned cminflt;
  long unsigned majflt;
  long unsigned cmajflt;
  long unsigned utime;
  long unsigned stime;
  long cutime;
  long cstime;
  long priority;
  long nice;
  long num_threads;
  long itrealvalue;
  long long unsigned starttime;
  long unsigned vsize;
  long rss;
  long unsigned rsslim;
  long unsigned startcode;
  long unsigned endcode;
  long unsigned startstack;
  long unsigned kstkesp;
  long unsigned kstkeip;
  long unsigned signal;
  long unsigned blocked;
  long unsigned sigignore;
  long unsigned sigcatch;
  long unsigned wchan;
  long unsigned nswap;
  long unsigned cnswap;
  int exit_signal;
  int processor;
  unsigned rt_priority;
  unsigned policy;
  long long unsigned delayacct_blkio_ticks;
  long unsigned guest_time;
  long unsigned cguest_time;
};


Stat read_proc_stat (int pid)
{
  Stat r;

  string proc = pid == -1 ? "self" : to_string(pid);
  string name = "/proc/" + proc + "/stat";

  ifstream ifs(name.c_str());

  ifs
    >> r.pid
    >> r.comm
    >> r.state
    >> r.ppid
    >> r.pgrp
    >> r.session
    >> r.tty_nr
    >> r.tpgid
    >> r.flags
    >> r.minflt
    >> r.cminflt
    >> r.majflt
    >> r.cmajflt
    >> r.utime
    >> r.stime
    >> r.cutime
    >> r.cstime
    >> r.priority
    >> r.nice
    >> r.num_threads
    >> r.itrealvalue
    >> r.starttime
    >> r.vsize
    >> r.rss
    >> r.rsslim
    >> r.startcode
    >> r.endcode
    >> r.startstack
    >> r.kstkesp
    >> r.kstkeip
    >> r.signal
    >> r.blocked
    >> r.sigignore
    >> r.sigcatch
    >> r.wchan
    >> r.nswap
    >> r.cnswap
    >> r.exit_signal
    >> r.processor
    >> r.rt_priority
    >> r.policy
    >> r.delayacct_blkio_ticks
    >> r.guest_time
    >> r.cguest_time
    ;

  return r;
}

/**
 * Returns the CPU time used by process pid.
 */
double cpu_time (int pid) {
  static double ticks = sysconf(_SC_CLK_TCK);
  Stat stat = read_proc_stat(pid);
  return (stat.utime + stat.stime) / ticks;
}


/**
 * Sets one rlimit.
 */
void set_rlimit (int opt, int lim) {
  struct rlimit rlim;
  rlim.rlim_cur = rlim.rlim_max = opt;
  int r = setrlimit(lim, &rlim);
  if (r != 0) {
    cerr << "error: problems in setrlimit()." << endl;
    exit(EXIT_FAILURE);
  }
}


/**
 * Sets all the rlimits for a player process.
 */
void set_all_rlimits () {
  const int MB = 1024*1024;

  set_rlimit(      8 , RLIMIT_NOFILE);
  set_rlimit(      1 , RLIMIT_NPROC );
  set_rlimit(32 * MB , RLIMIT_FSIZE );
  set_rlimit( 0 * MB , RLIMIT_CORE  );
  set_rlimit(64 * MB , RLIMIT_AS    );
  set_rlimit(64 * MB , RLIMIT_STACK );

  // In the case of RLIMIT_CPU, we set the soft and hard bounds differently
  // so that the soft bound raises SIGXCPU (and the second would rise SIGKILL).
  struct rlimit rlim;
  rlim.rlim_cur = CPU_TIME_LIMIT;
  rlim.rlim_max = CPU_TIME_LIMIT + 1;
  int r = setrlimit(RLIMIT_CPU, &rlim);
  if (r != 0) {
    cerr << "error: problems in setrlimit()." << endl;
    exit(EXIT_FAILURE);
  }
}



void update_alives(vector<bool>& alives, const vector<pid_t>& pids) {
  int status;
  int n = alives.size();

  for (int i = 0; i < n ; ++i) {
    if (alives[i]) {
      pid_t r = waitpid(pids[i], &status, WNOHANG);
      if (r == -1) {
        cerr << "error: problems in waitpid()." << endl;
        exit(EXIT_FAILURE);
      }
      if (r == pids[i]) {
        alives[i] = false;
      }   }   }   }




/**
 * This handler is called when too much time has passed.
 */

void alarm_handler (int) {
  cerr << "error: alarm" << endl;
  exit(EXIT_FAILURE);
}


void SecGame::run_master (const vector<string>& names, istream& is, ostream& os, ostream& rs, int seed) {

  cerr << "info: loading game" << endl;
  Board b0(is);
  cerr << "info: loaded game" << endl;

  b0.srandomize(seed);
  
  cerr << "info: setting alarm" << endl;
  signal(SIGALRM, alarm_handler);
  alarm(4 * CPU_TIME_LIMIT * 2);

  string tmp = make_tmp();
  cerr << "info: tmp = " << tmp << endl;

  vector<pid_t> pids;
  vector<bool> alives(4, true);
  vector<ofstream*> brds;
  vector<ifstream*> acts;

  cerr << "info: pipes start" << endl;
  for (int player = 0; player < 4; ++player) {
    cerr << "info: pipes " << player << endl;
    string brd_pipe = tmp + "/" + to_string(player) + ".brd";
    string act_pipe = tmp + "/" + to_string(player) + ".act";
    mymkfifo(brd_pipe);
    mymkfifo(act_pipe);
  }
  cerr << "info: pipes end" << endl;

  cerr << "info: players start" << endl;
  for (int player = 0; player < 4; ++player) {
    string name = names[player];
    cerr << "info: preparing player " << player << " as " << name << endl;
    b0.names[player] = name;
    string brd_pipe = tmp + "/" + to_string(player) + ".brd";
    string act_pipe = tmp + "/" + to_string(player) + ".act";
    pid_t pid = fork();
    if (pid < 0) {
      cerr << "error: problems in fork()." << endl;
      exit(EXIT_FAILURE);
    } else if (pid == 0) {
      char* argv[20];
      for (int i = 0; i < 20; ++i) argv[i] = 0;
      int c = 0;
      argv[c++] = strdup(("./AI" + name + ".exe").c_str());
      argv[c++] = strdup("-s");
      argv[c++] = strdup(to_string(seed).c_str());           
      argv[c++] = strdup("-p");
      argv[c++] = strdup(to_string(player).c_str());
      argv[c++] = strdup(name.c_str());
      argv[c++] = strdup(brd_pipe.c_str());
      argv[c++] = strdup(act_pipe.c_str());

      int r = execvp(argv[0], argv);
      if (r < 0) {
        cerr << "error: problems in execvp()." << endl;
        exit(EXIT_FAILURE);
      }
      _exit(0);
    } else {
      // parent
      pids.push_back(pid);
    }
  }
  cerr << "info: players end" << endl;


  cerr << "info: opening pipes start" << endl;
  for (int player = 0; player < 4; ++player) {
    cerr << "info: opening pipes " << player << endl;
    string brd_pipe = tmp + "/" + to_string(player) + ".brd";
    string act_pipe = tmp + "/" + to_string(player) + ".act";
    brds.push_back(new ofstream(brd_pipe.c_str()));
    acts.push_back(new ifstream(act_pipe.c_str()));
  }
  cerr << "info: open pipes end" << endl;


  cerr << "info: ignoring sigpipe" << endl;
  signal(SIGPIPE, SIG_IGN);

  b0.print_settings(os, true);
  b0.print_state(os);

  for (int round = 0; round < b0.number_rounds(); ++round) {
    cerr << "info: start round " << round << endl;
    os << "actions_asked" << endl;
    vector<Action> asked(4);
    for (int player = 0; player < 4; ++player) {
      cerr << "info:     start player " << player << " (" << pids[player] << ")" << endl;
      os << endl << player << endl;

      try {

        if (not alives[player]) throw 1;

        update_alives(alives, pids);
        if (not alives[player]) throw 1;

        double cpu = cpu_time(pids[player]);
        b0.statusPriv(player) = max(0.0, min(1.0, cpu / CPU_TIME_LIMIT));
        if (b0.statusPriv(player) >= 1.0) throw 1;

        *brds[player] << "go" << endl;
        b0.print_settings(*brds[player], true);
        b0.print_state(*brds[player]);

        update_alives(alives, pids);
        if (not alives[player]) throw 1;

        Action a(*acts[player]);

        update_alives(alives, pids);
        if (not alives[player]) throw 1;

        asked[player] = a;
        a.print(os);

      } catch (int dead) {
        cerr << "info:     dead player " << player << endl;
        alives[player] = false;
        b0.statusPriv(player) = -1;
        Action().print(os);
      }
      cerr << "info:     end player " << player << endl;
    }

    vector<Action> done(4);
    // cerr << "info:     start next" << endl;
    Board b1 = b0.next(asked, done);
    // cerr << "info:     end next" << endl;

    os << endl << "actions_done" << endl;
    for (int player = 0; player < 4; ++player) {
      os << endl << player << endl;
      done[player].print(os);
    }
    os << endl;
        
    b1.print_state(os);
    b1.srandomize(b0.randomize());    
    b0 = b1;
    cerr << "info: end round " << round << endl;
  }

  cerr << "info: writing good result file" << endl;

  vector<int> max_players;
  int max_score = numeric_limits<int>::min();

  for (int player = 0; player < 4; ++player) {
    cerr << "info: player " << b0.name(player) << " got score " << b0.score(player) << endl;
    if (max_score < b0.score(player)) {
      max_score = b0.score(player);
      max_players = vector<int>(1, player);
    }
    else if (max_score == b0.score(player))
      max_players.push_back(player);
  }
  cerr << "info: player(s)";
  for (int k = 0; k < int(max_players.size()); ++k)
    cerr << " " << b0.name(max_players[k]);
  cerr << " got top score" << endl;

  for (int player = 0; player < 4; ++player)
    cerr << "info: player " << b0.name(player) << " ended with status " << fixed << setprecision(3) << b0.statusPriv(player) << endl;

  for (int player = 0; player < 4; ++player) {
    rs << b0.name(player) << " " << fixed << setprecision(3) << b0.statusPriv(player) << " " << b0.score(player) << endl;
  }

  mysystem("rm -rf " + tmp);
  cerr << "info: game played" << endl;
}


void SecGame::run_child (int num, string name, string brd_pipe, string act_pipe, int seed) {
  
  // set the limits
  set_all_rlimits();

  // load the player
  Player* player = Registry::new_player(name);

  // open the pipes
  ifstream brd_stream(brd_pipe.c_str());
  ofstream act_stream(act_pipe.c_str());

  player->srandomize(seed + num);

  // redirect stdout to /dev/null
  stringstream buffer; //workaround because it seems that Docker can't access /dev/null
  cout.rdbuf(buffer.rdbuf());
  
  // main loop
  string s;
  while (brd_stream >> s) {
    my_assert(s == "go");

    Board b(brd_stream);
    int tmp = player->randomize();
    player->reset(num, b, Action());
    player->srandomize(tmp);
    player->play();

    Action(*player).print(act_stream);
  }   }
