#include "ANGLETest.h"

// Use this to select which configurations (e.g. which renderer, which GLES major version) these tests should be run against.
typedef ::testing::Types<TFT<Gles::Two, Rend::D3D9>, TFT<Gles::Two, Rend::D3D11>> TestFixtureTypes;
TYPED_TEST_CASE(BlendMinMaxTest, TestFixtureTypes);

template<typename T>
class BlendMinMaxTest : public ANGLETest
{
protected:
    BlendMinMaxTest() : ANGLETest(T::GetGlesMajorVersion(), T::GetRequestedRenderer())
    {
        setWindowWidth(128);
        setWindowHeight(128);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
        setConfigDepthBits(24);

        mProgram = 0;
        mColorLocation = -1;
    }

    struct Color
    {
        float values[4];
    };

    static GLubyte getExpected(bool blendMin, float curColor, GLubyte prevColor)
    {
        GLubyte curAsUbyte = (curColor * std::numeric_limits<GLubyte>::max()) + 0.5f;
        return blendMin ? std::min<GLubyte>(curAsUbyte, prevColor) : std::max<GLubyte>(curAsUbyte, prevColor);
    }

    void runTest()
    {
        const size_t colorCount = 1024;
        Color colors[colorCount];
        for (size_t i = 0; i < colorCount; i++)
        {
            for (size_t j = 0; j < 4; j++)
            {
                colors[i].values[j] = (rand() % 255) / 255.0f;
            }
        }

        GLubyte prevColor[4];
        for (size_t i = 0; i < colorCount; i++)
        {
            const Color &color = colors[i];
            glUseProgram(mProgram);
            glUniform4f(mColorLocation, color.values[0], color.values[1], color.values[2], color.values[3]);

            bool blendMin = (rand() % 2 == 0);
            glBlendEquation(blendMin ? GL_MIN : GL_MAX);

            drawQuad(mProgram, "aPosition", 0.5f);

            if (i > 0)
            {
                EXPECT_PIXEL_EQ(0, 0,
                                getExpected(blendMin, color.values[0], prevColor[0]),
                                getExpected(blendMin, color.values[1], prevColor[1]),
                                getExpected(blendMin, color.values[2], prevColor[2]),
                                getExpected(blendMin, color.values[3], prevColor[3]));
            }

            glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, prevColor);
        }
    }

    virtual void SetUp()
    {
        ANGLETest::SetUp();

        const std::string testVertexShaderSource = SHADER_SOURCE
        (
            attribute highp vec4 aPosition;

            void main(void)
            {
                gl_Position = aPosition;
            }
        );

        const std::string testFragmentShaderSource = SHADER_SOURCE
        (
            uniform highp vec4 color;
            void main(void)
            {
                gl_FragColor = color;
            }
        );

        mProgram = CompileProgram(testVertexShaderSource, testFragmentShaderSource);
        if (mProgram == 0)
        {
            FAIL() << "shader compilation failed.";
        }

        mColorLocation = glGetUniformLocation(mProgram, "color");

        glUseProgram(mProgram);

        glClearColor(0, 0, 0, 0);
        glClearDepthf(0.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glEnable(GL_BLEND);
        glDisable(GL_DEPTH_TEST);
    }

    void SetUpFramebuffer(GLenum colorFormat)
    {
        glGenFramebuffers(1, &mFramebuffer);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebuffer);
        glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebuffer);

        glGenRenderbuffers(1, &mColorRenderbuffer);
        glBindRenderbuffer(GL_RENDERBUFFER, mColorRenderbuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, colorFormat, getWindowWidth(), getWindowHeight());
        glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mColorRenderbuffer);

        ASSERT_GL_NO_ERROR();
    }

    virtual void TearDown()
    {
        glDeleteProgram(mProgram);
        glDeleteFramebuffers(1, &mFramebuffer);
        glDeleteRenderbuffers(1, &mColorRenderbuffer);

        ANGLETest::TearDown();
    }

    GLuint mProgram;
    GLint mColorLocation;

    GLuint mFramebuffer;
    GLuint mColorRenderbuffer;
};

TYPED_TEST(BlendMinMaxTest, RGBA8)
{
    SetUpFramebuffer(GL_RGBA8);
    runTest();
}

TYPED_TEST(BlendMinMaxTest, RGBA32f)
{
    SetUpFramebuffer(GL_RGBA32F);
    runTest();
}

TYPED_TEST(BlendMinMaxTest, RGBA16F)
{
    SetUpFramebuffer(GL_RGBA16F);
    runTest();
}
