#include "depth.hpp"

#include <algorithm>

#include <components/debug/debuglog.hpp>
#include <components/settings/settings.hpp>

namespace SceneUtil
{
    void setCameraClearDepth(osg::Camera* camera)
    {
        camera->setClearDepth(AutoDepth::isReversed() ? 0.0 : 1.0);
    }

    osg::Matrix getReversedZProjectionMatrixAsPerspectiveInf(double fov, double aspect, double near)
    {
        double A = 1.0/std::tan(osg::DegreesToRadians(fov)/2.0);
        return osg::Matrix(
            A/aspect,   0,      0,      0,
            0,          A,      0,      0,
            0,          0,      0,      -1,
            0,          0,      near,   0
        );
    }

    osg::Matrix getReversedZProjectionMatrixAsPerspective(double fov, double aspect, double near, double far)
    {
        double A = 1.0/std::tan(osg::DegreesToRadians(fov)/2.0);
        return osg::Matrix(
            A/aspect,   0,      0,                          0,
            0,          A,      0,                          0,
            0,          0,      near/(far-near),            -1,
            0,          0,      (far*near)/(far - near),    0
        );
    }

    osg::Matrix getReversedZProjectionMatrixAsOrtho(double left, double right, double bottom, double top, double near, double far)
    {
        return osg::Matrix(
            2/(right-left),             0,                          0,                  0,
            0,                          2/(top-bottom),             0,                  0,
            0,                          0,                          1/(far-near),       0,
            (right+left)/(left-right),  (top+bottom)/(bottom-top),  far/(far-near),     1
        );
    }

    bool isDepthFormat(GLenum format)
    {
        constexpr std::array<GLenum, 8> formats = {
            GL_DEPTH_COMPONENT32F,
            GL_DEPTH_COMPONENT32F_NV,
            GL_DEPTH_COMPONENT16,
            GL_DEPTH_COMPONENT24,
            GL_DEPTH_COMPONENT32,
            GL_DEPTH32F_STENCIL8,
            GL_DEPTH32F_STENCIL8_NV,
            GL_DEPTH24_STENCIL8,
        };

        return std::find(formats.cbegin(), formats.cend(), format) != formats.cend();
    }

    bool isDepthStencilFormat(GLenum format)
    {
        constexpr std::array<GLenum, 8> formats = {
            GL_DEPTH32F_STENCIL8,
            GL_DEPTH32F_STENCIL8_NV,
            GL_DEPTH24_STENCIL8,
        };

        return std::find(formats.cbegin(), formats.cend(), format) != formats.cend();
    }

    void getDepthFormatSourceFormatAndType(GLenum internalFormat, GLenum& sourceFormat, GLenum& sourceType)
    {
        switch (internalFormat)
        {
        case GL_DEPTH_COMPONENT16:
        case GL_DEPTH_COMPONENT24:
        case GL_DEPTH_COMPONENT32:
            sourceType = GL_UNSIGNED_INT;
            sourceFormat = GL_DEPTH_COMPONENT;
            break;
        case GL_DEPTH_COMPONENT32F:
        case GL_DEPTH_COMPONENT32F_NV:
            sourceType = GL_FLOAT;
            sourceFormat = GL_DEPTH_COMPONENT;
            break;
        case GL_DEPTH24_STENCIL8:
            sourceType = GL_UNSIGNED_INT_24_8_EXT;
            sourceFormat = GL_DEPTH_STENCIL_EXT;
            break;
        case GL_DEPTH32F_STENCIL8:
        case GL_DEPTH32F_STENCIL8_NV:
            sourceType = GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
            sourceFormat = GL_DEPTH_STENCIL_EXT;
            break;
        default:
            sourceType = GL_UNSIGNED_INT;
            sourceFormat = GL_DEPTH_COMPONENT;
            break;
        }
    }

    GLenum getDepthFormatOfDepthStencilFormat(GLenum internalFormat)
    {
        switch (internalFormat)
        {
        case GL_DEPTH24_STENCIL8:
            return GL_DEPTH_COMPONENT24;
            break;
        case GL_DEPTH32F_STENCIL8:
            return GL_DEPTH_COMPONENT32F;
            break;
        case GL_DEPTH32F_STENCIL8_NV:
            return GL_DEPTH_COMPONENT32F_NV;
            break;
        default:
            return internalFormat;
            break;
        }
    }

    void SelectDepthFormatOperation::operator()(osg::GraphicsContext* graphicsContext)
    {
        bool enableReverseZ = false;

        if (Settings::Manager::getBool("reverse z", "Camera"))
        {
            osg::ref_ptr<osg::GLExtensions> exts = osg::GLExtensions::Get(0, false);
            if (exts && exts->isClipControlSupported)
            {
                enableReverseZ = true;
                Log(Debug::Info) << "Using reverse-z depth buffer";
            }
            else
                Log(Debug::Warning) << "GL_ARB_clip_control not supported: disabling reverse-z depth buffer";
        }
        else
            Log(Debug::Info) << "Using standard depth buffer";

        SceneUtil::AutoDepth::setReversed(enableReverseZ);

        constexpr char errPreamble[] = "Postprocessing and floating point depth buffers disabled: ";
        std::vector<GLenum> requestedFormats;
        unsigned int contextID = graphicsContext->getState()->getContextID();
        if (SceneUtil::AutoDepth::isReversed())
        {
            if (osg::isGLExtensionSupported(contextID, "GL_ARB_depth_buffer_float"))
            {
                requestedFormats.push_back(GL_DEPTH32F_STENCIL8);
            }
            else if (osg::isGLExtensionSupported(contextID, "GL_NV_depth_buffer_float"))
            {
                requestedFormats.push_back(GL_DEPTH32F_STENCIL8_NV);
            }
            else
            {
                Log(Debug::Warning) << errPreamble << "'GL_ARB_depth_buffer_float' and 'GL_NV_depth_buffer_float' unsupported.";
            }
        }

        requestedFormats.push_back(GL_DEPTH24_STENCIL8);
        if (mSupportedFormats.empty())
        {
            SceneUtil::AutoDepth::setDepthFormat(requestedFormats.front());
        }
        else
        {
            for (auto requestedFormat : requestedFormats)
            {
                if (std::find(mSupportedFormats.cbegin(), mSupportedFormats.cend(), requestedFormat) != mSupportedFormats.cend())
                {
                    SceneUtil::AutoDepth::setDepthFormat(requestedFormat);
                    break;
                }
            }
        }
    }

    void AutoDepth::setDepthFormat(GLenum format)
    {
        sDepthInternalFormat = format;
        getDepthFormatSourceFormatAndType(sDepthInternalFormat, sDepthSourceFormat, sDepthSourceType);
    }
}
