/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
* Copyright 2008-2010 Pelican Mapping
* http://osgearth.org
*
* osgEarth 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, see <http://www.gnu.org/licenses/>
*/
#ifndef OSGEARTH_SHADER_COMPOSITION_H
#define OSGEARTH_SHADER_COMPOSITION_H 1

#include <osgEarth/Common>
#include <osgEarth/Revisioning>
#include <osgEarth/ThreadingUtils>
#include <string>
#include <map>
#include <osg/Shader>
#include <osg/Program>
#include <osg/StateAttribute>

namespace osgEarth
{        
    namespace ShaderComp
    {
        // User function injection points.
        enum FunctionLocation
        {
            LOCATION_VERTEX_PRE_TEXTURING   =0,
            LOCATION_VERTEX_PRE_LIGHTING    =1,
            LOCATION_VERTEX_POST_LIGHTING   =2,
            LOCATION_FRAGMENT_PRE_TEXTURING =3,
            LOCATION_FRAGMENT_PRE_LIGHTING  =4,
            LOCATION_FRAGMENT_POST_LIGHTING =5
        };

        // set of user functions, ordered by priority.
        typedef std::multimap<float, std::string> OrderedFunctionMap; // duplicate keys allowed

        // user function sets, categorized by function location.
        typedef std::map<FunctionLocation, OrderedFunctionMap> FunctionLocationMap;
    }

    /**
     * A factory class that generates shader functions for the osgEarth engine.
     * The default ShaderFactory is stored in the osgEarth registry. You can replace it
     * if you want to replace osgEarth's default shader templates.
     */
    class OSGEARTH_EXPORT ShaderFactory : public osg::Referenced
    {
    public:
        /** Creates a vertex shader main(). */
        virtual osg::Shader* createVertexShaderMain(
            const ShaderComp::FunctionLocationMap& functions =ShaderComp::FunctionLocationMap() ) const;

        /** Creates a fragment shader main(). */
        virtual osg::Shader* createFragmentShaderMain( 
            const ShaderComp::FunctionLocationMap& functions =ShaderComp::FunctionLocationMap() ) const;
        
        /**
         * Creates the function that sets up default texcoords in the vertex shader.
         * The name/prototype is:
         *    void osgearth_vert_setupTexturing(); 
         */
        virtual osg::Shader* createDefaultTextureVertexShader( int numTexCoordSets ) const;

        /**
         * Creates the function that applies texture data in the fragment shader.
         * The name/prototype is:
         *    osgearth_frag_applyTexturing( inout vec4 color );
         */
        virtual osg::Shader* createDefaultTextureFragmentShader( int numTexCoordSets ) const;

        /**
         * Creates the function that applies lighting calculations in the vertex shader.
         * The name/prototype is:
         *    void osgearth_vert_setupLighting();
         */
        virtual osg::Shader* createDefaultLightingVertexShader() const;

        /**
         * Creates the function that applies lighting coloring in the fragment shader.
         * The name/prototype is:
         *    void osgearth_frag_applyLighting( inout vec4 color ); 
         */
        virtual osg::Shader* createDefaultLightingFragmentShader() const;
    };

    /**
     * VirtualProgram enables basic GLSL shader composition within osgEarth.
     *
     * VirtualProgram has been adapted from the VirtualProgram shader composition work
     * originally done by Wojciech Lewandowski and found in OSG's osgvirtualprogram
     * example, and is used by permission.
     */
    class OSGEARTH_EXPORT VirtualProgram : public osg::Program
    {
    public:
        /**
         * Adds a shader function to the program.
         * Call this method (rather than setShader directly) to inject "user" functions into the
         * shader program.
         *
         * name: name of the function. This should be the actual function name in the shader source.
         * source: the shader source code.
         * location: Function location relative to the built-ins.
         * priority: Lets you control the order of functions that you inject at the same location.
         */
        void setFunction( 
            const std::string& name,
            const std::string& source, 
            ShaderComp::FunctionLocation loc,
            float priority =1.0f );

    public: 
        VirtualProgram( unsigned int mask = 0xFFFFFFFFUL );

        VirtualProgram( const VirtualProgram& VirtualProgram, 
                        const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY );

        META_StateAttribute( osgCandidate, VirtualProgram, Type( PROGRAM ) )

        /** return -1 if *this < *rhs, 0 if *this==*rhs, 1 if *this>*rhs.*/
        virtual int compare(const StateAttribute& sa) const
        {
           // check the types are equal and then create the rhs variable
           // used by the COMPARE_StateAttribute_Parameter macros below.
           COMPARE_StateAttribute_Types(VirtualProgram,sa)

           // compare each parameter in turn against the rhs.
           COMPARE_StateAttribute_Parameter(_mask)
           COMPARE_StateAttribute_Parameter(_shaderMap)
           return 0; // passed all the above comparison macros, must be equal.
        }

        /** If enabled, activate our program in the GL pipeline,
         * performing any rebuild operations that might be pending. */
        virtual void apply(osg::State& state) const;

        osg::Shader* getShader( const std::string& shaderSemantic, osg::Shader::Type type );   

        osg::Shader* setShader( const std::string& shaderSemantic, osg::Shader* shader );

        void removeShader( const std::string& shaderSemantic, osg::Shader::Type type );


    protected:
        typedef std::vector< osg::ref_ptr< osg::Shader > >            ShaderList;
        typedef std::pair< std::string, osg::Shader::Type >           ShaderSemantic;
        typedef std::map< ShaderSemantic, osg::ref_ptr<osg::Shader> > ShaderMap;
        typedef std::map< ShaderList, osg::ref_ptr<osg::Program> >    ProgramMap;

        mutable ProgramMap                   _programMap;
        ShaderMap                            _shaderMap;
        unsigned int                         _mask;

        ShaderComp::FunctionLocationMap _functions;
        ShaderComp::FunctionLocationMap _accumulatedFunctions;

        Threading::Mutex _functionsMutex;

        bool hasLocalFunctions() const;
        void refreshAccumulatedFunctions( const osg::State& state );

    public:
        void getFunctions( ShaderComp::FunctionLocationMap& out ) const;
    };

} // namespace osgEarth

#endif // OSGEARTH_SHADER_COMPOSITION_H
