#include "rlib.h"
#include "sudoku.h"
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <pthread.h>
#include "footer.h"

unsigned long long global_m_lock_count = 0;
pthread_mutex_t * global_m_lock = NULL;

#define WITH_MUTEX(src,update_footer) \
    if(global_m_lock == NULL){ \
        global_m_lock = malloc(sizeof(pthread_mutex_t)); \
        pthread_mutex_init(global_m_lock, NULL); \
    } \
    pthread_mutex_lock(global_m_lock);    \
    global_m_lock_count++; \
    if(update_footer) { \
    footer_printf("l:%s:%d (%lld)",__func__,__LINE__,global_m_lock_count); \
    } \
      src \
     if(update_footer) { \
    footer_printf("u:%s:%d (%lld)",__func__,__LINE__,global_m_lock_count); \
    } \
    pthread_mutex_unlock(global_m_lock);
    

typedef struct thread_data_t {
    int puzzle[N][N];
    int solution[N][N];
    pthread_t thread;
    unsigned long long  steps;
    unsigned long long steps_total;
    uint complexity;
    uint result_complexity;
    uint solved_count; 
    uint initial_count_minimum;
    uint initial_count_maximum;
    uint result_initial_count;
    uint id;
    pthread_mutex_t lock;
    bool is_done;
    nsecs_t start;
    nsecs_t finish;
    nsecs_t duration;
} thread_data_t;

thread_data_t * thread_data_create(){
    thread_data_t * data = malloc(sizeof(thread_data_t));
    memset(data,0,sizeof(thread_data_t));
    return data;
}
void thread_data_free(thread_data_t * data){
    
    free(data);
}



int * grid_with_minimal_complexity(thread_data_t * tdata){
    int * grid = grid_new();
    int * grid_game = NULL;
    while(true){
        tdata->result_initial_count = rand_int(tdata->initial_count_minimum,tdata->initial_count_maximum);
        
	grid_set_random_free_cells(grid,tdata->result_initial_count);
        //print_grid(grid,false);
        grid_game = grid_copy(grid);
        //footer_printf("Solving: %ld", tdata->result_initial_count);
        
    	tdata->start = nsecs();
	//tdata->steps = 0;
	tdata->result_complexity = rsolve(grid,&tdata->steps);
	tdata->steps_total += tdata->steps;
	tdata->steps = 0;
	tdata->solved_count++;
        if(tdata->result_complexity == 0){
            //print_grid(grid,true);
			//exit(5);
			//footer_printf("thread %d failed validation",tdata->id);
        }else{
           
		    //footer_printf("thread %d solved: %d",tdata->id, tdata->result_complexity);
        }
	//if(tdata->solution)
	//	free(tdata->solution);
	

	
	WITH_MUTEX({
	memcpy(tdata->solution,grid,N*N*sizeof(int));
	memcpy(tdata->puzzle,grid_game,N*N*sizeof(int));
    
	},false);
        if(tdata->result_complexity >= tdata->complexity){
            break;
        }else{
			free(grid_game);
            grid_reset(grid);
        }
    }
    return grid_game;
}

void * generate_game(void * arg){
	thread_data_t * tdata = (thread_data_t *)arg;
    tick();
	//unsigned int * result_complexity = (int *)calloc(sizeof(int),1);
    //unsigned int * result_initial_count = (int *)calloc(sizeof(int),1);
    //rr_disable_stdout();
   
       int * puzzle =  grid_with_minimal_complexity(tdata);
    
	//if(tdata->puzzle){
		//free(tdata->puzzle);

	//}
    WITH_MUTEX({
    tdata->finish = nsecs();
    tdata->duration = tdata->finish - tdata->start;
    tdata->is_done = true;
    },false);
	return NULL;
}

