/*
 * sh_game.c -- Space Hulk game state machine, grid utilities, animation
 *
 * 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"
#include <math.h>

/* ============================================================
 * GLOBALS
 * ============================================================ */

sh_gamestate_t sh_game;

/* ============================================================
 * COORDINATE CONVERSION
 * ============================================================ */

void SH_GridToWorld (int gx, int gy, vec3_t out)
{
	out[0] = (gx * SH_GRID_SIZE) + (SH_GRID_SIZE / 2);
	out[1] = (gy * SH_GRID_SIZE) + (SH_GRID_SIZE / 2);
	out[2] = 0;	/* floor level; adjusted by caller if needed */
}

void SH_WorldToGrid (vec3_t pos, int *gx, int *gy)
{
	*gx = (int)floorf(pos[0] / SH_GRID_SIZE);
	*gy = (int)floorf(pos[1] / SH_GRID_SIZE);
}

/* ============================================================
 * FACING HELPERS
 * ============================================================ */

float SH_FacingToYaw (sh_facing_t facing)
{
	switch (facing) {
	case SH_FACING_NORTH:	return 90;
	case SH_FACING_EAST:	return 0;
	case SH_FACING_SOUTH:	return 270;
	case SH_FACING_WEST:	return 180;
	}
	return 0;
}

sh_facing_t SH_YawToFacing (float yaw)
{
	/* normalize to 0-360 */
	yaw = fmodf(yaw, 360.0f);
	if (yaw < 0) yaw += 360.0f;

	if (yaw >= 315 || yaw < 45)	return SH_FACING_EAST;
	if (yaw >= 45 && yaw < 135)	return SH_FACING_NORTH;
	if (yaw >= 135 && yaw < 225)	return SH_FACING_WEST;
	return SH_FACING_SOUTH;
}

void SH_FacingToDir (sh_facing_t facing, int *dx, int *dy)
{
	*dx = 0;
	*dy = 0;
	switch (facing) {
	case SH_FACING_NORTH:	*dy =  1; break;
	case SH_FACING_EAST:	*dx =  1; break;
	case SH_FACING_SOUTH:	*dy = -1; break;
	case SH_FACING_WEST:	*dx = -1; break;
	}
}

/* ============================================================
 * GRID COLLISION
 * ============================================================ */

qboolean SH_GridBlocked (int gx, int gy, edict_t *ignore)
{
	vec3_t start, end, mins, maxs;
	trace_t trace;
	int i;

	/* trace downward through the BSP to see if a floor exists */
	SH_GridToWorld(gx, gy, start);
	VectorCopy(start, end);
	start[2] = 64;
	end[2] = -64;

	mins[0] = -14; mins[1] = -14; mins[2] = 0;
	maxs[0] =  14; maxs[1] =  14; maxs[2] = 56;

	trace = SV_Move(start, mins, maxs, end, MOVE_NOMONSTERS, ignore);

	if (trace.allsolid || trace.startsolid)
		return true;

	/* check if any marine occupies this cell */
	for (i = 0; i < sh_game.num_marines; i++)
	{
		if (!sh_game.marines[i].active || !sh_game.marines[i].alive)
			continue;
		if (sh_game.marines[i].ent == ignore)
			continue;
		if (sh_game.marines[i].grid_x == gx && sh_game.marines[i].grid_y == gy)
			return true;
	}

	/* check genestealers too */
	for (i = 0; i < sh_game.num_genestealers; i++)
	{
		if (!sh_game.genestealers[i].active || !sh_game.genestealers[i].alive)
			continue;
		if (sh_game.genestealers[i].ent == ignore)
			continue;
		if (sh_game.genestealers[i].grid_x == gx && sh_game.genestealers[i].grid_y == gy)
			return true;
	}

	return false;
}

/* ============================================================
 * ANIMATION SYSTEM
 * ============================================================ */

void SH_AnimPush (edict_t *ent, vec3_t start, vec3_t end,
		float start_yaw, float end_yaw, double duration)
{
	sh_anim_t *a;
	int i;

	/* find a free slot */
	for (i = 0; i < SH_MAX_ANIMS; i++)
	{
		if (!sh_game.anims[i].active)
		{
			a = &sh_game.anims[i];
			a->active = true;
			a->ent = ent;
			VectorCopy(start, a->start_pos);
			VectorCopy(end, a->end_pos);
			a->start_yaw = start_yaw;
			a->end_yaw = end_yaw;
			a->start_time = realtime;
			a->duration = duration;
			return;
		}
	}

	/* no free slot -- snap immediately */
	VectorCopy(end, ent->v.origin);
	ent->v.angles[1] = end_yaw;
	SV_LinkEdict(ent, false);
}

static void SH_AnimUpdate (void)
{
	int i;
	double t;
	float frac;
	sh_anim_t *a;

	for (i = 0; i < SH_MAX_ANIMS; i++)
	{
		a = &sh_game.anims[i];
		if (!a->active)
			continue;

		t = realtime - a->start_time;
		if (t >= a->duration)
		{
			/* finished: snap to final position */
			VectorCopy(a->end_pos, a->ent->v.origin);
			a->ent->v.angles[1] = a->end_yaw;
			SV_LinkEdict(a->ent, false);
			a->active = false;
			continue;
		}

		/* lerp position */
		frac = (float)(t / a->duration);
		a->ent->v.origin[0] = a->start_pos[0] + (a->end_pos[0] - a->start_pos[0]) * frac;
		a->ent->v.origin[1] = a->start_pos[1] + (a->end_pos[1] - a->start_pos[1]) * frac;
		a->ent->v.origin[2] = a->start_pos[2] + (a->end_pos[2] - a->start_pos[2]) * frac;

		/* lerp yaw (handle wraparound) */
		{
			float diff = a->end_yaw - a->start_yaw;
			if (diff > 180) diff -= 360;
			if (diff < -180) diff += 360;
			a->ent->v.angles[1] = a->start_yaw + diff * frac;
		}

		SV_LinkEdict(a->ent, false);
	}
}

