/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * 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
 * OpenSceneGraph Public License for more details.
*/

#ifndef OSG_CULLSETTINGS
#define OSG_CULLSETTINGS 1

#include <iosfwd>
#include <osg/Matrix>
#include <osg/ClearNode>

namespace osg {

// forward declare
class ArgumentParser;
class ApplicationUsage;

class OSG_EXPORT CullSettings
{
    public:

        CullSettings()
        {
            setDefaults();
            readEnvironmentalVariables();
        }

        CullSettings(ArgumentParser& arguments)
        {
            setDefaults();
            readEnvironmentalVariables();
            readCommandLine(arguments);
        }

        CullSettings(const CullSettings& cs);

        virtual ~CullSettings() {}

        CullSettings& operator = (const CullSettings& settings)
        {
            if (this==&settings) return *this;
            setCullSettings(settings);
            return *this;
        }


        virtual void setDefaults();


        enum VariablesMask
        {
            COMPUTE_NEAR_FAR_MODE                   = (0x1 << 0),
            CULLING_MODE                            = (0x1 << 1),
            LOD_SCALE                               = (0x1 << 2),
            SMALL_FEATURE_CULLING_PIXEL_SIZE        = (0x1 << 3),
            CLAMP_PROJECTION_MATRIX_CALLBACK        = (0x1 << 4),
            NEAR_FAR_RATIO                          = (0x1 << 5),
            IMPOSTOR_ACTIVE                         = (0x1 << 6),
            DEPTH_SORT_IMPOSTOR_SPRITES             = (0x1 << 7),
            IMPOSTOR_PIXEL_ERROR_THRESHOLD          = (0x1 << 8),
            NUM_FRAMES_TO_KEEP_IMPOSTORS_SPRITES    = (0x1 << 9),
            CULL_MASK                               = (0x1 << 10),
            CULL_MASK_LEFT                          = (0x1 << 11),
            CULL_MASK_RIGHT                         = (0x1 << 12),
            CLEAR_COLOR                             = (0x1 << 13),
            CLEAR_MASK                              = (0x1 << 14),
            LIGHTING_MODE                           = (0x1 << 15),
            LIGHT                                   = (0x1 << 16),
            DRAW_BUFFER                             = (0x1 << 17),
            READ_BUFFER                             = (0x1 << 18),

            NO_VARIABLES                            = 0x00000000,
            ALL_VARIABLES                           = 0x7FFFFFFF
        };

        typedef int InheritanceMask;

        /** Set the inheritance mask used in inheritCullSettings to control which variables get overwritten by the passed in CullSettings object.*/
        void setInheritanceMask(InheritanceMask mask) { _inheritanceMask = mask; }

        /** Get the inheritance mask used in inheritCullSettings to control which variables get overwritten by the passed in CullSettings object.*/
        InheritanceMask getInheritanceMask() const { return _inheritanceMask; }

        /** Set the local cull settings values from specified CullSettings object.*/
        void setCullSettings(const CullSettings& settings);

        /** Inherit the local cull settings variable from specified CullSettings object, according to the inheritance mask.*/
        virtual void inheritCullSettings(const CullSettings& settings) { inheritCullSettings(settings, _inheritanceMask); }

        /** Inherit the local cull settings variable from specified CullSettings object, according to the inheritance mask.*/
        virtual void inheritCullSettings(const CullSettings& settings, unsigned int inheritanceMask);

        /** read the environmental variables.*/
        void readEnvironmentalVariables();

        /** read the commandline arguments.*/
        void readCommandLine(ArgumentParser& arguments);


        enum InheritanceMaskActionOnAttributeSetting
        {
            DISABLE_ASSOCIATED_INHERITANCE_MASK_BIT,
            DO_NOT_MODIFY_INHERITANCE_MASK
        };

        void setInheritanceMaskActionOnAttributeSetting(InheritanceMaskActionOnAttributeSetting action) { _inheritanceMaskActionOnAttributeSetting = action; }
        InheritanceMaskActionOnAttributeSetting getInheritanceMaskActionOnAttributeSetting() const { return _inheritanceMaskActionOnAttributeSetting; }

