#include "legopathactor.h"

#include "define.h"
#include "geom/legoorientededge.h"
#include "legocachesoundmanager.h"
#include "legocameracontroller.h"
#include "legonamedplane.h"
#include "legonavcontroller.h"
#include "legopathboundary.h"
#include "legopathedgecontainer.h"
#include "legosoundmanager.h"
#include "legoworld.h"
#include "misc.h"
#include "mxmisc.h"
#include "mxtimer.h"
#include "mxutilities.h"
#include "mxvariabletable.h"

#include <mxdebug.h>
#include <vec.h>

DECOMP_SIZE_ASSERT(LegoPathActor, 0x154)
DECOMP_SIZE_ASSERT(LegoPathEdgeContainer, 0x3c)

#ifndef M_PI
#define M_PI 3.1416
#endif
#ifdef DTOR
#undef DTOR
#endif
#define DTOR(angle) ((angle) * M_PI / 180.)

// GLOBAL: LEGO1 0x100f3304
// STRING: LEGO1 0x100f32f4
const char* g_strHIT_WALL_SOUND = "HIT_WALL_SOUND";

// GLOBAL: LEGO1 0x100f3308
// GLOBAL: BETA10 0x101f1e1c
MxLong g_timeLastHitSoundPlayed = 0;

// FUNCTION: LEGO1 0x1002d700
// FUNCTION: BETA10 0x100ae6e0
LegoPathActor::LegoPathActor()
{
	m_boundary = NULL;
	m_actorTime = 0;
	m_lastTime = 0;
	m_unk0x7c = 0;
	m_userNavFlag = FALSE;
	m_actorState = c_initial;
	m_grec = NULL;
	m_pathController = NULL;
	m_collideBox = FALSE;
	m_unk0x148 = 0;
	m_unk0x14c = 0;
	m_unk0x140 = 0.0099999999f;
	m_unk0x144 = 0.8f;
	m_unk0x150 = 2.0f;
}

// FUNCTION: LEGO1 0x1002d820
// FUNCTION: BETA10 0x100ae80e
LegoPathActor::~LegoPathActor()
{
	if (m_grec) {
		delete m_grec;
	}
}

// FUNCTION: LEGO1 0x1002d8d0
// FUNCTION: BETA10 0x100ae8cd
MxResult LegoPathActor::VTable0x80(const Vector3& p_point1, Vector3& p_point2, Vector3& p_point3, Vector3& p_point4)
{
	Mx3DPointFloat p1, p2, p3;

	p1 = p_point3;
	p1 -= p_point1;
	m_BADuration = p1.LenSquared();

	if (m_BADuration > 0.0f) {
		m_BADuration = sqrtf(m_BADuration);
		p2 = p_point2;
		p3 = p_point4;
		m_spline.SetSpline(p_point1, p2, p_point3, p3);
		m_BADuration /= 0.001;
		return SUCCESS;
	}
	else {
		MxTrace("Warning: m_BADuration = %g, roi = %s\n", m_BADuration, m_roi->GetName());
		return FAILURE;
	}
}

