#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
#include <sys/time.h>
#include <unistd.h>
//#include "Random.h"

#define NUM_PLAYERS 2
#define DICE  5
#define SIDES 6
#define ROLLS 10
#define MIN_GET_ONBOARD   500
#define STRAIGHT_SCORE    1500
#define THREE_PAIRS_SCORE 1000

struct Player_Data {
	uint8_t onboard;
	uint8_t min_dice_will_roll;
	uint32_t min_score_will_keep;
	//uint8_t filler;
	uint32_t turn_score;
	uint32_t cumulative_score;
	uint32_t wins;
	//stats averages ?
	//
};

//static uint32_t Roll_Dice(uint64_t *s, struct Player_Data *pd);

/*
void Usage(void){
	printf("Usage:\n");
	printf("./rns [-d dice] [-h]\n");
	printf("\t[optional]\n");
	printf("\tdice\t= dice to roll\n"); 
	
}
*/

uint32_t lrand(uint64_t *s){
	// initial value of s must be 2 or higher
	uint64_t hi = *(uint64_t *)s / 41943011;
	uint64_t lo = *(uint64_t *)s % 41943011;
	*(uint64_t *)s = *(uint64_t *)s * lo - 2147483647 * hi;
	return (*(uint64_t *)s & 0xFFFFFFFF00000000) >> 32;
}


static uint8_t Has_Straight(uint8_t *roll_results, uint8_t dice){
	/* return 1 if dice are a straight, else 0 
	should only be run when dice == full amount (first roll)*/
	if(dice == 1)
		return 1;
	if(dice == 0)
	return 0;
	uint8_t num1 = 0;
	uint8_t num2 = SIDES;
	uint8_t match_found = 0;
	uint8_t *roll_results_ordered = malloc(dice*sizeof(uint8_t) );

	//find lowest rolled number
	for(num1=0; num1<dice; num1++){
		if(roll_results[num1] < num2)
			num2 = roll_results[num1];
	}
	roll_results_ordered[0] = num2;

	//search for roll that is 1 higher than previous for dice, return 0 if broken
	for(num1=1; num1<dice; num1++){
		match_found = 0;
		for(num2=0; num2<dice; num2++){
			if(roll_results[num2] == roll_results_ordered[num1-1]+1){
				roll_results_ordered[num1] = roll_results[num2];
				match_found = 1;
				break;
			}
		}
		if(match_found == 0)
			return 0;
	}
	free(roll_results_ordered);
	return 1;
}

static uint8_t Has_Three_Pair(uint8_t *num_counts, uint8_t dice){
	/* return 1 if has 3 pair else 0 */
	if(dice < 6)
		return 0;
	uint8_t num1 = 0;
	uint8_t num2 = 0;
	for(num1=0; num1<SIDES; num1++){
		if(num_counts[num1] > 1)
			num2++;
	}
	if(num2 > 2)
		return 1;
	return 0;
}

static uint32_t Roll_Dice(uint64_t *s, struct Player_Data *pd);

