/* osgCompute - Copyright (C) 2008-2009 SVT Group
*                                                                     
* This library 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 3 of
* the License, or (at your option) any later version.
*                                                                     
* This library 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 Lesse General Public License for more details.
*
* The full license is in LICENSE file included with this distribution.
*/

#ifndef OSGCOMPUTE_COMPUTATION
#define OSGCOMPUTE_COMPUTATION 1

#include <set>
#include <osg/Group>
#include <osgCompute/Resource>
#include <osgCompute/Visitor>
#include <osgCompute/ComputationBin>

#define OSGCOMPUTE_AFTERCHILDREN			0x1
#define OSGCOMPUTE_BEFORECHILDREN			0x2
#define OSGCOMPUTE_NOCHILDREN				0x10
#define OSGCOMPUTE_RENDER					0x100
#define OSGCOMPUTE_POSTRENDER				0x104
#define OSGCOMPUTE_PRERENDER				0x108
#define OSGCOMPUTE_NORENDER					0x200
#define OSGCOMPUTE_UPDATE					0x10000

namespace osg
{
    class NodeVisitor;
}

namespace osgUtil
{
    class CullVisitor;
}

#define META_Computation( libraryname, classname, binlibrary, binclass, visitorlibrary, visitorclass )  \
    META_Object( libraryname, classname )                                                       		\
    virtual std::string                  binLibraryName() const { return #binlibrary; }          		\
    virtual std::string                  binClassName() const { return #binclass; }              		\
    virtual std::string                  visitorLibraryName() const { return #visitorlibrary; }      	\
    virtual std::string                  visitorClassName() const { return #visitorclass; }          	\
    virtual osgCompute::ResourceVisitor* newVisitor() const { return new visitorlibrary::visitorclass; }


namespace osgCompute
{
	struct ResourceHandle
	{
		osg::ref_ptr<Resource>		_resource;
		bool						_attached;
	};

    typedef std::set< osg::GraphicsContext* >						ContextSet;
    typedef std::set< osg::GraphicsContext* >::iterator				ContextSetItr;
    typedef std::set< osg::GraphicsContext* >::const_iterator		ContextSetCnstItr;

	typedef std::list< ResourceHandle >								ResourceHandleList;
	typedef std::list< ResourceHandle >::iterator					ResourceHandleListItr;
	typedef std::list< ResourceHandle >::const_iterator				ResourceHandleListCnstItr;

    //! Abstract base node for computations.
    /**
    * A computation node allows you to execute your own parallel code/modules during <b>the update cycle or the rendering cycle</b> 
	* of your scene. However, if you do not need any interoperability with OpenGL you can launch your modules at any place and you 
	* do not need a computation node at all. A computation does the context handling between OpenGL and the compute API (e.g. CUDA). It 
	* does not implement specific algorithms its only a <b>container class</b> where you can add 
	* your osgCompute::Module objects. A module then implements specific algorithms, just like osg::Program is a container for your osg::Shader objects.
	* <br />
	* You can add an arbitray graph to a computation node. Every resource ( of type osgCompute::Resource ) that is 
	* located in this subgraph will be forwarded to modules which have been attached to the computation by addModule(). If the subgraph changes
	* very often, then use setAutoCheckSubgraph() to search for changes in each frame. Otherwise a computation searches for resources only once during
	* the first traversal.
    * Additional parameters that are not located in the sub-graph can be added via addResource(). Please note                
	* that each param is referenced by an identifier and modules should compare these identifiers in order to accept their resources of interest. 
	* The rendering of a computation's sub-graph is enabled by default.                          
    * ( see osg::Node::setNodeMask() to disable the traversal of the sub-graph in order to remove the sub-graph from the rendering cycle).
	* <br />
	* osgCompute::Computation is an interface class and you have to allocate a specialized computation like a osgCuda::Computation. 
	* For example to execute a CUDA algorithm which utilizes OpenGL memory you have to do the following steps:
	* 
	\code
	 osg::ref_ptr<osgCompute::Computation> myComputation = new osgCuda::Computation;
	 myComputation->addModule( myCudaAlgorithm );
	 myComputation->setComputeOrder( osgCompute::Computation::UPDATE_BEFORECHILDREN );
	 myComputation->addChild( mySceneWithOpenGLResources );
	 someGroupNode->addChild( myComputation );
	\endcode
	*
	* <br />
	*
	* To add additional resource directly, e.g. a osgCuda::Buffer, use the addResource() method:
	\code
	 osg::ref_ptr<osgCompute::Memory> myBuffer = new osgCuda::Buffer;
	 myComputation->addResource( myBuffer );
	\endcode
	*
	* <br />
	*
	* Modules are executed in the order they have been attached. For example, myFirstAlgorithm is
	* executed before mySecondAlgorithm with the following definition:
	\code
	 myComputation->addModule( myFirstAlgorithm );
	 myComputation->addModule( mySecondAlgorithm );
	\endcode
    *   
	* <br />
	* You can define your own execution order if you use a osgCompute::LaunchCallback(): 
	\code
	 osg::ref_ptr<osgCompute::LaunchCallback> myLaunchCallback = new MyOwnLaunchCallback();
	 myComputation->setLaunchCallback( myLaunchCallback );
	
	 ...
	
	 virtual void MyOwnLaunchCallback::operator()( Computation& computation )
	 {
	   osgCompute::ModuleList& modules = computation.getModules();
	   modules[1]->launch(); //mySecondAlgorithm
	   modules[0]->launch(); //myFirstAlgorithm
	 }
	\endcode
	*
	* <br />
	*
	* You have to specify at which time during the traversals modules should be launched (see xxx). With the following command you 
	* will execute your code during the update cycle before the subgraph is traversed:
	\code
	 myComputation->setComputeOrder( osgCompute::Computation::UPDATE_BEFORECHILDREN );
	\endcode
    **/                                                                                                        
    class LIBRARY_EXPORT Computation : public osg::Group
    {
    public: 
		/**
			The compute order defines when a computation should be executed during the traversals. 
			Currently modules will be executed only during the render traversal (PRERENDER or POSTRENDER) or 
			the update traversal (UPDATE). However you still have to decide if and when the child nodes are traversed. AFTERCHILDREN
			will launch the modules after the subgraph has been traversed. BEFORECHILDREN will launch them before
			any child nodes are handled. NOCHILDREN will completely stop the traversal after the modules have been
			launched. If you decide to execute the modules during the render traversal you have to choose between PRERENDER or POSTRENDER
			in the same way as for osgUtil::RenderBins.
		*/
        enum ComputeOrder
        {
            PRERENDER_AFTERCHILDREN = OSGCOMPUTE_PRERENDER | OSGCOMPUTE_AFTERCHILDREN,
            PRERENDER_BEFORECHILDREN = OSGCOMPUTE_PRERENDER | OSGCOMPUTE_BEFORECHILDREN,
            POSTRENDER_AFTERCHILDREN = OSGCOMPUTE_POSTRENDER | OSGCOMPUTE_AFTERCHILDREN,
            POSTRENDER_BEFORECHILDREN = OSGCOMPUTE_POSTRENDER | OSGCOMPUTE_BEFORECHILDREN,
			PRERENDER_NOCHILDREN = OSGCOMPUTE_PRERENDER | OSGCOMPUTE_NOCHILDREN,
			POSTRENDER_NOCHILDREN = OSGCOMPUTE_POSTRENDER | OSGCOMPUTE_NOCHILDREN,
            UPDATE_AFTERCHILDREN = OSGCOMPUTE_UPDATE | OSGCOMPUTE_AFTERCHILDREN,
            UPDATE_BEFORECHILDREN = OSGCOMPUTE_UPDATE | OSGCOMPUTE_BEFORECHILDREN,
			UPDATE_AFTERCHILDREN_NORENDER = OSGCOMPUTE_UPDATE | OSGCOMPUTE_AFTERCHILDREN | OSGCOMPUTE_NORENDER,
			UPDATE_BEFORECHILDREN_NORENDER = OSGCOMPUTE_UPDATE | OSGCOMPUTE_BEFORECHILDREN | OSGCOMPUTE_NORENDER,
        };

    public:

        /** 
		* Constructor. The object will be initialized with the compute order UPDATE_BEFORECHILDREN and is enabled by default.
		*/
        Computation();

        /** 
        * Is called during the traversal of the graph. Overwrite this method
        * in a derived class to design your own traversal. During the first update traversal a resource visitor will 		
		* collect resources in the sub-graph. These resources are then forwarded to the attached modules. 
        */
        virtual void accept( osg::NodeVisitor& nv );

		/** The derived class must provide a valid library name of the required computation bin. */
		virtual std::string binLibraryName() const = 0;
		/** The derived class must provide a valid class name of the required computation bin. */
		virtual std::string binClassName() const = 0;
		/** The derived class must provide a valid library name of the required computation bin. */
		virtual std::string visitorLibraryName() const = 0;
		/** The derived class must provide a valid class name of the required computation bin. */
		virtual std::string visitorClassName() const = 0;
		/** The derived class must provide a method which allows to create new bins*/
		virtual osgCompute::ResourceVisitor* newVisitor() const = 0;

        /** Add a module to the computation. */
        virtual void addModule( Module& module );
        /** Remove the module by reference. */
        virtual void removeModule( Module& module );
        /** Remove the module by identifier. */
        virtual void removeModule( const std::string& moduleIdentifier );
		/** Returns the module with this identifier. Returns NULL if it does not exist*/
		virtual const Module* getModule( const std::string& moduleIdentifier ) const;
		/** Returns the module with this identifier. Returns NULL if it does not exist. */
		virtual Module* getModule( const std::string& moduleIdentifier );
        /** Returns true if the computation uses the referenced module. */
        virtual bool hasModule( const std::string& moduleIdentifier ) const;
        /** Returns true if the computation uses the module. */
        virtual bool hasModule( Module& module ) const;
        /** Returns true if the computation has modules at all. */
        virtual bool hasModules() const;
        /** Return all modules.  The returned list is empty if no module is attached.*/
        virtual ModuleList& getModules();
		/** Return all modules.  The returned list is empty if no module is attached.*/
        virtual const ModuleList& getModules() const;
        /** Return the number of attached modules. */
        virtual unsigned int getNumModules() const;
		/** Remove all attached modules. */
        virtual void removeModules();

        /** Adds a resource to all currently or later attached modules. */
		virtual void addResource( Resource& resource, bool attach = true );
		/** Remove resource by identifier. Does nothing if no module with that identifier can be found.*/
        virtual void removeResource( const std::string& identifier );
		/** Remove resource by reference. Does nothing if no module with that identifier can be found. */
        virtual void removeResource( Resource& resource );
		/** Returns true if the computation has a resource which is addressed by this identifier. */
        virtual bool hasResource( const std::string& handle ) const;
		/** Returns true if the computation has this resource. */
		virtual bool hasResource( Resource& resource ) const;
		/** Returns true if resource has been attached to the computation. */
		virtual bool isResourceAttached( Resource& resource ) const;
		/** Returns all resources of that computation. */
        virtual ResourceHandleList& getResources();
		/** Returns all resources of that computation. */
        virtual const ResourceHandleList& getResources() const;
		/** Remove all resources. */
        virtual void removeResources();
		/** Notify computation that subgraph has changed. The computation will collect all resources of the new sub-graph during 
		* the next update traversal.
 		*/
        virtual void subgraphChanged();
		/**
		* Return a pointer to the parent computation node and NULL if there is none.
		*/
        virtual Computation* getParentComputation() const;
		/**
		* Set a launch callback. You can use a launch callback to define a different execution order for the modules. This callback
		* replaces the internal default launch() function. 
		*/
        virtual void setLaunchCallback( LaunchCallback* lc );
		/** Returns the launch callback and NULL if there is none. */
        virtual LaunchCallback* getLaunchCallback();
		/** Returns the launch callback and NULL if there is none.*/
        virtual const LaunchCallback* getLaunchCallback() const;

        /** 
        * The compute order is used to decide when to execute the computation 
        * during the rendering ( see osgCompute::osgComputationBin for further information )
        */
        virtual void setComputeOrder( ComputeOrder co );

        /** Returns the compute order */
        virtual ComputeOrder getComputeOrder() const;

		/** 
		* Set this flag to true if the computation's sub-graph changes very often. 
		* The computation will search for resources in each update traversal.
		*/
        virtual void setAutoCheckSubgraph( bool autoCheckSubgraph );
		/** Returns true if the AutoCheckSubgraph flag is activated.*/
        virtual bool getAutoCheckSubgraph() const;
        /** Activates the computation (a computation is enabled by default) */
        virtual void enable();

        /** Calling this method will deactivate all modules. */
        virtual void disable();

        /** Returns true if the computation and its modules are enabled */
        virtual bool isEnabled() const;

        /** Restore object to default state (Before initialization). Calls clearLocal(). */
        virtual void clear();
		/** 
		* Method is called by OSG to release all OpenGL resources. 
		* All resources used within the state's context will be released including the non GL 
		* resources.
		*/
        virtual void releaseGLObjects( osg::State* state ) const;

    protected:
		friend class ResourceVisitor;


        /** Destructor. Release the memory by calling clearLocal(). */
        virtual ~Computation() { clearLocal(); }

        void clearLocal();

        virtual void checkDevice();
        virtual void addContext( osg::State& state );
        virtual void update( osg::NodeVisitor& uv );
        virtual void collectResources();
        virtual void launch();
        virtual void addBin( osgUtil::CullVisitor& cv );
        virtual void handleevent( osg::NodeVisitor& ev );

        virtual void setParentComputation( Computation* parentComputation );

        bool                                	_enabled;
        LaunchCallback*                     	_launchCallback; 
        ModuleList                          	_modules;
        ResourceHandleList                      _resources;
        ComputeOrder                        	_computeOrder;
        bool                                	_subgraphChanged;
        osg::ref_ptr<ResourceVisitor>       	_resourceVisitor;
        Computation*                        	_parentComputation; 
        bool                                	_resourcesCollected;
        bool                                	_autoCheckSubgraph;

        mutable ContextSet                  	_contextSet;

        static bool isDeviceReady();
        static void setDeviceReady();
        static bool                             s_deviceReady;

    private:

        /** Copy constructor. This constructor should not be called.*/
        Computation( const Computation& ) {}

        /** Copy operator. This operator should not be called.*/
        Computation &operator=(const Computation &) { return *this; }
    };
}

#endif //OSGCOMPUTE_COMPUTATION
