#include "main.h"
#include "entity.h"
#include "loader.h"
#include "tilemap.h"
#include <stdbool.h>
#include <math.h>
#include "particles.h"

#define SIZE 4
#define ACCELERATION 1
#define FRICTION 2
#define MAX_SPEED 16
#define GRAVITY 1
#define WALKABLE_CHECK_DISTANCE 10
#define IDLE_TIME 90
#define ATTACK_DELAY 120
#define ATTACK_DELAY2 60
#define ATTACK_REPEAT 60
#define DETECTION_RANGE to_fixed(64)
#define GIVE_UP_RANGE to_fixed(96)
#define CHASE_RANGE to_fixed(48)
#define RUN_RANGE to_fixed(32)
#define TARGET_RANGE to_fixed(40)

struct walker_ext {
	unsigned idle_time, attack_delay, attack_delay2, attack_repeat;
	unsigned detection_range, give_up_range;
	signed facing;
};

enum {
	WALKER_NONE,
	WALKER_FALL,
	WALKER_IDLE,
	WALKER_PATROL,
	WALKER_ALERT_IDLE,
	WALKER_ALERT_CHASE,
	WALKER_ALERT_RUN,
	WALKER_ALERT_HALT,
	WALKER_ALERT_ATTACK,
};

enum {
	WALKER_A_IDLE,
	WALKER_A_IDLE2,
	WALKER_A_WALK,
	WALKER_A_WALK2,
	WALKER_A_WALK3,
	WALKER_A_WALK4,
	WALKER_A_JUMP,
	WALKER_A_FALL,
	WALKER_A_FALL2,
	WALKER_A_ATTACK,
};

struct anim walker_anims[] = {
	{WALKER_A_IDLE2, {0, 0, 16, 16}, 300},
	{WALKER_A_IDLE, {64, 0, 16, 16}, 2},
	{WALKER_A_WALK2, {0, 0, 16, 16}, 6},
	{WALKER_A_WALK3, {16, 0, 16, 16}, 6},
	{WALKER_A_WALK4, {32, 0, 16, 16}, 6},
	{WALKER_A_WALK, {48, 0, 16, 16}, 6},
	{WALKER_A_JUMP, {16, 0, 16, 16}, 300},
	{WALKER_A_FALL2, {32, 0, 16, 16}, 15},
	{WALKER_A_FALL2, {48, 0, 16, 16}, 15},
	{WALKER_A_ATTACK, {80, 0, 16, 16}, 15},
};

static int bullet_update(struct projectile *self) {
	self->x += self->velocity.x;
	if (collision_solid(tilemap_tile(tilemap, from_fixed(self->x), from_fixed(self->y)))) {
		self->velocity.x = -self->velocity.x;
		self->x += self->velocity.x;
	}
		
	self->y += self->velocity.y;
	if (collision_solid(tilemap_tile(tilemap, from_fixed(self->x), from_fixed(self->y)))) {
		self->velocity.y = -self->velocity.y;
		self->y += self->velocity.y;
	}

	self->hp--;
	int x = from_fixed(self->x);
	int y = from_fixed(self->y);
	self->hitbox = (struct hitbox) {.left = x, .right = x, .top = y, .bottom = y};
	if (hitbox_overlap(self->hitbox, entities.player[0].hitbox)) {
		entities.player[0].hurt(entities.player + 0, (struct damage) {1, 60});
		return 1;
	}
	if (self->hp == 0) {
		return 1;
		self->state = 0;
	}
	return 0;
}

static int bullet_draw(struct projectile *self, int camX, int camY) {
	SDL_Rect const *rect;
	if (self->hp & 0x2) {
		rect = &particle_red;
	} else {
		rect = &particle_white;
	}
	SDL_RenderCopy(renderer, self->texture, rect, &(SDL_Rect) {from_fixed(self->x) - camX - 1, from_fixed(self->y) - camY - 1, 4, 4});
	SDL_RenderCopy(renderer, self->texture, rect, &(SDL_Rect) {from_fixed(self->x - self->velocity.x) - camX - 1, from_fixed(self->y - self->velocity.y) - camY - 1, 4, 4});
	//SDL_RenderFillRect(renderer, &(SDL_Rect) {from_fixed(self->x) - camX - 8, from_fixed(self->y) - camY - 12, 16, 16});
	return 0;
}

