#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE_EXTENDED
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>
#include <unistd.h>

#include <ncurses.h>

#include <game.h>
#include <helper.h>
#include <tui.h>
#include <algorithm.h>
#include <config.h>
#include <items.h>

#define NOOBJ_OBJECT (struct object){.type = NOOBJ, .symbol = ' '}

unsigned m_id = 0;

static struct choice get_input(void)
{
	struct choice c;

start:;
	int res = getch();
	assert(res != ERR);
	switch(res)
	{
	case 'w':
		c.a = MOVE;
		c.u.d = UP;
		break;
	case 'a':
		c.a = MOVE;
		c.u.d = LEFT;
		break;
	case 's':
		c.a = MOVE;
		c.u.d = DOWN;
		break;
	case 'd':
		c.a = MOVE;
		c.u.d = RIGHT;
		break;
	case 'f':
		c.a = SPAWN;
		break;
	case 'q':
		c.a = QUIT;
		break;
	case 'i':
		c.a = INVENTORY;
		break;
	case KEY_RESIZE:
		c.a = REDRAW;
		break;
	case KEY_UP:
		log_scroll(UP);
		goto start;
	case KEY_DOWN:
		log_scroll(DOWN);
		goto start;
	default:
		c.a = INVALID;
	}

	return c;
}

//adds xp to m, increases stats and opens a messagebox
static void addxp(struct monster *m, unsigned xp)
{
	m->xp += xp;
	if(m->xp >= m->level *100) {
		m->xp -= m->level*100;
		m->level++;
		m->dmg++;
		m->max_hp += 10;
		m->current_hp += 10;
		const char *str = "!You leveled up!";
		messagebox(str, 1, strlen(str));
	}
}

//inflicts src's damage to target, removes targets monster if hp goes to zero
//gives src xp if target is killed
static void attack(struct monster *src, struct field *target)
{
	assert(target->has_monster);
	if(src->isplayer)
		logstr("%s hit %s for %d damage", src->name, target->m.name, src->dmg);
	
	if(src->dmg >= target->m.current_hp) {
		logstr("%s killed %s", src->name ,target->m.name);

		if(src->isplayer) {
			addxp(src, target->m.level*10);
		} else {
			messagebox("U done did dedden", 1, 0);
			exit(0);
		}
		darray_free(target->m.items);
		target->has_monster = false;
	} else {
		target->m.current_hp -= src->dmg;
	}
}

//returns true if adding was successful, otherwise false
static bool additem(struct monster *m, struct item *it)
{
	darray_append(m->items, *it);
	return true;
}

static void rmvitem(struct monster *m, size_t i)
{
	darray_remove(m->items, i);
}

//called by itemselect, uses an item if its usable
//equips it and does stat recalculation if its an equip
static bool inventorycallback(struct monster *m, size_t i)
{
	struct item *it = &darray_item(m->items,i);
	if(it->type == USE) {
		m->current_hp = MIN(m->max_hp, m->current_hp+it->u.u.restorehp);
		rmvitem(m, i);
		return true;
	} else if(it->type == EQUIP) {
		if(it->u.e.isequipped) {
			m->max_hp -= it->u.e.hp;
			m->current_hp = MAX(1, m->current_hp-it->u.e.hp);
			m->dmg -= it->u.e.dmg;
		} else {
			m->max_hp += it->u.e.hp;
			m->current_hp += it->u.e.hp;
			m->dmg += it->u.e.dmg;
		}

		it->u.e.isequipped = !it->u.e.isequipped;
	}
	return false;
}

static void inventory(struct monster *m)
{
	itemselect(m, inventorycallback);
}

//moves the object in src to target if target is empty
//puts an item in the inventory of src if src is a monster and target contains an item
//TODO: player moves on target, create new map
static struct point move_object(struct game *g, struct point src, struct point target)
{
	if((game_at(g, target)->u->ground.type == NOOBJ || game_at(g, target)->u->ground.type == TARGET) && !game_at(g, target)->has_monster) {
		game_at(g, target)->has_monster = true;
		game_at(g, target)->m = game_at(g, src)->m;
		game_at(g, src)->has_monster = false;
		return target;
	} else if(game_at(g, target)->u->ground.type == ITEM && !game_at(g, target)->has_monster && game_at(g, src)->has_monster) {
		if(!additem(&game_at(g, src)->m, &game_at(g, target)->u->it)) {
			logstr("Inventory is full");
			return src;
		}
		game_at(g, target)->has_monster = true;
		game_at(g, target)->m = game_at(g, src)->m;
		game_at(g, target)->u->ground = EMPTYFIELD;
		game_at(g, src)->has_monster = false;
		return target;
	} else {
		return src;
	}
}

