#include "helicopter.h"

#include "act3.h"
#include "act3_actions.h"
#include "isle.h"
#include "isle_actions.h"
#include "jukebox.h"
#include "jukebox_actions.h"
#include "legoanimationmanager.h"
#include "legocameracontroller.h"
#include "legocontrolmanager.h"
#include "legogamestate.h"
#include "legoutils.h"
#include "legoworld.h"
#include "misc.h"
#include "mxdebug.h"
#include "mxmisc.h"
#include "mxtimer.h"
#include "mxtransitionmanager.h"
#include "scripts.h"

DECOMP_SIZE_ASSERT(Helicopter, 0x230)
DECOMP_SIZE_ASSERT(HelicopterState, 0x0c)
DECOMP_SIZE_ASSERT(Vector2, 0x08)
DECOMP_SIZE_ASSERT(Vector3, 0x08)
DECOMP_SIZE_ASSERT(Vector4, 0x08)
DECOMP_SIZE_ASSERT(Mx3DPointFloat, 0x14)
DECOMP_SIZE_ASSERT(Mx4DPointFloat, 0x18)
DECOMP_SIZE_ASSERT(Matrix4, 0x08)
DECOMP_SIZE_ASSERT(MxMatrix, 0x48)

// FUNCTION: LEGO1 0x10001e60
// FUNCTION: BETA10 0x1002a0d0
Helicopter::Helicopter()
{
	m_maxLinearVel = 60;
}

// FUNCTION: LEGO1 0x10003230
Helicopter::~Helicopter()
{
	ControlManager()->Unregister(this);
	IslePathActor::Destroy(TRUE);
}

// FUNCTION: LEGO1 0x100032c0
// FUNCTION: BETA10 0x1002a16d
MxResult Helicopter::Create(MxDSAction& p_dsAction)
{
	MxResult result = IslePathActor::Create(p_dsAction);

	m_world = CurrentWorld();
	if (m_world->IsA("Act3")) {
		((Act3*) m_world)->SetHelicopter(this);
	}

	assert(m_world);

	if (m_world != NULL) {
		m_world->Add(this);
	}

	CreateState();
	return result;
}

// FUNCTION: LEGO1 0x10003320
// FUNCTION: BETA10 0x1002a240
void Helicopter::CreateState()
{
	m_state = (HelicopterState*) GameState()->GetState("HelicopterState");
	if (!m_state) {
		m_state = (HelicopterState*) GameState()->CreateState("HelicopterState");
	}
}

// FUNCTION: LEGO1 0x10003360
// FUNCTION: BETA10 0x1002a29a
void Helicopter::Exit()
{
	assert(UserActor() == this);

	if (GameState()->GetCurrentAct() == LegoGameState::e_act1) {
		SpawnPlayer(
			LegoGameState::e_helicopterSpawn,
			TRUE,
			IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
		);
	}

	IslePathActor::Exit();

	if (GameState()->GetCurrentAct() == LegoGameState::e_act1) {
		GameState()->m_currentArea = LegoGameState::e_copter;
		if (UserActor() && UserActor()->IsA("IslePathActor")) {
			((IslePathActor*) UserActor())
				->SpawnPlayer(
					LegoGameState::e_unk55,
					TRUE,
					IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
				);
		}
	}

	m_state->m_unk0x08 = 0;
	RemoveFromCurrentWorld(m_script, IsleScript::c_HelicopterDashboard_Bitmap);
	RemoveFromCurrentWorld(m_script, IsleScript::c_HelicopterArms_Ctl);
	RemoveFromCurrentWorld(m_script, IsleScript::c_Helicopter_TakeOff_Ctl);
	RemoveFromCurrentWorld(m_script, IsleScript::c_Helicopter_Land_Ctl);
	RemoveFromCurrentWorld(m_script, Act3Script::c_Helicopter_Pizza_Ctl);
	RemoveFromCurrentWorld(m_script, Act3Script::c_Helicopter_Donut_Ctl);
	RemoveFromCurrentWorld(m_script, Act3Script::c_Helicopter_Info_Ctl);
	RemoveFromCurrentWorld(m_script, 0x1d);
	RemoveFromCurrentWorld(m_script, 0x1e);
	RemoveFromCurrentWorld(m_script, 0x1f);
	AnimationManager()->FUN_1005f6d0(TRUE);
	ControlManager()->Unregister(this);
}