// FUNCTION: LEGO1 0x1002d9c0
// FUNCTION: BETA10 0x100ae9da
MxResult LegoPathActor::VTable0x88(
	LegoPathBoundary* p_boundary,
	float p_time,
	LegoEdge& p_srcEdge,
	float p_srcScale,
	LegoOrientedEdge& p_destEdge,
	float p_destScale
)
{
	Vector3* v1 = p_srcEdge.CWVertex(*p_boundary);
	Vector3* v2 = p_srcEdge.CCWVertex(*p_boundary);
	Vector3* v3 = p_destEdge.CWVertex(*p_boundary);
	Vector3* v4 = p_destEdge.CCWVertex(*p_boundary);

	Mx3DPointFloat p1, p2, p3, p4, p5;

	p1 = *v2;
	p1 -= *v1;
	p1 *= p_srcScale;
	p1 += *v1;

	p2 = *v4;
	p2 -= *v3;
	p2 *= p_destScale;
	p2 += *v3;

	m_boundary = p_boundary;
	m_destEdge = &p_destEdge;
	m_unk0xe4 = p_destScale;
	m_unk0x7c = 0;
	m_lastTime = p_time;
	m_actorTime = p_time;
	p_destEdge.GetFaceNormal(*p_boundary, p3);

	p4 = p2;
	p4 -= p1;
	p4.Unitize();

	MxMatrix matrix;
	Vector3 pos(matrix[3]);
	Vector3 dir(matrix[2]);
	Vector3 up(matrix[1]);
	Vector3 right(matrix[0]);

	matrix.SetIdentity();
	pos = p1;
	dir = p4;
	up = *m_boundary->GetUp();

	if (!m_cameraFlag || !m_userNavFlag) {
		dir *= -1.0f;
	}

	right.EqualsCross(up, dir);
	m_roi->UpdateTransformationRelativeToParent(matrix);

	if (!m_cameraFlag || !m_userNavFlag) {
		p5.EqualsCross(*p_boundary->GetUp(), p3);
		p5.Unitize();

		if (VTable0x80(p1, p4, p2, p5) == SUCCESS) {
			m_boundary->AddActor(this);
		}
		else {
			return FAILURE;
		}
	}
	else {
		m_boundary->AddActor(this);
		TransformPointOfView();
	}

	m_unk0xec = m_roi->GetLocal2World();
	return SUCCESS;
}

// FUNCTION: LEGO1 0x1002de10
// FUNCTION: BETA10 0x100aee61
MxResult LegoPathActor::VTable0x84(
	LegoPathBoundary* p_boundary,
	float p_time,
	Vector3& p_p1,
	Vector3& p_p4,
	LegoOrientedEdge* p_destEdge,
	float p_destScale
)
{
	assert(p_destEdge);

	Vector3* v3 = p_destEdge->CWVertex(*p_boundary);
	Vector3* v4 = p_destEdge->CCWVertex(*p_boundary);

	assert(v3 && v4);

	Mx3DPointFloat p2, p3, p5;

	p2 = *v4;
	p2 -= *v3;
	p2 *= p_destScale;
	p2 += *v3;

	m_boundary = p_boundary;
	m_destEdge = p_destEdge;
	m_unk0xe4 = p_destScale;
	m_unk0x7c = 0;
	m_lastTime = p_time;
	m_actorTime = p_time;
	p_destEdge->GetFaceNormal(*p_boundary, p3);

	MxMatrix matrix;
	Vector3 pos(matrix[3]);
	Vector3 dir(matrix[2]);
	Vector3 up(matrix[1]);
	Vector3 right(matrix[0]);

	matrix.SetIdentity();
	pos = p_p1;
	dir = p_p4;
	up = *m_boundary->GetUp();

	if (!m_cameraFlag || !m_userNavFlag) {
		dir *= -1.0f;
	}

	right.EqualsCross(up, dir);
	m_roi->UpdateTransformationRelativeToParent(matrix);

	if (m_cameraFlag && m_userNavFlag) {
		m_boundary->AddActor(this);
		TransformPointOfView();
	}
	else {
		p5.EqualsCross(*p_boundary->GetUp(), p3);
		p5.Unitize();

		if (VTable0x80(p_p1, p_p4, p2, p5) != SUCCESS) {
			MxTrace("Warning: m_BADuration = %g, roi = %s\n", m_BADuration, m_roi->GetName());
			return FAILURE;
		}

		m_boundary->AddActor(this);
	}

	m_unk0xec = m_roi->GetLocal2World();
	return SUCCESS;
}

