/*
 * This software is licensed under the terms of the MIT License.
 * See COPYING for further information.
 * ---
 * Copyright (c) 2011-2024, Lukas Weber <laochailan@web.de>.
 * Copyright (c) 2012-2024, Andrei Alexeyev <akari@taisei-project.org>.
 */

#include "youmu.h"

#include "audio/audio.h"
#include "dialog/youmu.h"
#include "global.h"
#include "plrmodes.h"
#include "stage.h"
#include "util/glm.h"

#define SHOT_BASIC_DAMAGE 60
#define SHOT_BASIC_DELAY 6

#define SHOT_HOMING_DAMAGE 120
#define SHOT_HOMING_DELAY 6

#define SHOT_SPREAD_DAMAGE 40
#define SHOT_SPREAD_DELAY 12

#define SHOT_ORBS_SPIRIT_DAMAGE 60
#define SHOT_ORBS_SPIRIT_RELEASED_DAMAGEFACTOR 1.5
#define SHOT_ORBS_SPIRIT_SPAWN_DELAY 11
#define SHOT_ORBS_DELAY_BASE 45
#define SHOT_ORBS_DELAY_PER_POWER -5
#define SHOT_ORBS_LIFETIME_BASE 100
#define SHOT_ORBS_LIFETIME_PER_POWER 10

#define BOMB_SLICE_DAMAGE 100
#define BOMB_SLICE_PERIOD 2

typedef struct YoumuBController {
	struct {
		Sprite *blast_huge_halo;
		Sprite *blast_huge_rays;
		Sprite *petal;
		Sprite *smoothdot;
		Sprite *stardust;
		Sprite *youmu_slice;
	} sprites;
	ShaderProgram *shot_shader;

	Player *plr;
	YoumuBombBGData bomb_bg;
} YoumuBController;

static void youmu_homing_trail(YoumuBController *ctrl, Projectile *p, cmplx v, int to) {
	uint32_t tmp = p->ent.spawn_id;
	float u = M_PI * 2.0f * (float)(splitmix32(&tmp) / (double)UINT32_MAX);

	RNG_ARRAY(R, 5);

	PARTICLE(
		.sprite_ptr = ctrl->sprites.stardust,
		.pos = p->pos + vrng_range(R[0], 3, 12) * vrng_dir(R[1]),
		.color = RGBA(0.0, 0.3 * vrng_real(R[2]), 0.3, 0.0),
		// .draw_rule = pdraw_timeout_fade(1, 0),
		.draw_rule = pdraw_timeout_scalefade_exp(0.001*I, vrng_range(R[3], 0.5, 1.0)*(1+I), 2, 0, 2),
		.move = move_linear(-v),
		.timeout = to,
		.scale = 0.5,
		.angle = vrng_angle(R[4]),
		.flags = PFLAG_NOREFLECT | PFLAG_PLRSPECIALPARTICLE | PFLAG_MANUALANGLE,
		.layer = LAYER_PARTICLE_MID,
	);

	PARTICLE(
		.sprite_ptr = ctrl->sprites.smoothdot,
		.pos = p->pos,
		.color = color_mul(RGBA(0.2, 0.24, 0.3, 0.2), &p->color),
		.move = move_asymptotic_simple(-0.5*v*cdir(0.2*sin(u+3*re(p->pos)/VIEWPORT_W*M_TAU) + 0.2*cos(u+3*im(p->pos)/VIEWPORT_H*M_TAU)), 2),
		.draw_rule = pdraw_timeout_scalefade_exp(0.5+0.5*I, 3+7*I, 1, 0, 2),
		.timeout = to,
		.flags = PFLAG_NOREFLECT,
		.layer = LAYER_PARTICLE_LOW,
	);
}