int main(int argc, char **argv){
	struct timeval cur;
	gettimeofday(&cur, NULL);
	uint64_t rseed = (uint64_t)cur.tv_usec + 3;
	uint32_t num1, num2;
	struct Player_Data *pd = (struct Player_Data *)malloc(sizeof(struct Player_Data) * NUM_PLAYERS);
	//uint32_t turn_score[2] = {0, 0};
	//uint32_t cumulative_score[2] = {0, 0};
	//uint8_t onboard[2] = {0, 0};
	//uint32_t wins[2] = {0, 0};
	uint32_t turn = 0;
	//const uint32_t players = 2;
	uint8_t ten_k_reached = 0;
	uint32_t tentative_winner = 0;
	uint32_t leader_score = 0;

	//initialize player data defaults
	for(num1=0; num1<NUM_PLAYERS; num1++){
		pd[num1].onboard = 0;
		pd[num1].min_dice_will_roll = 3;
		pd[num1].min_score_will_keep = 200;
		//pd[num1].filler = 0;
		pd[num1].turn_score = 0;
		pd[num1].cumulative_score = 0;
		pd[num1].wins = 0;
	}

	while(ten_k_reached != 1){ //untill 10K is reached by 1 player
		for(num1=0; num1<NUM_PLAYERS; num1++){
			printf("Player %u\n", num1);
			pd[num1].turn_score = Roll_Dice(&rseed, &pd[num1]);
			if(pd[num1].onboard == 1){
				printf("\tadded %u to total score\n", pd[num1].turn_score);
				pd[num1].cumulative_score += pd[num1].turn_score;
				printf("\t\ttotal: %u\n", pd[num1].cumulative_score);
			}else if(pd[num1].turn_score >= MIN_GET_ONBOARD){
				pd[num1].onboard = 1;
				printf("\tgot onboard with %u\n", pd[num1].turn_score);
				pd[num1].cumulative_score = pd[num1].turn_score;
			}else{
				printf("\tno score for turn\n");
				continue;
			}
			if(pd[num1].cumulative_score >= 10000){
				printf("10K Reached by player %u with score of %u\n", num1, pd[num1].cumulative_score);
				ten_k_reached = 1;
				tentative_winner = num1;
				break;
			}
		}
		turn++;
	}

	leader_score = pd[tentative_winner].cumulative_score;
	if(NUM_PLAYERS == 1){
		printf("Finished in %u turns with %u score\n", turn, leader_score);
		free(pd);
		return 0;
	}else{
		printf("tentative winner: %u score: %u\n", tentative_winner, leader_score);
	}
	printf("turns: %u\n", turn);
	//each player that is not tentative winner gets a chance to take win
	for(num1=0; num1<NUM_PLAYERS; num1++){
		if(num1 == tentative_winner)
			continue;
		pd[num1].onboard = 1;
		num2 = leader_score - pd[num1].cumulative_score;
		pd[num1].min_score_will_keep = num2;
		pd[num1].turn_score = Roll_Dice(&rseed, &pd[num1]);
		pd[num1].cumulative_score += pd[num1].turn_score;
		if(pd[num1].cumulative_score > leader_score)
			leader_score = pd[num1].cumulative_score;
		printf("Player %u got %u for total of %u\n", num1, pd[num1].turn_score, pd[num1].cumulative_score);
	}

	//player with highest score wins!
	for(num1=0; num1<NUM_PLAYERS; num1++){
		if(pd[num1].cumulative_score == leader_score) //a tie can happen here
			printf("winner: %u score: %u\n", num1, pd[num1].cumulative_score);
	}
	//printf("turns: %u\n", turn);
	printf("Game Over.\n");
	free(pd);
	return 0;
}

