/* 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>

namespace osg
{
    class NodeVisitor;
}

namespace osgUtil
{
    class CullVisitor;
}

#define META_Computation( libraryname, classname, binlibrary, binclass, ctxlibrary, ctxclass )  \
    META_Object( libraryname, classname )                                                       \
    virtual std::string                 binLibraryName() const { return #binlibrary; }          \
    virtual std::string                 binClassName() const { return #binclass; }              \
    virtual std::string                 contextLibraryName() const { return #ctxlibrary; }      \
    virtual std::string                 contextClassName() const { return #ctxclass; }          \
    virtual osgCompute::Context*        newContext() const { return new ctxlibrary::ctxclass; }

namespace osgCompute
{
    //! Abstract base node of any computation
    /**
    * A computation node allows you to execute your own parallel code during the rendering within                   <br>
    * a scene graph. Your code should be encapsulated by modules which are added to                                 <br>
    * a computation by addModule() and is executed in the order they were added. Please use a                       <br>
    * launch callback to change the order in which the modules are executed or derive from                          <br>
    * a computation in order to define your own computations.                                                       <br>
    * Please note that the rendering of the computation's sub graph is enabled by default.                          <br> 
    * ( see xxx to disable the traversal of the sub-graph in order to remove the sub-graph from rendering ).        <br>
    * Parameters that should be shared throughout modules can be added via addParamHandle(). Please note            <br>
    * that each param is referenced by a handle and modules comparing these handles in order to accept parameters.  <br>
    **/                                                                                                         
    class LIBRARY_EXPORT Computation : public osg::Group
    {
    public: 

        enum ComputeOrder
        {
            POST_TRAVERSAL = 0x1,
            PRE_TRAVERSAL = 0x2,
            POST_COMPUTE = 0x4,
            PRE_COMPUTE = 0x8,
            PRE_COMPUTE_POST_TRAVERSAL = PRE_COMPUTE | POST_TRAVERSAL,
            PRE_COMPUTE_PRE_TRAVERSAL = PRE_COMPUTE | PRE_TRAVERSAL,
            POST_COMPUTE_POST_TRAVERSAL = POST_COMPUTE | POST_TRAVERSAL,
            POST_COMPUTE_PRE_TRAVERSAL = POST_COMPUTE | PRE_TRAVERSAL,
        };

    public:

        /** Constructor. The object will be initialized with default values. */
        Computation();

        /** 
        * Is called during the traversal of the graph. Overwrite this method
        * in a derived class to design your own traversal.
        */
        virtual void accept( osg::NodeVisitor& nv );

        /** The derived class must provide a valid library name of the required context. */
        virtual std::string contextLibraryName() const = 0;
        /** The derived class must provide a valid class name of the required context. */
        virtual std::string contextClassName() const = 0;
        /** The derived class must provide a method which allows to create new contexts*/
        virtual osgCompute::Context* newContext() const = 0;

        /** 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;

        /** Load a module dynamically (not implemented yet) */
        virtual void loadModule( const std::string& modName );

        /** Add a module to the computation. */
        virtual void addModule( Module& module );

        /** Remove the module by providing a reference to the module. */
        virtual void removeModule( Module& module );

        /** Remove the module by providing the module's name. */
        virtual void removeModule( const std::string& moduleHandle );

        /** Returns true if the computation uses a module of module name. */
        virtual bool hasModule( const std::string& moduleHandle ) 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. */
        virtual ModuleList& getModules();
        virtual const ModuleList& getModules() const;

        /** Return the number of modules. */
        virtual unsigned int getNumModules() const;

        virtual void removeModules();

        /** Add a resource to the computation. */
        virtual void addResource( Resource& resource );
        virtual void removeResource( const std::string& handle );
        virtual void removeResource( Resource& resource );
        virtual bool hasResource( const std::string& handle ) const;
        virtual bool hasResource( Resource& resource ) const;
        virtual ResourceMap& getResources();
        virtual const ResourceMap& getResources() const;

        virtual void removeResources();

        virtual void resourcesChanged();

        inline Computation* getParentComputation() const;

        inline void setLaunchCallback( LaunchCallback* lc );
        inline LaunchCallback* getLaunchCallback();
        inline 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 )
        */
        inline void setComputeOrder( ComputeOrder co );

        /** Returns the compute order */
        inline ComputeOrder getComputeOrder();


        inline void setAutoCollectResources( bool autoCollectResources );
        inline bool getAutoCollectResources();

        /** Activates the computation (a computation is enabled by default) */
        inline void enable();

        /** If the computation is disabled its modules won't be executed during rendering */
        inline void disable();

        /** check whether the module is enabled */
        inline bool isEnabled() const;

        /** Restore object to default state. Calls clearLocal(). */
        virtual void clear();

    protected:
        friend class ContextVisitor;
        friend class ResourceVisitor;
        /** Destructor. Release the memory by calling clearLocal(). */
        virtual ~Computation() { clearLocal(); }

        void clearLocal();

        virtual bool setContext( Context& context );
        virtual void removeContext( unsigned int ctxId );
        virtual Context* getOrCreateContext( unsigned int ctxId );
        virtual Context* getContext( unsigned int ctxId );

        virtual void setupBin( osgUtil::CullVisitor& cv );
        virtual void distributeContext( Context& context );
        virtual void update( osg::NodeVisitor& uv );
        virtual void collectResources();
        virtual void handleevent( osg::NodeVisitor& ev );

        virtual void setParentComputation( Computation* parentComputation );

        bool                                _enabled;
        LaunchCallback*                     _launchCallback; 
        ModuleList                          _modules;
        ResourceMap                         _resources;
        ComputeOrder                        _computeOrder;
        bool                                _resourcesChanged;
        osg::ref_ptr<ResourceVisitor>       _resourceVisitor;
        Computation*                        _parentComputation; 
        bool                                _resourcesCollected;
        bool                                _autoCollectResources;

        ContextMap                          _contextMap;

        mutable OpenThreads::Mutex          _mutex;

    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; }
    };

    /////////////////////////////////////////////////////////////////////////////////////////////////
    // PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////////////
    //------------------------------------------------------------------------------
    inline void Computation::setLaunchCallback( LaunchCallback* lc ) 
    { 
        if( lc == _launchCallback )
            return;

        _launchCallback = lc; 
    }

    //------------------------------------------------------------------------------
    inline LaunchCallback* Computation::getLaunchCallback() 
    { 
        return _launchCallback; 
    }

    //------------------------------------------------------------------------------
    inline const LaunchCallback* Computation::getLaunchCallback() const 
    { 
        return _launchCallback; 
    }

    //------------------------------------------------------------------------------
    inline void Computation::setComputeOrder( Computation::ComputeOrder co )
    {
        _computeOrder = co;
    }

    //------------------------------------------------------------------------------
    inline Computation::ComputeOrder Computation::getComputeOrder()
    {
        return _computeOrder;
    }

    //------------------------------------------------------------------------------
    inline Computation* Computation::getParentComputation() const
    {
        return _parentComputation;
    }

    //------------------------------------------------------------------------------
    inline void Computation::setAutoCollectResources( bool autoCollectResources )
    {
        _autoCollectResources = autoCollectResources;
    }

    //------------------------------------------------------------------------------
    inline bool Computation::getAutoCollectResources()
    {
        return _autoCollectResources;
    }

    //------------------------------------------------------------------------------
    inline void Computation::enable() 
    { 
        _enabled = true; 
    }

    //------------------------------------------------------------------------------
    inline void Computation::disable() 
    { 
        _enabled = false; 
    }

    //------------------------------------------------------------------------------
    inline bool Computation::isEnabled() const
    {
        return _enabled;
    }

}

#endif //OSGCOMPUTE_COMPUTATION
