
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#define BOARD_SIZE 10
#define MAX_SHIP_SIZE 5
#define MIN_SHIP_SIZE 2

#define NUM_SHIP_TYPES (MAX_SHIP_SIZE - MIN_SHIP_SIZE + 1)

#define SHOT_ERR_EINVAL -1
#define SHOT_ERR_SUNKEN -2
#define SHOT_WATER       0
#define SHOT_HIT         1
#define SHOT_SUNKEN      2

#define QUOTE(str) #str
#define EVAL_QUOTE(str) QUOTE(str)

#define MAX_SHIP_SIZE_STR EVAL_QUOTE(MAX_SHIP_SIZE)
#define MIN_SHIP_SIZE_STR EVAL_QUOTE(MIN_SHIP_SIZE)

int _num_ships_to_place(int ship_types) {
	if(ship_types == 0)
		return 0;

	return ship_types + _num_ships_to_place(ship_types - 1);
}

int num_ships_to_place() { return _num_ships_to_place(NUM_SHIP_TYPES); }

int total_ships;

enum orientation {
	ORIENTATION_HORZ = 0,
	ORIENTATION_VERT = 1,
};

struct battleship_board;
typedef int (*turn_function_t)(struct battleship_board *);
typedef void (*win_function_t)(struct battleship_board *);


const char* field_marks[] = {".", "□", "▣"};

typedef char status_t;


struct battleship_board {
	uint8_t orig_field[BOARD_SIZE][BOARD_SIZE];
	uint8_t field[BOARD_SIZE][BOARD_SIZE];
	win_function_t win;
	turn_function_t turn;
	int alive_fields;
};


struct battleship_board enemy_board;
struct battleship_board player_board;

void init(void)
{
	int fd;
	unsigned int rand_val;
	// setting stdout and stdin to be unbuffered
	setvbuf(stdout,0,_IONBF,0);
	setvbuf(stdin,0,_IONBF,0);
	// seeding the random number generator
	fd = open("/dev/urandom", O_RDONLY);
	read(fd, &rand_val, sizeof(rand_val));
	close(fd);
	srand(rand_val);
}


/* Used to check whether a field is occupied. Fields outside of the map
 * are considered unoccupied
 */
int is_field_occupied(struct battleship_board *board, int x, int y)
{
	/* Check in bounds */
	if (!(x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE))
		return 0;
	return board->orig_field[x][y];
}

/* Only to be called in preparation phase, assumes 0 initialized field */
int can_field_have_ship(struct battleship_board *board, int x, int y)
{
	/* Check in bounds */
	if (!(x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE))
		return 0;

	for (int xi = -1; xi <= 1; xi++)
		for (int yi = -1; yi <= 1; yi++)
			if (is_field_occupied(board, x+xi, y+yi))
				return 0;
	return 1;
}

int does_ship_fit(struct battleship_board *board, int ship_size, int x, int y, enum orientation orientation)
{
	for (int i = 0; i < ship_size; i++)
		if (!can_field_have_ship(board, x + i * !orientation , y + i * orientation))
			return 0;
	return 1;
}

void place_ship(struct battleship_board *board, int ship_size, int x, int y, enum orientation orientation)
{
	for (int i = 0; i < ship_size; i++)
		board->orig_field[x + i * !orientation][y + i * orientation] = ship_size;
	board->alive_fields += ship_size;
}


/* Brute force spot, lol! */
int can_any_ship_be_placed(struct battleship_board *board, int size)
{
	for (int o = 0; o < 2; o++)
		for (int x = 0; x < BOARD_SIZE; x++)
			for (int y = 0; y < BOARD_SIZE; y++)
				if (does_ship_fit(board, size, x, y, (enum orientation)o))
					return 1;
	return 0;
}