static void youmu_particle_slice_draw(Projectile *p, int t, ProjDrawRuleArgs args) {
	// TODO: pick animation frame based on slice direction?
	AniPlayer *player_ani = args[0].as_ptr;
	Sprite *player_frame = aniplayer_get_frame(player_ani);

	float lifetime = p->timeout;
	float tt = t / lifetime;
	float f = 0.0f;

	if(tt > 0.1) {
		f = min(1.0f, (tt - 0.1f) / 0.2f);
	}

	if(tt > 0.5f) {
		f = 1.0f + (tt - 0.5f) / 0.5f;
	}

	SpriteParamsBuffer spbuf;
	SpriteParams sp = projectile_sprite_params(p, &spbuf);
	sp.scale.x *= f;
	r_draw_sprite(&sp);

	float slicelen = 500.0f;
	cmplx slicepos = p->pos - (tt > 0.1f) * slicelen * I*cdir(p->angle) * (5 * powf(tt - 0.1f, 1.1f) - 0.5f);

	r_draw_sprite(&(SpriteParams) {
		.sprite_ptr = player_frame,
		.pos.as_cmplx = slicepos,
		.shader_params = &spbuf.shader_params,
	});
}

TASK(youmu_haunting_shot_basic, { YoumuBController *ctrl; }) {
	YoumuBController *ctrl = ARGS.ctrl;
	Player *plr = ctrl->plr;

	for(;;) {
		WAIT_EVENT_OR_DIE(&plr->events.shoot);
		play_sfx_loop("generic_shot");

		cmplx v = -20 * I;

		for(int side = -1; side < 2; side += 2) {
			cmplx origin = plr->pos + 10*side + 5*I;
			youmu_common_shot(origin, move_linear(v), SHOT_BASIC_DAMAGE, ctrl->shot_shader);
		}

		WAIT(SHOT_BASIC_DELAY);
	}
}

TASK(youmu_burst_shot, { YoumuBController *ctrl; int num_shots; }) {
	YoumuBController *ctrl = ARGS.ctrl;
	Player *plr = ctrl->plr;
	ShaderProgram *shader = ctrl->shot_shader;

	int nshots = ARGS.num_shots;
	real base_speed = 8;

	for(int shot = 1; shot <= nshots; ++shot) {
		real np = shot / (real)nshots;

		Color *clr = color_mul_scalar(RGB(0.7 + 0.3 * (1-np), 0.8 + 0.2 * sqrt(1-np), 1.0), 0.5);
		real spread = 0.5 * (1 + 0.25 * sin(global.frames/10.0));
		real speed = base_speed * (1 - 0.25 * (1 - np));
		real boost = 3 * (1 - pow(1 - np, 2));

		for(int side = -1; side < 2; side += 2) {
			cmplx vel = speed * cnormalize(side * shot * spread - base_speed * I);

			PROJECTILE(
				.color = clr,
				.damage = SHOT_SPREAD_DAMAGE,
				.move = move_asymptotic_simple(vel, boost),
				.pos = plr->pos,
				.proto = pp_hghost,
				.shader_ptr = shader,
				.type = PROJ_PLAYER,
			);
		}

		YIELD;
	}
}

TASK(youmu_haunting_shot_spread, { YoumuBController *ctrl; }) {
	YoumuBController *ctrl = ARGS.ctrl;
	Player *plr = ctrl->plr;

	for(;;) {
		WAIT_EVENT_OR_DIE(&plr->events.shoot);

		if(plr->inputflags & INFLAG_FOCUS) {
			continue;
		}

		INVOKE_TASK(youmu_burst_shot, ctrl, 2 * player_get_effective_power(plr) / 100);
		WAIT(SHOT_SPREAD_DELAY);
	}
}

TASK(youmu_homing_shot, { YoumuBController *ctrl; }) {
	YoumuBController *ctrl = ARGS.ctrl;
	Player *plr = ctrl->plr;

	Projectile *p = TASK_BIND(PROJECTILE(
		.proto = pp_hghost,
		.pos = plr->pos,
		.color = RGB(0.75, 0.9, 1),
		.type = PROJ_PLAYER,
		.damage = SHOT_HOMING_DAMAGE,
		.shader_ptr = ctrl->shot_shader,
	));

	real speed = 10;
	real aim_strength = 0;
	real aim_strength_accel = 0.02;
	p->move = move_linear(-I * speed);
	cmplx target = VIEWPORT_W * 0.5;

	for(int i = 0; i < 60; ++i) {
		target = plrutil_homing_target(p->pos, target);
		cmplx aimdir = cnormalize(target - p->pos - p->move.velocity*10);
		p->move.velocity += aim_strength * aimdir;
		p->move.velocity *= speed / cabs(p->move.velocity);
		aim_strength += aim_strength_accel;

		youmu_homing_trail(ctrl, p, 0.5 * p->move.velocity, 12);
		YIELD;
	}
}