void thread_data_to_json_object(rjson_t * json ,thread_data_t * data){
	rjson_object_start(json);
	rjson_kv_int(json,"id",data->id);
	rjson_kv_int(json,"solved_count",data->solved_count);
	rjson_kv_number(json,"steps_total",data->steps_total + data->steps);
	rjson_kv_number(json,"steps",data->steps);
	rjson_kv_number(json,"result_initial_count",data->result_initial_count);
	rjson_kv_duration(json,"start",data->start);
	rjson_kv_duration(json,"finish",data->finish);
	rjson_kv_duration(json,"duration",nsecs() - data->start);
	rjson_kv_string(json,"puzzle",grid_to_string(data->puzzle));
	rjson_kv_string(json,"solution",grid_to_string(data->solution));
	rjson_object_close(json);
}
char * thread_data_to_json(thread_data_t * data, int runner_count){
	rjson_t * json = rjson();
	rjson_array_start(json);
	for(int i = 0; i < runner_count; i++){
	thread_data_to_json_object(json,&data[i]);
	}
	rjson_array_close(json);
	
	char * content = strdup(json->content);
	rjson_free(json);
	return content;
	
}

char * thread_data_to_string(thread_data_t * data){
	static char result[4096];
	memset(result,0,sizeof(result));
	sprintf(result,"id:%d\tcomplexity: %u\t total solved: %d \tsteps total: %s\tsteps current: %s\tinitc: %d\ttime: %s",
		data->id,
		data->result_complexity,
		data->solved_count,
		rtempc(rformat_number(data->steps_total + data->steps)),
		rtempc(rformat_number(data->steps)),
		data->result_initial_count,
		format_time(nsecs() - data->start)
	);
	return result;
}

char * runner_status_to_string(thread_data_t * runners, unsigned int runner_count){
	static char result[1024*1024];
	memset(result,0,sizeof(result));
	
	for(uint i = 0; i < runner_count; i++){
		strcat(result,thread_data_to_string(&runners[i]));
	       strcat(result,"\n");	
	}
	return result;

}

typedef struct serve_arguments_t {
	thread_data_t * runners;
	unsigned int runner_count;
	int port;
	nsecs_t time_winner;
	int puzzle_winner[N][N];
	int solution_winner[N][N];
	char start_timestamp[30];
} serve_arguments_t;


serve_arguments_t serve_arguments;

void get_totals(thread_data_t * runners, unsigned int runner_count, ulong * steps_total, ulong * solved_total,nsecs_t * longest_running){
	*steps_total = 0;
	*solved_total = 0;
	*longest_running = 0;
	nsecs_t end_time = nsecs();
	for(unsigned int i = 0; i < runner_count; i++){
		*steps_total += runners[i].steps_total + runners[i].steps;
		*solved_total += runners[i].solved_count;
		nsecs_t duration = runners[i].start ? end_time - runners[i].start : 0;
		if(duration > *longest_running){
			*longest_running = duration;
		}
	}
}

void http_response(rhttp_request_t * r, char * content){
	char headers[strlen(content) + 1000];
	sprintf(headers,"HTTP/1.1 200 OK\r\n"
	"Content-Length:%ld\r\n"
	"Content-Type: text/html\r\n"
	"Connection: close\r\n\r\n%s",
	strlen(content),content);
	rhttp_send_drain(r->c,headers,0);
	close(r->c);
}



int request_handler_json_processes(rhttp_request_t*r){
	if(!strncmp(r->path,"/json/processes",strlen("/json/processes"))){
		WITH_MUTEX({
		char * content = thread_data_to_json(serve_arguments.runners,serve_arguments.runner_count); // runner_status_to_string(serve_arguments.runners,serve_arguments.runner_count);
		http_response(r,content);

		free(content);
		},false);
		return 1;
	}
	return 0;
}