void randomly_generate_board(struct battleship_board *board)
{
	int x, y;
	enum orientation orientation;
	int ships_to_place;
	memset(board->orig_field, 0, sizeof(board->orig_field));
	board->alive_fields = 0;
	for(int ship_size = MAX_SHIP_SIZE; ship_size >= MIN_SHIP_SIZE; --ship_size) {
		ships_to_place = MAX_SHIP_SIZE + 1 - ship_size;
		do {
			/* If no space is left for any ship, try a new one! */
			if(!can_any_ship_be_placed(board, ship_size)) {
				memset(board->orig_field, 0, sizeof(board->orig_field));
				board->alive_fields = 0;
				ship_size = 6;
				continue;
			}
			/* Randomly place one ship */
			while (1) {
				x = rand() % BOARD_SIZE; y = rand() % BOARD_SIZE;
				orientation = (enum orientation)(rand() % 2);

				if (does_ship_fit(board, ship_size, x, y, orientation)) {
					place_ship(board, ship_size, x, y, orientation);
					ships_to_place--;
					break;
				}
			}
		}
		while (ships_to_place);
	}
	memcpy(board->field, board->orig_field, sizeof(board->field));
}



void print_board(struct battleship_board *board, int hide_unknowns)
{
	printf("------------\n");
	printf("  ");
	for(int i = 0; i < BOARD_SIZE; ++i)
		printf("%c", 'a' + i);
	printf("\n");
	printf("\n");
	for(int y = 0; y < BOARD_SIZE; ++y) {
		printf("%c ", '0' + y);
		for(int x = 0; x < BOARD_SIZE; ++x) {
			if(!board->orig_field[x][y] || (hide_unknowns && board->orig_field[x][y] == board->field[x][y]))
				printf("%s", field_marks[0]);
			else
				printf("%s", field_marks[1+!board->field[x][y]]);
		}
		printf("\n");
	}
	printf("------------\n");
}



char get_char() {
	char buf[0x20];
	read(0, buf, 0x20);
	return buf[0];
}

int get_int() {
	char buf[0x20];
	buf[0x19] = 0;
	read(0, buf, 0x19);
	return atoi(buf);
}


void manually_create_player_board(struct battleship_board* player_board)
{
	int ships_to_create[NUM_SHIP_TYPES];
	int num_ships_to_create = total_ships;
	int x, y, size, orientation;
	for(int i = MIN_SHIP_SIZE; i <= MAX_SHIP_SIZE; i++)
		ships_to_create[i-MIN_SHIP_SIZE] = MAX_SHIP_SIZE+1-i;

	memset(player_board->orig_field, 0, sizeof(player_board->orig_field));
	memset(player_board->field, 0, sizeof(player_board->field));
	player_board->alive_fields = 0;

	printf("Welcome to the board builder!\n");

	while(num_ships_to_create)
	{
		printf("This is your current board_state:\n");
		print_board(player_board, 0);

		printf("You can still set the following ships:\n");
		for(int i = MAX_SHIP_SIZE; i >= MIN_SHIP_SIZE; i--)
			printf("Ship of size %d: %d left\n", i, ships_to_create[i-MIN_SHIP_SIZE]);

		printf("Would you like to add a ship or restart?\n"
		       " 1 - Add a ship\n"
		       " 2 - Restart\n"
		       "Choice: ");

		switch(get_int()) {
			case 1:
				printf("Which size? ");
				size = get_int();
				if (size < MIN_SHIP_SIZE || size > MAX_SHIP_SIZE) {
					printf("Invalid size\n");
					break;
				}

				if (!ships_to_create[size-MIN_SHIP_SIZE]) {
					printf("No more ships of that size left!\n");
					break;
				}

				printf("Orientation?\n"
				       "  1 - Horizontal\n"
				       "  2 - Vertical\n"
				       "Choice: ");
				orientation = get_int() - 1;
				if (orientation != 0 && orientation != 1) {
					printf("Invalid choice!\n");
					break;
				}

				printf("X coordinate? ");
				x = get_char() - 'a';
				printf("Y coordinate? ");
				y = get_int();

				if (!does_ship_fit(player_board, size, x, y, (enum orientation)orientation)) {
					printf("Ship does not fit there!\n");
					break;
				}
				place_ship(player_board, size, x, y, (enum orientation)orientation);
				memcpy(player_board->field, player_board->orig_field, sizeof(player_board->field));
            ships_to_create[size-MIN_SHIP_SIZE]--;
            num_ships_to_create--;
            break;
			case 2:
				/* Code duplication FTW */
				num_ships_to_create = total_ships;
				for(int i = MIN_SHIP_SIZE; i <= MAX_SHIP_SIZE; i++)
					ships_to_create[i-MIN_SHIP_SIZE] = MAX_SHIP_SIZE+1-i;

				memset(player_board->orig_field, 0, sizeof(player_board->orig_field));
				memset(player_board->field, 0, sizeof(player_board->field));
				player_board->alive_fields = 0;
				break;
			default:
				printf("Invalid choice!\n");
				break;
		}

	}
}