// FUNCTION: LEGO1 0x1002e100
// FUNCTION: BETA10 0x100b0520
MxS32 LegoPathActor::VTable0x8c(float p_time, Matrix4& p_transform)
{
	if (m_userNavFlag && m_actorState == c_initial) {
		m_lastTime = p_time;

		Mx3DPointFloat p1, p2, p3, p4, p5;
		p5 = Vector3(m_roi->GetWorldDirection());
		p4 = Vector3(m_roi->GetWorldPosition());

		LegoNavController* nav = NavController();
		assert(nav);

		m_worldSpeed = nav->GetLinearVel();

		if (nav->CalculateNewPosDir(p4, p5, p2, p1, m_boundary->GetUp())) {
			Mx3DPointFloat p6;
			p6 = p2;
			MxS32 result = 0;

			m_unk0xe9 = m_boundary->Intersect(m_roi->GetWorldBoundingSphere().Radius(), p4, p2, p3, m_destEdge);
			if (m_unk0xe9 == -1) {
				MxTrace("Intersect returned -1\n");
				return -1;
			}
			else {
				if (m_unk0xe9 != 0) {
					p2 = p3;
				}
			}

			result = VTable0x68(p4, p2, p3);

			if (result > 0) {
				p2 = p4;
				m_unk0xe9 = 0;
				result = 0;
			}
			else {
				m_boundary->CheckAndCallPathTriggers(p4, p2, this);
			}

			LegoPathBoundary* oldBoundary = m_boundary;

			if (m_unk0xe9 != 0) {
				VTable0x9c();

				if (m_boundary == oldBoundary) {
					MxLong time = Timer()->GetTime();

					if (time - g_timeLastHitSoundPlayed > 1000) {
						g_timeLastHitSoundPlayed = time;
						const char* var = VariableTable()->GetVariable(g_strHIT_WALL_SOUND);

						if (var && var[0] != 0) {
							SoundManager()->GetCacheSoundManager()->Play(var, NULL, FALSE);
						}
					}

					m_worldSpeed *= m_unk0x144;
					nav->SetLinearVel(m_worldSpeed);
					Mx3DPointFloat p7(p2);
					p7 -= p6;

					if (p7.Unitize() == 0) {
						float f = sqrt(p1.LenSquared()) * m_unk0x140;
						p7 *= f;
						p1 += p7;
					}
				}
			}

			p_transform.SetIdentity();

			Vector3 right(p_transform[0]);
			Vector3 up(p_transform[1]);
			Vector3 dir(p_transform[2]);
			Vector3 pos(p_transform[3]);

			dir = p1;
			up = *m_boundary->GetUp();
			right.EqualsCross(up, dir);

			MxS32 res = right.Unitize();
			assert(res == 0);

			dir.EqualsCross(right, up);
			pos = p2;
			return result;
		}
		else {
			return -1;
		}
	}
	else if (p_time >= 0 && m_worldSpeed > 0) {
		float f = (m_BADuration - m_unk0x7c) / m_worldSpeed + m_lastTime;

		if (f < p_time) {
			m_unk0x7c = m_BADuration;
			m_unk0xe9 = 1;
		}
		else {
			f = p_time;
			m_unk0x7c += (f - m_lastTime) * m_worldSpeed;
			m_unk0xe9 = 0;
		}

		m_actorTime += (f - m_lastTime) * m_worldSpeed;
		m_lastTime = f;
		p_transform.SetIdentity();

		LegoResult r;
		if (m_userNavFlag) {
			r = m_spline.Evaluate(m_unk0x7c / m_BADuration, p_transform, *m_boundary->GetUp(), FALSE);
		}
		else {
			r = m_spline.Evaluate(m_unk0x7c / m_BADuration, p_transform, *m_boundary->GetUp(), TRUE);
		}

		assert(r == 0); // SUCCESS

		Vector3 pos1(p_transform[3]);
		Vector3 pos2(m_unk0xec[3]);
		Mx3DPointFloat p1;

		if (VTable0x68(pos2, pos1, p1) > 0) {
			m_lastTime = p_time;
			return 1;
		}
		else {
			m_boundary->CheckAndCallPathTriggers(pos2, pos1, this);
			pos2 = pos1;
		}

		if (m_unk0xe9 != 0) {
			VTable0x9c();
		}
	}
	else {
		return -1;
	}

	return 0;
}