// FUNCTION: LEGO1 0x10003480
// FUNCTION: BETA10 0x1002a3db
MxLong Helicopter::HandleClick()
{
	if (!CanExit()) {
		return 1;
	}

	if (!m_world) {
		m_world = CurrentWorld();
		assert(m_world);
	}

	AnimationManager()->FUN_1005f6d0(FALSE);

	if (UserActor()) {
		if (UserActor()->GetActorId() != GameState()->GetActorId()) {
			((IslePathActor*) UserActor())->Exit();
		}
	}

	switch (GameState()->GetCurrentAct()) {
	case LegoGameState::e_act1:
		m_script = *g_isleScript;
		AnimationManager()->FUN_10064670(NULL);
		SpawnPlayer(
			LegoGameState::e_unk41,
			TRUE,
			IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
		);
		((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::e_copter);
		Disable(TRUE, 0);
		TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, TRUE);
		SetActorState(c_disabled);
		PlayMusic(JukeboxScript::c_Jail_Music);
		break;
	case LegoGameState::e_act2:
		m_script = *g_act2mainScript;
		break;
	case LegoGameState::e_act3:
		m_script = *g_act3Script;
		break;
	}

	Enter();
	InvokeAction(Extra::ActionType::e_start, m_script, IsleScript::c_HelicopterDashboard, NULL);
	GetCurrentAction().SetObjectId(-1);
	ControlManager()->Register(this);
	return 1;
}

// FUNCTION: LEGO1 0x100035e0
// FUNCTION: BETA10 0x1002a587
MxLong Helicopter::HandleControl(LegoControlManagerNotificationParam& p_param)
{
	MxLong result = 0;

	assert(m_world);
	MxAtomId script;

	switch (GameState()->GetCurrentAct()) {
	case LegoGameState::e_act1:
		script = *g_isleScript;
		break;
	case LegoGameState::e_act2:
		script = *g_act2mainScript;
		break;
	case LegoGameState::e_act3:
		script = *g_act3Script;
		break;
	}

	if (p_param.m_enabledChild == 1) {
		MxU32 isPizza = FALSE;

		switch (p_param.m_clickedObjectId) {
		case IsleScript::c_HelicopterArms_Ctl:
			if (*g_act3Script == script) {
				((Act3*) CurrentWorld())->SetDestLocation(LegoGameState::e_infomain);
				TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE);
			}
			else if (m_state->m_unk0x08 != 0) {
				break;
			}

			Exit();
			GameState()->m_currentArea = LegoGameState::e_vehicleExited;
			result = 1;
			break;
		case IsleScript::c_Helicopter_TakeOff_Ctl: {
			if (*g_act3Script == script) {
				break;
			}

			Act1State* act1State = (Act1State*) GameState()->GetState("Act1State");
			assert(act1State);
			if (m_state->m_unk0x08 == 0) {
				act1State->m_state = Act1State::e_helicopter;
				m_state->m_unk0x08 = 1;
				m_world->RemoveActor(this);
				InvokeAction(Extra::ActionType::e_start, script, IsleScript::c_HelicopterTakeOff_Anim, NULL);
				SetActorState(c_initial);
			}

			result = 1;
			break;
		}
		case IsleScript::c_Helicopter_Land_Ctl:
			if (*g_act3Script == script) {
				break;
			}

			if (m_state->m_unk0x08 == 2) {
				m_state->m_unk0x08 = 3;
				m_world->RemoveActor(this);
				InvokeAction(Extra::ActionType::e_start, script, IsleScript::c_HelicopterLand_Anim, NULL);
				SetActorState(c_disabled);
			}

			result = 1;
			break;
		case Act3Script::c_Helicopter_Pizza_Ctl:
			if (*g_act3Script != script) {
				break;
			}

			isPizza = TRUE;
		case Act3Script::c_Helicopter_Donut_Ctl:
			if (*g_act3Script != script) {
				break;
			}

			assert(m_pathController);

			if (m_world && m_world->GetCameraController()) {
				Mx3DPointFloat location, direction, lookat;

				location = m_world->GetCameraController()->GetWorldLocation();
				direction = m_world->GetCameraController()->GetWorldDirection();

				lookat = direction;
				lookat *= 3.0f;
				location += lookat;

				Mx3DPointFloat v68, va4, up;
				Mx3DPointFloat v90(0, 1, 0);
				v68 = m_world->GetCameraController()->GetWorldUp();
				va4.EqualsCross(v68, direction);
				up.EqualsCross(va4, v90);

				if (isPizza) {
					if (((Act3*) m_world)->ShootPizza(m_pathController, location, direction, up) != SUCCESS) {
						MxTrace("Shoot pizza failed\n");
						break;
					}
				}
				else {
					if (((Act3*) m_world)->ShootDonut(m_pathController, location, direction, up) != SUCCESS) {
						MxTrace("Shoot donut failed\n");
						break;
					}
				}
			}

			result = 1;
			break;
		/* case Act3Script::c_Helicopter_Info_Ctl: */
		case IsleScript::c_Helicopter_Info_Ctl:
			if (GameState()->GetCurrentAct() == LegoGameState::e_act1) {
				((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::e_infomain);
				TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE);
				Exit();
			}
			else if (*g_act3Script == script) {
				((Act3*) CurrentWorld())->SetDestLocation(LegoGameState::e_infomain);
				TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE);
			}

			result = 1;
			break;
		// Unknown object ID
		case 0x1d:
			result = 1;
			break;
		}
	}

	return result;
}

