// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include "aemu/base/files/PathUtils.h"
#include "aemu/base/system/System.h"
#include "aemu/base/testing/TestSystem.h"

#include "OpenGLTestContext.h"

#include <gtest/gtest.h>

#include <memory>
#include <vector>

namespace gfxstream {
namespace gl {

struct GlValues {
    std::vector<GLint> ints;
    std::vector<GLfloat> floats;
};

struct GlBufferData {
    GLsizeiptr size;
    GLvoid* bytes;
    GLenum usage;
};

// Capabilities which, according to the GLES2 spec, start disabled.
static const GLenum kGLES2CanBeEnabled[] = {GL_BLEND,
                                            GL_CULL_FACE,
                                            GL_DEPTH_TEST,
                                            GL_POLYGON_OFFSET_FILL,
                                            GL_SAMPLE_ALPHA_TO_COVERAGE,
                                            GL_SAMPLE_COVERAGE,
                                            GL_SCISSOR_TEST,
                                            GL_STENCIL_TEST};

// Capabilities which, according to the GLES2 spec, start enabled.
static const GLenum kGLES2CanBeDisabled[] = {GL_DITHER};

// Modes for CullFace
static const GLenum kGLES2CullFaceModes[] = {GL_BACK, GL_FRONT,
                                             GL_FRONT_AND_BACK};

// Modes for FrontFace
static const GLenum kGLES2FrontFaceModes[] = {GL_CCW, GL_CW};

// Valid Stencil test functions
static const GLenum kGLES2StencilFuncs[] = {GL_NEVER,   GL_ALWAYS,  GL_LESS,
                                            GL_LEQUAL,  GL_EQUAL,   GL_GEQUAL,
                                            GL_GREATER, GL_NOTEQUAL};
// Valid Stencil test result operations
static const GLenum kGLES2StencilOps[] = {GL_KEEP,      GL_ZERO,     GL_REPLACE,
                                          GL_INCR,      GL_DECR,     GL_INVERT,
                                          GL_INCR_WRAP, GL_DECR_WRAP};

// Modes for the BlendEquation
static const GLenum kGLES2BlendEquations[] = {GL_FUNC_ADD, GL_FUNC_SUBTRACT,
                                              GL_FUNC_REVERSE_SUBTRACT};

// Valid Blend functions
static const GLenum kGLES2BlendFuncs[] = {GL_ZERO,
                                          GL_ONE,
                                          GL_SRC_COLOR,
                                          GL_ONE_MINUS_SRC_COLOR,
                                          GL_DST_COLOR,
                                          GL_ONE_MINUS_DST_COLOR,
                                          GL_SRC_ALPHA,
                                          GL_ONE_MINUS_SRC_ALPHA,
                                          GL_CONSTANT_COLOR,
                                          GL_ONE_MINUS_CONSTANT_COLOR,
                                          GL_CONSTANT_ALPHA,
                                          GL_ONE_MINUS_CONSTANT_ALPHA,
                                          GL_SRC_ALPHA_SATURATE};

// Valid GENERATE_MIPMAP_HINT values
static const GLenum kGLES2GenerateMipmapHints[] = {GL_DONT_CARE, GL_FASTEST,
                                                   GL_NICEST};

// Returns a string useful for failure messages describing |enumValue|.
std::string describeGlEnum(GLenum enumValue);

// For building other compare functions which return AssertionResult.
// Compares an |actual| against an |expected| value. Returns a failure values
// do not match; provide |description| to attach details to the failure message.
template <class T>
testing::AssertionResult compareValue(T expected,
                                      T actual,
                                      const std::string& description = "");

// Compares a global GL value, known by |name| and retrieved as a boolean,
// against an |expected| value.
testing::AssertionResult compareGlobalGlBoolean(const GLESv2Dispatch* gl,
                                                GLenum name,
                                                GLboolean expected);

// Compares a global GL value, known by |name| and retrieved as an integer,
// against an |expected| value.
testing::AssertionResult compareGlobalGlInt(const GLESv2Dispatch* gl,
                                            GLenum name,
                                            GLint expected);

testing::AssertionResult compareGlobalGlInt_i(const GLESv2Dispatch* gl,
                                              GLenum name,
                                              GLuint index,
                                              GLint expected);
// Compares a global GL value, known by |name| and retrieved as a float, against
// an |expected| value.
testing::AssertionResult compareGlobalGlFloat(const GLESv2Dispatch* gl,
                                              GLenum name,
                                              GLfloat expected);

// For building other compare functions which return AssertionResult.
// Compare the values at each index of a vector |actual| against an |expected|.
// Returns a failure if any values are mismatched; provide |description| to
// attach details to the failure message.
// |actual| is allowed to contain more elements than |expected|.
template <class T>
testing::AssertionResult compareVector(
        const std::vector<T>& expected,
        const std::vector<T>& actual,
        const std::string& description = "vector");

// Compares a vector of global GL values, known by |name| and retrieved as a
// boolean array, against |expected| values.
// Specify |size| if more space is needed than the size of |expected|.
testing::AssertionResult compareGlobalGlBooleanv(
        const GLESv2Dispatch* gl,
        GLenum name,
        const std::vector<GLboolean>& expected,
        GLuint size = 0);

testing::AssertionResult compareGlobalGlBooleanv_i(
        const GLESv2Dispatch* gl,
        GLenum name,
        GLuint index,
        const std::vector<GLboolean>& expected,
        GLuint size = 0);


// Compares a vector of global GL values, known by |name| and retrieved as an
// integer array, against |expected| values.
// Specify |size| if more space is needed than the size of |expected|.
testing::AssertionResult compareGlobalGlIntv(const GLESv2Dispatch* gl,
                                             GLenum name,
                                             const std::vector<GLint>& expected,
                                             GLuint size = 0);

testing::AssertionResult compareGlobalGlIntv_i(const GLESv2Dispatch* gl,
                                               GLenum name,
                                               GLuint index,
                                               const std::vector<GLint>& expected,
                                               GLuint size = 0);

// Compares a vector of global GL values, known by |name| and retrieved as a
// float array, against |expected| values.
// Specify |size| if more space is needed than the size of |expected|.
testing::AssertionResult compareGlobalGlFloatv(
        const GLESv2Dispatch* gl,
        GLenum name,
        const std::vector<GLfloat>& expected,
        GLuint size = 0);

// SnapshotTest - A helper class for performing a test related to saving or
// loading GL translator snapshots. As a test fixture, its setup will prepare a
// fresh GL state and paths for temporary snapshot files.
//
// doSnapshot saves a snapshot, clears the GL state, then loads the snapshot.
// saveSnapshot and loadSnapshot can be used to perform saves and loads
// independently.
//
// Usage example:
//     TEST_F(SnapshotTest, PreserveFooBar) {
//         // clean GL state is ready
//         EXPECT_TRUE(fooBarState());
//         modifyGlStateFooBar();
//         EXPECT_FALSE(fooBarState());  // GL state has been changed
//         doSnapshot(); // saves, resets, and reloads the state
//         EXPECT_FALSE(fooBarState());  // Snapshot preserved the state change
//     }
//
class SnapshotTest : public gfxstream::gl::GLTest {
   public:
    SnapshotTest() = default;