// FUNCTION: LEGO1 0x1002e740
// FUNCTION: BETA10 0x100b0f70
void LegoPathActor::VTable0x74(Matrix4& p_transform)
{
	if (m_userNavFlag) {
		m_roi->WrappedSetLocal2WorldWithWorldDataUpdate(p_transform);
		TransformPointOfView();
	}
	else {
		m_roi->WrappedSetLocal2WorldWithWorldDataUpdate(p_transform);
		m_roi->WrappedUpdateWorldData();

		if (m_cameraFlag) {
			TransformPointOfView();
		}
	}
}

// FUNCTION: LEGO1 0x1002e790
// FUNCTION: BETA10 0x100af208
void LegoPathActor::Animate(float p_time)
{
	MxMatrix transform;
	MxU32 b = FALSE;

	while (m_lastTime < p_time) {
		if (m_actorState != c_initial && !VTable0x90(p_time, transform)) {
			return;
		}

		if (VTable0x8c(p_time, transform) != 0) {
			break;
		}

		m_unk0xec = transform;
		b = TRUE;

		if (m_unk0xe9 != 0) {
			break;
		}
	}

	if (m_userNavFlag && m_unk0x148) {
		LegoNavController* nav = NavController();
		float vel = (nav->GetLinearVel() > 0)
						? -(nav->GetRotationalVel() / (nav->GetMaxLinearVel() * m_unk0x150) * nav->GetLinearVel())
						: 0;

		if ((MxS32) vel != m_unk0x14c) {
			m_unk0x14c = vel;
			LegoWorld* world = CurrentWorld();

			if (world) {
				world->GetCameraController()->RotateZ(DTOR(m_unk0x14c));
			}
		}
	}

	if (b) {
		VTable0x74(transform);
	}
}

// FUNCTION: LEGO1 0x1002e8b0
// FUNCTION: BETA10 0x100af2f7
void LegoPathActor::SwitchBoundary(LegoPathBoundary*& p_boundary, LegoOrientedEdge*& p_edge, float& p_unk0xe4)
{
	assert(m_boundary);
	m_boundary->SwitchBoundary(this, p_boundary, p_edge, p_unk0xe4);
}

// FUNCTION: LEGO1 0x1002e8d0
// FUNCTION: BETA10 0x100b1010
MxU32 LegoPathActor::VTable0x6c(
	LegoPathBoundary* p_boundary,
	Vector3& p_v1,
	Vector3& p_v2,
	float p_f1,
	float p_f2,
	Vector3& p_v3
)
{
	LegoAnimPresenterSet& presenters = p_boundary->GetPresenters();

	for (LegoAnimPresenterSet::iterator itap = presenters.begin(); itap != presenters.end(); itap++) {
		if ((*itap)->Intersect(p_v1, p_v2, p_f1, p_f2, p_v3)) {
			return 1;
		}
	}

	LegoPathActorSet& plpas = p_boundary->GetActors();
	LegoPathActorSet lpas(plpas);

	for (LegoPathActorSet::iterator itpa = lpas.begin(); itpa != lpas.end(); itpa++) {
		if (plpas.find(*itpa) != plpas.end()) {
			LegoPathActor* actor = *itpa;

			if (this != actor && !(actor->GetActorState() & LegoPathActor::c_noCollide)) {
				LegoROI* roi = actor->GetROI();

				if (roi != NULL && (roi->GetVisibility() || actor->GetCameraFlag())) {
					if (roi->Intersect(p_v1, p_v2, p_f1, p_f2, p_v3, m_collideBox && actor->m_collideBox)) {
						HitActor(actor, TRUE);
						actor->HitActor(this, FALSE);
						return 2;
					}
				}
			}
		}
	}

	return 0;
}