void generate_player_board(struct battleship_board* player_board)
{
	printf("How would you like to arrange your board?\n"
	       " 1 - Manually\n"
	       " 2 - Randomly\n"
	       "Choice: ");

	int repeat;
	do {
		repeat = 0;
		switch(get_int()) {
			case 1:
				manually_create_player_board(player_board);
				break;
			case 2:
				randomly_generate_board(player_board);
				break;
			default:
				printf("Wrong choice!\n");
				repeat = 1;
				break;
		}
	} while(repeat);
}

void win_player(struct battleship_board *board) {
	if (board == &enemy_board) {
		printf("INTERNAL ERROR!\n");
		_exit(1);
	}

	printf("Congratulations, your witness outsmarted the AI, here's your prize:\n");

	pid_t p = fork();
	if(p == -1) { _exit(1); }

	if(!p) {
		/* Child */
		execl("/bin/get_flag", "get_flag", NULL);
	} else {
		int r = 0;
		waitpid(p, &r, 0);
	}
}

void win_cpu(struct battleship_board *board) {
	if(board == &player_board) {
		printf("Congratulations, your AI won!\n");
	}
	else {
		printf("Game Over! You lost!\n");
	}
}




/********* General turn stuff *********/

int do_shot(struct battleship_board *board, int strength, int x, int y) {
	if(!(x >= 0 && x <= BOARD_SIZE && y >= 0 && y <= BOARD_SIZE)) {
		return SHOT_ERR_EINVAL;
	}

	/* If there was already a sunken ship, return error */
	if(board->orig_field[x][y] && !board->field[x][y])
		return SHOT_ERR_SUNKEN;

	/* Now perform shot */
	if(!board->field[x][y])
		return SHOT_WATER;

	board->field[x][y] -= strength;

	if(!board->field[x][y])
		return SHOT_SUNKEN;
	return SHOT_HIT;
}


int is_player_dead(struct battleship_board *board) {
	if (board->alive_fields)
		return 0;

	/* Sanity check that actually all fields are dead, helpful for debugging,
	 * remove in production */
	for (int x = 0; x < BOARD_SIZE; x++)
		for (int y = 0; y < BOARD_SIZE; y++)
			if (board->field[x][y]) {
				printf("INTERNAL ERROR in is_player_dead!!\n");
				_exit(1);
			}

	return 1;
}

void do_turn(struct battleship_board *self, struct battleship_board *enemy) {
	int r;
	while(1) {
		r = self->turn(enemy);

		if (r != SHOT_SUNKEN)
			break;

		if(is_player_dead(enemy)) {
			self->win(self);
			exit(0);
		}
		printf("The field was sunken, time for another shot!\n");
	}
}


/********* CPU TURN *********/

int do_smart_cpu_turn(struct battleship_board *board, int *x, int *y, int *strength) {

	while (1) {
		*x = rand() % BOARD_SIZE; *y = rand() % BOARD_SIZE;

		if (!board->field[*x][*y])
			continue;
		*strength = board->field[*x][*y];

		return do_shot(board, *strength, *x, *y);
	}
}

int do_random_cpu_turn(struct battleship_board *board, int *x, int *y, int *strength) {
	int r;
	while (1) {
		*strength = MIN_SHIP_SIZE + rand() % NUM_SHIP_TYPES;
		*x = rand() % BOARD_SIZE; *y = rand() % BOARD_SIZE;

		r = do_shot(board, *strength, *x, *y);
		if (r < 0)
			continue;
		return r;
	}
}


int do_cpu_turn(struct battleship_board *board) {
	int decision = rand() % 2;
	int x, y, strength, r;
	if(decision)
		r = do_smart_cpu_turn(board, &x, &y, &strength);
	else
		r = do_random_cpu_turn(board, &x, &y, &strength);

	printf("AI shot at (%c, %d) with strength %d\n", 'a' + x, y, strength);

	if(r == SHOT_HIT) {
		printf("... and hit a ship, but the field is still standing!\n");
	} else if (r == SHOT_SUNKEN) {
		board->alive_fields -= 1;
		printf("... and sunk the field\n");
	}
	else if (r == SHOT_WATER)
		printf("... and hit the water\n");
	return r;
}


