#include "DEM.h"

#define LOAD_BACKUP 1

static void Adjust_Sea_Level(int load){
	static int sea_level_chng = 100;
	static int is_rising = 0;

	if(load){
		sea_level_chng = ia->cur_sealevel;
		is_rising = ia->sea_rising;
		return;
	}
	if(is_rising){
		if(sea_level_chng == 100){
			is_rising = 0;
		}else{
			sea_level_chng++;
		}
	}else{
		if(sea_level_chng == -100){
			is_rising = 1;
		}else{
			sea_level_chng--;
		}
	}
	ia->cur_sealevel = sea_level_chng;
	ia->sea_rising = is_rising;
}

static void Adjust_Climate(int load){
    static float temp_chng = 32; //starting at high temp
    static int is_rising = 0;
	//every 10K years you'll get a 2c temp change

    if(load){
        temp_chng = ia->cur_temp;
        is_rising = ia->temp_rising;
        return;
    }
	if(is_rising){
        if(temp_chng == 32){
             is_rising = 0;
         }else{
            temp_chng+=0.2;
         }
	}else{
            if(temp_chng == 28){
                is_rising = 1;
            }else{
                temp_chng-=0.2;
            }
	}
    ia->cur_temp = temp_chng;
    ia->temp_rising = is_rising;
}

int Load_Terrain(char *filename){
	FILE *fp = fopen(filename, "rb");
	if(!fp){
		printf("Error opening file: %s\n", filename);
		return 1;
	}
	const int start_sealevel = 100;
	unsigned int height = 1800; //40
	unsigned int width  = 3600; //27

	ia = malloc(sizeof(struct image_attr));
	ia->image = malloc(sizeof(unsigned char)*height*width*3);
    ia->width = width;
    ia->height = height;
	ia->cur_sealevel = start_sealevel;
	ia->sea_rising = 0;
	ia->cur_temp = 32;
	ia->temp_rising = 0;

	char two_b[2];
	union{ char a[2]; int16_t b;}x;
	unsigned int count1, count2;
	unsigned char rgb[3];
	ia->data = (int16_t **)malloc(sizeof(int16_t *)*height);
	for(count1=0; count1<height; count1++){
		ia->data[count1] = (int16_t *)malloc(sizeof(int16_t)*width);
	}
	for(count1=0; count1<height; count1++){
		for(count2=0; count2<width; count2++){
			fread(&two_b, 1, 2, fp);
			x.a[0] = two_b[0];
			x.a[1] = two_b[1];
			ia->data[count1][count2] = x.b;
			if(x.b < start_sealevel){
				rgb[0] = 0;
				rgb[1] = 0;
				rgb[2] = 255;
			}else{
				rgb[0] = 0;
				rgb[1] = 255;
				rgb[2] = 0;
			}
			ia->image[count1*width*3+(count2*3)] = rgb[0];
			ia->image[count1*width*3+(count2*3+1)] = rgb[1];
			ia->image[count1*width*3+(count2*3+2)] = rgb[2];
		}
	}
	fclose(fp);
	return 0;
}

static void Gen_Detailed_Counts(unsigned int year, unsigned int regions){
	unsigned int h, w;
	unsigned int height = ia->height;
	unsigned int width = ia->width;
	unsigned int total_living = 0;
	uint32_t chng = 0;

	for(h=0; h<height; h++){
		for(w=0; w<width; w++){
			if(life[h][w]->alive){
				total_living++;

			}
		}
	}
	printf("Year: %u\n", year); 
	for(h=0; h<regions; h++){
            stats_t.disease += stats[h].disease;
            stats_t.drown += stats[h].drown;
			stats_t.exposure += stats[h].exposure;
            stats_t.old_age += stats[h].old_age;
            stats_t.died_attacking += stats[h].died_attacking;
            stats_t.died_defending += stats[h].died_defending;
            stats_t.new_life += stats[h].new_life;
            stats_t.new_species += stats[h].new_species;
	}
	printf("\tdisease: %lu\n", stats_t.disease);
	for(h=0; h<regions; h++){
		chng += stats[h].disease;
	}
	printf("\t\tchng: %u\n",chng); 

	printf("\tdrown: %lu\n", stats_t.drown);
	chng=stats[0].drown;
	for(h=1; h<regions; h++){
		chng += stats[h].drown;
	}
	printf("\t\tchng: %u\n",chng);

    printf("\texposure: %lu\n", stats_t.exposure);
    chng=stats[0].exposure;
    for(h=1; h<regions; h++){
        chng += stats[h].exposure;
    }
    printf("\t\tchng: %u\n",chng);

	printf("\told_age: %lu\n", stats_t.old_age);
	chng=stats[0].old_age;
	for(h=1; h<regions; h++){
		chng += stats[h].old_age;
	}
	printf("\t\tchng: %u\n",chng);

	printf("\tdied_attacking: %lu\n", stats_t.died_attacking);
	chng=stats[0].died_attacking;
	for(h=1; h<regions; h++){
		chng += stats[h].died_attacking;
	}
	printf("\t\tchng: %u\n",chng);

	printf("\tdied_defending: %lu\n", stats_t.died_defending);
	chng=stats[0].died_defending;
	for(h=1; h<regions; h++){
		chng += stats[h].died_defending;
	}
	printf("\t\tchng: %u\n",chng);

	printf("\tnew_life: %lu\n", stats_t.new_life);
	chng=stats[0].new_life;
	for(h=1; h<regions; h++){
		chng += stats[h].new_life;
	}
	printf("\t\tchng: %u\n",chng);

	printf("\tnew_species: %lu\n", stats_t.new_species);
	chng=stats[0].new_species;
	for(h=1; h<regions; h++){
		chng += stats[h].new_species;
	}
	printf("\t\tchng: %u\n",chng);
	printf("Total Living: %u\n", total_living);
    for(h=0; h<regions; h++){
        (void)memset((void *)&stats[h], '\0', sizeof(struct Stats) );
    }
}