TASK(youmu_haunting_shot_homing, { YoumuBController *ctrl; }) {
	YoumuBController *ctrl = ARGS.ctrl;
	Player *plr = ctrl->plr;

	for(;;) {
		WAIT_EVENT_OR_DIE(&plr->events.shoot);

		if(plr->inputflags & INFLAG_FOCUS) {
			continue;
		}

		INVOKE_TASK(youmu_homing_shot, ctrl);
		WAIT(SHOT_HOMING_DELAY);
	}
}

TASK(youmu_orb_homing_spirit_expire, { BoxedProjectile p; }) {
	Projectile *p = NOT_NULL(ENT_UNBOX(ARGS.p));

	PARTICLE(
		.sprite_ptr = p->sprite,
		.shader_ptr = p->shader,
		.color = &p->color,
		.timeout = 30,
		.draw_rule = pdraw_timeout_scalefade(1+I, 0.1+I, 1, 0),
		.pos = p->pos,
		.move = p->move,
		.angle = p->angle,
		.flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE | PFLAG_MANUALANGLE,
		.layer = LAYER_PLAYER_SHOT,
	);
}

static int youmu_orb_homing_spirit_timeout(Projectile *orb) {
	assume(orb != NULL);
	return orb->timeout - projectile_time(orb);
}

TASK(youmu_orb_homing_spirit, { YoumuBController *ctrl; cmplx pos; cmplx velocity; real damage; real spin; BoxedProjectile orb; }) {
	YoumuBController *ctrl = ARGS.ctrl;
	int timeout = youmu_orb_homing_spirit_timeout(ENT_UNBOX(ARGS.orb));

	if(timeout <= 0) {
		return;
	}

	Projectile *p = TASK_BIND(PROJECTILE(
		.proto = pp_hghost,
		.pos = ARGS.pos,
		.color = color_mul_scalar(RGB(0.75, 0.9, 1), 0.5),
		.type = PROJ_PLAYER,
		.timeout = timeout,
		.damage = ARGS.damage,
		.shader_ptr = ctrl->shot_shader,
		.flags = PFLAG_NOAUTOREMOVE
	));

	INVOKE_TASK_AFTER(&p->events.killed, youmu_orb_homing_spirit_expire, ENT_BOX(p));

	real speed = cabs(ARGS.velocity);
	real speed_target = speed;
	real aim_strength = -0.1;
	p->move = move_accelerated(ARGS.velocity, -0.06 * ARGS.velocity);
	p->move.retention = cdir(ARGS.spin);

	bool aim_peaked = false;
	bool orb_died = false;
	Projectile *orb = NULL;

	for(;;) {
		if(!orb_died) {
			orb = ENT_UNBOX(ARGS.orb);

			if(orb == NULL) {
				orb_died = true;
				p->timeout = 0;
				// p->move.velocity = 0;
				p->move.retention *= 0.9;
				p->damage *= SHOT_ORBS_SPIRIT_RELEASED_DAMAGEFACTOR;
				aim_peaked = false;
				aim_strength = -0.2;
				speed *= 1.2;
			}
		}

		cmplx target;

		if(orb) {
			target = orb->pos;
		} else {
			target = plrutil_homing_target(p->pos, re(global.plr.pos) - 128*I);
		}

		cmplx aimdir = cnormalize(target - p->pos - p->move.velocity);
		capproach_asymptotic_p(&p->move.retention, 0.8, 0.1, 1e-3);
		p->move.acceleration *= 0.95;
		p->move.acceleration += aim_strength * aimdir;

		if(aim_peaked) {
			if(!orb) {
				if(approach_p(&aim_strength, 0.00, 0.001) == 0) {
					p->flags &= ~PFLAG_NOAUTOREMOVE;
				}
			}
		} else {
			approach_p(&aim_strength, 0.1, 0.02);
			if(aim_strength == 0.1) {
				aim_peaked = true;
			}
		}

		real s = max(speed, cabs(p->move.velocity));
		p->move.velocity = s * cnormalize(p->move.velocity + aim_strength * s * aimdir);
		approach_asymptotic_p(&speed, speed_target, 0.05, 1e-5);

		youmu_homing_trail(ctrl, p, 0.5 * p->move.velocity, 12);
		YIELD;
	}
}

