/*
-----------------------------------------------------------------------------
This source file is part of OGRE
    (Object-oriented Graphics Rendering Engine)
For the latest info, see http://www.ogre3d.org/

Copyright (c) 2000-2005 The OGRE Team
Also see acknowledgements in Readme.html

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser 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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt.
-----------------------------------------------------------------------------
*/
#include "OgreStableHeaders.h"
#include "OgreCompositorChain.h"
#include "OgreCompositionTechnique.h"
#include "OgreCompositorInstance.h"
#include "OgreCompositionTargetPass.h"
#include "OgreCompositionPass.h"
#include "OgreViewport.h"
#include "OgreCamera.h"
#include "OgreRenderTarget.h"
#include "OgreLogManager.h"
#include "OgreCompositorManager.h"
#include "OgreSceneManager.h"
#include "OgreRenderQueueInvocation.h"
namespace Ogre {
CompositorChain::CompositorChain(Viewport *vp):
    mViewport(vp),
	mOriginalScene(0),
    mDirty(true),
	mAnyCompositorsEnabled(false)
{
	mOldClearEveryFrameBuffers = mViewport->getClearBuffers();
    assert(mViewport);
}
//-----------------------------------------------------------------------
CompositorChain::~CompositorChain()
{
	destroyResources();
}
//-----------------------------------------------------------------------
void CompositorChain::destroyResources(void)
{
	clearCompiledState();

	if (mViewport)
	{
		removeAllCompositors();
		mViewport->getTarget()->removeListener(this);
		/// Destroy "original scene" compositor instance
		mOriginalScene->getTechnique()->destroyInstance(mOriginalScene);
		mViewport = 0;
	}
}
//-----------------------------------------------------------------------
CompositorInstance* CompositorChain::addCompositor(CompositorPtr filter, size_t addPosition, size_t technique)
{
	// Init on demand
	if (!mOriginalScene)
	{
		mViewport->getTarget()->addListener(this);
		
		/// Create base "original scene" compositor
		CompositorPtr base = CompositorManager::getSingleton().load("Ogre/Scene",
			ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME);
		mOriginalScene = base->getSupportedTechnique(0)->createInstance(this);
	}


	filter->touch();
    if(technique >= filter->getNumSupportedTechniques())
    {
        /// Warn user
        LogManager::getSingleton().logMessage(
            "CompositorChain: Compositor " + filter->getName() + " has no supported techniques.", LML_CRITICAL
        );
        return 0;
    }
    CompositionTechnique *tech = filter->getSupportedTechnique(technique);
    CompositorInstance *t = tech->createInstance(this);
    
    if(addPosition == LAST)
        addPosition = mInstances.size();
    else
        assert(addPosition <= mInstances.size() && "Index out of bounds.");
    mInstances.insert(mInstances.begin()+addPosition, t);
    
    mDirty = true;
	mAnyCompositorsEnabled = true;
    return t;
}
//-----------------------------------------------------------------------
void CompositorChain::removeCompositor(size_t index)
{
    assert (index < mInstances.size() && "Index out of bounds.");
    Instances::iterator i = mInstances.begin() + index;
    (*i)->getTechnique()->destroyInstance(*i);
    mInstances.erase(i);
    
    mDirty = true;
}
//-----------------------------------------------------------------------
size_t CompositorChain::getNumCompositors()
{
    return mInstances.size();
}
//-----------------------------------------------------------------------
void CompositorChain::removeAllCompositors()
{
    Instances::iterator i, iend;
    iend = mInstances.end();
    for (i = mInstances.begin(); i != iend; ++i)
    {
        (*i)->getTechnique()->destroyInstance(*i);
    }
    mInstances.clear();
    
    mDirty = true;
}
//-----------------------------------------------------------------------
void CompositorChain::_removeInstance(CompositorInstance *i)
{
	mInstances.erase(std::find(mInstances.begin(), mInstances.end(), i));
	i->getTechnique()->destroyInstance(i);
}
//-----------------------------------------------------------------------
void CompositorChain::_queuedOperation(CompositorInstance::RenderSystemOperation* op)
{
	mRenderSystemOperations.push_back(op);

}
//-----------------------------------------------------------------------
CompositorInstance *CompositorChain::getCompositor(size_t index)
{
    assert (index < mInstances.size() && "Index out of bounds.");
    return mInstances[index];
}

//-----------------------------------------------------------------------
CompositorChain::InstanceIterator CompositorChain::getCompositors()
{
    return InstanceIterator(mInstances.begin(), mInstances.end());
}
//-----------------------------------------------------------------------
void CompositorChain::setCompositorEnabled(size_t position, bool state)
{
    getCompositor(position)->setEnabled(state);
}
//-----------------------------------------------------------------------
void CompositorChain::preRenderTargetUpdate(const RenderTargetEvent& evt)
{
	/// Compile if state is dirty
	if(mDirty)
		_compile();

	// Do nothing if no compositors enabled
	if (!mAnyCompositorsEnabled)
	{
		return;
	}


	/// Update dependent render targets; this is done in the preRenderTarget 
	/// and not the preViewportUpdate for a reason: at this time, the
	/// target Rendertarget will not yet have been set as current. 
	/// ( RenderSystem::setViewport(...) ) if it would have been, the rendering
	/// order would be screwed up and problems would arise with copying rendertextures.
    Camera *cam = mViewport->getCamera();
    /// Iterate over compiled state
    CompositorInstance::CompiledState::iterator i;
    for(i=mCompiledState.begin(); i!=mCompiledState.end(); ++i)
    {
		/// Skip if this is a target that should only be initialised initially
		if(i->onlyInitial && i->hasBeenRendered)
			continue;
		i->hasBeenRendered = true;
		/// Setup and render
        preTargetOperation(*i, i->target->getViewport(0), cam);
        i->target->update();
        postTargetOperation(*i, i->target->getViewport(0), cam);
    }
}
//-----------------------------------------------------------------------
void CompositorChain::preViewportUpdate(const RenderTargetViewportEvent& evt)
{
	// Only set up if there is at least one compositor enabled, and it's this viewport
    if(evt.source != mViewport || !mAnyCompositorsEnabled)
        return;

	// set original scene details from viewport
	CompositionPass* pass = mOriginalScene->getTechnique()->getOutputTargetPass()->getPass(0);
	if (pass->getClearBuffers() != mViewport->getClearBuffers() ||
		pass->getClearColour() != mViewport->getBackgroundColour())
	{
		pass->setClearBuffers(mViewport->getClearBuffers());
		pass->setClearColour(mViewport->getBackgroundColour());
		_compile();
	}

	Camera *cam = mViewport->getCamera();
	/// Prepare for output operation
	preTargetOperation(mOutputOperation, mViewport, cam);
}
//-----------------------------------------------------------------------
void CompositorChain::preTargetOperation(CompositorInstance::TargetOperation &op, Viewport *vp, Camera *cam)
{
    SceneManager *sm = cam->getSceneManager();
	/// Set up render target listener
	mOurListener.setOperation(&op, sm, sm->getDestinationRenderSystem());
	mOurListener.notifyViewport(vp);
	/// Register it
	sm->addRenderQueueListener(&mOurListener);
	/// Set visiblity mask
	mOldVisibilityMask = sm->getVisibilityMask();
	sm->setVisibilityMask(op.visibilityMask);
	/// Set whether we find visibles
	mOldFindVisibleObjects = sm->getFindVisibleObjects();
	sm->setFindVisibleObjects(op.findVisibleObjects);
    /// Set LOD bias level
    mOldLodBias = cam->getLodBias();
    cam->setLodBias(cam->getLodBias() * op.lodBias);
	/// Set material scheme 
	mOldMaterialScheme = vp->getMaterialScheme();
	vp->setMaterialScheme(op.materialScheme);
    /// XXX TODO
    //vp->setClearEveryFrame( true );
    //vp->setOverlaysEnabled( false );
    //vp->setBackgroundColour( op.clearColour );
}
//-----------------------------------------------------------------------
void CompositorChain::postTargetOperation(CompositorInstance::TargetOperation &op, Viewport *vp, Camera *cam)
{
    SceneManager *sm = cam->getSceneManager();
	/// Unregister our listener
	sm->removeRenderQueueListener(&mOurListener);
	/// Flush remaing operations
	mOurListener.flushUpTo((uint8)RENDER_QUEUE_COUNT);
	/// Restore default scene and camera settings
	sm->setVisibilityMask(mOldVisibilityMask);
	sm->setFindVisibleObjects(mOldFindVisibleObjects);
    cam->setLodBias(mOldLodBias);
	vp->setMaterialScheme(mOldMaterialScheme);
}
//-----------------------------------------------------------------------
void CompositorChain::postViewportUpdate(const RenderTargetViewportEvent& evt)
{
	// Only tidy up if there is at least one compositor enabled, and it's this viewport
    if(evt.source != mViewport || !mAnyCompositorsEnabled)
        return;

	postTargetOperation(mOutputOperation, mViewport, mViewport->getCamera());
}
//-----------------------------------------------------------------------
void CompositorChain::viewportRemoved(const RenderTargetViewportEvent& evt)
{
	// this chain is now orphaned
	// can't delete it since held from outside, but release all resources being used
	destroyResources();

}
//-----------------------------------------------------------------------
void CompositorChain::clearCompiledState()
{
	for (RenderSystemOperations::iterator i = mRenderSystemOperations.begin();
		i != mRenderSystemOperations.end(); ++i)
	{
		delete *i;
	}
	mRenderSystemOperations.clear();

	/// Clear compiled state
	mCompiledState.clear();
	mOutputOperation = CompositorInstance::TargetOperation(0);

}
//-----------------------------------------------------------------------
void CompositorChain::_compile()
{
	clearCompiledState();

	bool compositorsEnabled = false;

    /// Set previous CompositorInstance for each compositor in the list
    CompositorInstance *lastComposition = mOriginalScene;
	mOriginalScene->mPreviousInstance = 0;
    for(Instances::iterator i=mInstances.begin(); i!=mInstances.end(); ++i)
    {
        if((*i)->getEnabled())
        {
			compositorsEnabled = true;
            (*i)->mPreviousInstance = lastComposition;
            lastComposition = (*i);
        }
    }
    

    /// Compile misc targets
    lastComposition->_compileTargetOperations(mCompiledState);
    
    /// Final target viewport (0)
	mOutputOperation.renderSystemOperations.clear();
    lastComposition->_compileOutputOperation(mOutputOperation);

	// Deal with viewport settings
	if (compositorsEnabled != mAnyCompositorsEnabled)
	{
		mAnyCompositorsEnabled = compositorsEnabled;
		if (mAnyCompositorsEnabled)
		{
			// Save old viewport clearing options
			mOldClearEveryFrameBuffers = mViewport->getClearBuffers();
			// Don't clear anything every frame since we have our own clear ops
			mViewport->setClearEveryFrame(false);
		}
		else
		{
			// Reset clearing options
			mViewport->setClearEveryFrame(mOldClearEveryFrameBuffers > 0, 
				mOldClearEveryFrameBuffers);
		}
	}
    
    mDirty = false;
}
//-----------------------------------------------------------------------
void CompositorChain::_markDirty()
{
    mDirty = true;
}
//-----------------------------------------------------------------------
Viewport *CompositorChain::getViewport()
{
    return mViewport;
}
//-----------------------------------------------------------------------
void CompositorChain::RQListener::renderQueueStarted(uint8 id, 
	const String& invocation, bool& skipThisQueue)
{
	// Skip when not matching viewport
	// shadows update is nested within main viewport update
	if (mSceneManager->getCurrentViewport() != mViewport)
		return;

	flushUpTo(id);
	/// If noone wants to render this queue, skip it
	/// Don't skip the OVERLAY queue because that's handled seperately
	if(!mOperation->renderQueues.test(id) && id!=RENDER_QUEUE_OVERLAY)
	{
		skipThisQueue = true;
	}
}
//-----------------------------------------------------------------------
void CompositorChain::RQListener::renderQueueEnded(uint8 id, 
	const String& invocation, bool& repeatThisQueue)
{
}
//-----------------------------------------------------------------------
void CompositorChain::RQListener::setOperation(CompositorInstance::TargetOperation *op,SceneManager *sm,RenderSystem *rs)
{
	mOperation = op;
	mSceneManager = sm;
	mRenderSystem = rs;
	currentOp = op->renderSystemOperations.begin();
	lastOp = op->renderSystemOperations.end();
}
//-----------------------------------------------------------------------
void CompositorChain::RQListener::flushUpTo(uint8 id)
{
	/// Process all RenderSystemOperations up to and including render queue id.
    /// Including, because the operations for RenderQueueGroup x should be executed
	/// at the beginning of the RenderQueueGroup render for x.
	while(currentOp != lastOp && currentOp->first <= id)
	{
		currentOp->second->execute(mSceneManager, mRenderSystem);
		++currentOp;
	}
}
//-----------------------------------------------------------------------
}