        /** Apply the action, specified by the InheritanceMaskActionOnAttributeSetting, to apply to the inheritance bit mask.
          * This method is called by CullSettings::set*() parameter methods to ensure that CullSettings inheritance mechanisms doesn't overwrite the local parameter settings.*/
        inline void applyMaskAction(unsigned int maskBit)
        {
            if (_inheritanceMaskActionOnAttributeSetting==DISABLE_ASSOCIATED_INHERITANCE_MASK_BIT)
            {
                _inheritanceMask = _inheritanceMask & (~maskBit);
            }
        }


        /** Switch the creation of Impostors on or off.
          * Setting active to false forces the CullVisitor to use the Impostor
          * LOD children for rendering. Setting active to true forces the
          * CullVisitor to create the appropriate pre-rendering stages which
          * render to the ImpostorSprite's texture.*/
        void setImpostorsActive(bool active) { _impostorActive = active; applyMaskAction(IMPOSTOR_ACTIVE); }

        /** Get whether impostors are active or not. */
        bool getImpostorsActive() const { return _impostorActive; }

        /** Set the impostor error threshold.
          * Used in calculation of whether impostors remain valid.*/
        void setImpostorPixelErrorThreshold(float numPixels) { _impostorPixelErrorThreshold=numPixels;  applyMaskAction(IMPOSTOR_PIXEL_ERROR_THRESHOLD); }

        /** Get the impostor error threshold.*/
        float getImpostorPixelErrorThreshold() const { return _impostorPixelErrorThreshold; }

        /** Set whether ImpostorSprite's should be placed in a depth sorted bin for rendering.*/
        void setDepthSortImpostorSprites(bool doDepthSort) { _depthSortImpostorSprites = doDepthSort; applyMaskAction(DEPTH_SORT_IMPOSTOR_SPRITES); }

        /** Get whether ImpostorSprite's are depth sorted bin for rendering.*/
        bool getDepthSortImpostorSprites() const { return _depthSortImpostorSprites; }

        /** Set the number of frames that an ImpostorSprite is kept whilst not being beyond,
          * before being recycled.*/
        void setNumberOfFrameToKeepImpostorSprites(int numFrames) { _numFramesToKeepImpostorSprites = numFrames; applyMaskAction(NUM_FRAMES_TO_KEEP_IMPOSTORS_SPRITES); }

        /** Get the number of frames that an ImpostorSprite is kept whilst not being beyond,
          * before being recycled.*/
        int getNumberOfFrameToKeepImpostorSprites() const { return _numFramesToKeepImpostorSprites; }

        enum ComputeNearFarMode
        {
            DO_NOT_COMPUTE_NEAR_FAR = 0,
            COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES,
            COMPUTE_NEAR_FAR_USING_PRIMITIVES,
            COMPUTE_NEAR_USING_PRIMITIVES
        };

        void setComputeNearFarMode(ComputeNearFarMode cnfm) { _computeNearFar=cnfm; applyMaskAction(COMPUTE_NEAR_FAR_MODE); }
        ComputeNearFarMode getComputeNearFarMode() const { return _computeNearFar;}

        void setNearFarRatio(double ratio) { _nearFarRatio = ratio; applyMaskAction(NEAR_FAR_RATIO); }
        double getNearFarRatio() const { return _nearFarRatio; }