static void youmu_orb_explode(YoumuBController *ctrl, Projectile *orb) {
	PARTICLE(
		.sprite_ptr = ctrl->sprites.blast_huge_rays,
		.pos = orb->pos,
		.timeout = 20,
		.color = RGBA(0.1, 0.5, 0.1, 0.0),
		.draw_rule = pdraw_timeout_scalefade_exp(0.01*(1+I), 1, 1, 0, 2),
		.flags = PFLAG_REQUIREDPARTICLE | PFLAG_MANUALANGLE | PFLAG_NOMOVE,
		.angle = rng_angle(),
		.layer = LAYER_PARTICLE_LOW,
	);

	PARTICLE(
		.sprite_ptr = ctrl->sprites.blast_huge_halo,
		.pos = orb->pos,
		.timeout = 30,
		.color = RGBA(0.1, 0.1, 0.5, 0.0),
		.draw_rule = pdraw_timeout_scalefade_exp(1, 0.01*(1+I), 1, 0, 2),
		.flags = PFLAG_REQUIREDPARTICLE | PFLAG_MANUALANGLE | PFLAG_NOMOVE,
		.angle = rng_angle(),
		.layer = LAYER_PARTICLE_LOW,
	);

	PARTICLE(
		.sprite_ptr = ctrl->sprites.blast_huge_halo,
		.pos = orb->pos,
		.timeout = 40,
		.color = RGBA(0.5, 0.1, 0.1, 0.0),
		.draw_rule = pdraw_timeout_scalefade_exp(0.8, -0.3*(1+I), 1, 0, 2),
		.flags = PFLAG_REQUIREDPARTICLE | PFLAG_MANUALANGLE | PFLAG_NOMOVE,
		.angle = rng_angle(),
		.layer = LAYER_PARTICLE_LOW,
	);

	// TODO sound effect;
	kill_projectile(orb);
}

TASK(youmu_orb_update, { YoumuBController *ctrl; BoxedProjectile orb; }) {
	YoumuBController *ctrl = ARGS.ctrl;
	Player *plr = ctrl->plr;
	Projectile *orb = TASK_BIND(ARGS.orb);

	for(;;) {
		float tf = glm_ease_bounce_out(1.0f - projectile_timeout_factor(orb));
		orb->color.g = tf * tf;
		orb->color.b = tf;
		orb->scale = 0.5 + 0.5 * tf;
		orb->angle += 0.2;

		if(!(plr->inputflags & INFLAG_FOCUS)) {
			youmu_orb_explode(ctrl, orb);
			break;
		}

		YIELD;
	}
}

TASK(youmu_orb_death, { BoxedProjectile orb; BoxedTask control_task; }) {
	CANCEL_TASK(ARGS.control_task);
	Projectile *orb = NOT_NULL(ENT_UNBOX(ARGS.orb));

	PARTICLE(
		.proto = pp_blast,
		.pos = orb->pos,
		.timeout = 20,
		.draw_rule = pdraw_blast(),
		.flags = PFLAG_REQUIREDPARTICLE,
		.layer = LAYER_PARTICLE_LOW,
	);

	PARTICLE(
		.proto = pp_blast,
		.pos = orb->pos,
		.timeout = 23,
		.draw_rule = pdraw_blast(),
		.flags = PFLAG_REQUIREDPARTICLE,
		.layer = LAYER_PARTICLE_LOW,
	);
}

