/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2002-2021 Q3Rally Team (Per Thormann - q3rally@gmail.com)

This file is part of q3rally source code.

q3rally source code 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.

q3rally source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with q3rally; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "g_local.h"


/*
=================
G_TempRallyEntity

Spawns an event entity that will not be auto-removed
The origin will be snapped to save net bandwidth, so care
must be taken if the origin is right on a surface (snap towards start vector first)
=================
*/
gentity_t *G_TempRallyEntity( vec3_t origin, int event ) {
	gentity_t		*e;
	vec3_t			snapped;

	e = G_Spawn();
	e->s.eType = ET_EVENTS + event;

	e->classname = "tempEntity";
	e->eventTime = level.time;
//	e->freeAfterEvent = qtrue;

	VectorCopy( origin, snapped );
	SnapVector( snapped );		// save network bandwidth
	G_SetOrigin( e, snapped );

	// find cluster for PVS
	trap_LinkEntity( e );

	return e;
}


void G_GetPointOnCurveBetweenCheckpoints( gentity_t *start, gentity_t *end, float f, vec3_t origin )
{
	vec3_t		startHandle, endHandle;

	VectorAdd( start->s.origin2, start->s.angles2, startHandle );
	VectorMA( end->s.origin2, -1, end->s.angles2, endHandle );

	VectorScale( start->s.origin2, (1-f)*(1-f)*(1-f), origin );
	VectorMA( origin, 3*f*(1-f)*(1-f), startHandle, origin );
	VectorMA( origin, 3*f*f*(1-f), endHandle, origin );
	VectorMA( origin, f*f*f, end->s.origin2, origin );
}

void G_GetDervOnCurveBetweenCheckpoints( gentity_t *start, gentity_t *end, float f, vec3_t vec )
{
	vec3_t		startHandle, endHandle;

	VectorAdd( start->s.origin2, start->s.angles2, startHandle );
	VectorMA( end->s.origin2, -1, end->s.angles2, endHandle );

	VectorScale( start->s.origin2, -3*(1-f)*(1-f), vec );
	VectorMA( vec, -3*((-3*f+4)*f-1), startHandle, vec );
	VectorMA( vec, -3*((3*f-2)*f), endHandle, vec );
	VectorMA( vec, 3*f*f, end->s.origin2, vec );
}

void G_Get2ndDervOnCurveBetweenCheckpoints( gentity_t *start, gentity_t *end, float f, vec3_t vec )
{
	vec3_t		startHandle, endHandle;

	VectorAdd( start->s.origin2, start->s.angles2, startHandle );
	VectorMA( end->s.origin2, -1, end->s.angles2, endHandle );

	VectorScale( start->s.origin2, 6*(1-f), vec );
	VectorMA( vec, 6*(3*f-2), startHandle, vec );
	VectorMA( vec, 6*(1-3*f), endHandle, vec );
	VectorMA( vec, 6*f, end->s.origin2, vec );
}


/*
=================
G_ResetCar
=================
*/
void G_ResetCar( gentity_t *ent ) {
//	int			i;
	vec3_t		origin, end, angles;
	vec3_t		mins, maxs;
	trace_t		tr;
	gentity_t	*tent;
//	qboolean	reset;

/*
	if ( !g_cheats.integer ) {
		trap_SendServerCommand( ent-g_entities, va("print \"Cheats are not enabled on this server.\n\""));
		return;
	}
*/
	// doesnt work when player is dead
	if (ent->client->ps.pm_type == PM_DEAD)
		return;

	// wait for wheels to be off the ground for 3 seconds
/*
	reset = qfalse;
	for (i = 0; i < NUM_CAR_POINTS; i++){
		if (i < FIRST_FRAME_POINT){
			if (!ent->client->car.sPoints[i].onGroundTime) return;
			if (ent->client->car.sPoints[i].onGroundTime + 3000 > level.time) return;
		}
		// some other point is has been on the ground for at least a second
		else if (ent->client->car.sPoints[i].onGroundTime + 1000 > level.time){
			reset = qtrue;
			break;
		}
	}

	if (!reset)
		return;
*/

	// if a wheel has been on the ground in the last 1 seconds
	// or the body has not been on the ground for more than 1 second then dont reset
	if( ent->client->car.wheelOnGroundTime + 1000 > level.time ||
		ent->client->car.onGroundTime - ent->client->car.offGroundTime < 800 )
		return;
	
	tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
	tent->s.clientNum = ent->s.clientNum;

	VectorCopy(ent->client->ps.origin, origin);
	VectorClear(angles);

	origin[2] += 128;
	angles[YAW] = ent->client->ps.viewangles[YAW];

	VectorCopy(origin, end);
	end[2] -= 1000;

	VectorSet(mins, -CAR_LENGTH/2, -CAR_LENGTH/2, -18);
	VectorSet(maxs, CAR_LENGTH/2, CAR_LENGTH/2, 18);
	trap_Trace(&tr, origin, mins, maxs, end, ent->s.clientNum, ent->clipmask);

	if (tr.fraction){
		VectorCopy(tr.endpos, ent->client->ps.origin);
		ent->client->ps.origin[2] += 5;
	}
	else
		VectorCopy(origin, ent->client->ps.origin);

	VectorCopy(angles, ent->client->ps.viewangles);

//	PM_InitializeVehicle(&ent->client->car, ent->client->ps.origin, ent->client->ps.viewangles, vec3_origin, car_frontweight_dist.value );
	ent->client->car.initializeOnNextMove = qtrue;

	ent->client->ps.eFlags ^= EF_TELEPORT_BIT;

	// set the times so that we dont get multiple car resets in a row
	ent->client->car.onGroundTime = level.time;
	ent->client->car.offGroundTime = level.time;

	tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
	tent->s.clientNum = ent->s.clientNum;

//	TeleportPlayer( ent, origin, angles );
}