char * statistics_to_json_object(serve_arguments_t * args, rjson_t * json ,thread_data_t * data){
	ulong steps_total = 0;
		ulong solved_total = 0;
		nsecs_t longest_running = 0;
		get_totals(args->runners, args->runner_count, &steps_total, &solved_total, &longest_running);
			
	rjson_object_start(json);
	rjson_kv_string(json,"start",args->start_timestamp);
	rjson_kv_number(json,"steps_total",steps_total);
	rjson_kv_number(json,"solved_total",solved_total);
	rjson_kv_number(json,"steps_per_puzzle",solved_total != 0 ? steps_total / (solved_total + args->runner_count) : 0);
	rjson_kv_string(json,"longest_running",format_time(longest_running));
	rjson_kv_string(json,"time_winner",format_time(args->time_winner));
	rjson_kv_string(json,"puzzle_winner",grid_to_string(args->puzzle_winner));
	rjson_kv_string(json,"solution_winner",grid_to_string(args->solution_winner));
	rjson_object_close(json);
	return json;
}

int request_handler_json_statistics(rhttp_request_t *r ){
	serve_arguments_t args = *(serve_arguments_t *)r->context;
	if(!strncmp(r->path, "/json/statistics",strlen("/json/statistics"))){
		rjson_t * json = rjson();
		statistics_to_json_object(&args,json,args.runners);
		char * content = strdup(json->content);
		rjson_free(json);
		http_response(r,content);
		free(content);
		return 1;
	}
	return 0;
}
int request_handler_root(rhttp_request_t *r){
	serve_arguments_t args = *(serve_arguments_t *)r->context;
	if(!strcmp(r->path,"/")){
	WITH_MUTEX({
		char * content = runner_status_to_string(args.runners,args.runner_count);
		ulong steps_total = 0;
		ulong solved_total = 0;
		nsecs_t longest_running = 0;
		get_totals(args.runners, args.runner_count, &steps_total, &solved_total, &longest_running);
		char html[1024*1024*2] = {0};
		sprintf(html, "<html><meta http-equiv=\"refresh\" content=\"5\"><head>"
				"<body style=\"background-color:black;color:white;\"><pre>"
				"%s"
				"Started: %s\n"
				"\n"
				"Steps total: %s\n"
				"Solved total: %s\n"
				"Steps per puzzle: %s\n"
				"Longest running: %s\n"
				"Generation time hardest puzzle: %s\n"
				"\n"
				"Hardest puzzle:\n"
				"%s\n"
				"Solution:\n"
				"%s\n"
				"</pre>"
				"</body></html>",
				content, 
				args.start_timestamp,
				rtempc(rformat_number(steps_total)),
				rtempc(rformat_number(solved_total)),
				rtempc(rformat_number(solved_total != 0 ? steps_total / (solved_total + args.runner_count) : 0)),
				rtempc(format_time(longest_running)),
				rtempc(format_time(args.time_winner)),
				rtempc(grid_to_string(args.puzzle_winner)), 
				rtempc(grid_to_string(args.solution_winner))
		);
		char response[1024*1024*3];
		memset(response,0,sizeof(response));
		sprintf(response,"HTTP/1.1 200 OK\r\n"
				"Content-Length: %zu\r\n"
				"Content-Type: text/html\r\n"
				"Connection: close:\r\n\r\n",
				strlen(html));
		rhttp_send_drain(r->c, response,0);
		rhttp_send_drain(r->c, html,0);
	},true);
	return 1;
	}
	return 0;
}

int request_handler_empty(rhttp_request_t * r){
	if(!strncmp(r->path,"/empty",strlen("/empty"))){
		int grid[N][N];
		memset(grid,0,N*N*sizeof(int));
		char * content = grid_to_string(grid);
		char response[1024];
	       response[0] = 0;
       	        sprintf(
			response, 
			"HTTP/1.1 200 OK\r\nContent-Length:%zu\r\nConnection: close\r\n\r\n",
			strlen(content)
		);	
		rhttp_send_drain(r->c,response,0);
		rhttp_send_drain(r->c,content,0);
		return 1;
	}
	return 0;
}