static void bullet_free(struct projectile *self) {}

static collision_T collide(struct entity *self) {
	return tilemap_area(tilemap, from_fixed(self->x) - SIZE / 2, from_fixed(self->y) - SIZE, from_fixed(self->x) + SIZE / 2, from_fixed(self->y));
}

static int move(struct entity *self, signed direction) {
	int on_ground = false;
	int const dx = direction * ACCELERATION;
	self->velocity.x += dx;
	// deaccel
	if (dx == 0) {
		if (self->velocity.x < -FRICTION) {
			self->velocity.x += FRICTION;
		} else if (self->velocity.x > FRICTION) {
			self->velocity.x -= FRICTION;
		} else {
			self->velocity.x = 0;
		}
	}
	// speed cap
	if (self->velocity.x > MAX_SPEED) {
		self->velocity.x = MAX_SPEED;
	} else if (self->velocity.x < -MAX_SPEED) {
		self->velocity.x = -MAX_SPEED;
	}
	// x collision
	self->x += self->velocity.x;
	collision_T const cx = collide(self);
	if (collision_solid(cx)) {
		if (self->velocity.x < 0) {
			self->x += to_fixed(8) - ((self->x - to_fixed(SIZE / 2)) % to_fixed(8)); // left
		} else if (self->velocity.x == 0) {
			//fputs("what?\n", stderr);
		} else {
			self->x -= ((self->x + to_fixed(SIZE / 2)) % to_fixed(8)); // right
		}
		self->velocity.x = 0;
	}
	if (self->velocity.x < 0) {
		self->facing = -1;
	} else if (self->velocity.x > 0) {
		self->facing = +1;
	}
	self->velocity.y += GRAVITY;
	// y collision
	self->y += self->velocity.y;
	collision_T const cy = collide(self);
	if (collision_solid(cy)) {
		if (self->velocity.y < 0) {
			self->y += to_fixed(8) - ((self->y - to_fixed(SIZE)) % to_fixed(8)); // up
			self->velocity.y = 0;
		} else if (self->velocity.y == 0) {
			//fputs("what?\n", stderr);
		} else {
			self->y -= ((self->y) % to_fixed(8)); // down
			self->velocity.y = to_fixed(1); // crazy but it works
			on_ground = true;
		}
	}
	
	if (collision_hazard(cx | cy)) {
		self->hurt(self, (struct damage) {1, 60});
	}
	
	self->hitbox.left = from_fixed(self->x) - SIZE / 2;
	self->hitbox.right = from_fixed(self->x) + SIZE / 2;
	self->hitbox.top = from_fixed(self->y) - SIZE;
	self->hitbox.bottom = from_fixed(self->y);
	return on_ground;
}

static void anim(struct entity *self, unsigned anim) {
	self->anim = walker_anims[anim];
}

static void attack(struct entity const *const self) {
	entities.projectile[entities.projectiles] = (struct projectile) {
		.update = bullet_update,
		.draw = bullet_draw,
		.free = bullet_free,
		.x = self->x, .y = self->y - to_fixed(3),
		.velocity = (struct vec2) {self->velocity.x + to_fixed(self->facing), 0},
		.hitbox = {
			0, 0, 0, 0,
		},
		.state = WALKER_IDLE,
		.hp = 300,
		.timer = 0,
		.facing = self->facing,
		.faction = FACTION_ENEMY,
		.iframes = 0,
		.texture = NULL,
		.ext = NULL,
	};
	//anim(entities->projectile + entities->projectiles, WALKER_A_IDLE);
	entities.projectile[entities.projectiles].texture = res_get_texture("particles").data;
	entities.projectiles++;
}

static inline int is_walkable(int const x, int const y) {
	collision_T const front = tilemap_tile(tilemap, from_fixed(x), from_fixed(y) - 4);
	collision_T const floor = tilemap_tile(tilemap, from_fixed(x), from_fixed(y) + 4);
	return !collision_solid(front) && collision_solid(floor) && !collision_hazard(floor);
}

static void walker_free(struct entity *self) {
	self->state = 0;
	free(self->ext), self->ext = NULL;
}