void G_DropRearWeapon( gentity_t *ent ) {
	gentity_t	*drop;
	gitem_t		*item;
	int			i;

	for (i = RWP_SMOKE; i < WP_NUM_WEAPONS; i++){
		if (ent->client->ps.stats[STAT_WEAPONS] & ( 1 << i )){
			ent->client->ps.stats[STAT_WEAPONS] &= ~( 1 << i );

			if (ent->client->ps.ammo[ i ]){
				item = BG_FindItemForWeapon( i );
				drop = Drop_Item(ent, item, 0);
				drop->count = ent->client->ps.ammo[i];
				ent->client->ps.ammo[i] = 0;
			}
		}
	}
}

void CenterPrint_All( const char *s ){
	trap_SendServerCommand( -1, va("cp \"%s\n\"", s) );
}

qboolean isRallyRace( void ){
	if(g_gametype.integer == GT_RACING
		|| g_gametype.integer == GT_RACING_DM
		|| g_gametype.integer == GT_TEAM_RACING
		|| g_gametype.integer == GT_TEAM_RACING_DM){
		return qtrue;
	}

	return qfalse;
}

qboolean isRallyNonDMRace( void ){
	if(g_gametype.integer == GT_RACING
		|| g_gametype.integer == GT_TEAM_RACING){
		return qtrue;
	}

	return qfalse;
}

/*
=================
isRaceObserver

Assumes ent has a valid client
=================
*/
qboolean isRaceObserver( int clientNum ){
//	return (ent->client->finishRaceTime && ent->client->finishRaceTime + RACE_OBSERVER_DELAY < level.time);
	return g_entities[clientNum].raceObserver;
}

