#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#define CMD_LEN        10
#define NAME_LEN       20
#define POV_3D_SIZE    10
#define POV_SIZE       3

#define CMD_NULL          "-"
#define CMD_FWD           's'
#define CMD_ATTACK        'a'
#define CMD_FLEE          'f'
#define CMD_RIGHT         'x'
#define CMD_LEFT          'w'
#define CMD_BACKTOSTATION 'r'
#define CMD_GOTOMAZE      'v'

#define SC_TITLE    0
#define SC_SETNAME  1
#define SC_STATION  2
#define SC_MAP      3
#define SC_MONSTER  4
#define SC_GAMEOVER 5
#define SC_WON      6

#define NORTH 0
#define EAST  1
#define WEST  2
#define SOUTH 3

#define TILE_WALL  'w'
#define TILE_FREE  ' '
#define TILE_DOOR  'd'
#define TILE_START 's'

typedef struct Round {
	char action;
	int herodmg;
	int monsterdmg;
	struct Round* next;
} Round;

typedef struct {
	int x;
	int y;
	char orientation;
} Position;

typedef struct {
	char name[NAME_LEN];
	int hp;
	int gold;
	int atk;
	int def;
} Character;

typedef struct {
	Character hero;
	Character monster;
	Position position;
	char screen;
} Gamestate;

const char* statepath = "w.state";
char pov[POV_SIZE][POV_SIZE];
unsigned char pov3d[POV_3D_SIZE][POV_3D_SIZE];
Gamestate state;

struct Round* firstround = NULL;
struct Round* currentround = NULL;

int mapsize = 15;
char map[15][15] = {
	"wwwwwwwwwwwwwww",
	"w             w",
	"w wwwww wwwww w",
	"w w w d w   w w",
	"w w w w w   w w",
	"w w   w w   w w",
	"wdwwwwwdwwwwwdw",
	"w   w w     w w",
	"w   www     w w",
	"ws  w       w w",
	"wwwwwww     w w",
	"w           w w",
	"w           w w",
	"w           d w",
	"wwwwwwwwwwwwwww"
};

Character monsters[2] = {
	{ "Alien bleu", 2, 2, 1, 1 },
	{ "Vilaine araignée", 5, 8, 2, 1 }
};

int dice()
{
	return 1 + (rand() % 6);
}

int dices(int count)
{
	int result = 0;
	for (int i = 0; i < count; i++)
	{
		result += dice();
	}
	return result;
}

char charatpos(int x, int y)
{
	if (x < 0 || y < 0 || x >= mapsize || y >= mapsize)
	{
		return ' ';
	}
	return map[y][x];
}

void drawmap()
{
	for (int y = 0; y < mapsize; y++) {
		for (int x = 0; x < mapsize; x++) {
			char toprint = charatpos(x,y);
			if (y == state.position.y && x == state.position.x)
			{
				if (state.position.orientation == NORTH) toprint = '^';
				if (state.position.orientation == SOUTH) toprint = 'v';
				if (state.position.orientation == EAST) toprint = '>';
				if (state.position.orientation == WEST) toprint = '<';
			}
			printf("%c", toprint);
		}
		printf("\n");
	}
}

void savestate()
{
	FILE* file = fopen(statepath, "w");
	fwrite(&state, sizeof(Gamestate), 1, file);
	fclose(file);
}

char currentchar()
{
	return charatpos(state.position.x, state.position.y);
}

