#ifndef GAME_H
#define GAME_H
#include <stddef.h>
#include <stdbool.h>
#include <assert.h>
#include <helper.h>
#include <darray.h>

#define MAX(x,y) (x >= y ? x : y)
#define MIN(x,y) (x <= y ? x : y)

#define ORC (struct monster)\
			{\
				.symbol = 'O',\
				.name = "Zog Zog", \
				.id = m_id++, .current_hp = 10, .max_hp = 10, .dmg = 1,\
				.level = 5 , .xp = 0, .items = darray_new(), .isplayer = false\
			}

#define EMPTYFIELD  (struct object)\
					{\
						.type = NOOBJ,\
						.symbol = ' ',\
					}
#define WALLFIELD (struct object)\
				{\
					.type = WALL,\
					.symbol = '#',\
				}

extern unsigned m_id;

enum objtype {NOOBJ = 0, MONSTER, ITEM, WALL, TARGET};
enum itemtype {NOITEM = 0, EQUIP, USE};

struct equip
{
	unsigned hp;
	unsigned dmg;
	bool isequipped;
};

struct use
{
	unsigned restorehp;
};

struct object
{
	enum objtype type;
	char symbol;
};

struct item
{
	struct object o;
	const char *name;
	enum itemtype type;
	union
	{
		struct equip e;
		struct use u;
	}u;
};


struct monster
{
	char symbol;
	const char *name;
	unsigned id;
	unsigned current_hp;
	unsigned max_hp;
	unsigned dmg;
	unsigned level;
	unsigned xp;
	darray(struct item) items;
	bool isplayer;
};

struct field
{
	union
	{
		struct object ground;
		struct item it;
	} *u;
	struct monster m;
	bool has_monster;
};

struct game
{
	struct field *map;
	size_t xs, ys;
};

static inline struct field *game_at(const struct game *g, struct point p)
{
	return &g->map[p.y * g->xs + p.x];
}

static inline struct game create_game(size_t y, size_t x)
{
	size_t res = y * x;
	if (y != 0 && res / y != x) {
		assert(false);
    }
	struct game ret;
	ret.map = xmalloc(x * y * sizeof(*ret.map));
	ret.xs = x;
	ret.ys = y;
	
	for(size_t i = 0; i < x*y; i++)
	{
		ret.map[i].u = xmalloc(sizeof(*ret.map[i].u));
		ret.map[i].has_monster = false;
	}

	return ret;
}

static inline void delete_game(struct game *g)
{
	for(size_t y = 0; y < g->ys; y++)
	{
		for(size_t x = 0; x < g->xs; x++)
		{
			if(game_at(g, (struct point){y,x})->has_monster)
				darray_free(game_at(g, (struct point){y,x})->m.items);
			free(game_at(g, (struct point){y,x})->u);
		}
	}
	free(g->map);
}

enum action {MOVE, SPAWN, QUIT, INVENTORY, REDRAW, INVALID};
enum direction {UP,DOWN,LEFT,RIGHT};

struct choice
{
	enum action a;
	union
	{
		enum direction d;
	}u;
};

//returns a point that is relative to p in the direction d
static inline struct point relative(struct point p, enum direction d)
{
	switch(d)
	{
	case UP:
		return (struct point){p.y - 1, p.x};
	case DOWN:
		return (struct point){p.y + 1, p.x};
	case LEFT:
		return (struct point){p.y, p.x - 1};
	case RIGHT:
		return (struct point){p.y, p.x + 1};
	default:
		assert(false);
	}
}

static inline bool in_bounds(struct game *g, struct point p)
{
	if(p.y >= 0 && (size_t)p.y < g->ys && p.x >= 0 && (size_t)p.x < g->xs)
		return true;
	else
		return false;
}

//randomly puts an enemy in an empty spot on the map
static inline void spawn_enemy(struct game *g) 
{
	while(true)
	{
		struct point p = {randrange(0, g->ys), randrange(0, g->xs)};
		if(in_bounds(g, p) && game_at(g, p)->u->ground.type != WALL && !game_at(g, p)->has_monster) {
			game_at(g, p)->m = ORC;
			game_at(g, p)->has_monster = true;
			break;
		}
	}
}

void loop(); //main game loop

#endif
