#pragma once

#include "SilverLining.h"
#include <osg/Camera>
#include <osg/CullSettings>
#include "SkyDrawable.h"
#include "CloudsDrawable.h"

/** This class intercepts OSG's method for automatically computing the near and far clip planes, taking into
    account SilverLining's objects in the scene (sky box, clouds, and possibly an atmospheric limb.) **/
class SilverLiningProjectionMatrixCallback : public osg::CullSettings::ClampProjectionMatrixCallback 
{
public:
    SilverLiningProjectionMatrixCallback(SilverLining::Atmosphere *atm, osg::Camera *cam) : osg::CullSettings::ClampProjectionMatrixCallback(),
        atmosphere(atm), camera(cam), skyDrawable(0), minFar(30000)
    {
        stockCullVisitor = osgUtil::CullVisitor::create();
    }

	void setSkyDrawable(SkyDrawable *sd) {skyDrawable = sd;}
    void setCloudsDrawable(CloudsDrawable *cd) {cloudsDrawable = cd;}

protected:
    bool clampProjectionMatrixImplementation(osg::Matrixf& projection, 
        double& znear, double& zfar) const
    {
       double cloudNear, cloudFar;
       computeNearFarClouds(cloudNear, cloudFar);
       if (cloudNear < znear) znear = cloudNear;
       if (cloudFar > zfar) zfar = cloudFar;
	   if (zfar < minFar) zfar = minFar; 

       bool ret = stockCullVisitor->clampProjectionMatrixImplementation(projection, znear, zfar);

	   if (skyDrawable)
	   {            
			skyDrawable->setSkyboxSize(((zfar - znear) * 0.5 + znear) * 2.0);
	   }

       return ret;
    }

    bool clampProjectionMatrixImplementation(osg::Matrixd& projection, 
        double& znear, double& zfar) const
    {
       double cloudNear, cloudFar;
       computeNearFarClouds(cloudNear, cloudFar);
       if (cloudNear < znear) znear = cloudNear;
       if (cloudFar > zfar) zfar = cloudFar;
	   if (zfar < minFar) zfar = minFar;

       bool ret = stockCullVisitor->clampProjectionMatrixImplementation(projection, znear, zfar);

	   if (skyDrawable)
	   {
	       skyDrawable->setSkyboxSize(((zfar - znear) * 0.5 + znear) * 2.0);
	   }

       return ret;
    }

    void computeNearFarClouds(double &znear, double &zfar) const
    {
        static double preMin = 0, preMax =  0;
        const double fudge = 5000.0;
        double minDist = 1E10; double maxDist = -1E10;

        osg::Vec3d camPos, dirVector, upVector;
        camera->getViewMatrixAsLookAt(camPos, dirVector, upVector); 

		double dToOrigin = camPos.length();

		bool hasLimb = atmosphere->GetConfigOptionBoolean("enable-atmosphere-from-space");
		if (hasLimb)
		{
			// Compute distance to edge of atmospheric limb
			double earthRadius = atmosphere->GetConfigOptionDouble("earth-radius-meters");
			double atmosphereHeight = earthRadius +
				+ atmosphere->GetConfigOptionDouble("atmosphere-height");
			double atmosphereThickness = atmosphere->GetConfigOptionDouble("atmosphere-scale-height-meters")
				+ earthRadius;

            if (dToOrigin > atmosphereThickness) // Bail if limb not rendered
            {
               const double dToLimbEdge = sqrt(dToOrigin * dToOrigin + atmosphereHeight * atmosphereHeight);
               maxDist = dToLimbEdge;

               const double dToLimbBase = sqrt(dToOrigin * dToOrigin + earthRadius * earthRadius);
               minDist = dToLimbBase;
            }
		}


        if( cloudsDrawable ) 
            cloudsDrawable->adjustNearFar( camPos, minDist, maxDist );

        double fovy, ar, zn, zf;
        camera->getProjectionMatrixAsPerspective(fovy, ar, zn, zf);
        double halfFovx = (fovy * ar) * 0.5;
        minDist *= cos(halfFovx * (3.14159265 / 180.0));

        znear = minDist - fudge;
        zfar = maxDist + fudge;

        if( preMin != znear || preMax != zfar )
        {
            preMin = znear;
            preMax = zfar;
        }

        // Tweak to make sure small model like OSG Cow will not get clipped with cumulus clouds in SilverLining OSG example
        // Beware: This trick can decrease depth buffer precision for distant objects

        if( zfar > minFar ) {
            static double originalNearFarRatio = stockCullVisitor->getNearFarRatio();
            double newNearFarRatio = originalNearFarRatio * minFar / zfar;
            stockCullVisitor->setNearFarRatio( newNearFarRatio );
        }
    }
    osgUtil::CullVisitor *stockCullVisitor;
    SilverLining::Atmosphere *atmosphere;
    osg::Camera *camera;
	SkyDrawable *skyDrawable;
    CloudsDrawable *cloudsDrawable;
	double minFar;


};