void update3dpov()
{
	for (int i = 0; i < POV_3D_SIZE; i++)
	{
		memset(pov3d[i], ' ', POV_3D_SIZE);
	}

	// front constants
	for (int i = 2; i < 8; i++)
	{
		pov3d[1][i] = '_';
		pov3d[7][i] = '_';
	}

	if (pov[2][0] == TILE_FREE)
	{
		// front left free
		pov3d[1][0] = '_';
		pov3d[1][1] = '_';
		pov3d[7][0] = '_';
		pov3d[7][1] = '_';
	}
	else
	{
		// front left wall
		pov3d[0][0] = '\\';
		pov3d[1][1] = '\\';
		pov3d[8][1] = '/';
		pov3d[9][0] = '/';
		for (int i = 2; i < 8; i++)	pov3d[i][1] = '|';
	}

	if (pov[2][2] == TILE_FREE)
	{
		// front right free
		pov3d[1][8] = '_';
		pov3d[1][9] = '_';
		pov3d[7][8] = '_';
		pov3d[7][9] = '_';
	}
	else
	{
		// front right wall
		pov3d[8][8] = '\\';
		pov3d[9][9] = '\\';
		pov3d[0][9] = '/';
		pov3d[1][8] = '/';
		for (int i = 2; i < 8; i++)	pov3d[i][7+1] = '|';
	}

	if (pov[1][1] == TILE_FREE)
	{
		// far constants
		pov3d[3][4] = '_';
		pov3d[3][5] = '_';
		pov3d[5][4] = '_';
		pov3d[5][5] = '_';

		if (pov[0][0] == TILE_FREE)
		{
			// far left free
			pov3d[3][2] = '_';
			pov3d[3][3] = '_';
			pov3d[5][2] = '_';
			pov3d[5][3] = '_';

			// if front is free as well
			if (pov[2][0] == TILE_FREE)
			{
				pov3d[3][0] = '_';
				pov3d[3][1] = '_';
				pov3d[5][0] = '_';
				pov3d[5][1] = '_';
			}
		}
		else
		{
			// far left wall
			pov3d[2][2] = '\\';
			pov3d[3][3] = '\\';
			pov3d[7][2] = '/';
			pov3d[6][3] = '/';
			pov3d[4][3] = '|';
			pov3d[5][3] = '|';

			// ensure front view is complete
			for (int i = 2; i < 8; i++)	pov3d[i][1] = '|';
		}

		if (pov[0][2] == TILE_FREE)
		{
			// far right free
			pov3d[3][6] = '_';
			pov3d[3][7] = '_';
			pov3d[5][6] = '_';
			pov3d[5][7] = '_';

			// if front is free as well
			if (pov[2][2] == TILE_FREE)
			{
				pov3d[3][8] = '_';
				pov3d[3][9] = '_';
				pov3d[5][8] = '_';
				pov3d[5][9] = '_';
			}
		}
		else
		{
			// far right wall
			pov3d[6][6] = '\\';
			pov3d[7][7] = '\\';
			pov3d[2][7] = '/';
			pov3d[3][6] = '/';
			pov3d[4][5+1] = '|';
			pov3d[5][5+1] = '|';

			// ensure front view is complete
			for (int i = 2; i < 8; i++)	pov3d[i][7+1] = '|';
		}
	}

	const char doorchar = '#';
	if (pov[1][1] == TILE_DOOR)
	{
		// front door
		for (int i = 4; i <= 7; i++)
		{
			for (int j = 3; j < 7; j++)
				pov3d[i][j] = doorchar;
		}
	}
	if (pov[2][0] == TILE_DOOR)
	{
		for (int i = 5; i < 9; i++) pov3d[i][0] = doorchar;
	}
	if (pov[2][2] == TILE_DOOR)
	{
		for (int i = 5; i < 9; i++) pov3d[i][9] = doorchar;
	}

	if (currentchar() == TILE_START)
	{
		pov3d[0][4] = '[';
		pov3d[0][5] = ']';
	}
}

void updatepov()
{
	int x = state.position.x;
	int y = state.position.y;
	char orientation = state.position.orientation;

	for (int frontoffset = 2; frontoffset >= 0; frontoffset--)
	{
		for (int rtoloffset = -1; rtoloffset < 2; rtoloffset++)
		{
			int newX = x, newY = y;
			switch (orientation)
			{
			case NORTH:
				newX += rtoloffset;
				newY -= frontoffset;
				break;
			case SOUTH:
				newX -= rtoloffset;
				newY += frontoffset;
				break;
			case EAST:
				newX += frontoffset;
				newY += rtoloffset;
				break;
			case WEST:
				newX -= frontoffset;
				newY -= rtoloffset;
				break;
			}
			pov[2 - frontoffset][1 + rtoloffset] = charatpos(newX, newY);
		}
	}
}

void forward()
{
	if (pov[1][1] != TILE_WALL)
	{
		switch (state.position.orientation)
		{
		case NORTH:
			state.position.y -= 2;
			break;
		case SOUTH:
			state.position.y += 2;
			break;
		case EAST:
			state.position.x += 2;
			break;
		case WEST:
			state.position.x -= 2;
			break;
		}
	}
}

void right()
{
	switch (state.position.orientation)
	{
	case NORTH:
		state.position.orientation = EAST;
			break;
	case SOUTH:
		state.position.orientation = WEST;
			break;
	case EAST:
		state.position.orientation = SOUTH;
			break;
	case WEST:
		state.position.orientation = NORTH;
			break;
	}
}