int request_handler_404(rhttp_request_t *r){
	char content[] = "HTTP/1.1 404 Document not found\r\nContent-Length:3\r\nConnection:close\r\n\r\n404";
	rhttp_send_drain(r->c,content,0);
	return 1;
}

int request_handler(rhttp_request_t * r){
	rhttp_request_handler_t request_handlers[] ={
		request_handler_root,
		request_handler_empty,
		request_handler_json_processes,
		request_handler_json_statistics,
		rhttp_file_request_handler,
		request_handler_404,
		NULL 
	};
	int i = -1;
	while(request_handlers[++i])
		if(request_handlers[i](r))
			return 1;
	return 0;
}


void * serve_thread(void *arg){
	serve_arguments_t * arguments = (serve_arguments_t *)arg;
	
	rhttp_serve("0.0.0.0",arguments->port,1024,1,1,request_handler,(void *)arguments);
	return NULL;
}

void generate_games(unsigned int game_count, unsigned int timeout, unsigned int complexity){
    pthread_t thread_serve; 

    
    thread_data_t runners[game_count];
    serve_arguments.runners = runners;
    serve_arguments.runner_count = game_count;
    serve_arguments.port = 9999;
    serve_arguments.time_winner = 0;
    strcpy(serve_arguments.start_timestamp,rstrtimestamp());
    memset(serve_arguments.solution_winner,0,sizeof(serve_arguments.solution_winner));
    memset(serve_arguments.puzzle_winner,0,sizeof(serve_arguments.puzzle_winner));
    
    pthread_create(&thread_serve,0,serve_thread,(void *)&serve_arguments);
    //pthread_mutex_t lock;
    //pthread_mutex_init(&lock,NULL);
    for(unsigned int i = 0; i < game_count; i++){
        runners[i].initial_count_maximum = 30;
        runners[i].initial_count_minimum = 1;
        runners[i].complexity = complexity;
        runners[i].is_done = false;
        runners[i].id = i;
	memset(runners[i].solution,0,N*N*sizeof(int));
	memset(runners[i].puzzle,0,N*N*sizeof(int));
        runners[i].solved_count = 0;
	runners[i].duration = 0;
	runners[i].steps = 0;
	runners[i].steps_total = 0;
	//runners[i].lock = lock;
        pthread_create(&runners[i].thread,NULL,generate_game,(void *)(&runners[i]));
    }
    unsigned int highest_complexity = complexity;
    for(unsigned int i = 0; i < timeout; i++){
	sleep(1);
	WITH_MUTEX({
        footer_printf("main");
        //pthread_mutex_lock(&lock);
        for(unsigned int ithread = 0; ithread < game_count; ithread++){
            if(runners[ithread].is_done){
                pthread_join(runners[ithread].thread,NULL);
               		
		if(runners[ithread].result_complexity > highest_complexity){
         	highest_complexity = runners[ithread].result_complexity; 
                  	for(uint j = 0; j < game_count; j++){
                        runners[j].complexity = highest_complexity;
                    
			}

 			printf("\r\n");
			print_grid(runners[ithread].puzzle,true);
			printf("\n");
			print_grid(runners[ithread].solution,false);
               
	
			memcpy(serve_arguments.puzzle_winner,runners[ithread].puzzle,N*N*sizeof(int));
			memcpy(serve_arguments.solution_winner,runners[ithread].solution,N*N*sizeof(int));
			serve_arguments.time_winner = runners[ithread].duration;


               	printf("Thread %d is done (%s)\n",ithread,format_time(runners[ithread].duration));
                	printf("Complexity: %ld\n",runners[ithread].result_complexity);
                	printf("Initial values: %ld\n",runners[ithread].result_initial_count);
                
                }
                runners[ithread].is_done = false;
                //free(runners[ithread].puzzle);
                //runners[ithread].puzzle = NULL;
                //free(runners[ithread].solution);
                //runners[ithread].solution = NULL;
		pthread_create(&runners[ithread].thread,NULL,generate_game,(void *)(&runners[ithread]));
            
            }
        }

        //pthread_mutex_unlock(&lock);
    },true);
    }
    
    for(unsigned int i = 0; i < game_count; i++){
        
        pthread_cancel(runners[i].thread);
        
    }
    pthread_testcancel();
}