/********* PLAYER TURN *********/

int do_player_turn(struct battleship_board *board) {
	int x, y, strength, r, c;

	while (1) {
		printf("Are you sure you do not want to give up already?\n"
				 " 1 - Give up!\n"
				 " 2 - I'm confident I can beat the AI!\n"
				 "Choice: ");
		c = get_int();

		if(c == 1)
		{
			board->win(board);
			exit(0);
		}
		if(c != 2)
			continue;

		printf("Where would you like to shoot?\n");
		printf("X: ");
		x = get_char() - 'a';
		printf("Y: ");
		y = get_int();

		printf("How strong would you like to shoot?\n");
		printf("Strength: ");
		strength = get_int();

		r = do_shot(board, strength, x, y);
		if (r < 0) {
			if (r == SHOT_ERR_EINVAL)
				printf("Invalid coordinates, choose again!\n");

			if (r == SHOT_ERR_SUNKEN)
				printf("You already sunk a field there, choose again!\n");
			continue;
		}

		printf("You shot at (%c, %d) with strength %d\n", 'a' + x, y, strength);

		if(r == SHOT_HIT) {
			printf("... and hit a ship, but did not sink it\n");
		} else if (r == SHOT_SUNKEN) {
			board->alive_fields -= 1;
			printf("... and sunk the field\n");
		}
		else if (r == SHOT_WATER)
			printf("... and hit the water\n");

		return r;
	}
}




int main()
{
	init();

	printf("Welcome to Battleships Cave B100X!\n"
	       "To get to the treasure, you'll have to prove your worth\n"
	       "against our newly developed high-tech AI!\n"
	       "The rules are as follows:\n"
	       " Ship placing:\n"
	       "  - Each player arranges ships onto a 10x10 field\n"
	       "  - The ship sizes range from " MIN_SHIP_SIZE_STR " to " MAX_SHIP_SIZE_STR "\n"
	       "  - You can place 1 ship of the highest size, 2 of the second highest and so forth...\n"
	       "  - Ships can be placed horizontally and vertically, but may not touch in any way\n"
	       "  - NEW: Fields have Health Points (HP) that start equal to the size of the ship\n"
	       " Fighting phase:\n"
	       "  - Players name coordinate and strength to perform a shot\n"
	       "   - If the shot hits a field but does not sink it, the player will be informed of the hit, but not the HP\n"
	       "   - If a shot hits and sinks a field, the player can shoot again\n"
	       "  - Note: If you shoot stronger than the remaining HP, a field will gain 256 extra health!\n"
	       "  - The player that first sinks all ships/fields of the opponent wins\n");

	int sleep_time = 1;
	total_ships = num_ships_to_place();
	randomly_generate_board(&enemy_board);
	enemy_board.turn = do_cpu_turn;
	enemy_board.win = win_cpu;

	generate_player_board(&player_board);

	printf("Here is your board:\n");
	print_board(&player_board, 0);

	printf("You're given the chance not to embarrass yourself against the AI and let another AI play.\n"
			 "What would you like to do?\n"
	       " 1 - Let AI play\n"
	       " 2 - I want to play\n"
	       "Choice: ");

	if (get_int() == 2) {
		printf("So you think you can outsmart an AI, huh?\n");
		player_board.turn = do_player_turn;
		player_board.win = win_player;
	}
	else {
		printf("AI chosen\n");
		player_board.turn = do_cpu_turn;
		player_board.win = win_cpu;
		sleep_time = 2;
	}


	/* Main loop */

	while (1) {
		printf("================ PLAYER TURN ================\n");
		printf("---------------- your  board ----------------\n");
		print_board(&player_board, 0);
		printf("Alive fields: %d\n", player_board.alive_fields);
		printf("---------------- enemy board ----------------\n");
		print_board(&enemy_board, 1);
		printf("Alive fields: %d\n", enemy_board.alive_fields);
		do_turn(&player_board, &enemy_board);
		sleep(sleep_time);
		printf("================ ENEMY  TURN ================\n");
		do_turn(&enemy_board, &player_board);
	}


}
