#include "ANGLETest.h"

class MaxTextureSizeTest : public ANGLETest
{
protected:
    MaxTextureSizeTest()
    {
        setWindowWidth(512);
        setWindowHeight(512);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }

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

        const std::string vsSource = SHADER_SOURCE
        (
            precision highp float;
            attribute vec4 position;
            varying vec2 texcoord;

            void main()
            {
                gl_Position = position;
                texcoord = (position.xy * 0.5) + 0.5;
            }
        );

        const std::string textureFSSource = SHADER_SOURCE
        (
            precision highp float;
            uniform sampler2D tex;
            varying vec2 texcoord;

            void main()
            {
                gl_FragColor = texture2D(tex, texcoord);
            }
        );

        const std::string blueFSSource = SHADER_SOURCE
        (
            precision highp float;

            void main()
            {
                gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
            }
        );

        mTextureProgram = compileProgram(vsSource, textureFSSource);
        mBlueProgram = compileProgram(vsSource, blueFSSource);
        if (mTextureProgram == 0 || mBlueProgram == 0)
        {
            FAIL() << "shader compilation failed.";
        }

        mTextureUniformLocation = glGetUniformLocation(mTextureProgram, "tex");

        glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTexture2DSize);
        glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &mMaxTextureCubeSize);
        glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &mMaxRenderbufferSize);

        ASSERT_GL_NO_ERROR();
    }

    virtual void TearDown()
    {
        glDeleteProgram(mTextureProgram);
        glDeleteProgram(mBlueProgram);

        ANGLETest::TearDown();
    }

    GLuint mTextureProgram;
    GLint mTextureUniformLocation;

    GLuint mBlueProgram;

    GLint mMaxTexture2DSize;
    GLint mMaxTextureCubeSize;
    GLint mMaxRenderbufferSize;
};

TEST_F(MaxTextureSizeTest, specification_tex_image)
{
    GLuint tex;
    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    GLsizei textureWidth = mMaxTexture2DSize;
    GLsizei textureHeight = 64;

    std::vector<GLubyte> data(textureWidth * textureHeight * 4);
    for (int y = 0; y < textureHeight; y++)
    {
        for (int x = 0; x < textureWidth; x++)
        {
            GLubyte* pixel = data.data() + ((y * textureWidth + x) * 4);

            // Draw a gradient, red in direction, green in y direction
            pixel[0] = static_cast<GLubyte>((float(x) / textureWidth) * 255);
            pixel[1] = static_cast<GLubyte>((float(y) / textureHeight) * 255);
            pixel[2] = 0;
            pixel[3] = 255;
        }
    }

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureWidth, textureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
    EXPECT_GL_NO_ERROR();

    glUseProgram(mTextureProgram);
    glUniform1i(mTextureUniformLocation, 0);

    drawQuad(mTextureProgram, "position", 0.5f);

    std::vector<GLubyte> pixels(getWindowWidth() * getWindowHeight() * 4);
    glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());

    for (int y = 1; y < getWindowHeight(); y++)
    {
        for (int x = 1; x < getWindowWidth(); x++)
        {
            const GLubyte* prevPixel = pixels.data() + (((y - 1) * getWindowWidth() + (x - 1)) * 4);
            const GLubyte* curPixel = pixels.data() + ((y * getWindowWidth() + x) * 4);

            EXPECT_GE(curPixel[0], prevPixel[0]);
            EXPECT_GE(curPixel[1], prevPixel[1]);
            EXPECT_EQ(curPixel[2], prevPixel[2]);
            EXPECT_EQ(curPixel[3], prevPixel[3]);
        }
    }
}