int main() {
    setbuf(stdout,NULL);

    srand(time(NULL));
   // setbuf(stdout,0);
    int cores = sysconf(_SC_NPROCESSORS_ONLN);
    int threads = cores * 4 - 1;
    sprintf(footer_prefix, "Cores: %d - Threads: %d - ", cores,threads);
    // Highest: 1481563980
    generate_games(threads ,13371337,1);
    exit(0);

/*
0 0 9 0 0 0 0 0 0 
0 0 0 0 0 0 3 0 0 
6 0 1 5 0 2 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 8 0 0 0 
9 0 4 7 0 0 0 0 5 
0 0 7 8 0 0 0 0 0 
0 0 8 0 0 0 0 0 0 
0 4 0 0 3 0 0 6 2

Attempts: 
213212476


*/
/*
0 0 0 0 0 0 0 0 0 
0 0 0 5 0 0 0 0 0 
0 0 0 7 0 2 0 0 0 
0 0 0 0 0 1 0 0 0 
0 0 4 0 0 0 0 9 3 
0 0 0 0 0 8 0 0 0 
0 0 0 4 0 5 0 0 0 
0 0 0 0 0 0 0 0 2 
0 0 8 3 0 0 0 0 5 
Thread 2 is done (459.04s)
Complexity: 125004041
Initial values: 14
*/
/*
0 0 0 0 0 0 0 0 0 
0 8 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 6 5 0 
0 0 4 0 0 0 9 0 0 
0 0 0 2 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 
0 0 0 0 4 0 0 9 1 
0 0 0 0 6 5 0 0 0 
Thread 2 is done (1528.38s)
Complexity: 748250144
Initial values: 11
*/
/*0 3 0 0 0 0 0 0 0 
0 0 5 0 0 0 0 0 0 
0 0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 4 0 
0 0 0 0 9 0 0 0 0 
0 0 9 0 3 0 0 0 0 
0 0 0 0 0 6 0 0 0 
0 0 4 0 0 0 0 7 0 
0 6 7 0 0 4 0 2 0 
Thread 0 is done (2635.64s)
Complexity: 1481563980
Initial values: 14*/
/*
GROOTSTE!   
Complexity: 774858414
0 0 0 4 0 0 9 0 0 
0 0 0 0 0 0 0 0 0 
0 2 0 5 0 8 0 0 0 
5 0 2 0 0 0 0 0 3 
0 0 0 0 0 0 0 0 0 
0 7 0 0 0 0 0 0 0 
0 9 0 0 0 0 4 3 0 
2 0 0 0 5 7 0 6 0 
0 0 7 1 0 0 2 0 0 
Generated in: 1393.95s
*/
/*
ONEINDIG?
0 2 0 0 0 0 0 0 0 
0 0 4 0 0 0 0 0 0 
6 0 1 8 0 0 0 0 0 
3 0 6 0 0 0 8 0 0 
0 0 0 0 3 0 2 4 0 
0 4 0 1 0 2 5 0 0 
7 0 0 2 0 0 1 6 8 
0 3 0 0 0 0 7 2 4 
8 0 0 0 0 6 0 0 5
*/

/*
0 0 0 0 3 7 0 0 0 
0 0 8 0 0 2 5 0 3 
0 2 0 8 4 0 0 0 0 
0 0 6 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 7 
0 0 0 2 0 0 0 6 0 
0 0 0 0 0 0 6 0 5 
0 0 0 6 8 0 0 4 0 
4 0 0 1 0 0 0 0 0 
*/
    __attribute_maybe_unused__ int grid_empty[N][N] = {
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0},
        {0, 0, 0, 0, 0, 0, 0, 0, 0}
    };
    
}