void left()
{
	switch (state.position.orientation)
	{
	case NORTH:
		state.position.orientation = WEST;
			break;
	case SOUTH:
		state.position.orientation = EAST;
			break;
	case EAST:
		state.position.orientation = NORTH;
			break;
	case WEST:
		state.position.orientation = SOUTH;
			break;
	}
}

void dumpstate()
{
	printf("Name:%s\nx:%d\ny:%d\norientation:%c\n",
		state.hero.name,
		state.position.x,
		state.position.y,
		state.position.orientation);
}

void clearscreen()
{
#ifdef _WIN32
	system("cls");
#else
	system("clear");
#endif
}

void init()
{
	srand(time(NULL));
	FILE* file = fopen(statepath, "r");
	if (file)
	{
		fread(&state, sizeof(Gamestate), 1, file);
		fclose(file);
	}
	else
	{
		state.screen = SC_TITLE;
	}
}

int attack(Character* attacker, Character* defender)
{
	char tmp[50];

	int a = dices(attacker->atk) ;
	int d = dices(defender->def) ;

	if (a > d)
	{
		int dmg = a - d;
		defender->hp -= dmg;
		return dmg;
	}

	return 0;
}

void createhero(char* name)
{
	strcpy(state.hero.name, name);
	state.hero.hp = 10;
	state.hero.gold = 0;
	state.hero.atk = 1;
	state.hero.def = 1;
}

void createround(char action, int herodmg, int monsterdmg)
{
	Round* newround = malloc(sizeof(Round));
	newround->next = NULL;
	newround->action = action;
	newround->herodmg = herodmg;
	newround->monsterdmg = monsterdmg;
	if (firstround == NULL)
	{
		firstround = newround;
	}
	else
	{
		currentround->next = newround;
	}
	currentround = newround;
}

void freerounds()
{
	Round* p = firstround;
	while (p)
	{

		Round* n = p->next;
		free(p);
		p = n;
	}
	firstround = NULL;
	currentround = NULL;
}

void update(char* command)
{
	char c = command[0];
	if (state.screen == SC_MAP)
	{
		switch (c)
		{
		case CMD_FWD:
		case '\0':
			int x = state.position.x;
			int y = state.position.y;
			forward();
			if ( (x != state.position.x || y != state.position.y) && dice() > 4)
			{
				memcpy(&state.monster, &(monsters[0]), sizeof(Character));
				state.screen = SC_MONSTER;
			}
			break;
		case CMD_RIGHT:
			right();
			break;
		case CMD_LEFT:
			left();
			break;
		case CMD_BACKTOSTATION:
			if (currentchar() == TILE_START)
			{
				state.screen = SC_STATION;
			}
			break;
		default:
			break;
		}
		updatepov();
	}
	else if (state.screen == SC_TITLE)
	{
		if (strcmp(command, CMD_NULL) != 0)
		{
			state.screen = SC_SETNAME;
		}
	}
	else if (state.screen == SC_SETNAME)
	{
		if (strlen(command) > 0)
		{
			createhero(command);
			state.screen = SC_STATION;
		}
	}
	else if (state.screen == SC_STATION)
	{
		if (c == CMD_GOTOMAZE)
		{
			state.screen = SC_MAP;
			state.position.x = 1;
			state.position.y = 9;
			state.position.orientation = NORTH;
			updatepov();
		}
	}
	else if (state.screen == SC_MONSTER)
	{
		int fleefails = 0;
		if (c == CMD_FLEE)
		{
			if (dice() > 3)
			{
				state.screen = SC_MAP;
				createround(c, 0, 0);
				freerounds();
			}
			else
			{
				fleefails = 1;
			}
		}

		if (c == CMD_ATTACK || fleefails)
		{
			// hero attacks
			int monsterdmg = 0;
			int herodmg = 0;
			if (!fleefails)
			{
				monsterdmg = attack(&state.hero, &state.monster);
			}

			if (state.monster.hp <= 0)
			{
				state.hero.gold += state.monster.gold;
				state.screen = SC_WON;
			}
			else
			{
				// monster attacks
				herodmg = attack(&state.monster, &state.hero);
				if (state.hero.hp <= 0)
				{
					state.hero.hp = 0;
					state.screen = SC_GAMEOVER;
				}
			}
			createround(c, herodmg, monsterdmg);
		}
	}
	else if (state.screen == SC_GAMEOVER)
	{
		state.hero.hp = 10;
		state.screen = SC_STATION;
		freerounds();
	}
	else if (state.screen == SC_WON)
	{
		state.screen = SC_MAP;
		freerounds();
	}

}


void debugoutput()
{
	//drawmap();
}