        enum CullingModeValues
        {
            NO_CULLING                  = 0x0,
            VIEW_FRUSTUM_SIDES_CULLING  = 0x1,
            NEAR_PLANE_CULLING          = 0x2,
            FAR_PLANE_CULLING           = 0x4,
            VIEW_FRUSTUM_CULLING        = VIEW_FRUSTUM_SIDES_CULLING|
                                          NEAR_PLANE_CULLING|
                                          FAR_PLANE_CULLING,
            SMALL_FEATURE_CULLING       = 0x8,
            SHADOW_OCCLUSION_CULLING    = 0x10,
            CLUSTER_CULLING             = 0x20,
            DEFAULT_CULLING             = VIEW_FRUSTUM_SIDES_CULLING|
                                          SMALL_FEATURE_CULLING|
                                          SHADOW_OCCLUSION_CULLING|
                                          CLUSTER_CULLING,
            ENABLE_ALL_CULLING          = VIEW_FRUSTUM_CULLING|
                                          SMALL_FEATURE_CULLING|
                                          SHADOW_OCCLUSION_CULLING|
                                          CLUSTER_CULLING
        };

        typedef int CullingMode;

        /** Set the culling mode for the CullVisitor to use.*/
        void setCullingMode(CullingMode mode) { _cullingMode = mode; applyMaskAction(CULLING_MODE); }

        /** Returns the current CullingMode.*/
        CullingMode getCullingMode() const { return _cullingMode; }


        void setCullMask(osg::Node::NodeMask nm) { _cullMask = nm; applyMaskAction(CULL_MASK); }
        osg::Node::NodeMask getCullMask() const { return _cullMask; }

        void setCullMaskLeft(osg::Node::NodeMask nm) { _cullMaskLeft = nm; applyMaskAction(CULL_MASK_LEFT); }
        osg::Node::NodeMask getCullMaskLeft() const { return _cullMaskLeft; }

        void setCullMaskRight(osg::Node::NodeMask nm) { _cullMaskRight = nm; applyMaskAction(CULL_MASK_RIGHT); }
        osg::Node::NodeMask getCullMaskRight() const { return _cullMaskRight; }

        /** Set the LOD bias for the CullVisitor to use.*/
        void setLODScale(float scale) { _LODScale = scale; applyMaskAction(LOD_SCALE); }

        /** Get the LOD bias.*/
        float getLODScale() const { return _LODScale; }

        /** Threshold at which small features are culled.
        \param value Bounding volume size in screen space. Default is 2.0. */
        void setSmallFeatureCullingPixelSize(float value) { _smallFeatureCullingPixelSize=value; applyMaskAction(SMALL_FEATURE_CULLING_PIXEL_SIZE); }

        /** Get the Small Feature Culling Pixel Size.*/
        float getSmallFeatureCullingPixelSize() const { return _smallFeatureCullingPixelSize; }



        /** Callback for overriding the CullVisitor's default clamping of the projection matrix to computed near and far values.
          * Note, both Matrixf and Matrixd versions of clampProjectionMatrixImplementation must be implemented as the CullVisitor
          * can target either Matrix data type, configured at compile time.*/
        struct ClampProjectionMatrixCallback : public osg::Referenced
        {
            virtual bool clampProjectionMatrixImplementation(osg::Matrixf& projection, double& znear, double& zfar) const = 0;
            virtual bool clampProjectionMatrixImplementation(osg::Matrixd& projection, double& znear, double& zfar) const = 0;
        };

        /** set the ClampProjectionMatrixCallback.*/
        void setClampProjectionMatrixCallback(ClampProjectionMatrixCallback* cpmc) { _clampProjectionMatrixCallback = cpmc; applyMaskAction(CLAMP_PROJECTION_MATRIX_CALLBACK); }
        /** get the non const ClampProjectionMatrixCallback.*/
        ClampProjectionMatrixCallback* getClampProjectionMatrixCallback() { return _clampProjectionMatrixCallback.get(); }
        /** get the const ClampProjectionMatrixCallback.*/
        const ClampProjectionMatrixCallback* getClampProjectionMatrixCallback() const { return _clampProjectionMatrixCallback.get(); }


        /** Write out internal settings of CullSettings. */
        void write(std::ostream& out);

    protected:

        InheritanceMask                             _inheritanceMask;
        InheritanceMaskActionOnAttributeSetting     _inheritanceMaskActionOnAttributeSetting;

        ComputeNearFarMode                          _computeNearFar;
        CullingMode                                 _cullingMode;
        float                                       _LODScale;
        float                                       _smallFeatureCullingPixelSize;

