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

#define CMD_LEN        10
#define NAME_LEN       64
#define POV_3D_SIZE    10
#define POV_SIZE       3
#define INVENTORY_SIZE 1
#define MAP_SIZE       15

#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 CMD_GOTOMARKET    'm'
#define CMD_SHOWMAP       'm'

#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 SC_MARKET   7
#define SC_SHOWMAP  8

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

#define TILE_WALL      0
#define TILE_FREE      1
#define TILE_DOOR      2
#define TILE_START     3
#define TILE_POLE      4
#define TILE_ROOM      5
#define TILE_INIT      6

#define ITEM_POTION  'p'

typedef struct {
	char id;
	char name[NAME_LEN];
	int price;
	int count;
} Item;

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;
	Position position;
	Item inventory[INVENTORY_SIZE];
	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;
Character currentmonster;

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

char map[MAP_SIZE * MAP_SIZE];

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 tileonmap(char tile)
{
	switch (tile)
	{
	case TILE_FREE:      return ' '; // free passageway
	case TILE_DOOR:      return '+'; // door
	case TILE_START:     return 's'; // start point
	case TILE_ROOM:      return ' '; // place where one can stand
	case TILE_WALL:      return '#'; // wall
	case TILE_POLE:      return '#'; // "poles"
	case TILE_INIT:      return '.'; // not yet initialized by dfs
	}
	return '!';
}

char charatpos(int x, int y)
{
	return map[y * MAP_SIZE + x];
}

void setatpos(int x, int y, char c)
{
	if (x < 0 || y < 0 || x >= MAP_SIZE || y >= MAP_SIZE)
	{
		return;
	}
	map[y * MAP_SIZE + x] = c;
}

void drawmap()
{
	for (int y = 0; y < MAP_SIZE; y++) {
		printf("%02d ", y);
		for (int x = 0; x < MAP_SIZE; x++) {
			char toprint = tileonmap(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");
	}
	printf("   ");
	for (int x = 0; x < MAP_SIZE; x++) {
		printf("%d", x % 10);
	}
	printf("\n\n");
}

void drawpov3d()
{
	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");
}

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;
			}
			if (newX >= 0 && newY >= 0 && newX < MAP_SIZE && newY < MAP_SIZE)
			{
				pov[2 - frontoffset][1 + rtoloffset] = charatpos(newX, newY);
			}
		}
	}
	update3dpov();
}

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()
{
	printf("Démarrage...\n");
	map[0] = 0;
	srand(time(NULL));
	FILE* file = fopen(statepath, "r");
	if (file)
	{
		printf("Chargement de la partie...\n");
		fread(&state, sizeof(Gamestate), 1, file);
		fclose(file);
	}
	else
	{
		state.screen = SC_TITLE;
	}
	printf("Début de la boucle principale...\n");
}

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;

	Item potion = { ITEM_POTION, "potion", 20, 0 };
	state.inventory[0] = potion;
}

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 gotomonster()
{
	//int index = dice() == 6 ? 1 : 0;
	int index = 0;
	memcpy(&currentmonster, &(monsters[index]), sizeof(Character));
	state.screen = SC_MONSTER;
}

void gotostation()
{
	state.screen = SC_STATION;
}

void gotomarket()
{
	state.screen = SC_MARKET;
}

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