    void SetUp() override;

    // Mimics FrameBuffer.onSave, with fewer objects to manage.
    // |streamFile| is a filename into which the snapshot will be saved.
    // |textureFile| is a filename into which the textures will be saved.
    void saveSnapshot(const std::string streamFile,
                      const std::string textureFile);

    // Mimics FrameBuffer.onLoad, with fewer objects to manage.
    // Assumes that a valid display is present.
    // |streamFile| is a filename from which the snapshot will be loaded.
    // |textureFile| is a filename from which the textures will be loaded.
    void loadSnapshot(const std::string streamFile,
                      const std::string textureFile);

    // Performs a teardown and reset of graphics objects in preparation for
    // a snapshot load.
    void preloadReset();

    // Mimics saving and then loading a graphics snapshot.
    // To verify that state has been reset to some default before the load,
    // assertions can be performed in |preloadCheck|.
    void doSnapshot(std::function<void()> preloadCheck);

protected:
    android::base::TestSystem mTestSystem;
    std::string mSnapshotPath = {};
};

// SnapshotPreserveTest - A helper class building on SnapshotTest for granular
// testing of the GL snapshot. This is specifically for the common case where a
// piece of GL state has a known default, and our test aims to verify that the
// snapshot preserves this piece of state when it has been changed from the
// default.
//
// This acts as an abstract class; implementations should override the state
// check state change functions to perform the assertions and operations
// relevant to the part of GL state that they are testing.
// doCheckedSnapshot can be but does not need to be overwritten. It performs the
// following:
//      - check for default state
//      - make state changes, check that the state changes are in effect
//      - save a snapshot, reset the GL state, then check for default state
//      - load the snapshot, check that the state changes are in effect again
//
// Usage example with a subclass:
//     class SnapshotEnableFooTest : public SnapshotPreserveTest {
//         void defaultStateCheck() override { EXPECT_FALSE(isFooEnabled()); }
//         void changedStateCheck() override { EXPECT_TRUE(isFooEnabled());  }
//         void stateChange() override { enableFoo(); }
//     };
//     TEST_F(SnapshotEnableFooTest, PreserveFooEnable) {
//         doCheckedSnapshot();
//     }
//
class SnapshotPreserveTest : public SnapshotTest {
public:
    // Asserts that we are working from a clean starting state.
    virtual void defaultStateCheck() {
        ADD_FAILURE() << "Snapshot test needs a default state check function.";
    }

