//
// C++ Implementation: OgreEntityRenderer
//
// Description:
//
//
// Author: Erik Ogenvik <erik@ogenvik.org>, (C) 2006
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.//
//
#include "OgreEntityRenderer.h"
#include "EntityCEGUITexture.h"
#include "components/ogre/SkeletonDisplay.h"

#include <OgreSceneNode.h>
#include <OgreSceneManager.h>
#include <OgreEntity.h>
#include <OgrePixelCountLodStrategy.h>
#include <OgreSkeletonInstance.h>
#include <CEGUI/Image.h>
#include <CEGUI/Window.h>

#include <memory>


namespace Ember::OgreView::Gui {

/**
 * @brief Stops the animation when the observed resource is unloaded.
 *
 * This prevents crashes, since the animation state will be incorrect when the skeleton has been unloaded (which happens when it's reloaded).
 */
class OgreEntityRendererResourceListener : public Ogre::Resource::Listener {
private:
	Ogre::AnimationState** mActiveAnimation;

public:
	OgreEntityRendererResourceListener(OgreEntityRenderer& renderer, Ogre::AnimationState** activeAnimation)
			: mActiveAnimation(activeAnimation) {
	}

	void unloadingComplete(Ogre::Resource*) override {
		*mActiveAnimation = nullptr;
	}

};

OgreEntityRenderer::OgreEntityRenderer(CEGUI::Window* image) :
		MovableObjectRenderer(image, image->getName().c_str()),
		mEntity(nullptr),
		mActiveAnimation(nullptr),
		mMeshListener(std::make_unique<OgreEntityRendererResourceListener>(*this, &mActiveAnimation)),
		mSkeletonListener(std::make_unique<OgreEntityRendererResourceListener>(*this, &mActiveAnimation)),
		mShowSkeleton(false) {
}

OgreEntityRenderer::~OgreEntityRenderer() {
	setEntity(nullptr);
}

Ogre::Entity* OgreEntityRenderer::getEntity() {
	return mEntity;
}

void OgreEntityRenderer::showEntity(const std::string& mesh) {
	unloadEntity();
	try {
		std::string entityName(mTexture->getImage()->getName().c_str());
		entityName += "_entity";
		mEntity = mTexture->getRenderContext()->getSceneNode()->getCreator()->createEntity(entityName, mesh);
		setEntity(mEntity);
		mTexture->getRenderContext()->setActive(true);
	} catch (const std::exception& ex) {
		logger->error("Error when creating entity: {}", ex.what());
	}
}

float OgreEntityRenderer::getMovableBoundingRadius() {
	return mEntity->getBoundingRadius();
}

void OgreEntityRenderer::setEntity(Ogre::Entity* entity) {
	mActiveAnimation = nullptr;
	Ogre::SceneNode* node = mTexture->getRenderContext()->getSceneNode();

	node->detachAllObjects();
	if (mEntity) {
		if (mMeshListener) {
			mEntity->getMesh()->removeListener(mMeshListener.get());
		}
		if (mEntity->hasSkeleton()) {
			mEntity->getSkeleton()->removeListener(mSkeletonListener.get());
		}
	}

	mSkeletonDisplay.reset();

	if (entity) {
		node->attachObject(entity);
		mTexture->getRenderContext()->repositionCamera();
		if (mAutoShowFull) {
			showFull();
		}

		entity->getMesh()->addListener(mMeshListener.get());
		if (entity->hasSkeleton()) {
			entity->getSkeleton()->addListener(mSkeletonListener.get());
			if (mShowSkeleton) {
				mSkeletonDisplay = std::make_unique<SkeletonDisplay>(*entity);
			}
		}
	}
}

Ogre::SceneManager* OgreEntityRenderer::getSceneManager() {
	return mTexture->getRenderContext()->getSceneManager();
}

void OgreEntityRenderer::unloadEntity() {
	setEntity(nullptr);
	if (mEntity) {
		Ogre::SceneManager* scenemgr = mTexture->getRenderContext()->getSceneManager();
		scenemgr->destroyEntity(mEntity);
		mEntity = nullptr;
	}
}

bool OgreEntityRenderer::getWireframeMode() {
	return (mTexture->getRenderContext()->getCamera()->getPolygonMode() == Ogre::PM_WIREFRAME);
}

void OgreEntityRenderer::setWireframeMode(bool enabled) {
	Ogre::PolygonMode mode = enabled ? Ogre::PM_WIREFRAME : Ogre::PM_SOLID;
	mTexture->getRenderContext()->getCamera()->setPolygonMode(mode);
}

void OgreEntityRenderer::setForcedLodLevel(int lodLevel) {
	if (mEntity) {
		mEntity->setMeshLodBias(1, lodLevel, lodLevel);
	}
}

void OgreEntityRenderer::clearForcedLodLevel() {
	if (mEntity) {
		mEntity->setMeshLodBias(1.0, 0, std::numeric_limits<unsigned short>::max());
	}
}

void OgreEntityRenderer::enableAnimation(const std::string& animationName) {
	if (mEntity && mEntity->getAllAnimationStates()) {
		for (const auto& entry: mEntity->getAllAnimationStates()->getAnimationStates()) {
			if (entry.second->getAnimationName() == animationName) {
				mActiveAnimation = entry.second;
				entry.second->setEnabled(true);
			} else {
				entry.second->setEnabled(false);
			}
		}
	}
}

std::vector<std::string> OgreEntityRenderer::getEntityAnimationNames() const {
	std::vector<std::string> names;

	if (mEntity && mEntity->getAllAnimationStates()) {
		for (const auto& entry: mEntity->getAllAnimationStates()->getAnimationStates()) {
			names.emplace_back(entry.second->getAnimationName());
		}
	}
	return names;
}

bool OgreEntityRenderer::frameStarted(const Ogre::FrameEvent& event) {
	if (mActiveAnimation) {
		mActiveAnimation->addTime(event.timeSinceLastFrame);
	}
	return MovableObjectRenderer::frameStarted(event);
}

void OgreEntityRenderer::setShowSkeleton(bool showSkeleton) {
	mShowSkeleton = showSkeleton;

	if (!mShowSkeleton) {
		mSkeletonDisplay.reset();
	} else {
		if (mEntity) {
			mSkeletonDisplay = std::make_unique<SkeletonDisplay>(*mEntity);
		}
	}


}


}