TASK(youmu_orb_shot, { YoumuBController *ctrl; int lifetime; real spirit_damage; int spirit_spawn_delay; }) {
	YoumuBController *ctrl = ARGS.ctrl;
	Player *plr = ctrl->plr;

	Projectile *orb = TASK_BIND(PROJECTILE(
		.proto = pp_youhoming,
		.pos = plr->pos,
		.color = RGB(1, 1, 1),
		.type = PROJ_PLAYER,
		.timeout = ARGS.lifetime,
		.move = move_asymptotic(-30.0*I, -0.7*I, 0.8),
		.flags = PFLAG_MANUALANGLE | PFLAG_NOCOLLISION | PFLAG_NOAUTOREMOVE,
	));

	INVOKE_TASK(youmu_orb_update, ctrl, ENT_BOX(orb));
	INVOKE_TASK_AFTER(&orb->events.killed, youmu_orb_death, ENT_BOX(orb), THIS_TASK);

	real pdmg = ARGS.spirit_damage;
	cmplx v = 5 * I;
	int spawn_delay = ARGS.spirit_spawn_delay;

	for(;;) {
		WAIT(spawn_delay);
		INVOKE_TASK(youmu_orb_homing_spirit, ctrl, orb->pos, v, pdmg,  0.1, ENT_BOX(orb));
		INVOKE_TASK(youmu_orb_homing_spirit, ctrl, orb->pos, v, pdmg, -0.1, ENT_BOX(orb));
	}
}

TASK(youmu_haunting_shot_orbs, { YoumuBController *ctrl; }) {
	YoumuBController *ctrl = ARGS.ctrl;
	Player *plr = ctrl->plr;

	for(;;) {
		WAIT_EVENT_OR_DIE(&plr->events.shoot);

		if(!(plr->inputflags & INFLAG_FOCUS)) {
			continue;
		}

		int power_rank = player_get_effective_power(plr) / 100;

		INVOKE_TASK(youmu_orb_shot,
			.ctrl = ctrl,
			.lifetime = SHOT_ORBS_LIFETIME_BASE + power_rank * SHOT_ORBS_LIFETIME_PER_POWER,
			.spirit_damage = SHOT_ORBS_SPIRIT_DAMAGE,
			.spirit_spawn_delay = SHOT_ORBS_SPIRIT_SPAWN_DELAY
		);

		WAIT(SHOT_ORBS_DELAY_BASE + power_rank * SHOT_ORBS_DELAY_PER_POWER);
	}
}

TASK(youmu_haunting_bomb_slice_petal, { YoumuBController *ctrl; cmplx pos; cmplx vel; }) {
	YoumuBController *ctrl = ARGS.ctrl;
	Projectile *p = TASK_BIND(PARTICLE(
		.sprite_ptr = ctrl->sprites.petal,
		.pos = ARGS.pos,
		.draw_rule = pdraw_petal_random(),
		.move = move_accelerated(ARGS.vel, ARGS.vel * cdir(0.1)),
		.layer = LAYER_PARTICLE_HIGH | 0x2,
		.flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE,
	));

	real transition_time = 40;

	for(real t = 0; t <= transition_time; ++t) {
		p->color = *color_mul_scalar(RGBA(0.2, 0.2, 1, 0), min(1, t / transition_time));
		YIELD;
	}
}

TASK(youmu_haunting_bomb_slice, { YoumuBController *ctrl; cmplx pos; real angle; }) {
	YoumuBController *ctrl = ARGS.ctrl;

	Projectile *p = TASK_BIND(PARTICLE(
		.sprite_ptr = ctrl->sprites.youmu_slice,
		.color = RGBA(1, 1, 1, 0),
		.pos = ARGS.pos,
		.draw_rule = {
			.func = youmu_particle_slice_draw,
			.args[0].as_ptr = &ctrl->plr->ani,
		},
		.flags = PFLAG_NOREFLECT | PFLAG_REQUIREDPARTICLE | PFLAG_MANUALANGLE | PFLAG_NOMOVE,
		.timeout = 100,
		.angle = ARGS.angle,
		.layer = LAYER_PARTICLE_HIGH | 0x1,
	));

	cmplx petal_dir = cdir(ARGS.angle);

	for(int t = 0;; ++t) {
		real lifetime = p->timeout;
		real tt = t/lifetime;
		real a = 0;

		if(tt > 0.5) {
			a = max(0, 1 - (tt - 0.5) / 0.5);
		} else {
			a = min(1, tt / 0.2);
		}

		p->color = *RGBA(a, a, a, 0);

		if(t % 5 == 0) {
			INVOKE_TASK(youmu_haunting_bomb_slice_petal, ctrl, p->pos - 400 * petal_dir, petal_dir);
		}

		if(t % BOMB_SLICE_PERIOD == 0) {
			Ellipse e = {
				.origin = p->pos,
				.axes = CMPLX(512, 64),
				.angle = p->angle + M_PI * 0.5,
			};

			// FIXME: this may still be too slow for lasers in some cases
			stage_clear_hazards_in_ellipse(e, CLEAR_HAZARDS_ALL | CLEAR_HAZARDS_NOW);
			ent_area_damage_ellipse(e, &(DamageInfo) { BOMB_SLICE_DAMAGE, DMG_PLAYER_BOMB }, NULL, NULL);
		}

		YIELD;
	}
}