    // Asserts that any expected changes to state have occurred.
    virtual void changedStateCheck() {
        ADD_FAILURE()
                << "Snapshot test needs a post-change state check function.";
    }

    // Modifies the state.
    virtual void stateChange() {
        ADD_FAILURE() << "Snapshot test needs a state-changer function.";
    }

    // Sets up a non-default state and asserts that a snapshot preserves it.
    virtual void doCheckedSnapshot();
};

// SnapshotSetValueTest - A helper class for testing preservation of pieces of
// GL state where default and changed state checks are comparisons against the
// same type of expected reference value.
//
// The expected |m_default_value| and |m_changed_value| should be set before
// a checked snapshot is attempted.
//
// Usage example with a subclass:
//     class SnapshotSetFooTest : public SnapshotSetValueTest<Foo> {
//         void stateCheck(Foo expected) { EXPECT_EQ(expected, getFoo()); }
//         void stateChange() override { setFoo(*m_changed_value); }
//     };
//     TEST_F(SnapshotSetFooTest, SetFooValue) {
//         setExpectedValues(kFooDefaultValue, kFooTestValue);
//         doCheckedSnapshot();
//     }
//
template <class T>
class SnapshotSetValueTest : public SnapshotPreserveTest {
public:
    // Configures the test to assert against values which it should consider
    // default and values which it should expect after changes.
    void setExpectedValues(T defaultValue, T changedValue) {
        m_default_value = std::unique_ptr<T>(new T(defaultValue));
        m_changed_value = std::unique_ptr<T>(new T(changedValue));
    }

    // Checks part of state against an expected value.
    virtual void stateCheck(T expected) {
        ADD_FAILURE() << "Snapshot test needs a state-check function.";
    };

    void defaultStateCheck() override { stateCheck(*m_default_value); }
    void changedStateCheck() override { stateCheck(*m_changed_value); }

    void doCheckedSnapshot() override {
        if (m_default_value == nullptr || m_changed_value == nullptr) {
            FAIL() << "Snapshot test not provided expected values.";
        }
        SnapshotPreserveTest::doCheckedSnapshot();
    }

protected:
    std::unique_ptr<T> m_default_value;
    std::unique_ptr<T> m_changed_value;
};

}  // namespace gl
}  // namespace gfxstream