// FUNCTION: LEGO1 0x10003c20
// FUNCTION: BETA10 0x1002ab4c
MxLong Helicopter::HandleEndAnim(LegoEndAnimNotificationParam& p_param)
{
	MxLong result = 0;

	switch (m_state->m_unk0x08) {
	case 1: {
		if (GameState()->GetCurrentAct() == LegoGameState::e_act1) {
			Act1State* act1State = (Act1State*) GameState()->GetState("Act1State");
			assert(act1State);
			act1State->m_state = Act1State::e_helicopter;
			SpawnPlayer(
				LegoGameState::e_unk42,
				TRUE,
				IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
			);
		}
		else {
			SpawnPlayer(
				LegoGameState::e_unk49,
				TRUE,
				IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
			);
		}

		m_state->m_unk0x08 = 2;

		MxMatrix matrix;
		matrix.SetIdentity();
		matrix.RotateX(0.5235987901687622); // PI / 6, 30 deg
		Vector3 at(matrix[3]), dir(matrix[2]), up(matrix[1]);

		assert(m_world && m_world->GetCameraController());
		m_world->GetCameraController()->SetWorldTransform(at, dir, up);
		TransformPointOfView();
		result = 1;
		break;
	}
	case 3: {
		MxMatrix matrix;
		matrix.SetIdentity();

		Vector3 at(matrix[3]), dir(matrix[2]), up(matrix[1]);
		at[1] = 1.25;

		assert(m_world && m_world->GetCameraController());
		m_world->GetCameraController()->SetWorldTransform(at, dir, up);

		if (GameState()->GetCurrentAct() == LegoGameState::e_act1) {
			Act1State* act1State = (Act1State*) GameState()->GetState("Act1State");
			assert(act1State);
			act1State->m_state = Act1State::e_none;
			SpawnPlayer(
				LegoGameState::e_unk41,
				TRUE,
				IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
			);
		}
		else {
			SpawnPlayer(
				LegoGameState::e_unk48,
				TRUE,
				IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
			);
		}

		m_state->m_unk0x08 = 0;
		result = 1;
		break;
	}
	}

	return result;
}