void G_PrintMapStats( gentity_t *player, qboolean generateArenaFile, char *longname ){
	gentity_t		*ent;
	vec3_t			start, delta, last;
	int				i, j;
	fileHandle_t	arenaFile;
	char			string[1024];
	char			serverinfo[MAX_INFO_STRING];

	float			trackLength = 0;
	float			metricTrackLength = 0;
	int				numCheckpoints = 0;
	int				numLaps = 0;
	int				numSpawnPoints = 0;
	int				numObserverSpots = 0;
	int				numWeapons = 0;
	int				numPowerups = 0;

	VectorClear(start);
	VectorClear(last);

	for(i = 0; i < MAX_GENTITIES; i++){
		ent = &g_entities[i];
		if (!ent) continue;

		if (!strcmp(ent->classname, "rally_checkpoint")){
			numCheckpoints++;

			if (ent->laps){
				numLaps = ent->laps;
			}
		}
		else if (!strcmp(ent->classname, "info_player_start") ||
			!strcmp(ent->classname, "info_player_deathmatch")){
			numSpawnPoints++;
		}
		else if (!strcmp(ent->classname, "info_observer_spot")){
			numObserverSpots++;
		}
		else if (ent->s.eType == ET_ITEM){
			if (ent->item->giType == IT_WEAPON || ent->item->giType == IT_RFWEAPON)
				numWeapons++;
			else if (ent->item->giType == IT_POWERUP)
				numPowerups++;
			
		}
	}

	// track length
	for(i = 0; i < numCheckpoints; i++){
		for(j = 0; j < MAX_GENTITIES; j++){
			ent = &g_entities[j];
			if (!ent) continue;

			if ( !strcmp(ent->classname, "rally_checkpoint") ){
				if (ent->number == 1 && i == 0){
					VectorCopy(ent->s.origin, start);
					VectorCopy(ent->s.origin, last);
					break;
				}
				else if (ent->number == i + 1){
					VectorSubtract(last, ent->s.origin, delta);
					trackLength += VectorLength(delta);
					VectorCopy(ent->s.origin, last);
					break;
				}
			}
		}
	}
	VectorSubtract(last, start, delta);
	trackLength += VectorLength(delta);

	metricTrackLength = trackLength / CP_M_2_QU / 1000.0F;
	trackLength = trackLength / CP_FT_2_QU / 5280.0F;

	trap_SendServerCommand( player->s.number, va("print \"Laps: %i\nCheckpoints: %i\nSpawn Points: %i\nObserver Spots: %i\nNumber of Weapons: %i\nNumber of Powerups: %i\nTrack Length: %.3fmi / %.3fkm\n\"", 
							numLaps,
							numCheckpoints,
							numSpawnPoints,
							numObserverSpots,
							numWeapons,
							numPowerups,
							trackLength,
							metricTrackLength));

	if (!generateArenaFile)
		return;

	// generate .arena file
/*
{
map "demobowl_01"
longname "Demolition Derby Bowl"
fraglimit 10
type "q3r_derby"
starts "10"
}
*/
	trap_GetServerinfo( serverinfo, sizeof(serverinfo) );

	if ( isRallyRace() )
		Com_sprintf( string, sizeof(string), "{\nmap \"%s\"\nlongname \"%s\"\nfraglimit %i\ntype \"q3r_racing q3r_team_racing q3r_racing_dm q3r_team_racing_dm\"\nstarts \"%i\"\nlaps \"%i\"\nlength \"%.3f miles\"\ncheckpoints \"%i\"\nobserverspots \"%i\"\nweapons \"%i\"\npowerups \"%i\"\n}\n", Info_ValueForKey( serverinfo, "mapname" ), longname, level.numberOfLaps, numSpawnPoints, numLaps, trackLength, numCheckpoints, numObserverSpots, numWeapons, numPowerups);
	else if ( g_gametype.integer == GT_DERBY )
		Com_sprintf( string, sizeof(string), "{\nmap \"%s\"\nlongname \"%s\"\nfraglimit %i\ntype \"q3r_derby\"\nstarts \"%i\"\nobserverspots \"%i\"\nweapons \"%i\"\npowerups \"%i\"\n}\n", Info_ValueForKey( serverinfo, "mapname" ), longname, g_fraglimit.integer, numSpawnPoints, numObserverSpots, numWeapons, numPowerups);
	else if ( g_gametype.integer == GT_CTF )
		Com_sprintf( string, sizeof(string), "{\nmap \"%s\"\nlongname \"%s\"\nfraglimit %i\ntype \"q3r_ctf\"\nstarts \"%i\"\nobserverspots \"%i\"\nweapons \"%i\"\npowerups \"%i\"\n}\n", Info_ValueForKey( serverinfo, "mapname" ), longname, g_fraglimit.integer, numSpawnPoints, numObserverSpots, numWeapons, numPowerups);
	else if ( g_gametype.integer == GT_DEATHMATCH || g_gametype.integer == GT_TEAM )
		Com_sprintf( string, sizeof(string), "{\nmap \"%s\"\nlongname \"%s\"\nfraglimit %i\ntype \"q3r_dm q3r_team\"\nstarts \"%i\"\nobserverspots \"%i\"\nweapons \"%i\"\npowerups \"%i\"\n}\n", Info_ValueForKey( serverinfo, "mapname" ), longname, g_fraglimit.integer, numSpawnPoints, numObserverSpots, numWeapons, numPowerups);
	else
	{
		Com_Printf("Unknown game type while writing .arena file\n");
		return;
	}

	trap_FS_FOpenFile( va("scripts/%s.arena", Info_ValueForKey( serverinfo, "mapname" )), &arenaFile, FS_WRITE );
	if ( !arenaFile ) {
		return;
	}
	trap_FS_Write( string, strlen( string ), arenaFile );
	trap_FS_FCloseFile( arenaFile );
}