#include "Transform.h"
#include "Entity.h"

namespace XEngine::transform {

namespace {

utl::vector<math::m4x4>		to_world;
utl::vector<math::m4x4>		inv_world;
utl::vector<math::v3>		positions;
utl::vector<math::v3>		orientations;
utl::vector<math::v4>		rotations;
utl::vector<math::v3>		scales;
utl::vector<u8>				has_transform;
utl::vector<u8>				changes_from_previous_frame;
u8							read_write_flag;

void
calculate_transform_matrices(id::id_type index)
{
	assert(rotations.size() >= index);
	assert(positions.size() >= index);
	assert(scales.size() >= index);

	using namespace DirectX;
	XMVECTOR r{ XMLoadFloat4(&rotations[index]) };
	XMVECTOR t{ XMLoadFloat3(&positions[index]) };
	XMVECTOR s{ XMLoadFloat3(&scales[index]) };

	XMMATRIX world{ XMMatrixAffineTransformation(s, XMQuaternionIdentity(), r, t) };
	XMStoreFloat4x4(&to_world[index], world);

	world.r[3] = XMVectorSet(0.f, 0.f, 0.f, 1.f);
	XMMATRIX inverse_world{  XMMatrixInverse(nullptr,world) };
	XMStoreFloat4x4(&inv_world[index], inverse_world);

	has_transform[index] = 1;
}

math::v3
calculate_orientation(math::v4 rotation)
{
	using namespace DirectX;
	XMVECTOR rotation_quat{ XMLoadFloat4(&rotation) };
	XMVECTOR front{ XMVectorSet(0.f,0.f,1.f,0.f) };
	math::v3 orientation;
	XMStoreFloat3(&orientation, XMVector3Rotate(front, rotation_quat));
	return orientation;
}


void
set_rotation(transform_id id, const math::v4& rotation_quaternion)
{
	const u32 index{ id::index(id) };
	rotations[index] = rotation_quaternion;
	orientations[index] = calculate_orientation(rotation_quaternion);
	has_transform[index] = 0;
	changes_from_previous_frame[index] |= component_flags::rotation;
}
void
set_orientation(transform_id id, const math::v3& rotation_quaternion)
{

}
void
set_position(transform_id id, const math::v3& position)
{
	const u32 index{ id::index(id) };
	positions[index] = position;
	has_transform[index] = 0;
	changes_from_previous_frame[index] |= component_flags::position;
}
void
set_scale(transform_id id, const math::v3& scale)
{
	const u32 index{ id::index(id) };
	scales[index] = scale;
	has_transform[index] = 0;
	changes_from_previous_frame[index] |= component_flags::scale;
}


} // namespace

component
create(init_info info, game_entity::entity entity)
{
	assert(entity.is_valid());
	const id::id_type entity_index{ id::index(entity.get_id()) };

	if (positions.size() > entity_index)
	{
		math::v4 rotation{ info.rotation };
		rotations[entity_index] = rotation;
		orientations[entity_index] = calculate_orientation(rotation);
		positions[entity_index] = math::v3{ info.position };
		scales[entity_index] = math::v3{ info.scale };
		has_transform[entity_index] = 0;
		changes_from_previous_frame[entity_index] = (u8)component_flags::all;
	}
	else
	{
		assert(positions.size() == entity_index);
		rotations.emplace_back(info.rotation);
		orientations.emplace_back(calculate_orientation(math::v4{ info.rotation }));
		positions.emplace_back(info.position);
		scales.emplace_back(info.scale);
		has_transform.emplace_back((u8)0);
		to_world.emplace_back();
		inv_world.emplace_back();
		changes_from_previous_frame.emplace_back((u8)component_flags::all);
	}

	return component{ transform_id{ entity.get_id() } };
}
void
remove([[maybe_unused]] component c)
{
	assert(c.is_valid());
}

void
get_transform_matrices(const game_entity::entity_id id, math::m4x4& world, math::m4x4& inverse_world)
{
	assert(game_entity::entity{ id }.is_valid());

	const id::id_type entity_index{ id::index(id) };
	if (!has_transform[entity_index])
	{
		calculate_transform_matrices(entity_index);
	}

	world = to_world[entity_index];
	inverse_world = inv_world[entity_index];

}

void
get_update_component_flags(const game_entity::entity_id *const ids, u32 count, u8 *const flags)
{
	assert(ids && count && flags);
	read_write_flag = 1;

	for (u32 i{ 0 }; i < count; ++i)
	{
		assert(game_entity::entity{ ids[i] }.is_valid());
		flags[i] = changes_from_previous_frame[id::index(ids[i])];
	}
}
void
update(const component_cache *const cache, u32 count)
{
	assert(cache && count);

	if (read_write_flag)
	{
		memset(changes_from_previous_frame.data(), 0, changes_from_previous_frame.size());
		read_write_flag = 0;
	}

	for (u32 i{ 0 }; i < count; ++i)
	{
		const component_cache& c{ cache[i] };
		assert(component{ c.id }.is_valid());
		if (c.flags & component_flags::rotation)
		{
			set_rotation(c.id, c.rotation);
		}
		if (c.flags & component_flags::orientation)
		{
			set_orientation(c.id, c.orientation);
		}
		if (c.flags & component_flags::position)
		{
			set_position(c.id, c.position);
		}
		if (c.flags & component_flags::scale)
		{
			set_scale(c.id, c.scale);
		}
	}
}

math::v3
component::orientation() const
{
	assert(is_valid());
	return orientations[id::index(_id)];
}

math::v3
component::position() const
{
	assert(is_valid());
	return positions[id::index(_id)];
}
math::v3
component::scale() const
{
	assert(is_valid());
	return scales[id::index(_id)];

}
math::v4
component::rotation() const
{
	assert(is_valid());
	return rotations[id::index(_id)];

}

}