void Save_Current_State(uint64_t year){
	char *filename = (char *)malloc( strlen("BACKUP.lr") + 1);
	(void)strcpy(filename, "BACKUP.lr");
	FILE *fp = fopen(filename, "wb");
    if(!fp){
        printf("Error opening file: %s\n", filename);
        exit(1);
    }
	uint32_t width = ia->width;
	uint32_t height = ia->height;
	uint32_t w = 0, h = 0;
	size_t bytes = 0;

	bytes += fwrite((const void *)&stats_t, sizeof(struct Stats_T), 1, fp);

	for(h=0; h<height; h++){
		for(w=0; w<width; w++){
			bytes += fwrite((const void *)life[h][w], sizeof(struct Species), 1, fp);
		}
	}
	bytes += fwrite((const void *)&ia->cur_sealevel, sizeof(int), 1, fp);
	bytes += fwrite((const void *)&ia->sea_rising, sizeof(int), 1, fp);
	bytes += fwrite((const void *)&ia->cur_temp, sizeof(float), 1, fp);
	bytes += fwrite((const void *)&ia->temp_rising, sizeof(int), 1, fp);
	bytes += fwrite((const void *)&year, sizeof(uint64_t), 1, fp);
	bytes += fwrite((const void *)life_speed_up_H, sizeof(char)*height, 1, fp);
	bytes += fwrite((const void *)life_speed_up_W, sizeof(char)*width, 1, fp);
	printf("Saved current state backup!\n");
	printf("bytes wrote: %zu\n\n", bytes);
	free(filename);
	fclose(fp);
}

uint64_t Load_State(void){
    char *filename = (char *)malloc( strlen("BACKUP.lr") + 1);
    (void)strcpy(filename, "BACKUP.lr");
    FILE *fp = fopen(filename, "rb");
    if(!fp){
        printf("Error opening file: %s\n", filename);
        exit(1);
    }
	uint32_t width = ia->width;
	uint32_t height = ia->height;
	uint32_t w = 0, h = 0;
	uint64_t year = 0;
	size_t bytes = 0;

	bytes += fread((void *)&stats_t, sizeof(struct Stats_T), 1, fp);
	for(h=0; h<height; h++){
		for(w=0; w<width; w++){
			bytes += fread((void *)life[h][w], sizeof(struct Species), 1, fp);
		}
	}
	bytes += fread((void *)&ia->cur_sealevel, sizeof(int), 1, fp);
	bytes += fread((void *)&ia->sea_rising, sizeof(int), 1, fp);
	bytes += fread((void *)&ia->cur_temp, sizeof(float), 1, fp);
	bytes += fread((void *)&ia->temp_rising, sizeof(int), 1, fp);
	bytes += fread((void *)&year, sizeof(uint64_t), 1, fp);
	bytes += fread((void *)life_speed_up_H, sizeof(char)*height, 1, fp);
	bytes += fread((void *)life_speed_up_W, sizeof(char)*width, 1, fp);

	printf("bytes read: %zu\n", bytes);
	free(filename);
	fclose(fp);
	return w; //year
}

void World_Thread(void){
	uint64_t year = 1;
	size_t height = ia->height;
	size_t width = ia->width;
	struct timeval starttime,endtime;
    static struct timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = 10000;
	No_Exit = 1;
	double time1 = 0;
#ifndef LOAD_BACKUP
	//location to place initial tribes for fresh simulation
	const int w = 2200;
	const int h = 900;

	unsigned char rgb[3];
	unsigned int n3 = 0;
#endif
	unsigned int n1 = 0;
	unsigned int n2 = 0;
	unsigned int Tcount = 0;

	//unsigned int regions = Threads*2; //evens and odds
	unsigned int thread_step = floor(height/Threads);

	printf("Threads: %u thread_step: %u\n", Threads, thread_step);
	
	pthread_t OpenGL_thread_ptr;
	pthread_t State_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 );
	unsigned int State_t_ids;
	unsigned int OpenGL_t_ids;

	Actions_t_info = (struct Thread_I *)malloc( sizeof(struct Thread_I) * Threads );
	State_t_info   = (struct Thread_I *)malloc( sizeof(struct Thread_I) );
	Opengl_t_info  = (struct Thread_I *)malloc( sizeof(struct Thread_I) );

	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<Threads; n1++){
		Actions_t_info[n1].start_h = n2;
		Actions_t_info[n1].end_h   = (n1+1 != Threads) ? (n1+1)*thread_step - 1: height - 1;
		n2 += thread_step;
		printf("start_h: %u end_h: %u\n", Actions_t_info[n1].start_h, Actions_t_info[n1].end_h);
		Actions_t_ids[n1] = n1;
	}

	//initialize empty life array
	Allocate_Life_Array(height, width);
	printf("Allocated Life array\n");

