/*
 * sh_marine.c -- Marine spawning, AP tracking, action execution
 *
 * Copyright (C) 2026 fish fvch studios
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#include "quakedef.h"
#include "sh_game.h"

/* ============================================================
 * SPAWN
 * ============================================================ */

void SH_Marine_Spawn (int index, int gx, int gy,
		sh_facing_t facing, sh_weapon_t weapon, const char *name)
{
	sh_marine_t *m;
	vec3_t pos;

	if (index < 0 || index >= SH_MAX_MARINES)
		return;

	m = &sh_game.marines[index];
	memset(m, 0, sizeof(*m));

	m->active = true;
	m->alive = true;
	m->squad_index = index;
	m->grid_x = gx;
	m->grid_y = gy;
	m->facing = facing;
	m->weapon = weapon;
	m->ap_max = SH_DEFAULT_AP;
	m->ap = m->ap_max;
	m->health = 1;
	q_strlcpy(m->name, name, sizeof(m->name));

	/* allocate a quake edict */
	m->ent = ED_Alloc();
	m->ent->v.classname = PR_SetEngineString("sh_marine");
	m->ent->v.movetype = MOVETYPE_NONE;	/* we control all movement */
	m->ent->v.solid = SOLID_BBOX;

	/* use the player model as placeholder */
	m->ent->v.modelindex = SV_ModelIndex("progs/player.mdl");

	/* position at grid center */
	SH_GridToWorld(gx, gy, pos);
	VectorCopy(pos, m->ent->v.origin);
	m->ent->v.angles[1] = SH_FacingToYaw(facing);

	/* bbox same as player */
	m->ent->v.mins[0] = -16; m->ent->v.mins[1] = -16; m->ent->v.mins[2] = -24;
	m->ent->v.maxs[0] =  16; m->ent->v.maxs[1] =  16; m->ent->v.maxs[2] =  32;

	SV_LinkEdict(m->ent, false);

	Con_Printf("SH: Spawned %s at grid (%d, %d) facing %s\n",
		m->name, gx, gy,
		facing == SH_FACING_NORTH ? "North" :
		facing == SH_FACING_EAST  ? "East" :
		facing == SH_FACING_SOUTH ? "South" : "West");
}

/* ============================================================
 * PHASE MANAGEMENT
 * ============================================================ */

void SH_Marine_BeginPlayerPhase (void)
{
	int i;
	for (i = 0; i < sh_game.num_marines; i++)
	{
		if (!sh_game.marines[i].active || !sh_game.marines[i].alive)
			continue;
		sh_game.marines[i].ap = sh_game.marines[i].ap_max;
		sh_game.marines[i].has_acted = false;
		sh_game.marines[i].on_overwatch = false;
		sh_game.marines[i].overwatch_ap = 0;
	}

	/* roll command points (1d6) */
	sh_game.command_points = (rand() % SH_MAX_COMMAND_POINTS) + 1;
	sh_game.command_points_used = 0;
}

qboolean SH_Marine_AllDone (void)
{
	int i;
	for (i = 0; i < sh_game.num_marines; i++)
	{
		sh_marine_t *m = &sh_game.marines[i];
		if (!m->active || !m->alive)
			continue;
		/* a marine is "done" if has_acted with 0 AP, or is on overwatch */
		if (m->ap > 0 && !m->on_overwatch && !m->has_acted)
			return false;
		/* also check if they have AP but haven't ended turn */
		if (m->ap > 0 && !m->on_overwatch)
			return false;
	}
	return true;
}

void SH_Marine_SelectNext (void)
{
	int start = sh_game.selected_marine;
	int i;

	for (i = 1; i <= sh_game.num_marines; i++)
	{
		int idx = (start + i) % sh_game.num_marines;
		sh_marine_t *m = &sh_game.marines[idx];
		if (m->active && m->alive && m->ap > 0 && !m->on_overwatch)
		{
			sh_game.selected_marine = idx;
			sh_game.camera_marine = idx;
			SH_Log(SH_LOG_INFO, "Selected: %s (%d AP)", m->name, m->ap);
			return;
		}
	}
	/* no marine with AP found, stay on current */
}

void SH_Marine_SelectPrev (void)
{
	int start = sh_game.selected_marine;
	int i;

	for (i = 1; i <= sh_game.num_marines; i++)
	{
		int idx = (start - i + sh_game.num_marines) % sh_game.num_marines;
		sh_marine_t *m = &sh_game.marines[idx];
		if (m->active && m->alive && m->ap > 0 && !m->on_overwatch)
		{
			sh_game.selected_marine = idx;
			sh_game.camera_marine = idx;
			SH_Log(SH_LOG_INFO, "Selected: %s (%d AP)", m->name, m->ap);
			return;
		}
	}
}

/* ============================================================
 * ACTION EXECUTION
 * ============================================================ */