void drawrounds()
{
	printf("Rencontre avec %s.\n\n", currentmonster.name);
	Round* p = firstround;
	int n = 1;
	while (p)
	{
		printf("Tour %d\n", n++);
		if (p->action == CMD_ATTACK)
		{
			printf("%s perd %d points de vie\n", currentmonster.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 dfs(int x, int y)
{
	if (charatpos(x,y) != TILE_START)
	{
		setatpos(x, y, TILE_ROOM);
	}

	bool nok = false;
	bool eok = false;
	bool sok = false;
	bool wok = false;

	while (! (nok && eok && sok && wok))
	{
		char orientation = rand() % 4;
		switch (orientation)
		{
		case NORTH:
			nok = true;
			if (charatpos(x, y - 2) == TILE_INIT)
			{
				setatpos(x, y - 1, dice() == 6 ? TILE_DOOR : TILE_FREE);
				dfs(x, y - 2);
			}
			break;
		case SOUTH:
			sok = true;
			if (charatpos(x, y + 2) == TILE_INIT)
			{
				setatpos(x, y + 1, dice() == 6 ? TILE_DOOR : TILE_FREE);
				dfs(x, y + 2);
			}
			break;
		case EAST:
			eok = true;
			if (charatpos(x - 2, y) == TILE_INIT)
			{
				setatpos(x - 1, y, dice() == 6 ? TILE_DOOR : TILE_FREE);
				dfs(x - 2, y);
			}
			break;
		case WEST:
			wok = true;
			if (charatpos(x + 2, y) == TILE_INIT)
			{
				setatpos(x + 1, y, dice() == 6 ? TILE_DOOR : TILE_FREE);
				dfs(x + 2, y);
			}
			break;
		}
	}
}

void initmap()
{
	memset(&map, TILE_INIT, MAP_SIZE * MAP_SIZE);

	for (int x = 0; x < MAP_SIZE; x ++)
	{
		for (int y = 0; y < MAP_SIZE; y++)
		{
			if (x == 0 || y == 0 || x == MAP_SIZE - 1 || y == MAP_SIZE - 1)
			{
				// boundaries
				setatpos(x, y, TILE_WALL);
			}
			else if (x % 2 && y % 2)
			{
				// free tiles not yet initialized by dfs
				setatpos(x, y, TILE_INIT);
			}
			else if (!(x % 2 ) && !(y % 2))
			{
				// unused tiles aka "poles"
				setatpos(x, y, TILE_POLE);
			}
			else
			{
				// init all intersections with walls
				setatpos(x, y, TILE_WALL);

				//... and put a few free tiles here and there to create bigger rooms
				if (dice() == 1)
				{
					setatpos(x, y, TILE_FREE);
				}
			}
		}
	}

	// starting point
	setatpos(1, 13, TILE_START);

	// create map
	dfs(1, 13);
}

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)
			{
				//gotomonster();
			}
			break;
		case CMD_RIGHT:
			right();
			break;
		case CMD_LEFT:
			left();
			break;
		case CMD_BACKTOSTATION:
			if (currentchar() == TILE_START)
			{
				gotostation();
			}
			break;
		case CMD_SHOWMAP:
			state.screen = SC_SHOWMAP;
		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);
			gotostation();
		}
	}
	else if (state.screen == SC_STATION)
	{
		if (c == CMD_GOTOMAZE)
		{
			state.screen = SC_MAP;
			state.position.x = 1;
			state.position.y = 13;
			state.position.orientation = NORTH;

			if (map[0] == 0)
			{
				initmap();
			}
			updatepov();
		}
		else if (c == CMD_GOTOMARKET)
		{
			gotomarket();
		}
	}
	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, &currentmonster);
			}

			if (currentmonster.hp <= 0)
			{
				state.hero.gold += currentmonster.gold;
				state.screen = SC_WON;
			}
			else
			{
				// monster attacks
				herodmg = attack(&currentmonster, &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();
	}
	else if (state.screen == SC_MARKET)
	{
		if (c == CMD_BACKTOSTATION)
		{
			gotostation();
		}
		else
		{
			for (int i = 0; i < INVENTORY_SIZE; i++)
			{
				Item item = state.inventory[i];
				if (c == item.id && item.price <= state.hero.gold)
				{
					state.hero.gold -= item.price;
					state.inventory[i].count++;
				}
			}
		}
	}
	else if (state.screen == SC_SHOWMAP)
	{
		state.screen = SC_MAP;
	}
}

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

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)
	{

		drawpov3d();
		//printf("\n");
		//drawtopdownpov();
		//drawmap();

		// todo: would need a compass?
		/*printf("\n\n");
		switch (state.position.orientation)
		{
			case NORTH:
				printf("         ^\n");
				printf("        - -\n");
				printf("         -\n");
				break;
			case EAST:
				printf("         -\n");
				printf("        - >\n");
				printf("         -\n");
				break;
			case SOUTH:
				printf("         -\n");
				printf("        - -\n");
				printf("         v\n");
				break;
			case WEST:
				printf("         -\n");
				printf("        < -\n");
				printf("         -\n");
				break;
		}*/

		printf("\n\n");		
		if (charatpos(state.position.x, state.position.y) == TILE_START)
		{
			printf("r: Retour à la station\n");
		}
	}
	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");
		printf("m: Aller au super space market\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", currentmonster.name);
		printf("%s gagne %d crédits galactiques.\n", state.hero.name, currentmonster.gold);
	}
	else if (state.screen == SC_GAMEOVER)
	{
		drawrounds();
		printf("%s est fatigué.e.\nIel retourne à la station.\n", state.hero.name);
	}
	else if (state.screen == SC_MARKET)
	{
		printf("%s est au super space\nmarket.\n\n", state.hero.name);

		for (int i = 0; i < INVENTORY_SIZE; i++)
		{
			Item item = state.inventory[i];
			printf("%c: %s\n", item.id, item.name);
			printf("%s en a %d. Prix: %d\n", state.hero.name, item.count, item.price);
		}

		printf("\nr: Retour à la station\n");
	}
	else if (state.screen == SC_SHOWMAP)
	{
		// need a spell, or to have found a map?
		drawmap();
	}

	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);
		printf("\npotions : %d", state.inventory[0].count);
	}

	debugoutput();

	printf("\n");
	for (int i = 0; i < screenwidth; i++) printf("-");
	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_STATION)
	{
		update(command);
		draw();
		getcommand(command);
	}

	printf("Partie enregistrée.");
	savestate();
	return 0;
}