static int walker_update(struct entity *self) {
	struct walker_ext *const ext = self->ext;
	if (self->hp <= 0) {
		walker_free(self);
		entities.enemies--;
		return 1;
	}
	switch (self->state) {
		case WALKER_FALL:
			if (move(self, 0) != false) {
				anim(self, WALKER_A_WALK);
				self->state = WALKER_PATROL;
			}
			break;
		
		case WALKER_IDLE:
			if (move(self, 0) == false) {
				self->velocity.y = 0;
				anim(self, WALKER_A_FALL);
				self->state = WALKER_FALL;
			}
			self->timer--;
			if (self->timer == 0) {
				ext->facing = -ext->facing;
				anim(self, WALKER_A_WALK);
				self->state = WALKER_PATROL;
			}
			if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) < ext->detection_range) {
				self->state = WALKER_ALERT_IDLE;
				self->timer = ext->attack_delay;
			}
			break;

		case WALKER_PATROL:
			if (move(self, ext->facing) == false) {
				self->velocity.y = 0;
				anim(self, WALKER_A_FALL);
				self->state = WALKER_FALL;
			}
			if (!is_walkable(self->x + to_fixed(ext->facing * WALKABLE_CHECK_DISTANCE), self->y)) {
				anim(self, WALKER_A_IDLE);
				self->state = WALKER_IDLE;
				self->timer = ext->idle_time;
			}
			if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) < ext->detection_range) {
				anim(self, WALKER_A_IDLE);
				self->state = WALKER_ALERT_IDLE;
				self->timer = ext->attack_delay;
			}
			break;
		
		case WALKER_ALERT_IDLE:
			move(self, 0);
			ext->facing = self->x > entities.player[0].x? -1: 1;
			self->facing = ext->facing;
			self->timer--;
			if (self->timer == 0) {
				anim(self, WALKER_A_ATTACK);
				self->state = WALKER_ALERT_ATTACK;
				self->timer = ext->attack_delay2;
			}
			if (abs(entities.player[0].x - self->x) > CHASE_RANGE) {
				if (is_walkable(self->x + to_fixed(ext->facing * WALKABLE_CHECK_DISTANCE), self->y)) {
					anim(self, WALKER_A_WALK);
					self->state = WALKER_ALERT_CHASE;
				}
			} else if (abs(entities.player[0].x - self->x) < RUN_RANGE) {
				if (is_walkable(self->x + to_fixed(-ext->facing * WALKABLE_CHECK_DISTANCE), self->y)) {
					ext->facing = -ext->facing;
					anim(self, WALKER_A_WALK);
					self->state = WALKER_ALERT_RUN;
				} else if (is_walkable(self->x + to_fixed(ext->facing * WALKABLE_CHECK_DISTANCE), self->y)) {
					anim(self, WALKER_A_WALK);
					self->state = WALKER_ALERT_RUN;
				}
			}
			if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) > ext->give_up_range) {
				self->state = WALKER_IDLE;
				self->timer = ext->idle_time;
			}
			break;

		case WALKER_ALERT_CHASE:
			move(self, ext->facing);
			if (abs(entities.player[0].x - self->x) < TARGET_RANGE || !is_walkable(self->x + to_fixed(ext->facing * WALKABLE_CHECK_DISTANCE), self->y)) {
				anim(self, WALKER_A_IDLE);
				self->state = WALKER_ALERT_IDLE;
				self->timer = ext->attack_delay;
			}
			break;

		case WALKER_ALERT_RUN:
			move(self, ext->facing);
			if (abs(entities.player[0].x - self->x) > TARGET_RANGE) {
				anim(self, WALKER_A_IDLE);
				self->state = WALKER_ALERT_IDLE;
				self->timer = ext->attack_delay;
			}
			if (!is_walkable(self->x + to_fixed(ext->facing * WALKABLE_CHECK_DISTANCE), self->y)) {
				anim(self, WALKER_A_IDLE);
				self->state = WALKER_ALERT_HALT;
			}
			break;

		case WALKER_ALERT_HALT: // hack workaround for a weird issue where enemies would rapidly switch between moving and idling
			move(self, 0);
			if (self->velocity.x == 0) {
				ext->facing = -ext->facing;
				anim(self, WALKER_A_WALK);
				self->state = WALKER_ALERT_RUN;
			}
			break;
		
		case WALKER_ALERT_ATTACK:
			ext->facing = self->x > entities.player[0].x? -1: 1;
			self->facing = ext->facing;
			if (self->timer > 0) {
				self->timer--;
			}
			if (self->timer == 0 && self->y + to_fixed(4) > entities.player[0].y && self->y <= entities.player[0].y) {
				attack(self);
				anim(self, WALKER_A_IDLE);
				self->state = WALKER_ALERT_IDLE;
				self->timer = ext->attack_repeat;
			}
			if (abs(entities.player[0].x - self->x) + abs(entities.player[0].y - self->y) > ext->give_up_range) {
				anim(self, WALKER_A_IDLE);
				self->state = WALKER_IDLE;
				self->timer = ext->idle_time;
			}
			break;
	}
	if (self->iframes > 0) {
		self->iframes--;
	}
	self->anim.length--;
	if (self->anim.length == 0) {
		anim(self, self->anim.frame);
	}
	return 0;
}