qboolean SH_Marine_TryAction (int marine_index, sh_action_t action)
{
	sh_marine_t *m;
	vec3_t old_pos, new_pos;
	float old_yaw, new_yaw;
	int dx, dy, new_gx, new_gy;

	if (marine_index < 0 || marine_index >= sh_game.num_marines)
		return false;

	m = &sh_game.marines[marine_index];
	if (!m->active || !m->alive)
		return false;

	switch (action)
	{
	case SH_ACTION_MOVE_FORWARD:
		if (m->ap < SH_AP_MOVE_FORWARD)
		{
			SH_Log(SH_LOG_INFO, "Not enough AP to move.");
			return false;
		}

		SH_FacingToDir(m->facing, &dx, &dy);
		new_gx = m->grid_x + dx;
		new_gy = m->grid_y + dy;

		if (SH_GridBlocked(new_gx, new_gy, m->ent))
		{
			SH_Log(SH_LOG_INFO, "Blocked!");
			return false;
		}

		/* capture old position for animation */
		SH_GridToWorld(m->grid_x, m->grid_y, old_pos);
		SH_GridToWorld(new_gx, new_gy, new_pos);

		/* update grid state */
		m->grid_x = new_gx;
		m->grid_y = new_gy;
		m->ap -= SH_AP_MOVE_FORWARD;

		/* push animation */
		old_yaw = SH_FacingToYaw(m->facing);
		SH_AnimPush(m->ent, old_pos, new_pos, old_yaw, old_yaw, SH_ANIM_MOVE_DURATION);

		SH_Log(SH_LOG_INFO, "%s moves forward. (%d AP)", m->name, m->ap);
		return true;

	case SH_ACTION_TURN_LEFT:
		/* counter-clockwise: N -> W -> S -> E -> N */
		old_yaw = SH_FacingToYaw(m->facing);
		m->facing = (sh_facing_t)((m->facing + 3) % 4);
		new_yaw = SH_FacingToYaw(m->facing);

		/* turns are free (SH_AP_TURN == 0) but still cost if configured */
		if (m->ap < SH_AP_TURN)
			return false;
		m->ap -= SH_AP_TURN;

		SH_GridToWorld(m->grid_x, m->grid_y, old_pos);
		SH_AnimPush(m->ent, old_pos, old_pos, old_yaw, new_yaw, SH_ANIM_TURN_DURATION);

		SH_Log(SH_LOG_INFO, "%s turns left. (%d AP)", m->name, m->ap);
		return true;

	case SH_ACTION_TURN_RIGHT:
		/* clockwise: N -> E -> S -> W -> N */
		old_yaw = SH_FacingToYaw(m->facing);
		m->facing = (sh_facing_t)((m->facing + 1) % 4);
		new_yaw = SH_FacingToYaw(m->facing);

		if (m->ap < SH_AP_TURN)
			return false;
		m->ap -= SH_AP_TURN;

		SH_GridToWorld(m->grid_x, m->grid_y, old_pos);
		SH_AnimPush(m->ent, old_pos, old_pos, old_yaw, new_yaw, SH_ANIM_TURN_DURATION);

		SH_Log(SH_LOG_INFO, "%s turns right. (%d AP)", m->name, m->ap);
		return true;

	case SH_ACTION_TURN_180:
		old_yaw = SH_FacingToYaw(m->facing);
		m->facing = (sh_facing_t)((m->facing + 2) % 4);
		new_yaw = SH_FacingToYaw(m->facing);

		if (m->ap < SH_AP_TURN)
			return false;
		m->ap -= SH_AP_TURN;

		SH_GridToWorld(m->grid_x, m->grid_y, old_pos);
		SH_AnimPush(m->ent, old_pos, old_pos, old_yaw, new_yaw, SH_ANIM_TURN_DURATION);

		SH_Log(SH_LOG_INFO, "%s turns around. (%d AP)", m->name, m->ap);
		return true;

	case SH_ACTION_END_TURN:
		m->ap = 0;
		m->has_acted = true;
		SH_Log(SH_LOG_INFO, "%s ends turn.", m->name);
		/* auto-select next marine with AP */
		SH_Marine_SelectNext();
		return true;

	case SH_ACTION_OVERWATCH:
		if (m->ap < SH_AP_OVERWATCH)
		{
			SH_Log(SH_LOG_INFO, "Need at least %d AP for Overwatch.", SH_AP_OVERWATCH);
			return false;
		}
		m->overwatch_ap = m->ap;
		m->ap = 0;
		m->on_overwatch = true;
		SH_Log(SH_LOG_ALERT, "%s goes on OVERWATCH!", m->name);
		SH_Marine_SelectNext();
		return true;

	case SH_ACTION_SHOOT:
	case SH_ACTION_MELEE:
	case SH_ACTION_DOOR:
		/* not implemented in Phase 1 */
		SH_Log(SH_LOG_INFO, "Not yet implemented.");
		return false;

	case SH_ACTION_NONE:
	default:
		return false;
	}
}
