/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
* Copyright 2008-2013 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_VIRTUAL_PROGRAM_H
#define OSGEARTH_VIRTUAL_PROGRAM_H 1

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

#ifdef OSG_GLES2_AVAILABLE
#    define GLSL_VERSION_STR             "100"
#    define GLSL_DEFAULT_PRECISION_FLOAT "precision mediump float;"
#else
#    define GLSL_VERSION_STR             "110" 
#    define GLSL_DEFAULT_PRECISION_FLOAT ""
#endif

namespace osgEarth
{
    namespace ShaderComp
    {
        // User function injection points.
        enum FunctionLocation
        {
            // vertex is in model space (equivalent to gl_Vertex).
            LOCATION_VERTEX_MODEL = 0,

            // vertex is in view(aka eye) coordinates, with the camera at 0,0,0 
            // looking down the -Z axis.
            LOCATION_VERTEX_VIEW = 1,

            // vertex is in post-perspective coordinates; [-w..w] along each axis
            LOCATION_VERTEX_CLIP = 2,

            // fragment is being colored.
            LOCATION_FRAGMENT_COLORING = 3,

            // fragment is being lit.
            LOCATION_FRAGMENT_LIGHTING = 4
        };

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


    /**
     * 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.
     *
     * VirtualProgram in a state attribute. Once you've installed it on a StateSet,
     * you can still call the set* functions but you'd better mark the state set with
     * a DYNAMIC data variance.
     */
    class OSGEARTH_EXPORT VirtualProgram : public osg::StateAttribute // osg::Program
    {
    public:
        static const osg::StateAttribute::Type SA_TYPE;

        typedef osg::Program::AttribBindingList AttribBindingList;

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

        /**
         * Whether this VP should inherit shaders from parent state sets. This is
         * the normal operation. You can set this to "false" to "reset" the VP.
         */
        void setInheritShaders( bool value );

    public: 
        /**
         * Constructs a new VP
         */
        VirtualProgram( unsigned int mask = 0xFFFFFFFFUL );

        /**
         * Copy constructor
         */
        VirtualProgram( const VirtualProgram& VirtualProgram, 
                        const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY );

        META_StateAttribute( osgEarth, VirtualProgram, SA_TYPE);

        /** dtor */
        virtual ~VirtualProgram() { }

        /** 
         * Compare this program against another (used for state-sorting)
         * return -1 if *this < *rhs, 0 if *this==*rhs, 1 if *this>*rhs.
         */
        virtual int compare(const StateAttribute& sa) const;

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

        /**
         * Gets a shader by its ID.
         */
        osg::Shader* getShader( const std::string& shaderID ) const;

        /** 
         * Adds a shader to this VP's shader table.
         */
        osg::Shader* setShader( 
            const std::string&                 shaderID, 
            osg::Shader*                       shader,
            osg::StateAttribute::OverrideValue ov         =osg::StateAttribute::ON );
        
        osg::Shader* setShader(
            osg::Shader*                       shader,
            osg::StateAttribute::OverrideValue ov         =osg::StateAttribute::ON );

        /**
         * Removes a shader from the local VP.
         */
        void removeShader( const std::string& shaderID );

        /** Add an attribute location binding. */
        void addBindAttribLocation( const std::string& name, GLuint index );

        /** Remove an attribute location binding. */
        void removeBindAttribLocation( const std::string& name );

        /** Gets a reference to the attribute bindings. */
        const AttribBindingList& getAttribBindingList() const { return _attribBindingList; }

        /** Access to the property template. */
        osg::Program* getTemplate() { return _template.get(); }
        const osg::Program* getTemplate() const { return _template.get(); }

    public:
        typedef std::vector< osg::ref_ptr<osg::Shader> > ShaderVector;

    protected:

        typedef std::pair< osg::ref_ptr<osg::Shader>, osg::StateAttribute::OverrideValue > ShaderEntry;
        typedef std::map< std::string, ShaderEntry > ShaderMap;
        typedef std::map< ShaderVector, osg::ref_ptr<osg::Program> > ProgramMap;

        osg::ref_ptr<osg::Program>   _template;

        ProgramMap                   _programCache;
        ShaderMap                    _shaderMap;
        unsigned int                 _mask;
        AttribBindingList            _attribBindingList;
        bool                         _useLightingShaders;
        //osg::ref_ptr<osg::Shader>    _vertMain;
        //osg::ref_ptr<osg::Shader>    _fragMain;

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

        Threading::Mutex _functionsMutex;
        bool _inherit;
        mutable Threading::ReadWriteMutex _programCacheMutex;

        bool hasLocalFunctions() const;
        void refreshAccumulatedFunctions( const osg::State& state );
        void addToAccumulatedMap(ShaderMap& accumShaderMap, const std::string& shaderID, const ShaderEntry& newEntry) const;
        osg::Program* buildProgram( osg::State& state, ShaderMap& accumShaderMap, AttribBindingList& bindings );
        void addShadersToProgram(const ShaderVector& shaders, const AttribBindingList& attribBindings, osg::Program* program );
        void addTemplateDataToProgram(osg::Program* program );

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

} // namespace osgEarth

#endif // OSGEARTH_VIRTUAL_PROGRAM_H