qboolean SH_AnimBusy (void)
{
	int i;
	for (i = 0; i < SH_MAX_ANIMS; i++)
	{
		if (sh_game.anims[i].active)
			return true;
	}
	return false;
}

/* ============================================================
 * MESSAGE LOG
 * ============================================================ */

void SH_Log (sh_log_type_t type, const char *fmt, ...)
{
	va_list ap;
	sh_log_msg_t *msg;
	char buf[SH_LOG_MSG_LEN];

	va_start(ap, fmt);
	q_vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);

	/* also print to console so we can see it during development */
	Con_Printf("[SH] %s\n", buf);

	/* check for stacking with previous message */
	if (sh_game.log.count > 0)
	{
		int prev = (sh_game.log.write_index - 1 + SH_MAX_LOG_MESSAGES) % SH_MAX_LOG_MESSAGES;
		msg = &sh_game.log.messages[prev];
		if ((int)msg->type == (int)type &&
		    msg->round == sh_game.round_number &&
		    strcmp(msg->text, buf) == 0)
		{
			msg->stack_count++;
			sh_game.log.updated = true;
			return;
		}
	}

	/* write new message */
	msg = &sh_game.log.messages[sh_game.log.write_index];
	q_strlcpy(msg->text, buf, SH_LOG_MSG_LEN);
	msg->type = type;
	msg->round = sh_game.round_number;
	msg->stack_count = 1;

	sh_game.log.write_index = (sh_game.log.write_index + 1) % SH_MAX_LOG_MESSAGES;
	if (sh_game.log.count < SH_MAX_LOG_MESSAGES)
		sh_game.log.count++;
	sh_game.log.updated = true;
}

/* ============================================================
 * CONSOLE COMMANDS
 * ============================================================ */

static void SH_Cmd_NewGame (void)
{
	SH_NewGame();
}

/* ============================================================
 * INIT / SHUTDOWN
 * ============================================================ */

void SH_Init (void)
{
	memset(&sh_game, 0, sizeof(sh_game));
	sh_game.phase = SH_PHASE_INACTIVE;

	Cmd_AddCommand("sh_newgame", SH_Cmd_NewGame);

	SH_Input_Init();

	Con_Printf("Space Hulk system initialized.\n");
}

void SH_Shutdown (void)
{
	memset(&sh_game, 0, sizeof(sh_game));
}

/* ============================================================
 * NEW GAME
 * ============================================================ */

void SH_NewGame (void)
{
	int i;

	if (!sv.active)
	{
		Con_Printf("SH_NewGame: no map loaded. Load a map first.\n");
		return;
	}

	memset(&sh_game, 0, sizeof(sh_game));

	/* spawn a single marine at grid 2,2 facing north for Phase 1 testing */
	SH_Marine_Spawn(0, 2, 2, SH_FACING_NORTH, SH_WEAPON_STORM_BOLTER, "Sgt. Marcus");
	sh_game.num_marines = 1;
	sh_game.selected_marine = 0;

	/* begin player phase */
	sh_game.round_number = 1;
	sh_game.phase = SH_PHASE_PLAYER;
	SH_Marine_BeginPlayerPhase();

	/* clear anims */
	for (i = 0; i < SH_MAX_ANIMS; i++)
		sh_game.anims[i].active = false;

	SH_Log(SH_LOG_INFO, "Round %d -- PLAYER PHASE", sh_game.round_number);
	Con_Printf("Space Hulk: game started. Use W/A/D to move, Space to end turn.\n");
}

/* ============================================================
 * ACTIVE CHECK
 * ============================================================ */

qboolean SH_Active (void)
{
	return (sh_game.phase != SH_PHASE_INACTIVE);
}

/* ============================================================
 * FRAME -- called every render frame from _Host_Frame
 * ============================================================ */

void SH_Frame (void)
{
	if (!SH_Active())
		return;

	/* always update animations */
	SH_AnimUpdate();

	/* don't advance game logic while animations are playing */
	if (SH_AnimBusy())
		return;

	switch (sh_game.phase) {
	case SH_PHASE_PLAYER:
		/* input-driven: SH_Input_KeyEvent dispatches actions.
		 * check if all marines are done. */
		if (SH_Marine_AllDone())
		{
			sh_game.phase = SH_PHASE_ROUND_END;
		}
		break;

	case SH_PHASE_ROUND_END:
		sh_game.round_number++;
		SH_Marine_BeginPlayerPhase();
		sh_game.phase = SH_PHASE_PLAYER;
		SH_Log(SH_LOG_INFO, "Round %d -- PLAYER PHASE", sh_game.round_number);
		break;

	case SH_PHASE_BRIEFING:
	case SH_PHASE_ALIEN:
	case SH_PHASE_OVERWATCH_REACT:
	case SH_PHASE_VICTORY:
	case SH_PHASE_DEFEAT:
	case SH_PHASE_INACTIVE:
		/* not implemented in Phase 1 */
		break;
	}
}