void drawrounds()
{
	printf("Rencontre avec %s.\n\n", state.monster.name);
	Round* p = firstround;
	while (p)
	{
		if (p->action == CMD_ATTACK)
		{
			printf("%s perd %d points de vie\n", state.monster.name, p->monsterdmg);
			printf("%s perd %d points de vie\n\n", state.hero.name, p->herodmg);
		}
		else if (p->action == CMD_FLEE)
		{
			printf("%s n'arrive pas à fuir et perd %d points de vie\n\n", state.hero.name, p->herodmg);
		}
		else
		{
			printf("??\n");
		}
		p = p->next;
	}
}

void draw()
{
	clearscreen();

	int screenwidth = 30;
	for (int i = 0; i < screenwidth; i++) printf("-");
	printf("\n");

	if (state.screen == SC_TITLE)
	{
		printf("        _        _\n");
		printf("       / /      / /\n");
		printf("      / /      / /\n");
		printf("     / /__a   / /__égende\n");
		printf("    |____/   |____/\n");
		printf("\n");
		printf("        du  ___\n");
		printf("           / _ \\ \n");
		printf("          / / | |\n");
		printf("         / /_/ /ongeon\n");
		printf("        /____ /\n");
		printf("\n");
		printf("          _____\n");
		printf("   de l' / ___/\n");
		printf("        / /_\n");
		printf("       / /___space\n");
		printf("      /_____/\n");
	}
	else if (state.screen == SC_SETNAME)
	{
		printf("Bonjour.\n\n");
		printf("Bienvenue à la station\n");
		printf("spaciale intergalactique.\n\n");
		printf("Veuillez saisir votre nom.\n");
	}
	else if (state.screen == SC_MAP)
	{
		update3dpov();
		int shift = 9;
		for (int i = 0; i < shift; i++) printf(" ");
		printf("o----------o\n");

		for (int i = 0; i < POV_3D_SIZE; i++)
		{
			for (int s = 0; s < shift; s++) printf(" ");
			printf("|");
			for (int j = 0; j < POV_3D_SIZE; j++)
			{
				printf("%c", pov3d[i][j]);
			}
			printf("|\n");
		}
		for (int i = 0; i < shift; i++) printf(" ");
		printf("o----------o");

		if (charatpos(state.position.x, state.position.y) == TILE_START)
		{
			printf("\n\nr: Retour à la station\n");
		}
		else
		{
			printf("\n               ^\n");
			printf("            <w s x>\n");
		}
		//drawmap();
	}
	else if (state.screen == SC_STATION)
	{
		printf("%s est à la station\nspatiale intergalactique.\n", state.hero.name);
		printf("\n");

		printf("v: Partir en vaisseau\n");
	}
	else if (state.screen == SC_MONSTER)
	{
		drawrounds();

		printf("a: Attaquer\n");
		printf("f: Tenter de fuir\n");
	}
	else if (state.screen == SC_WON)
	{
		drawrounds();

		printf("%s est vaincu.\n", state.monster.name);
		printf("%s gagne %d crédits galactiques.\n", state.hero.name, state.monster.gold);
	}
	else if (state.screen == SC_GAMEOVER)
	{
		drawrounds();
		printf("%s est fatigué.e.\nIel retourne à la station.\n", state.hero.name);
	}

	if (state.screen != SC_TITLE && state.screen != SC_SETNAME)
	{
		printf("\n");
		for (int i = 0; i < screenwidth; i++) printf("-");
		printf("\n");
		printf("%s | P.V. : %d | C.G. : %d", state.hero.name, state.hero.hp, state.hero.gold);
	}

	debugoutput();

	printf("\n");
	for (int i = 0; i < screenwidth; i++) printf("-");
	printf("\n> ");

}

void drawtopdownpov()
{
	for (int y = 0; y < POV_SIZE; y++)
	{
		for (int x = 0; x < POV_SIZE; x++)
		{
			if (y == 0 && pov[1][1] != ' ')
			{
				printf(" ");
			}
			else if (y == 2 && x == 1)
			{
				printf("^");
			}
			else
			{
				printf("%c", pov[y][x]);
			}
		}
		printf("\n");
	}
}

void getcommand(char* command)
{
	fgets(command, sizeof(char) * CMD_LEN, stdin);
	command[strcspn(command, "\n")] = 0;
}

int main()
{
	init();

	char command[CMD_LEN] = CMD_NULL;

	while (command[0] != 'q' || state.screen == SC_MONSTER)
	{
		update(command);
		draw();
		getcommand(command);
	}

	savestate();
	return 0;
}