TEST_F(MaxTextureSizeTest, specification_tex_storage)
{
    if (getClientVersion() < 3 && (!extensionEnabled("GL_EXT_texture_storage") || !extensionEnabled("GL_OES_rgb8_rgba8")))
    {
        return;
    }

    GLuint tex;
    glGenTextures(1, &tex);
    glBindTexture(GL_TEXTURE_2D, tex);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    GLsizei textureWidth = 64;
    GLsizei textureHeight = mMaxTexture2DSize;

    std::vector<GLubyte> data(textureWidth * textureHeight * 4);
    for (int y = 0; y < textureHeight; y++)
    {
        for (int x = 0; x < textureWidth; x++)
        {
            GLubyte* pixel = data.data() + ((y * textureWidth + x) * 4);

            // Draw a gradient, red in direction, green in y direction
            pixel[0] = static_cast<GLubyte>((float(x) / textureWidth) * 255);
            pixel[1] = static_cast<GLubyte>((float(y) / textureHeight) * 255);
            pixel[2] = 0;
            pixel[3] = 255;
        }
    }

    if (getClientVersion() < 3)
    {
        glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8_OES, textureWidth, textureHeight);
    }
    else
    {
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8_OES, textureWidth, textureHeight);
    }
    EXPECT_GL_NO_ERROR();

    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
    EXPECT_GL_NO_ERROR();

    glUseProgram(mTextureProgram);
    glUniform1i(mTextureUniformLocation, 0);

    drawQuad(mTextureProgram, "position", 0.5f);

    std::vector<GLubyte> pixels(getWindowWidth() * getWindowHeight() * 4);
    glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());

    for (int y = 1; y < getWindowHeight(); y++)
    {
        for (int x = 1; x < getWindowWidth(); x++)
        {
            const GLubyte* prevPixel = pixels.data() + (((y - 1) * getWindowWidth() + (x - 1)) * 4);
            const GLubyte* curPixel = pixels.data() + ((y * getWindowWidth() + x) * 4);

            EXPECT_GE(curPixel[0], prevPixel[0]);
            EXPECT_GE(curPixel[1], prevPixel[1]);
            EXPECT_EQ(curPixel[2], prevPixel[2]);
            EXPECT_EQ(curPixel[3], prevPixel[3]);
        }
    }
}

TEST_F(MaxTextureSizeTest, render_to_texture)
{
    GLuint fbo = 0;
    GLuint textureId = 0;
    // create a 1-level texture at maximum size
    glGenTextures(1, &textureId);
    glBindTexture(GL_TEXTURE_2D, textureId);

    GLsizei textureWidth = 64;
    GLsizei textureHeight = mMaxTexture2DSize;

    // texture setup code
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA_EXT, textureHeight, textureWidth, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, NULL);
    EXPECT_GL_NO_ERROR();

    // create an FBO and attach the texture
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureId, 0);

    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GL_FRAMEBUFFER_COMPLETE);

    const int frameCount = 64;
    for (int i = 0; i < frameCount; i++)
    {
        // clear the screen
        glBindFramebuffer(GL_FRAMEBUFFER, 0);

        GLubyte clearRed = static_cast<GLubyte>((float(i) / frameCount) * 255);
        GLubyte clearGreen = 255 - clearRed;
        GLubyte clearBlue = 0;

        glClearColor(clearRed / 255.0f, clearGreen / 255.0f, clearBlue / 255.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // render blue into the texture
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        drawQuad(mBlueProgram, "position", 0.5f);

        // copy corner of texture to LL corner of window
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, 0);
        glBindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, fbo);
        glBlitFramebufferANGLE(0, 0, textureWidth - 1, getWindowHeight() - 1,
                               0, 0, textureWidth - 1, getWindowHeight() - 1,
                               GL_COLOR_BUFFER_BIT, GL_NEAREST);
        glBindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, 0);
        EXPECT_GL_NO_ERROR();

        EXPECT_PIXEL_EQ(textureWidth / 2, getWindowHeight() / 2, 0, 0, 255, 255);
        EXPECT_PIXEL_EQ(textureWidth + 10, getWindowHeight() / 2, clearRed, clearGreen, clearBlue, 255);

        swapBuffers();
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glBindTexture(GL_TEXTURE_2D, 0);

    glDeleteFramebuffers(1, &fbo);
    glDeleteTextures(1, &textureId);
}