#ifdef LOAD_BACKUP
		year = Load_State();
		Adjust_Sea_Level(1);
		Adjust_Climate(1);
		printf("loaded backup file year: %lu sealevel: %d rising: %d\n", year, ia->cur_sealevel, ia->sea_rising);
#else
	//"plant" first primate tribe [somewhere in africa :)]
		stats_t.total_runtime = 4;
		for(n1=w-5; n1<w+5; n1++){
			for(n2=h-5; n2<h+5; n2++){
				//need to check terrain for water
				if(ia->data[n2][n1] > ia->cur_sealevel){
					life[n2][n1] = (struct Species *)malloc( sizeof(struct Species) );
					Add_First_Life( life[n2][n1] );
					Gen_Tribe_Color(life[n2][n1], rgb);
					ia->image[width*n2*3 + n1*3]     = rgb[0];
					ia->image[width*n2*3 + n1*3 + 1] = rgb[1];
					ia->image[width*n2*3 + n1*3 + 2] = rgb[2];
					n3++;
//eliminate !
					life_speed_up_H[n2] = 1;
					life_speed_up_W[n1] = 1;

					life[n2][n1]->alive = 1;
				}
			}
		}
		printf("Added %d initial tribes!\n", n3);
#endif

//eliminate !
	FM_Alloc(height, width);
	FM_Reset(height, width);


	//launch OpenGL thread
	sleep(1);
	if(pthread_create(&OpenGL_thread_ptr, NULL, Run_GL, NULL) != 0){
		printf("Error: pthread_create failed to launch opengl thread !\n");
		exit(1);
	}
	printf("launched opengl thread!\n");
	sleep(1);

	printf("equatorial temp: %f\n", ia->cur_temp);
	printf("sea level: %d\n", ia->cur_sealevel);

	if(year%1000 == 0){
		Adjust_Sea_Level(0);
		Adjust_Climate(0);
	}

	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 thread: %d !\n", Tcount);
			exit(1);
		}
	}
	for(Tcount=0; Tcount < regions; Tcount++){
		if(pthread_join(Actions_thread_ptrs[Tcount], NULL) != 0){
			printf("Error: pthread_join failed for thread: %d !\n", Tcount);
			exit(1);
		}
	}


// eliminate !
	FM_Reset(height, width);
	year++;

//create State Thread
	if(year%1000 == 0){
		gettimeofday(&endtime, NULL);
		time1 = ((double)(endtime.tv_sec*1000000-starttime.tv_sec*1000000+endtime.tv_usec-starttime.tv_usec))/1000000;
		stats_t.total_runtime += time1;
		Gen_Detailed_Counts(year, regions);
		if(ia->sea_rising)
			printf("Current: Sec/Year %fs cur_sealevel %dm rising +1m/1Kyears", (float)time1/1000, ia->cur_sealevel);
		else
		printf("Current: Sec/Year %fs cur_sealevel %dm falling -1m/1Kyears", (float)time1/1000, ia->cur_sealevel);
		printf("equatorial mean temp: %.02fc changing: %.02fc/1Kyears\n", ia->cur_temp, (ia->temp_rising ? 0.2 : -0.2) );
		printf("Total: Runtime %.02lf hrs Avg Sec/Year %lfs\n", (double)stats_t.total_runtime/60/60, (double)stats_t.total_runtime/year);
		Adjust_Sea_Level(0);
		Adjust_Climate(0);
		Save_Current_State(year);
		gettimeofday(&starttime, NULL);

        if (pthread_join(OpenGL_thread_ptr, NULL) != 0){
            printf("Error: pthread_join failed to join OpenGL thread!\n");
            exit(1);
        }


		free(ia->image);
		for(n1=0; n1<height; n1++){
			free(ia->data[n1]);
		}
		free(ia->data);
		free(ia);
		free(threads_p);
		free(t_ids);
		free(thread_i);
		for(n1=0; n1<height; n1++){
			for(n2=0; n2<width; n2++){
				free(life[n1][n2]);
			}
			free(life[n1]);
		}
		free(life);
		free(life_speed_up_H);
		free(life_speed_up_W);
		for(n1=0; n1<height; n1++){
			free(fought[n1]);
			free(mated[n1]);
		}
		free(fought);
		free(mated);
		free(stats);
}