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

#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) myerr("error in mkdtemp()");
    return string(s);
}


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


/**
 * Like system() but for strings and with error control.
 */
void mysystem (string command) {
    int r1 = system(command.c_str());
    if (r1 == -1) myerr("error at system().");
}


/**
 * 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" : i2s(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) myerr("error in setrlimit().");
}


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

    set_rlimit(     10 , 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) myerr("error in setrlimit().");
}



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) myerrx("error in waitpid().");
            if (r == pids[i]) {
                cerr << "death of " << i << endl;
                alives[i] = false;
}   }   }   }




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

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



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

    cerr << "info: loading game" << endl;
    Board b0(is, true);
    int nb_players = b0.nb_players();
    int nb_rounds = b0.nb_rounds();
    cerr << "info: loaded game" << endl;

    cerr << "info: setting alarm" << endl;
    signal(SIGALRM, alarm_handler);
    alarm(nb_players * cpu_time_limit * 2);

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

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

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

    cerr << "info: players start" << endl;
    for (int player = 0; player < nb_players; ++player) {
        string name = names[player];
        cerr << "info: preparing player " << player << " as " << name << endl;
        b0.names_[player] = name;
        string brd_pipe = tmp + "/" + i2s(player) + ".brd";
        string act_pipe = tmp + "/" + i2s(player) + ".act";
        pid_t pid = fork();
        if (pid < 0) {
            myerr("error in fork().");
        } 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("-p");
            argv[c++] = strdup(i2s(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) myerr("error in execvp(). Maybe player does not exist?");
            _exit(0);
        } else {
            // parent
            pids.push_back(pid);
        }
    }
    cerr << "info: players end" << endl;


    cerr << "info: opening pipes start" << endl;
    for (int player = 0; player < nb_players; ++player) {
        cerr << "info: opening pipes " << player << endl;
        string brd_pipe = tmp + "/" + i2s(player) + ".brd";
        string act_pipe = tmp + "/" + i2s(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_preamble(os);
    b0.print(os);
    Board b1 = b0;
    for (int round = 1; round <= nb_rounds; ++round) {
        vector<Action> actions(nb_players);
        cerr << "info: start round " << round-1 << endl;
        os << "actions" << endl;

        for (int player = 0; player < nb_players; ++player) {
            cerr << "info:     start player " << player << " (" << pids[player] << ")" << endl;
            os << 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]);
                b1.status_[player] = max(0.0, min(1.0, cpu / cpu_time_limit));
                if (b1.status(player) >= 1.0) throw 1;

                *brds[player] << "go" << endl;
                b1.print_preamble(*brds[player]);
                b1.print(*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;

                actions[player] = a;

                a.print(os);

            } catch (int dead) {
                cerr << "info:     dead player " << player << endl;
                alives[player] = false;
                b1.status_[player] = -1;
                os << -1 << endl;
            }

            cerr << "info:     end player " << player << endl;
        }

        Action actions_done;
        Board b2 = b1.next(actions, actions_done);
        os << "movements" << endl;
        actions_done.print(os);
        b2.print(os);
        b1 = b2;
        cerr << "info: end round " << round << endl;
    }


    cerr << "info: writing good result file" << endl;
    for (int player = 0; player < nb_players; ++player) {
        rs << b1.names_[player] << " " << b1.status(player) << " " << b1.score(player) << endl;
    }

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


void SecGame::run_child (int num, string name, string brd_pipe, string act_pipe) {

    // 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());

    // redirect stdout to /dev/null
    cout.rdbuf(ofstream("/dev/null").rdbuf());

    // redirect stderr to /dev/null
    // cerr.rdbuf(ofstream("/dev/null").rdbuf());

    // set random seed of this child
    srand(time(0) + 42583741 * getpid() + 36734281 * num);

    // main loop
    string s;
    while (brd_stream >> s) {
        assert(s == "go");

        //cerr << "begin child" << endl;

        Board b(brd_stream, true);

        Action a;
        player->reset(num, b, a);

        player->play();

        Action(*player).print(act_stream);

        //cerr << "end child" << endl;

    }

}