static int walker_hurt(struct entity *self, struct damage damage) {
	if (self->iframes == 0) {
		self->hp -= damage.damage;
		self->iframes = damage.iframes;
		for (int x = -1; x <= 1; x += 2) {
			for (int y = -1; y <= 1; y += 2) {
				struct particle *part = entities.particle + entities.particles;
				part->x = self->x;
				part->y = self->y;
				part->velocity = (struct vec2) {to_fixed(x), to_fixed(y) - 8};
				part->rect = particle_red;
				part->acceleration = (struct vec2) {0, 1};
				part->hp = 30;
				entities.particles++;
			}
		}
	}
	return 0;
}

static int walker_draw(struct entity *self, int camX, int camY) {
	if (!(self->iframes & 0x4)) {
		SDL_Rect rect = self->anim.rect;
		if (self->facing == -1) {
			rect.y += 16;
		}
		SDL_RenderCopy(renderer, self->texture, &rect, &(SDL_Rect) {from_fixed(self->x) - camX - 8, from_fixed(self->y) - camY - 12, 16, 16});
	}
	return 0;
}

struct entity *walker_new(struct entities *entities) {
	struct entity *self = entities->enemy + entities->enemies;
	*self = (struct entity) {
		.update = walker_update,
		.hurt = walker_hurt,
		.draw = walker_draw,
		.free = walker_free,
		.x = 0, .y = 0,
		.velocity = {.x = 0, .y = 0},
		.hitbox = {
			0, 0, 0, 0,
		},
		.state = WALKER_FALL,
		.hp = 3,
		.timer = 0,
		.facing = 1,
		.faction = FACTION_ENEMY,
		.iframes = 0,
		.texture = NULL,
		.ext = NULL,
	};
	self->ext = malloc(sizeof (struct walker_ext));
	*(struct walker_ext *) self->ext = (struct walker_ext) {
		.idle_time = IDLE_TIME,
		.attack_delay = ATTACK_DELAY,
		.attack_delay2 = ATTACK_DELAY2,
		.attack_repeat = ATTACK_REPEAT,
		.detection_range = DETECTION_RANGE,
		.give_up_range = GIVE_UP_RANGE,
		.facing = 1,
	};
	anim(self, WALKER_A_FALL);
	self->texture = res_get_texture("enemy").data;
	entities->enemies++;
	return self;
}

int walker_property(struct entity *const restrict self, char const *const restrict property, char const *const restrict value) {
	struct walker_ext *const ext = self->ext;
	if (strcmp(property, "x") == 0) {
		self->x = to_fixed(atoi(value));
	} else if (strcmp(property, "y") == 0) {
		self->y = to_fixed(atoi(value));
	} else if (strcmp(property, "idle time") == 0) {
		ext->idle_time = atoi(value);
	} else if (strcmp(property, "attack delay") == 0) {
		ext->attack_delay = atoi(value);
	} else if (strcmp(property, "attack delay 2") == 0) {
		ext->attack_delay2 = atoi(value);
	} else if (strcmp(property, "attack repeat") == 0) {
		ext->attack_repeat = atoi(value);
	} else if (strcmp(property, "detection range") == 0) {
		ext->detection_range = to_fixed(atoi(value));
	} else if (strcmp(property, "give up range") == 0) {
		ext->give_up_range = to_fixed(atoi(value));
	} else {
		return 1;
	}
	return 0;
}