// FUNCTION: LEGO1 0x10003e90
void Helicopter::VTable0x74(Matrix4& p_transform)
{
	if (m_userNavFlag) {
		m_roi->UpdateTransformationRelativeToParent(p_transform);
		TransformPointOfView();
	}
	else {
		m_roi->SetLocal2World(p_transform);
		m_roi->WrappedUpdateWorldData();
		if (m_cameraFlag) {
			TransformPointOfView();
		}
	}
}

// FUNCTION: LEGO1 0x10003ee0
void Helicopter::Animate(float p_time)
{
	if (m_state->m_unk0x08 == 4 || m_state->m_unk0x08 == 5) {
		float f = m_unk0x1f0 - p_time + 3000.0f;
		if (f >= 0) {
			float f2 = f / -3000.0f + 1;
			if (f2 < 0) {
				f2 = 0;
			}
			if (f2 > 1.0f) {
				f2 = 1.0f;
			}

			MxMatrix mat;
			Vector3 v1(m_unk0x160[3]);
			Vector3 v2(mat[3]);
			Vector3 v3(m_unk0x1a8[3]);

			mat.SetIdentity();
			m_unk0x1f4.InterpolateToMatrix(mat, f2);

			v2 = v3;
			v2 -= v1;
			v2 *= f2;
			v2 += v1;

			m_world->GetCameraController()->TransformPointOfView(mat, 0);
		}
		else {
			if (m_state->m_unk0x08 == 4) {
				((Act3*) m_world)->FUN_10073400();
			}
			else {
				((Act3*) m_world)->FUN_10073430();
			}

			SetActorState(c_disabled);
		}
	}
	else {
		LegoPathActor::Animate(p_time);
	}
}

// FUNCTION: LEGO1 0x100042a0
void Helicopter::FUN_100042a0(const Matrix4& p_matrix)
{
	MxMatrix local48;
	MxMatrix local90;

	Vector3 vec1(local48[3]);    // local98  // esp+0x30
	Vector3 vec2(local90[3]);    // localac  // esp+0x1c
	Vector3 vec3(m_unk0x1a8[0]); // locala8  // esp+0x20
	Vector3 vec4(m_unk0x1a8[1]); // localb8  // esp+0x10
	Vector3 vec5(m_unk0x1a8[2]); // EDI

	// the typecast makes this function match for unknown reasons
	Vector3 vec6((const float*) m_unk0x1a8[3]); // locala0  // esp+0x28

	m_world->GetCameraController()->GetPointOfView(local48);
	m_unk0x1a8.SetIdentity();
	local90 = p_matrix;

	vec2[1] += 20.0f;
	vec4 = vec2;
	vec4 -= vec1;
	vec4.Unitize();

	vec5[0] = vec5[2] = 0.0f;
	vec5[1] = -1.0f;

	vec3.EqualsCross(vec4, vec5);
	vec3.Unitize();
	vec4.EqualsCross(vec5, vec3);
	vec6 = vec2;

	local90 = m_unk0x1a8;
	m_unk0x160 = local48;

	vec1.Clear();
	vec2.Clear();

	m_unk0x1f0 = Timer()->GetTime();

	m_unk0x1f4.SetStartEnd(local48, local90);
	m_unk0x1f4.NormalizeDirection();
}

// FUNCTION: LEGO1 0x10004640
void Helicopter::FUN_10004640(const Matrix4& p_matrix)
{
	if (m_state->m_unk0x08 != 4 && m_state->m_unk0x08 != 5) {
		m_state->m_unk0x08 = 4;
		FUN_100042a0(p_matrix);
	}
}

// FUNCTION: LEGO1 0x10004670
void Helicopter::FUN_10004670(const Matrix4& p_matrix)
{
	if (m_state->m_unk0x08 != 4 && m_state->m_unk0x08 != 5) {
		m_state->m_unk0x08 = 5;
		FUN_100042a0(p_matrix);
	}
}
