#include "common.h"

#include "EmergencyPed.h"
#include "DMAudio.h"
#include "ModelIndices.h"
#include "Vehicle.h"
#include "Fire.h"
#include "General.h"
#include "CarCtrl.h"
#include "Accident.h"

CEmergencyPed::CEmergencyPed(uint32 type) : CPed(type)
{
	switch (type){
		case PEDTYPE_EMERGENCY:
			SetModelIndex(MI_MEDIC);
			m_pRevivedPed = nil;
			field_1360 = 0;
			break;
		case PEDTYPE_FIREMAN:
			SetModelIndex(MI_FIREMAN);
			m_pRevivedPed = nil;
			break;
		default:
			break;
	}
	m_nEmergencyPedState = EMERGENCY_PED_READY;
	m_pAttendedAccident = nil;
	m_bStartedToCPR = false;
}

bool
CEmergencyPed::InRange(CPed *victim)
{
	if (!m_pMyVehicle)
		return true;

	if ((m_pMyVehicle->GetPosition() - victim->GetPosition()).Magnitude() > 30.0f)
		return false;

	return true;
}

void
CEmergencyPed::ProcessControl(void)
{
	if (m_nZoneLevel > LEVEL_GENERIC && m_nZoneLevel != CCollision::ms_collisionInMemory)
		return;

	CPed::ProcessControl();
	if (bWasPostponed)
		return;

	if(!DyingOrDead()) {
		GetWeapon()->Update(m_audioEntityId);

		if (IsPedInControl() && m_moved.Magnitude() > 0.0f)
			Avoid();

		switch (m_nPedState) {
			case PED_SEEK_POS:
				Seek();
				break;
			case PED_SEEK_ENTITY:
				if (m_pSeekTarget) {
					m_vecSeekPos = m_pSeekTarget->GetPosition();
					Seek();
				} else {
					ClearSeek();
				}
				break;
			default:
				break;
		}

		switch (m_nPedType) {
			case PEDTYPE_EMERGENCY:
				if (IsPedInControl() || m_nPedState == PED_DRIVING)
					MedicAI();
				break;
			case PEDTYPE_FIREMAN:
				if (IsPedInControl())
					FiremanAI();
				break;
			default:
				return;
		}
	}
}

// This function was buggy and incomplete in both III and VC, firemen had to be in 5.0m range of fire etc. etc.
// Copied some code from MedicAI to make it work.
void
CEmergencyPed::FiremanAI(void)
{
	float fireDist;
	CFire *nearestFire;

	switch (m_nEmergencyPedState) {
		case EMERGENCY_PED_READY:
			nearestFire = gFireManager.FindNearestFire(GetPosition(), &fireDist);
			if (nearestFire) {
				m_nPedState = PED_NONE;
				SetSeek(nearestFire->m_vecPos, 1.0f);
				SetMoveState(PEDMOVE_RUN);
				m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
				m_pAttendedFire = nearestFire;
#ifdef FIX_BUGS
				bIsRunning = true;
				++nearestFire->m_nFiremenPuttingOut;
#endif
			}
			break;
		case EMERGENCY_PED_DETERMINE_NEXT_STATE:
			nearestFire = gFireManager.FindNearestFire(GetPosition(), &fireDist);
			if (nearestFire && nearestFire != m_pAttendedFire) {
				m_nPedState = PED_NONE;
				SetSeek(nearestFire->m_vecPos, 1.0f);
				SetMoveState(PEDMOVE_RUN);
#ifdef FIX_BUGS
				bIsRunning = true;
				if (m_pAttendedFire) {
					--m_pAttendedFire->m_nFiremenPuttingOut;
				}
				++nearestFire->m_nFiremenPuttingOut;
				m_pAttendedFire = nearestFire;
			} else if (!nearestFire) {
#else
				m_pAttendedFire = nearestFire;
			} else {
#endif
				m_nEmergencyPedState = EMERGENCY_PED_STOP;
			}

			// "Extinguish" the fire (Will overwrite the stop decision above if the attended and nearest fires are same)
			if (fireDist < 5.0f) {
				SetIdle();
				m_nEmergencyPedState = EMERGENCY_PED_STAND_STILL;
			}
			break;
		case EMERGENCY_PED_STAND_STILL:
			if (!m_pAttendedFire->m_bIsOngoing)
				m_nEmergencyPedState = EMERGENCY_PED_STOP;

			// Leftover
			// fireDist = 30.0f;
			nearestFire = gFireManager.FindNearestFire(GetPosition(), &fireDist);
			if (nearestFire) {
#ifdef FIX_BUGS
				if(nearestFire != m_pAttendedFire && (nearestFire->m_vecPos - GetPosition()).Magnitude() < 30.0f)
#endif
					m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
			}
			Say(SOUND_PED_EXTINGUISHING_FIRE);
			break;
		case EMERGENCY_PED_STOP:
#ifdef FIX_BUGS
			bIsRunning = false;
			if (m_pAttendedFire)
#endif
				--m_pAttendedFire->m_nFiremenPuttingOut;

			m_nPedState = PED_NONE;
			SetWanderPath(CGeneral::GetRandomNumber() & 7);
			m_pAttendedFire = nil;
			m_nEmergencyPedState = EMERGENCY_PED_READY;
			SetMoveState(PEDMOVE_WALK);
			break;
		default: break;
	}
}