inline MxU32 LegoPathActor::FUN_1002edd0(
	list<LegoPathBoundary*>& p_boundaries,
	LegoPathBoundary* p_boundary,
	Vector3& p_v1,
	Vector3& p_v2,
	float p_f1,
	float p_f2,
	Vector3& p_v3,
	MxS32 p_und
)
{
	MxU32 result = VTable0x6c(p_boundary, p_v1, p_v2, p_f1, p_f2, p_v3);

	if (result != 0) {
		return result;
	}

	p_boundaries.push_back(p_boundary);

	if (p_und >= 2) {
		return 0;
	}

	LegoS32 numEdges = p_boundary->GetNumEdges();
	for (MxS32 i = 0; i < numEdges; i++) {
		LegoOrientedEdge* edge = p_boundary->GetEdges()[i];
		LegoPathBoundary* boundary = (LegoPathBoundary*) edge->OtherFace(p_boundary);

		if (boundary != NULL) {
			list<LegoPathBoundary*>::const_iterator it;

			for (it = p_boundaries.begin(); !(it == p_boundaries.end()); it++) {
				if ((*it) == boundary) {
					break;
				}
			}

			if (it == p_boundaries.end()) {
				result = FUN_1002edd0(p_boundaries, boundary, p_v1, p_v2, p_f1, p_f2, p_v3, p_und + 1);

				if (result != 0) {
					return result;
				}
			}
		}
	}

	return 0;
}

// FUNCTION: LEGO1 0x1002ebe0
// FUNCTION: BETA10 0x100af35e
MxS32 LegoPathActor::VTable0x68(Vector3& p_v1, Vector3& p_v2, Vector3& p_v3)
{
	assert(m_boundary && m_roi);

	Mx3DPointFloat v2(p_v2);
	v2 -= p_v1;

	float len = v2.LenSquared();

	if (len <= 0.001) {
		return 0;
	}

	len = sqrt((double) len);
	v2 /= len;

	float radius = m_roi->GetWorldBoundingSphere().Radius();
	list<LegoPathBoundary*> boundaries;

	return FUN_1002edd0(boundaries, m_boundary, p_v1, v2, len, radius, p_v3, 0);
}

// FUNCTION: LEGO1 0x1002f020
// FUNCTION: BETA10 0x100af54a
void LegoPathActor::ParseAction(char* p_extra)
{
	LegoActor::ParseAction(p_extra);

	char value[256];
	value[0] = '\0';

	if (KeyValueStringParse(value, g_strPERMIT_NAVIGATE, p_extra)) {
		SetUserNavFlag(TRUE);
		NavController()->ResetMaxLinearVel(m_worldSpeed);
		SetUserActor(this);
	}

	char* token;
	if (KeyValueStringParse(value, g_strPATH, p_extra)) {
		char name[12];

		token = strtok(value, g_parseExtraTokens);
		strcpy(name, token);

		token = strtok(NULL, g_parseExtraTokens);
		MxS32 src = atoi(token);

		token = strtok(NULL, g_parseExtraTokens);
		float srcScale = atof(token);

		token = strtok(NULL, g_parseExtraTokens);
		MxS32 dest = atoi(token);

		token = strtok(NULL, g_parseExtraTokens);
		float destScale = atof(token);

		LegoWorld* world = CurrentWorld();
		if (world != NULL) {
			world->PlaceActor(this, name, src, srcScale, dest, destScale);
		}
	}

	if (KeyValueStringParse(value, g_strCOLLIDEBOX, p_extra)) {
		token = strtok(value, g_parseExtraTokens);
		m_collideBox = atoi(token);
	}
}