static uint32_t Roll_Dice(uint64_t *s, struct Player_Data *pd){
	uint32_t num1, num2, num3;
	uint8_t roll_results[DICE];
	uint8_t num_counts[SIDES];
	uint32_t num_scores[SIDES];
	uint8_t kept_dice[DICE];
	uint32_t score = 0;
	uint32_t score_final = 0;
	//const uint16_t Straight_score = 1500;
	//const uint16_t Three_pairs_score = 1000;
	uint32_t rolling_dice = DICE;
	uint8_t turn_not_over = 1;

	//struct Player_Data

	//for(num1=0; num1<ROLLS; num1++){

	while(turn_not_over == 1){
		//printf("%u\n", num1);
		printf("\t");
		for(num2=0; num2<rolling_dice; num2++){
			roll_results[num2] = lrand(s) % SIDES + 1;
			printf("%u ", roll_results[num2]);
		}
		printf("\n");
		sleep(1);
		(void)memset(num_counts, '\0', sizeof(uint8_t) * SIDES);
        for(num2=0; num2<rolling_dice; num2++){
            num_counts[ roll_results[num2]-1 ]++;
        }
		(void)memset(num_scores, '\0', sizeof(uint32_t) * SIDES);
		(void)memset(kept_dice, '\0', sizeof(uint8_t) * DICE);
		//set kept_dice
		for(num2=0; num2<rolling_dice; num2++){
			if(roll_results[num2] == 1 || roll_results[num2] == 5){
				kept_dice[num2] = 1;
			}
		}
		for(num2=0; num2<SIDES; num2++){
			if(num_counts[num2] > 2){
				for(num3=0; num3<rolling_dice; num3++){
					if(roll_results[num3] == num2+1)
						kept_dice[num3] = 1;
				}
			}
		}
		//set num_scores
		for(num2=0; num2<SIDES; num2++){
			if(num2 == 0){
				if(num_counts[0] < 3){
					num_scores[0] = 100*num_counts[0];
					//kept_dice[] = 
					continue;
				}
				//if(num_counts[0] == 3){
				//	num_scores[0] = 1000;
				//	continue;
				//}
				num_scores[0] = 1000;
				for(num3=3; num3<num_counts[0]; num3++){
					num_scores[0] *= 2;
				}
				continue;
			}
			if(num2 == 4){
				if(num_counts[4] < 3){
					num_scores[4] = 50*num_counts[4];
					continue;
				}
				num_scores[4] = 500;
				for(num3=3; num3<num_counts[4]; num3++){
					num_scores[4] *= 2;
				}
				continue;
			}
			if(num_counts[num2] < 3)
				continue;
			num_scores[num2] = 100*(num2+1);
			for(num3=3; num3<num_counts[num2]; num3++){
				num_scores[num2] *= 2;
			}
		}
		//(void)memset(kept_dice, '\0', sizeof(uint8_t) * DICE);
		//for(num2=0; num2<DICE; num2++){
			//printf highest score if kept only num2 dice
		score = 0;
		for(num3=0; num3<SIDES; num3++){
			score += num_scores[num3];
		}
		//}
//#if DICE > 5
		if(rolling_dice > 5){
			if( Has_Three_Pair(num_counts, rolling_dice) ){
			//must do entirely different if greater than 6 dice
			//could potentially have multiple three pairs ...
			//or three pairs + 1 + 5 + 3 of kind + etc.
				if(score < THREE_PAIRS_SCORE){
					//score = THREE_PAIRS_SCORE;
					//reset kept_dice[]
					//(void)memset(kept_dice, 1, sizeof(uint8_t) * DICE);
					//FIXME: must fix for more than 6 dice
					rolling_dice = DICE;
					score_final += THREE_PAIRS_SCORE;
					continue;
				}
			}
		}
//#endif

//#if DICE > 4
		if(rolling_dice == DICE){
			if( Has_Straight(roll_results, rolling_dice) ){
				if(score < STRAIGHT_SCORE){
					//score = STRAIGHT_SCORE;
					//reset kept_dice[]
					//(void)memset(kept_dice, 1, sizeof(uint8_t) * DICE);
					rolling_dice = DICE;
					score_final += STRAIGHT_SCORE;
					continue;
				}
			}
		}
//#endif
		//do zilch test here
		if(score == 0){
			turn_not_over = 0;
			score_final = 0;
			continue;
		}
		num1 = rolling_dice; //backup
		num3 = 0;
		for(num2=0; num2<rolling_dice; num2++){
			if(kept_dice[num2] == 1)
				num3++;
		}
		//rolling_dice -= num3;
		if(rolling_dice == num3){
			rolling_dice = DICE;
			score_final += score;
			//can use all dice
			continue;
		}else{
			rolling_dice -= num3;
		}

		//printf("\tscore: %u\n", score);
		//decide what dice to keep/roll or to end turn here
		if(pd->onboard != 1){
			if(score_final+score < MIN_GET_ONBOARD){
				//must keep rolling
				num3 = 0;
				//have 3+ of kind ?
				for(num2=0; num2<SIDES; num2++){
					if(num_counts[num2] > 2){ //FIXME: for more than 6 dice
						rolling_dice = num1 - num_counts[num2];
						num3 = 1;
						//score = num_scores[num2];
						score_final += num_scores[num2];
						break;
					}
				}
				if(num3 == 1)
					continue;
				//keep only a 1 to increase probabilities?
				if(num_counts[0] != 0){ //we have a 1
					if(num_counts[0] < 3){ //we have less than 1000
						//keep just 1
						rolling_dice = num1 - 1;
						//num3 = 1;
						//score = 100;
						score_final += 100;
						continue;
					}
					//keep them all
					rolling_dice = num1 - num_counts[0];
					//score = num_scores[0];
					score_final += num_scores[0];
					continue;
				}

				//have only a 5 ?
				if(num_counts[4] != 0){ //we have a 5
					rolling_dice = num1 - 1;
					//score = 50;
					score_final += 50;
					continue;
				}
				printf("Error 1 !\n");
				exit(0);
				//continue;
			}
		}
		if(score_final+score < pd->min_score_will_keep){
			//must keep rolling
			num3 = 0;
			for(num2=0; num2<SIDES; num2++){
				if(num_counts[num2] > 2){//FIXME: for more than 6 dice
					rolling_dice = num1 - num_counts[num2];
					num3 = 1;
					//score = num_scores[num2];
					score_final += num_scores[num2];
					break;
				}
			}
			if(num3 == 1)
				continue;
			if(num_counts[0] != 0){
				if(num_counts[0] < 3){
					rolling_dice = num1 - 1;
					//score = 100;
					score_final += 100;
					continue;
				}
				rolling_dice = num1 - num_counts[0];
				//score = num_scores[0];
				score_final += num_scores[0];
				continue;
			}
			if(num_counts[4] != 0){
				rolling_dice = num1 - 1;
				//score = 50;
				score_final += 50;
				continue;
			}
			printf("Error 2 !\n");
			exit(0);
			//continue;
		}else{
			turn_not_over = 0;
		}
		//if(score_final+score >= pd->min_score_will_keep){

		//}
		//if( < ){
			//will keep rolling if new rolling_dice value is not too low

		//}
		//if(pd->onboard != 1){
		//	if(score < MIN_GET_ONBOARD){
				//must keep rolling

		//	}
		if( rolling_dice < pd->min_dice_will_roll){
			//will keep rolling if new rolling_dice value is not too low
			//take score and end turn
			turn_not_over = 0;
			//score_final += score;
			//continue;
		}
		//keep only a 1 or 5 to increase probabilities?

		//can eliminate X dice to increase probabilities?

		//calculate new rolling_dice and score then continue
		score_final += score;
	}

	return score_final;
}