void
CEmergencyPed::MedicAI(void)
{
	float distToEmergency;
	if (!bInVehicle && IsPedInControl()) {
		ScanForThreats();
		if (m_threatEntity && m_threatEntity->IsPed() && ((CPed*)m_threatEntity)->IsPlayer()) {
			if (((CPed*)m_threatEntity)->GetWeapon()->IsTypeMelee()) {
				SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity);
			} else {
				SetFlee(m_threatEntity, 6000);
				Say(SOUND_PED_FLEE_SPRINT);
			}
			return;
		}
	}

	if (InVehicle()) {
		if (m_pMyVehicle->IsCar() && m_objective != OBJECTIVE_LEAVE_CAR) {
			if (gAccidentManager.FindNearestAccident(m_pMyVehicle->GetPosition(), &distToEmergency)
				&& distToEmergency < 25.0f && m_pMyVehicle->m_vecMoveSpeed.Magnitude() < 0.01f) {

				m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_NONE;
				SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle);
				Say(SOUND_PED_LEAVE_VEHICLE);
			} else if (m_pMyVehicle->pDriver == this && m_nPedState == PED_DRIVING
				&& m_pMyVehicle->AutoPilot.m_nCarMission == MISSION_NONE && !(CGeneral::GetRandomNumber() & 31)) {

				bool waitUntilMedicEntersCar = false;
				for (int i = 0; i < m_numNearPeds; ++i) {
					CPed *nearPed = m_nearPeds[i];
					if (nearPed->m_nPedType == PEDTYPE_EMERGENCY) {
						if ((nearPed->m_nPedState == PED_SEEK_CAR || nearPed->m_nPedState == PED_ENTER_CAR)
							&& nearPed->m_pMyVehicle == m_pMyVehicle) {
							waitUntilMedicEntersCar = true;
							break;
						}
					}
				}
				if (!waitUntilMedicEntersCar) {
					CCarCtrl::JoinCarWithRoadSystem(m_pMyVehicle);
					m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE;
					m_pMyVehicle->m_bSirenOrAlarm = 0;
					m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 12;
					m_pMyVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_SLOW_DOWN_FOR_CARS;
					if (m_pMyVehicle->bIsAmbulanceOnDuty) {
						m_pMyVehicle->bIsAmbulanceOnDuty = false;
						--CCarCtrl::NumAmbulancesOnDuty;
					}
				}
			}
		}
	}

	CVector headPos, midPos;
	CAccident *nearestAccident;
	if (IsPedInControl()) {
		switch (m_nEmergencyPedState) {
			case EMERGENCY_PED_READY:
				nearestAccident = gAccidentManager.FindNearestAccident(GetPosition(), &distToEmergency);
				field_1360 = 0;
				if (nearestAccident) {
					m_pRevivedPed = nearestAccident->m_pVictim;
					m_pRevivedPed->RegisterReference((CEntity**)&m_pRevivedPed);
					m_pRevivedPed->m_pedIK.GetComponentPosition((RwV3d*)&midPos, PED_MID);
					m_pRevivedPed->m_pedIK.GetComponentPosition((RwV3d*)&headPos, PED_HEAD);
					SetSeek((headPos + midPos) * 0.5f, 1.0f);
					SetObjective(OBJECTIVE_NONE);
					bIsRunning = true;
					m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
					m_pAttendedAccident = nearestAccident;
					++m_pAttendedAccident->m_nMedicsAttending;
				} else {
					if (m_pMyVehicle) {
						if (!bInVehicle) {
							if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_pMyVehicle->pDriver || m_pMyVehicle->m_nGettingInFlags) {

								CPed* driver = m_pMyVehicle->pDriver;
								if (driver && driver->m_nPedType != PEDTYPE_EMERGENCY && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) {
									SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, driver);
								} else if (m_objective != OBJECTIVE_ENTER_CAR_AS_DRIVER
									&& m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER
									&& m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) {
									SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, m_pMyVehicle);
								}
							} else {
								SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle);
							}
						}
					} else if (m_nPedState != PED_WANDER_PATH) {
						SetWanderPath(CGeneral::GetRandomNumber() & 7);
					}
				}
				break;
			case EMERGENCY_PED_DETERMINE_NEXT_STATE:
				nearestAccident = gAccidentManager.FindNearestAccident(GetPosition(), &distToEmergency);
				if (nearestAccident) {
					if (nearestAccident != m_pAttendedAccident || m_nPedState != PED_SEEK_POS) {
						m_pRevivedPed = nearestAccident->m_pVictim;
						m_pRevivedPed->RegisterReference((CEntity**)&m_pRevivedPed);
						if (!InRange(m_pRevivedPed)) {
							m_nEmergencyPedState = EMERGENCY_PED_STOP;
							break;
						}
						m_pRevivedPed->m_pedIK.GetComponentPosition((RwV3d*)&midPos, PED_MID);
						m_pRevivedPed->m_pedIK.GetComponentPosition((RwV3d*)&headPos, PED_HEAD);
						SetSeek((headPos + midPos) * 0.5f, nearestAccident->m_nMedicsPerformingCPR * 0.5f + 1.0f);
						SetObjective(OBJECTIVE_NONE);
						bIsRunning = true;
						--m_pAttendedAccident->m_nMedicsAttending;
						++nearestAccident->m_nMedicsAttending;
						m_pAttendedAccident = nearestAccident;
					}
				} else {
					m_nEmergencyPedState = EMERGENCY_PED_STOP;
					bIsRunning = false;
				}
				if (distToEmergency < 5.0f) {
					if (m_pRevivedPed->m_pFire) {
						bIsRunning = false;
						SetMoveState(PEDMOVE_STILL);
					} else if (distToEmergency < 4.5f) {
						bIsRunning = false;
						SetMoveState(PEDMOVE_WALK);
						if (distToEmergency < 1.0f
							|| distToEmergency < 4.5f && m_pAttendedAccident->m_nMedicsPerformingCPR) {
							m_nEmergencyPedState = EMERGENCY_PED_START_CPR;
						}
					}
				}
				break;
			case EMERGENCY_PED_START_CPR:
				if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f || m_pRevivedPed->bFadeOut) {
					m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
				} else {
					m_pRevivedPed->m_bloodyFootprintCountOrDeathTime = CTimer::GetTimeInMilliseconds();
					SetMoveState(PEDMOVE_STILL);
					m_nPedState = PED_CPR;
					m_nLastPedState = PED_CPR;
					SetLookFlag(m_pRevivedPed, 0);
					SetLookTimer(500);
					Say(SOUND_PED_HEALING);
					if (m_pAttendedAccident->m_nMedicsPerformingCPR) {
						SetIdle();
						m_nEmergencyPedState = EMERGENCY_PED_STAND_STILL;
					} else {
						m_nEmergencyPedState = EMERGENCY_PED_FACE_TO_PATIENT;
						m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_CPR, 4.0f);
						bIsDucking = true;
					}
					SetLookTimer(2000);
					++m_pAttendedAccident->m_nMedicsPerformingCPR;
					m_bStartedToCPR = true;
				}
				break;
			case EMERGENCY_PED_FACE_TO_PATIENT:
				if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f)
					m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
				else {
					m_pRevivedPed->m_pedIK.GetComponentPosition((RwV3d*)&midPos, PED_MID);
					m_pRevivedPed->m_pedIK.GetComponentPosition((RwV3d*)&headPos, PED_HEAD);
					midPos = (headPos + midPos) * 0.5f;
					m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(
						midPos.x, midPos.y,
						GetPosition().x, GetPosition().y);
					m_fRotationDest = CGeneral::LimitAngle(m_fRotationDest);
					m_pLookTarget = m_pRevivedPed;
					m_pLookTarget->RegisterReference((CEntity**)&m_pLookTarget);
					TurnBody();

					if (Abs(m_fRotationCur - m_fRotationDest) < DEGTORAD(45.0f))
						m_nEmergencyPedState = EMERGENCY_PED_PERFORM_CPR;
					else
						m_fRotationCur = (m_fRotationCur + m_fRotationDest) * 0.5f;
				}
				break;
			case EMERGENCY_PED_PERFORM_CPR:
				if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f) {
					m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
					break;
				}
				m_pRevivedPed->m_pedIK.GetComponentPosition((RwV3d*)&midPos, PED_MID);
				m_pRevivedPed->m_pedIK.GetComponentPosition((RwV3d*)&headPos, PED_HEAD);
				midPos = (headPos + midPos) * 0.5f;
				m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(
					midPos.x, midPos.y,
					GetPosition().x, GetPosition().y);
				m_fRotationDest = CGeneral::LimitAngle(m_fRotationDest);
				m_pLookTarget = m_pRevivedPed;
				m_pLookTarget->RegisterReference((CEntity**)&m_pLookTarget);
				TurnBody();
				if (CTimer::GetTimeInMilliseconds() <= m_lookTimer) {
					SetMoveState(PEDMOVE_STILL);
					break;
				}
				m_nEmergencyPedState = EMERGENCY_PED_STOP_CPR;
				m_nPedState = PED_NONE;
				SetMoveState(PEDMOVE_WALK);
				m_pVehicleAnim = nil;
				if (!m_pRevivedPed->bBodyPartJustCameOff) {
					m_pRevivedPed->m_fHealth = 100.0f;
					m_pRevivedPed->m_nPedState = PED_NONE;
					m_pRevivedPed->m_nLastPedState = PED_WANDER_PATH;
					m_pRevivedPed->SetGetUp();
					m_pRevivedPed->bUsesCollision = true;
					m_pRevivedPed->SetMoveState(PEDMOVE_WALK);
					m_pRevivedPed->RestartNonPartialAnims();
					m_pRevivedPed->bIsPedDieAnimPlaying = false;
					m_pRevivedPed->bKnockedUpIntoAir = false;
					m_pRevivedPed->m_pCollidingEntity = nil;
				}
				break;
			case EMERGENCY_PED_STOP_CPR:
				m_nEmergencyPedState = EMERGENCY_PED_STOP;
				bIsDucking = true;
				break;
			case EMERGENCY_PED_STAND_STILL:
				if (!m_pRevivedPed || m_pRevivedPed->m_fHealth > 0.0f)
					m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
				else {
					if (!m_pAttendedAccident->m_pVictim)
						m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
					if (!m_pAttendedAccident->m_nMedicsPerformingCPR)
						m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
					if (gAccidentManager.UnattendedAccidents())
						m_nEmergencyPedState = EMERGENCY_PED_DETERMINE_NEXT_STATE;
				}
				break;
			case EMERGENCY_PED_STOP:
				m_bStartedToCPR = false;
				m_nPedState = PED_NONE;
				if (m_pAttendedAccident) {
					m_pAttendedAccident->m_pVictim = nil;
					--m_pAttendedAccident->m_nMedicsAttending;
					m_pAttendedAccident = nil;
				}
				SetWanderPath(CGeneral::GetRandomNumber() & 7);
				m_pRevivedPed = nil;
				m_nEmergencyPedState = EMERGENCY_PED_READY;
				SetMoveState(PEDMOVE_WALK);
				break;
			default: break;
		}
	}
}

src/peds/EmergencyPed.cpp