// FUNCTION: LEGO1 0x1002f1b0
// FUNCTION: BETA10 0x100af899
MxResult LegoPathActor::VTable0x9c()
{
	Mx3DPointFloat local34;
	Mx3DPointFloat local48;
	MxU32 local1c = 1;
	MxU32 local20 = 1;

	if (m_grec != NULL) {
		if (m_grec->HasPath()) {
			local1c = 0;
			local20 = 0;

			Mx3DPointFloat vec;
			switch (m_pathController->GetNextPathEdge(*m_grec, local34, local48, m_unk0xe4, m_destEdge, m_boundary)) {
			case 0:
			case 1:
				break;
			default:
				assert(0);
				return FAILURE;
			}
		}
		else {
			delete m_grec;
			m_grec = NULL;
		}
	}

	if (local1c != 0) {
		SwitchBoundary(m_boundary, m_destEdge, m_unk0xe4);
	}

	if (local20 != 0) {
		Mx3DPointFloat local78;

		assert(m_boundary && m_destEdge);

		Vector3* v1 = m_destEdge->CWVertex(*m_boundary);
		Vector3* v2 = m_destEdge->CCWVertex(*m_boundary);

		assert(v1 && v2);

		LERP3(local34, *v1, *v2, m_unk0xe4);

		m_destEdge->GetFaceNormal(*m_boundary, local78);
		local48.EqualsCross(*m_boundary->GetUp(), local78);
		local48.Unitize();
	}

	Vector3 rightRef(m_unk0xec[0]);
	Vector3 upRef(m_unk0xec[1]);
	Vector3 dirRef(m_unk0xec[2]);

	upRef = *m_boundary->GetUp();

	rightRef.EqualsCross(upRef, dirRef);
	rightRef.Unitize();

	dirRef.EqualsCross(rightRef, upRef);
	dirRef.Unitize();

	Mx3DPointFloat localc0(m_unk0xec[3]);
	Mx3DPointFloat local84(m_unk0xec[2]);
	Mx3DPointFloat local70(local34);

	local70 -= localc0;
	float len = local70.LenSquared();
	if (len >= 0.0f) {
		len = sqrt(len);
		local84 *= len;
		local48 *= len;
	}

	if (!m_userNavFlag) {
		local84 *= -1.0f;
	}

	if (VTable0x80(localc0, local84, local34, local48) != SUCCESS) {
		MxTrace("Warning: m_BADuration = %g, roi = %s\n", m_BADuration, m_roi->GetName());
		return FAILURE;
	}

	m_unk0x7c = 0.0f;
	return SUCCESS;
}

// FUNCTION: LEGO1 0x1002f650
// FUNCTION: BETA10 0x100afd67
void LegoPathActor::VTable0xa4(MxBool& p_und1, MxS32& p_und2)
{
	switch (GetActorId()) {
	case c_pepper:
		p_und1 = TRUE;
		p_und2 = 2;
		break;
	case c_mama:
		p_und1 = FALSE;
		p_und2 = 1;
		break;
	case c_papa:
		p_und1 = TRUE;
		p_und2 = 1;
		break;
	case c_nick:
	case c_brickster:
		p_und1 = TRUE;
		p_und2 = rand() % p_und2 + 1;
		break;
	case c_laura:
		p_und1 = FALSE;
		p_und2 = 2;
		break;
	default:
		p_und1 = TRUE;
		p_und2 = 1;
		break;
	}
}

// FUNCTION: LEGO1 0x1002f700
// FUNCTION: BETA10 0x100afe4c
void LegoPathActor::VTable0xa8()
{
	m_lastTime = Timer()->GetTime();
	m_roi->SetLocal2World(m_unk0xec);
	m_roi->WrappedUpdateWorldData();

	if (m_userNavFlag) {
		m_roi->WrappedSetLocal2WorldWithWorldDataUpdate(m_unk0xec);
		TransformPointOfView();
	}
}

// FUNCTION: LEGO1 0x1002f770
void LegoPathActor::UpdatePlane(LegoNamedPlane& p_namedPlane)
{
	p_namedPlane.SetName(m_boundary->GetName());
	p_namedPlane.SetPosition(GetWorldPosition());
	p_namedPlane.SetDirection(GetWorldDirection());
	p_namedPlane.SetUp(GetWorldUp());
}

// FUNCTION: LEGO1 0x1002f830
void LegoPathActor::PlaceActor(LegoNamedPlane& p_namedPlane)
{
	if (p_namedPlane.IsPresent()) {
		LegoWorld* world = CurrentWorld();
		world->PlaceActor(this, p_namedPlane.GetName(), 0, 0.5f, 1, 0.5f);
		SetLocation(p_namedPlane.GetPosition(), p_namedPlane.GetDirection(), p_namedPlane.GetUp(), TRUE);
	}
}
