/*
    Living Realms is a primate evolution simulator.
    Copyright (C) 2011  Sterling Pickens

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "config.h"
#ifdef HAVE_PTHREAD_H
#include <pthread.h>
#elif HAVE_PTHREADS_H
#include <pthreads.h>
#endif
#include <math.h>
#include "Globals.h"
#include "DEM.h"
#include "Life.h"
#include "OpenGL.h"

extern int No_Exit;
extern struct Species ***life;
extern struct Stats_T stats_t;
extern struct image_attr *ia;
extern unsigned int Threads;
extern struct Thread_I *Actions_t_info;
extern double Backup_Freq_Runtime;
extern double Year_Runtime;
extern uint32_t Backup_Freq;
#ifndef WITHOUT_HOST_PT_BARRIER
extern pthread_barrier_t Barrier1;
extern pthread_barrier_t Barrier2;
extern pthread_barrier_t Barrier3;
#else
extern lrpthread_barrier_t Barrier1;
extern lrpthread_barrier_t Barrier2;
extern lrpthread_barrier_t Barrier3;
#endif
extern pthread_mutex_t Region_Mutexes[HEIGHT/REGION_SIZE];
extern unsigned char Region_Handled[HEIGHT/REGION_SIZE];
extern char *filename_backup_load;
extern char *filename_backup_save;
extern char *filename_dem;
extern char *life_speed_up_X;
extern char *life_speed_up_Y;
extern int Stats_Handled;
extern unsigned char **Alive;
extern unsigned char **Fought;
extern unsigned char **Mated;

void Usage(void){
	printf("%s (%s %s) Usage:\n", LR_NAME, LR_VERSION, LR_DATE);
	printf("lr [-l filename1] [-s filename2] [-f freq] [-t threads] [-v/-h]\n");
	printf("\t[optional]\n");
	printf("\tfilename1\t= backup file to load. Default to a fresh simulation\n");
	printf("\tfilename2\t= file to save backups to. Default: BACKUP.lr\n");
	printf("\tfreq\t= unsigned integer frequency to do backup (1K year multiple). Default 1\n");
	printf("\tthreads\t= unsigned integer number of \"action\" threads to use (1 added for gui). Default 1\n");
	printf("\t-v or -h\t= print this menu and exit\n");
}

int main(int argc, char **argv){
	unsigned int n3 = 0;
	unsigned int n1 = 0;
	unsigned int n2 = 0;
	unsigned int Tcount = 0;
	int ret_val = 0;
    filename_dem = (char *)malloc(strlen("data.dem") + 1);
    strcpy(filename_dem, "data.dem");
	if(argc > 10){
		Usage();
		exit(1);
	}
	Year_Runtime = 0;
	Backup_Freq = 1000;
	Backup_Freq_Runtime = 0;
	filename_backup_load = NULL;
	filename_backup_save = NULL;
	uint32_t *options = (uint32_t *)malloc( sizeof(uint32_t) * 5 );
	(void)memset((void *)options, '\0', sizeof(uint32_t) * 5 );
	Threads = 1;
	if(argc > 1){ //if atleast one option
		for(n1=1; n1<argc; n1++){
			if( strcmp( (const char *)argv[n1], "-l") == 0){
				options[0] = n1+1;
				if(n1+1 > argc-1)
					n2 = 1;
			}
			if( strcmp( (const char *)argv[n1], "-s") == 0){
				options[1] = n1+1;
				if(n1+1 > argc-1)
					n2 = 1;
			}
			if( strcmp( (const char *)argv[n1], "-f") == 0){
				options[2] = n1+1;
				if(n1+1 > argc-1)
					n2 = 1;
			}
			if( strcmp( (const char *)argv[n1], "-t") == 0){
				options[3] = n1+1;
				if(n1+1 > argc-1)
					n2 = 1;
			}
			if( strcmp( (const char *)argv[n1], "-v") == 0){
				if(n1+1 > argc-1)
					n2 = 1;
			}
			if( strcmp( (const char *)argv[n1], "-h") == 0){
				if(n1+1 > argc-1)
					n2 = 1;
			}
		}
		if(n2){
			Usage();
			free(filename_dem);
			free(options);
			exit(1);
		}
	}

	n2 = 1;
	if(options[0] != 0){
		filename_backup_load = (char *)malloc( sizeof(char) * strlen(argv[options[0]]) + 1 );
		(void)strcpy(filename_backup_load, (const char *)argv[options[0]]);
	}else{
		filename_backup_load = NULL;
	}
	if(options[1] != 0){
		filename_backup_save = (char *)malloc( sizeof(char) * strlen(argv[options[1]]) + 1 );
		(void)strcpy(filename_backup_save, (const char *)argv[options[1]]);
	}else{
		filename_backup_save = (char *)malloc( sizeof(char) * strlen("BACKUP.lr") + 1 );
		(void)strcpy(filename_backup_save, "BACKUP.lr");
	}
	if(options[2] != 0){
		Backup_Freq = atoi(argv[options[2]]);
		if(Backup_Freq < 100){
			printf("Error: Frequency input is not a valid number!\n");
			printf("\tacceptable range 100+\n");
			n2 = 2;
		}
	}
	if(options[3] != 0){
		Threads = atoi(argv[options[3]]);
		if(Threads == 0 || Threads > (uint32_t)(HEIGHT/REGION_SIZE)){  //don't break later
			printf("Error: Threads input is not a valid number!\n");
			printf("\tacceptable range 1 to %u\n", (uint32_t)(HEIGHT/REGION_SIZE));
			n2 = 2;
		}
	}
	free(options);
	if(!n2){
		Usage();
		free(filename_dem);
		free(filename_backup_load);
		free(filename_backup_save);
		exit(1);
	}
    if( Load_Terrain() ){
        printf("Exiting!\n");
		free(filename_dem);
		free(filename_backup_load);
		free(filename_backup_save);
        exit(1);
    }

	printf("Threads: %u backup_freq: %u filename_load: %s filename_save: %s\n", Threads, Backup_Freq, filename_backup_load, filename_backup_save);
	printf("Dem dimensions(W x H): %u x %u\n", WIDTH, HEIGHT);

	pthread_t OpenGL_thread_ptr;
	pthread_t *Actions_thread_ptrs = (pthread_t *)malloc(sizeof(pthread_t) * Threads );
	unsigned int *Actions_t_ids    = (unsigned int *)malloc( sizeof(unsigned int) * Threads );
	Actions_t_info = (struct Thread_I *)malloc( sizeof(struct Thread_I) * Threads );
	stats = (struct Stats *)malloc( sizeof(struct Stats) * Threads );

	(void)memset((void *)&stats_t, '\0', sizeof(struct Stats_T) );
	for(n1=0; n1<Threads; n1++){
		(void)memset((void *)&stats[n1], '\0', sizeof(struct Stats) );
	}

	for ( n1=0; n1 < (HEIGHT/REGION_SIZE); n1++ ) {
		ret_val = pthread_mutex_init(&Region_Mutexes[n1], NULL);
		Region_Handled[n1] = 0;
		if(ret_val != 0){
			printf("Error: pthread_mutex_init returned %d\n", ret_val);
			free(filename_dem);
			free(filename_backup_load);
			free(filename_backup_save);
			free(Actions_thread_ptrs);
			free(Actions_t_ids);
			free(Actions_t_info);
			free(stats);
			exit(1);
		}
	}

	n2 = 0;
	for(n1=0; n1<Threads; n1++){
		Actions_t_ids[n1] = n1;
		Actions_t_info[n1].start_h = n2;
		n2 += (uint32_t)(HEIGHT/Threads);
		Actions_t_info[n1].end_h = n2;
	}
	Actions_t_info[Threads-1].end_h = (uint32_t)HEIGHT;

	//initialize life array
	Allocate_Life_Array();
    life_speed_up_Y = (char *)malloc(sizeof(char)*HEIGHT);
    life_speed_up_X = (char *)malloc(sizeof(char)*WIDTH);
	Alive = (unsigned char **)malloc(sizeof(unsigned char *)*HEIGHT);
	for(n3=0; n3<HEIGHT; n3++){
		Alive[n3] = (unsigned char *)malloc(sizeof(unsigned char)*(size_t)(WIDTH>>3));
		(void)memset( (void *)Alive[n3], '\0', sizeof(unsigned char)*(size_t)(WIDTH>>3) );
	}
    (void)memset( (void *)life_speed_up_Y, '\0', sizeof(char)*HEIGHT );
    (void)memset( (void *)life_speed_up_X, '\0', sizeof(char)*WIDTH );

	printf("Allocated Life array\n");
	n3 = 0;
	if(filename_backup_load != NULL){
		if( Load_State() ){
			printf("Exiting!\n");
			free(filename_dem);
			free(filename_backup_load);
			free(filename_backup_save);
			free(Actions_thread_ptrs);
			free(Actions_t_ids);
			free(Actions_t_info);
			free(life_speed_up_Y);
			free(life_speed_up_X);
			free(stats);
			for(n1=0; n1 < (HEIGHT/REGION_SIZE); n1++){
				(void)pthread_mutex_destroy(&Region_Mutexes[n1]);
			}
			for(n3=0; n3<HEIGHT; n3++){
				free(Alive[n3]);
			}
			free(Alive);
			exit(1);
		}
		for(n1=0; n1<HEIGHT; n1++){
			for(n2=0; n2<WIDTH; n2++){
				if( IS_SET(Alive, n1, n2) ){
					life_speed_up_Y[n1] = 1;
					life_speed_up_X[n2] = 1;
				}
			}
		}
		Adjust_Sea_Level(1);
		Adjust_Climate(1);
	}else{
		Adjust_Sea_Level(0);
		Adjust_Climate(0);
		printf("cur_sealevel: %d\n", stats_t.cur_sealevel);
		//"plant" first primate tribe [somewhere in africa :)]
		stats_t.total_runtime = 4; //set to 4 seconds to account for initialization
		for(n1=INITIAL_TRIBE_X-TRIBE_SQ; n1<INITIAL_TRIBE_X+TRIBE_SQ; n1++){
			for(n2=INITIAL_TRIBE_Y-TRIBE_SQ; n2<INITIAL_TRIBE_Y+TRIBE_SQ; n2++){
				//need to check terrain for water
				if(ia->dem[n2][n1] > stats_t.cur_sealevel){
					Add_First_Life( life[n2][n1] );
					n3++;
					SET_ONE(Alive, n2, n1);
					life_speed_up_X[n1] = 1;
					life_speed_up_Y[n2] = 1;
				}
			}
		}
		stats_t.total_living = n3;
		printf("Added %u initial tribes!\n", n3);
	}
	printf("\tyear: %u sealevel: %d rising: %d ", stats_t.year, stats_t.cur_sealevel, stats_t.sea_rising);
	printf("temp: %.02f rising %d\n", stats_t.cur_temp, stats_t.temp_rising);

	No_Exit = 1;

	printf("Launching OpenGL thread.\n");
	glutInit(&argc,argv);
	if(pthread_create(&OpenGL_thread_ptr, NULL, Run_GL, NULL) != 0){
		printf("Error: pthread_create failed to launch opengl thread !\n");
		free(filename_dem);
		free(filename_backup_load);
		free(filename_backup_save);
		free(Actions_thread_ptrs);
		free(Actions_t_ids);
		free(Actions_t_info);
		free(life_speed_up_Y);
		free(life_speed_up_X);
		free(stats);
		for(n1=0; n1 < (HEIGHT/REGION_SIZE); n1++){
			(void)pthread_mutex_destroy(&Region_Mutexes[n1]);
		}
		for(n3=0; n3<HEIGHT; n3++){
			free(Alive[n3]);
		}
		free(Alive);
		exit(1);
	}
sleep(5);
#ifndef WITHOUT_HOST_PT_BARRIER
	ret_val = pthread_barrier_init(&Barrier1, NULL, Threads);
#else
	ret_val = lrpthread_barrier_init(&Barrier1, NULL, Threads);
#endif
	if(ret_val != 0){
		printf("Error: pthread_barrier_init Failed: %d\n", ret_val);
		printf("\tExiting!\n");
		free(filename_dem);
		free(filename_backup_load);
		free(filename_backup_save);
		free(Actions_thread_ptrs);
		free(Actions_t_ids);
		free(Actions_t_info);
		free(life_speed_up_Y);
		free(life_speed_up_X);
		free(stats);
		for(n1=0; n1 < (HEIGHT/REGION_SIZE); n1++){
			(void)pthread_mutex_destroy(&Region_Mutexes[n1]);
		}
		for(n3=0; n3<HEIGHT; n3++){
			free(Alive[n3]);
		}
		free(Alive);
		exit(1);
	}
#ifndef WITHOUT_HOST_PT_BARRIER
	ret_val = pthread_barrier_init(&Barrier2, NULL, Threads);
#else
	ret_val = lrpthread_barrier_init(&Barrier2, NULL, Threads);
#endif
	if(ret_val != 0){
		printf("Error: pthread_barrier_init Failed: %d\n", ret_val);
		printf("\tExiting!\n");
#ifndef WITHOUT_HOST_PT_BARRIER
		(void)pthread_barrier_destroy(&Barrier1);
#else
		(void)lrpthread_barrier_destroy(&Barrier1);
#endif
		free(filename_dem);
		free(filename_backup_load);
		free(filename_backup_save);
		free(Actions_thread_ptrs);
		free(Actions_t_ids);
		free(Actions_t_info);
		free(life_speed_up_Y);
		free(life_speed_up_X);
		free(stats);
		for(n1=0; n1 < (HEIGHT/REGION_SIZE); n1++){
			(void)pthread_mutex_destroy(&Region_Mutexes[n1]);
		}
		for(n3=0; n3<HEIGHT; n3++){
			free(Alive[n3]);
		}
		free(Alive);
		exit(1);
	}
#ifndef WITHOUT_HOST_PT_BARRIER
	ret_val = pthread_barrier_init(&Barrier3, NULL, Threads);
#else
	ret_val = lrpthread_barrier_init(&Barrier3, NULL, Threads);
#endif
	if(ret_val != 0){
		printf("Error: pthread_barrier_init Failed: %d\n", ret_val);
		printf("\tExiting!\n");
#ifndef WITHOUT_HOST_PT_BARRIER
		(void)pthread_barrier_destroy(&Barrier1);
		(void)pthread_barrier_destroy(&Barrier2);
#else
		(void)lrpthread_barrier_destroy(&Barrier1);
		(void)lrpthread_barrier_destroy(&Barrier2);
#endif
		free(filename_dem);
		free(filename_backup_load);
		free(filename_backup_save);
		free(Actions_thread_ptrs);
		free(Actions_t_ids);
		free(Actions_t_info);
		free(life_speed_up_Y);
		free(life_speed_up_X);
		free(stats);
		for(n1=0; n1 < (HEIGHT/REGION_SIZE); n1++){
			(void)pthread_mutex_destroy(&Region_Mutexes[n1]);
		}
		for(n3=0; n3<HEIGHT; n3++){
			free(Alive[n3]);
		}
		free(Alive);
		exit(1);
	}

	Fought = (unsigned char **)malloc(sizeof(unsigned char *)*HEIGHT);
	Mated  = (unsigned char **)malloc(sizeof(unsigned char *)*HEIGHT);
	for(n3=0; n3<HEIGHT; n3++){
		Fought[n3] = (unsigned char *)malloc(sizeof(unsigned char)*(size_t)(WIDTH>>3));
		(void)memset( (void *)Fought[n3], '\0', sizeof(unsigned char)*(size_t)(WIDTH>>3) );
	}
	for(n3=0; n3<HEIGHT; n3++){
		Mated[n3] = (unsigned char *)malloc(sizeof(unsigned char)*(size_t)(WIDTH>>3));
		(void)memset( (void *)Mated[n3], '\0', sizeof(unsigned char)*(size_t)(WIDTH>>3) );
	}

	Stats_Handled = 0;
	printf("Launching %u Action threads.\n", Threads);
	for(Tcount = 0; Tcount < Threads; Tcount++){
		if(pthread_create(&Actions_thread_ptrs[Tcount], NULL, Tribe_Actions, (void*)&Actions_t_ids[Tcount]) != 0){
			printf("Error: pthread_create failed for action thread: %d !\n", Tcount);
			printf("\tExiting!\n");
#ifndef WITHOUT_HOST_PT_BARRIER
			(void)pthread_barrier_destroy(&Barrier1);
			(void)pthread_barrier_destroy(&Barrier2);
			(void)pthread_barrier_destroy(&Barrier3);
#else
			(void)lrpthread_barrier_destroy(&Barrier1);
			(void)lrpthread_barrier_destroy(&Barrier2);
			(void)lrpthread_barrier_destroy(&Barrier3);
#endif
			free(filename_dem);
			free(filename_backup_load);
			free(filename_backup_save);
			free(Actions_thread_ptrs);
			free(Actions_t_ids);
			free(Actions_t_info);
			free(life_speed_up_Y);
			free(life_speed_up_X);
			free(stats);
			for(n1=0; n1 < (HEIGHT/REGION_SIZE); n1++){
				(void)pthread_mutex_destroy(&Region_Mutexes[n1]);
			}
			for(n3=0; n3<HEIGHT; n3++){
				free(Alive[n3]);
				free(Fought[n3]);
				free(Mated[n3]);
			}
        	free(Alive);
			free(Fought);
			free(Mated);
			exit(1);
		}
	}

	if(pthread_join(OpenGL_thread_ptr, NULL) != 0){
		printf("Error: pthread_join failed to join OpenGL thread!\n");
        printf("\tExiting!\n");
#ifndef WITHOUT_HOST_PT_BARRIER
		(void)pthread_barrier_destroy(&Barrier1);
		(void)pthread_barrier_destroy(&Barrier2);
		(void)pthread_barrier_destroy(&Barrier3);
#else
		(void)lrpthread_barrier_destroy(&Barrier1);
		(void)lrpthread_barrier_destroy(&Barrier2);
		(void)lrpthread_barrier_destroy(&Barrier3);
#endif
		free(filename_dem);
		free(filename_backup_load);
		free(filename_backup_save);
		free(Actions_thread_ptrs);
		free(Actions_t_ids);
		free(Actions_t_info);
		free(life_speed_up_Y);
		free(life_speed_up_X);
		free(stats);
		for(n1=0; n1 < (HEIGHT/REGION_SIZE); n1++){
			(void)pthread_mutex_destroy(&Region_Mutexes[n1]);
		}
		for(n3=0; n3<HEIGHT; n3++){
			free(Alive[n3]);
			free(Fought[n3]);
			free(Mated[n3]);
		}
		free(Alive);
		free(Fought);
		free(Mated);
		exit(1);
	}

	for(Tcount=0; Tcount < Threads; Tcount++){
		if(pthread_join(Actions_thread_ptrs[Tcount], NULL) != 0){
			printf("Error: pthread_join failed for action thread: %d !\n", Tcount);
			printf("\tExiting!\n");
#ifndef WITHOUT_HOST_PT_BARRIER
			(void)pthread_barrier_destroy(&Barrier1);
			(void)pthread_barrier_destroy(&Barrier2);
			(void)pthread_barrier_destroy(&Barrier3);
#else
			(void)lrpthread_barrier_destroy(&Barrier1);
			(void)lrpthread_barrier_destroy(&Barrier2);
			(void)lrpthread_barrier_destroy(&Barrier3);
#endif
			free(filename_dem);
			free(filename_backup_load);
			free(filename_backup_save);
			free(Actions_thread_ptrs);
			free(Actions_t_ids);
			free(Actions_t_info);
			free(life_speed_up_Y);
			free(life_speed_up_X);
			free(stats);
			for(n1=0; n1 < (HEIGHT/REGION_SIZE); n1++){
				(void)pthread_mutex_destroy(&Region_Mutexes[n1]);
			}
			for(n3=0; n3<HEIGHT; n3++){
				free(Alive[n3]);
				free(Fought[n3]);
				free(Mated[n3]);
			}
			free(Alive);
			free(Fought);
			free(Mated);
			exit(1);
		}
	}

	printf("Cleaning up allocated objects!\n");
	free(ia->image);
	for(n1=0; n1<HEIGHT; n1++){
		free(ia->dem[n1]);
	}
	free(ia->dem);
	free(ia);
	for(n1=0; n1<HEIGHT; n1++){
		free(Alive[n1]);
		free(Fought[n1]);
		free(Mated[n1]);
		for(n2=0; n2<WIDTH; n2++){
			free(life[n1][n2]);
		}
		free(life[n1]);
	}
	free(Alive);
	free(Fought);
	free(Mated);
	free(life);
	free(stats);
	for(n1=0; n1 < (HEIGHT/REGION_SIZE); n1++){
		(void)pthread_mutex_destroy(&Region_Mutexes[n1]);
	}
	free(filename_dem);
	free(Actions_thread_ptrs);
	free(Actions_t_info);
	free(life_speed_up_Y);
	free(life_speed_up_X);
#ifndef WITHOUT_HOST_PT_BARRIER
	if(pthread_barrier_destroy(&Barrier1) != 0){
		printf("pthread_barrier_destroy(&Barrier1) Failed:\n");
		exit(1);
	}
	if(pthread_barrier_destroy(&Barrier2) != 0){
		printf("pthread_barrier_destroy(&Barrier2) Failed:\n");
		exit(1);
	}
	if(pthread_barrier_destroy(&Barrier3) != 0){
		printf("pthread_barrier_destroy(&Barrier3) Failed:\n");
		exit(1);
	}
#else
	if(lrpthread_barrier_destroy(&Barrier1) != 0){
		printf("lrpthread_barrier_destroy(&Barrier1) Failed:\n");
		exit(1);
	}
	if(lrpthread_barrier_destroy(&Barrier2) != 0){
		printf("lrpthread_barrier_destroy(&Barrier2) Failed:\n");
		exit(1);
	}
	if(lrpthread_barrier_destroy(&Barrier3) != 0){
		printf("lrpthread_barrier_destroy(&Barrier3) Failed:\n");
		exit(1);
	}
#endif

	return 0;
}