        ref_ptr<ClampProjectionMatrixCallback>      _clampProjectionMatrixCallback;
        double                                      _nearFarRatio;
        bool                                        _impostorActive;
        bool                                        _depthSortImpostorSprites;
        float                                       _impostorPixelErrorThreshold;
        int                                         _numFramesToKeepImpostorSprites;

        Node::NodeMask                              _cullMask;
        Node::NodeMask                              _cullMaskLeft;
        Node::NodeMask                              _cullMaskRight;


};

template<class matrix_type, class value_type>
bool clampProjectionMatrix(matrix_type& projection, double& znear, double& zfar, value_type nearFarRatio)
{
    double epsilon = 1e-6;
    if (zfar<znear-epsilon)
    {
        if (zfar != -FLT_MAX || znear != FLT_MAX)
        {
            OSG_INFO<<"_clampProjectionMatrix not applied, invalid depth range, znear = "<<znear<<"  zfar = "<<zfar<<std::endl;
        }
        return false;
    }

    if (zfar<znear+epsilon)
    {
        // znear and zfar are too close together and could cause divide by zero problems
        // late on in the clamping code, so move the znear and zfar apart.
        double average = (znear+zfar)*0.5;
        znear = average-epsilon;
        zfar = average+epsilon;
        // OSG_INFO << "_clampProjectionMatrix widening znear and zfar to "<<znear<<" "<<zfar<<std::endl;
    }

    if (fabs(projection(0,3))<epsilon  && fabs(projection(1,3))<epsilon  && fabs(projection(2,3))<epsilon )
    {
        // OSG_INFO << "Orthographic matrix before clamping"<<projection<<std::endl;

        value_type delta_span = (zfar-znear)*0.02;
        if (delta_span<1.0) delta_span = 1.0;
        value_type desired_znear = znear - delta_span;
        value_type desired_zfar = zfar + delta_span;

        // assign the clamped values back to the computed values.
        znear = desired_znear;
        zfar = desired_zfar;

        projection(2,2)=-2.0f/(desired_zfar-desired_znear);
        projection(3,2)=-(desired_zfar+desired_znear)/(desired_zfar-desired_znear);

        // OSG_INFO << "Orthographic matrix after clamping "<<projection<<std::endl;
    }
    else
    {

        // OSG_INFO << "Persepective matrix before clamping"<<projection<<std::endl;

        //std::cout << "_computed_znear"<<_computed_znear<<std::endl;
        //std::cout << "_computed_zfar"<<_computed_zfar<<std::endl;

        value_type zfarPushRatio = 1.02;
        value_type znearPullRatio = 0.98;

        //znearPullRatio = 0.99;

        value_type desired_znear = znear * znearPullRatio;
        value_type desired_zfar = zfar * zfarPushRatio;

        // near plane clamping.
        double min_near_plane = zfar*nearFarRatio;
        if (desired_znear<min_near_plane) desired_znear=min_near_plane;

        // assign the clamped values back to the computed values.
        znear = desired_znear;
        zfar = desired_zfar;

        value_type trans_near_plane = (-desired_znear*projection(2,2)+projection(3,2))/(-desired_znear*projection(2,3)+projection(3,3));
        value_type trans_far_plane = (-desired_zfar*projection(2,2)+projection(3,2))/(-desired_zfar*projection(2,3)+projection(3,3));

        value_type ratio = fabs(2.0/(trans_near_plane-trans_far_plane));
        value_type center = -(trans_near_plane+trans_far_plane)/2.0;

        projection.postMult(osg::Matrix(1.0f,0.0f,0.0f,0.0f,
                                        0.0f,1.0f,0.0f,0.0f,
                                        0.0f,0.0f,ratio,0.0f,
                                        0.0f,0.0f,center*ratio,1.0f));

        // OSG_INFO << "Persepective matrix after clamping"<<projection<<std::endl;
    }
    return true;
}



}

#endif
