#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <mpi.h>
#include "Main.hpp"
#include "GSATThreadMPI.hpp"
#include "color.h"

extern int world_rank;
extern int world_size;

/* --- Public --- */

GSATThreadMPI::GSATThreadMPI(int _nbThread, int argc, char** argv) {
	nbThread = _nbThread;
	gsat = std::vector<GSAT*>(_nbThread);
	threads = std::vector<std::thread>(_nbThread);
	for(int i = 0; i < _nbThread; i++) {
		gsat[i] = new GSAT();
		gsat[i]->setParameters(argc,argv);
  		gsat[i]->initialize();
	}
	srand(getpid());
}

GSATThreadMPI::~GSATThreadMPI() {
	for(int i = 0; i < nbThread; i++) {
		delete gsat[i];
	}
}

bool GSATThreadMPI::solve() {
	threads.clear();
	this->end(false);
	find = false;
	//Lance thread
	for(int i = 0; i < nbThread; i++) {
		threads[i] = std::thread(&GSATThreadMPI::solverThread, this, i);
	}
	//Lance thread de synchronisation MPI
	mpiSync = std::thread(&GSATThreadMPI::mpiWait, this, this);
	//Attend resultat
	for(int i = 0; i < nbThread; i++){
		threads[i].join();
	}
	mpiSync.join();
	//Si c'est le proc qui à trouvé
	if(find) {
		this->mpiSendResult(DISPLAY_RANK);
	}
  	return find;
}

void GSATThreadMPI::end() {
	this->end(true);
}

void GSATThreadMPI::end(bool endOrNot) {
	bEnd = endOrNot;
}

void GSATThreadMPI::printResult() {
	printf(GREEN);
	printf("s %s\n",find?"SATISFIABLE":"NOT FOUND");
	printf(YELLOW);
    printf("c real time : %.4f seconds\n", result.calcTime);
    printf("c [pid:%6d][process:%2d][thread:%2d][iteration:%4d][fill:%d][heuristic:%d]Satisfied clauses (begin: %d)(end:%d)\n",
		result.pid,
		result.rank,
		result.threadId,
		result.nbIteration,
		result.heuristicFill,
		result.heuristicSolve,
		result.nbSatisfiedClausesFill,
		result.nbSatisfiedClausesSolve);
    printf(RESET);
    fflush(stdout);
}

void GSATThreadMPI::setResult(bool isFind, int pid, int rank, int thread, double time, unsigned int nbIte, unsigned int heuriFill, unsigned int heuriSolve, unsigned int satisfFill, unsigned int satisfSolve) {
	find = isFind;
	result.pid = pid;
	result.rank = rank;
	result.threadId = thread;
	result.calcTime = time;
	result.nbIteration = nbIte;
	result.heuristicFill = heuriFill;
	result.heuristicSolve = heuriSolve;
	result.nbSatisfiedClausesFill = satisfFill;
	result.nbSatisfiedClausesSolve = satisfSolve;
}

/* --- Private --- */

void GSATThreadMPI::solverThread(int id) {
	//Resolution
	double startTime = gsat[id]->realTime();
	bool solve = false;
	int cpt = 0;
	while(!bEnd && !solve){
		//Pour eviter que les processeurs effectue tous les meme calculs
        int fill = rand() % 4 + 2;
        int h = rand() % 7;
        solve = gsat[id]->start(fill, h);
        if(solve) {
        	printf(CYAN);
        }
		printf("c [pid:%6d][process:%2d][thread:%2d][iteration:%4d][fill:%d][heuristic:%d]Satisfied clauses (begin: %d)(end:%d)\n",
			getpid(),
			world_rank,
			id,
		   	gsat[id]->getNbIterations(),
		   	gsat[id]->getHeuristicFill(),
		   	gsat[id]->getHeuristicSolve(),
		   	gsat[id]->getNbSatisfiedClausesFill(),
		   	gsat[id]->getNbSatisfiedClausesSolve());
		if(solve) {
			printf(RESET);
		}
		cpt++;
	}
	//Si 1er arreter
	if(!bEnd && solve) {
		this->mpiNotify(world_rank);
		this->end(true);
		find = true;
		this->setResult(
			true,
			getpid(), 
			world_rank, 
			id, gsat[id]->realTime() - startTime, 
			gsat[id]->getNbIterations(),
			gsat[id]->getHeuristicFill(),
			gsat[id]->getHeuristicSolve(),
			gsat[id]->getNbSatisfiedClausesFill(),
			gsat[id]->getNbSatisfiedClausesSolve()
		);
	}
}

void GSATThreadMPI::mpiWait(GSATThreadMPI* gsat) {
    int buff;
    MPI_Recv(&buff, 1, MPI_INT, MPI_ANY_SOURCE, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
    if(buff != world_rank) {
        gsat->end();
    }
}

void GSATThreadMPI::mpiNotify(int rank) {
	MPI_Request request;
    for(int i = 0; i < world_size; i++) {
        MPI_Isend(&rank, 1, MPI_INT, i, 0, MPI_COMM_WORLD, &request);
    }
}

void GSATThreadMPI::mpiSendResult(int rankTo) {
	MPI_Request request;
	//Envoi des infos au rank determiné
	MPI_Isend(&result.pid, 1, MPI_INT, rankTo, 0, MPI_COMM_WORLD, &request);
	MPI_Isend(&result.rank, 1, MPI_INT, rankTo, 0, MPI_COMM_WORLD, &request);
	MPI_Isend(&result.threadId, 1, MPI_INT, rankTo, 0, MPI_COMM_WORLD, &request);
	MPI_Isend(&result.calcTime, 1, MPI_DOUBLE, rankTo, 0, MPI_COMM_WORLD, &request);
	MPI_Isend(&result.nbIteration, 1, MPI_UNSIGNED, rankTo, 0, MPI_COMM_WORLD, &request);
	MPI_Isend(&result.heuristicFill, 1, MPI_UNSIGNED, rankTo, 0, MPI_COMM_WORLD, &request);
	MPI_Isend(&result.heuristicSolve, 1, MPI_UNSIGNED, rankTo, 0, MPI_COMM_WORLD, &request);
	MPI_Isend(&result.nbSatisfiedClausesFill, 1, MPI_UNSIGNED, rankTo, 0, MPI_COMM_WORLD, &request);
	MPI_Isend(&result.nbSatisfiedClausesSolve, 1, MPI_UNSIGNED, rankTo, 0, MPI_COMM_WORLD, &request);
}