static inline struct point attack_move(struct game *g, struct point src, struct point target)
{
	assert(game_at(g, src)->has_monster);
	struct field *t = game_at(g, target);

	if(t->has_monster) {
		attack(&game_at(g, src)->m, t);
	} else {
		return move_object(g, src, target);
	}
	return src;
}

static void run_ai(struct game *g, struct point player)
{
	darray(unsigned) checked = darray_new();
	for(size_t y = 0; y < g->ys; y++)
	{
		for(size_t x = 0; x < g->xs; x++)
		{
			pointarr res;
			if(game_at(g, (struct point){y,x})->has_monster && !game_at(g, (struct point){y,x})->m.isplayer) {
				unsigned *id;
				darray_foreach(id, checked)
				{
					if(*id == game_at(g, (struct point){y,x})->m.id)
						goto next;
				}
				darray_append(checked, game_at(g, (struct point){y,x})->m.id);
				
				if(insight(g, ((struct point){y,x}), player)) {
					res = astar(g, (struct point){y,x}, player, 20);
					if(res.size >= 2) {
						attack_move(g, (struct point){y,x}, darray_item(res, res.size-2));
					} else {
						struct point ps[4] = 
						{
							{y, x-1},
							{y, x+1},
							{y-1, x},
							{y+1, x}
						};
						move_object(g, (struct point){y,x}, ps[randrange(0,3)]);
					}
					darray_free(res);
				}

			}
		next:;
		}
	}
	darray_free(checked);
}

//creates game and spawns loot and enemies
static struct game setup_game()
{
	struct game g = make_maze(randrange(GAME_MIN_Y, GAME_MAX_Y), randrange(GAME_MIN_X, GAME_MAX_X));
	
	game_at(&g, (struct point){1,1})->m = (struct monster) //TODO: keep player between levels
	{
		.symbol = 'P',
		.id = -1,
		.name = "Player",
		.max_hp = 20,
		.current_hp = 20,
		.dmg = 2,
		.level = 1,
		.xp = 0,
		.items = darray_new(),
		.isplayer = true
	};
	game_at(&g, (struct point){1,1})->has_monster = true;
	
	size_t num_enemies = randrange(MIN_ENEMIES, MAX_ENEMIES);
	for(size_t i = 0; i < num_enemies; i++)
	{
		spawn_enemy(&g);
	}

	size_t num_items = randrange(MIN_LOOT, MAX_LOOT);
	for(size_t i = 0; i < num_items; i++)
	{
		spawn_item(&g);
	}
	
	return g;
}


void loop()
{
	struct game g = setup_game();

	struct point player = {1,1};

	while(true)
	{
		charclear();
		charprint("(づ｡◕‿‿◕｡)づ");
		charprint("Name: %s", game_at(&g, player)->m.name);
		charprint("HP : %u/%u", game_at(&g, player)->m.current_hp, game_at(&g, player)->m.max_hp);
		charprint("DMG: %u", game_at(&g, player)->m.dmg);
		charprint("LVL: %u", game_at(&g, player)->m.level);
		charprint("XP : %u", game_at(&g, player)->m.xp);
		draw_map(&g, player);

		struct choice c = get_input();
		switch(c.a)
		{
		case INVALID:
			logstr("Invalid input, try again");
			continue;
		case QUIT:
			delete_game(&g);
			return;
		case MOVE:;
			struct point oldpos = player;
			struct point targetpos =  relative(player, c.u.d);

			if(!in_bounds(&g, targetpos)) {
				logstr("Target not in bounds");
				continue;
			}

			player = attack_move(&g, player, targetpos);
			if(game_at(&g, player)->u->ground.type == TARGET)
			{
				delete_game(&g);
				loop();
				return;
			}
			run_ai(&g, oldpos);
			break;
		case SPAWN:
			spawn_enemy(&g);
			break;
		case INVENTORY:
			inventory(&game_at(&g, player)->m);
			break;
		case REDRAW:
			setup();
			printlog();
			break;
		}
	}
}