TASK(youmu_haunting_bomb, { YoumuBController *ctrl; }) {
	YoumuBController *ctrl = ARGS.ctrl;
	Player *plr = ctrl->plr;
	cmplx origin = plr->pos;

	INVOKE_SUBTASK(youmu_common_bomb_background, ENT_BOX(plr), &ctrl->bomb_bg);

	YIELD;

	int i = 0;
	do {
		cmplx ofs = cdir(i) * (100 + 10 * i * i * 0.01);
		real angle = carg(ofs) + M_PI/2;
		INVOKE_TASK(youmu_haunting_bomb_slice, ctrl, origin + ofs, angle);
		WAIT(3);
		++i;
	} while(player_is_bomb_active(plr));
}

TASK(youmu_haunting_bomb_handler, { YoumuBController *ctrl; }) {
	YoumuBController *ctrl = ARGS.ctrl;
	Player *plr = ctrl->plr;

	BoxedTask bomb_task = { 0 };

	for(;;) {
		WAIT_EVENT_OR_DIE(&plr->events.bomb_used);
		CANCEL_TASK(bomb_task);
		play_sfx("bomb_youmu_b");
		bomb_task = cotask_box(INVOKE_SUBTASK(youmu_haunting_bomb, ctrl));
	}
}

TASK(youmu_haunting_controller, { BoxedPlayer plr; }) {
	YoumuBController *ctrl = TASK_MALLOC(sizeof(*ctrl));
	ctrl->plr = TASK_BIND(ARGS.plr);
	ctrl->sprites.blast_huge_halo = res_sprite("part/blast_huge_halo");
	ctrl->sprites.blast_huge_rays = res_sprite("part/blast_huge_rays");
	ctrl->sprites.petal = res_sprite("part/petal");
	ctrl->sprites.smoothdot = res_sprite("part/smoothdot");
	ctrl->sprites.stardust = res_sprite("part/stardust");
	ctrl->sprites.youmu_slice = res_sprite("part/youmu_slice");
	ctrl->shot_shader = res_shader("sprite_particle");

	youmu_common_init_bomb_background(&ctrl->bomb_bg);

	INVOKE_SUBTASK(youmu_haunting_shot_basic, ctrl);
	INVOKE_SUBTASK(youmu_haunting_shot_homing, ctrl);
	INVOKE_SUBTASK(youmu_haunting_shot_spread, ctrl);
	INVOKE_SUBTASK(youmu_haunting_shot_orbs, ctrl);
	INVOKE_SUBTASK(youmu_haunting_bomb_handler, ctrl);

	STALL;
}

static void youmu_haunting_init(Player *plr) {
	INVOKE_TASK(youmu_haunting_controller, ENT_BOX(plr));
}

static void youmu_haunting_preload(ResourceGroup *rg) {
	const int flags = RESF_DEFAULT;

	res_group_preload(rg, RES_SPRITE, flags,
		"proj/youmu",
		"part/youmu_slice",
	NULL);

	res_group_preload(rg, RES_TEXTURE, flags,
		"youmu_bombbg1",
	NULL);

	res_group_preload(rg, RES_SFX, flags | RESF_OPTIONAL,
		"bomb_youmu_b",
	NULL);
}

PlayerMode plrmode_youmu_b = {
	.name = "Haunting Revelation",
	.description = "Ghosts are real, but they’re nothing to be afraid of. Unless, of course, you happen to stand in the way of a determined sword-wielding lady…",
	.spellcard_name = "Aegis Sword “Saigyō Omnidirectional Slash”",
	.character = &character_youmu,
	.dialog = &dialog_tasks_youmu,
	.shot_mode = PLR_SHOT_YOUMU_HAUNTING,
	.procs = {
		.property = youmu_common_property,
		.init = youmu_haunting_init,
		.preload = youmu_haunting_preload,
	},
};
