//
// Copyright (c) 2014 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "libANGLE/AttributeMap.h"
#include "libANGLE/Config.h"

// Create a generic, valid EGL config that can be modified to test sorting and
// filtering routines
static egl::Config GenerateGenericConfig()
{
    egl::Config config;

    config.bufferSize = 24;
    config.redSize = 8;
    config.greenSize = 8;
    config.blueSize = 8;
    config.luminanceSize = 0;
    config.alphaSize = 8;
    config.alphaMaskSize = 0;
    config.bindToTextureRGB = EGL_TRUE;
    config.bindToTextureRGBA = EGL_TRUE;
    config.colorBufferType = EGL_RGB_BUFFER;
    config.configCaveat = EGL_NONE;
    config.configID = 0;
    config.conformant = EGL_OPENGL_ES2_BIT;
    config.depthSize = 24;
    config.level = 0;
    config.matchNativePixmap = EGL_NONE;
    config.maxPBufferWidth = 1024;
    config.maxPBufferHeight = 1024;
    config.maxPBufferPixels = config.maxPBufferWidth * config.maxPBufferWidth;
    config.maxSwapInterval = 0;
    config.minSwapInterval = 4;
    config.nativeRenderable = EGL_OPENGL_ES2_BIT;
    config.nativeVisualID = 0;
    config.nativeVisualType = 0;
    config.renderableType = EGL_FALSE;
    config.sampleBuffers = 0;
    config.samples = 0;
    config.stencilSize = 8;
    config.surfaceType = EGL_PBUFFER_BIT | EGL_WINDOW_BIT | EGL_SWAP_BEHAVIOR_PRESERVED_BIT;
    config.transparentType = EGL_NONE;
    config.transparentRedValue = 0;
    config.transparentGreenValue = 0;
    config.transparentBlueValue = 0;

    return config;
}

static std::vector<egl::Config> GenerateUniqueConfigs(size_t count)
{
    std::vector<egl::Config> configs;

    for (size_t i = 0; i < count; i++)
    {
        egl::Config config = GenerateGenericConfig();
        config.samples = i;
        configs.push_back(config);
    }

    return configs;
}

// Add unique configs to a ConfigSet and expect that the size of the
// set is equal to the number of configs added.
TEST(ConfigSetTest, Size)
{
    egl::ConfigSet set;

    std::vector<egl::Config> uniqueConfigs = GenerateUniqueConfigs(16);
    for (size_t i = 0; i < uniqueConfigs.size(); i++)
    {
        set.add(uniqueConfigs[i]);
        EXPECT_EQ(set.size(), i + 1);
    }
}

// [EGL 1.5] section 3.4:
// EGL_CONFIG_ID is a unique integer identifying different EGLConfigs. Configuration IDs
// must be small positive integers starting at 1 and ID assignment should be compact;
// that is, if there are N EGLConfigs defined by the EGL implementation, their
// configuration IDs should be in the range [1, N].
TEST(ConfigSetTest, IDs)
{
    egl::ConfigSet set;

    std::set<EGLint> ids;

    std::vector<egl::Config> uniqueConfigs = GenerateUniqueConfigs(16);
    for (size_t i = 0; i < uniqueConfigs.size(); i++)
    {
        EGLint id = set.add(uniqueConfigs[i]);

        // Check that the config that was inserted has the ID that was returned
        // by ConfigSet::add
        EXPECT_EQ(id, set.get(id).configID);

        ids.insert(id);
    }

    // Verify configCount unique IDs
    EXPECT_EQ(ids.size(), set.size());

    // Check that there are no gaps and the IDs are in the range [1, N].
    EXPECT_EQ(*std::min_element(ids.begin(), ids.end()), 1);
    EXPECT_EQ(*std::max_element(ids.begin(), ids.end()), static_cast<EGLint>(set.size()));
}

TEST(ConfigSetTest, Filtering_BitSizes)
{
    egl::ConfigSet set;

    struct VariableConfigBitSize
    {
        EGLint Name;
        EGLint(egl::Config::*ConfigMember);
    };

    VariableConfigBitSize testMembers[] = 
    {
        { EGL_RED_SIZE,     &egl::Config::redSize     },
        { EGL_GREEN_SIZE,   &egl::Config::greenSize   },
        { EGL_BLUE_SIZE,    &egl::Config::blueSize    },
        { EGL_ALPHA_SIZE,   &egl::Config::alphaSize   },
        { EGL_DEPTH_SIZE,   &egl::Config::depthSize   },
        { EGL_STENCIL_SIZE, &egl::Config::stencilSize },
    };

    // Generate configsPerType configs with varying bit sizes of each type
    size_t configsPerType = 4;
    for (size_t i = 0; i < ArraySize(testMembers); i++)
    {
        for (size_t j = 0; j < configsPerType; j++)
        {
            egl::Config config = GenerateGenericConfig();

            // Set all the other tested members of this config to 0
            for (size_t k = 0; k < ArraySize(testMembers); k++)
            {
                config.*(testMembers[k].ConfigMember) = 0;
            }

            // Set the tested member of this config to i so it ranges from
            // [1, configsPerType]
            config.*(testMembers[i].ConfigMember) = j + 1;

            set.add(config);
        }
    }

    // for each tested member, filter by it's type and verify that the correct number
    // of results are returned
    for (size_t i = 0; i < ArraySize(testMembers); i++)
    {
        // Start with a filter of 1 to not grab the other members
        for (size_t j = 0; j < configsPerType; j++)
        {
            egl::AttributeMap filter;
            filter.insert(testMembers[i].Name, j + 1);

            std::vector<const egl::Config *> filteredConfigs = set.filter(filter);

            EXPECT_EQ(filteredConfigs.size(), configsPerType - j);
        }
    }
}

// Verify the sorting, [EGL 1.5] section 3.4.1.2 pg 30:
// [configs are sorted] by larger total number of color bits (for an RGB
// color buffer this is the sum of EGL_RED_SIZE, EGL_GREEN_SIZE, EGL_BLUE_SIZE,
// and EGL_ALPHA_SIZE; for a luminance color buffer, the sum of EGL_LUMINANCE_SIZE
// and EGL_ALPHA_SIZE).If the requested number of bits in attrib list for a
// particular color component is 0 or EGL_DONT_CARE, then the number of bits
// for that component is not considered.
TEST(ConfigSetTest, Sorting_BitSizes)
{
    egl::ConfigSet set;
    size_t testConfigCount = 64;
    for (size_t i = 0; i < testConfigCount; i++)
    {
        egl::Config config = GenerateGenericConfig();

        // Give random-ish bit sizes to the config
        config.redSize =   (i *  2) %  3;
        config.greenSize = (i +  5) %  7;
        config.blueSize =  (i +  7) % 11;
        config.alphaSize = (i + 13) % 17;

        set.add(config);
    }

    egl::AttributeMap greaterThan1BitFilter;
    greaterThan1BitFilter.insert(EGL_RED_SIZE, 1);
    greaterThan1BitFilter.insert(EGL_GREEN_SIZE, 1);
    greaterThan1BitFilter.insert(EGL_BLUE_SIZE, 1);
    greaterThan1BitFilter.insert(EGL_ALPHA_SIZE, 1);

    std::vector<const egl::Config *> filteredConfigs = set.filter(greaterThan1BitFilter);
    for (size_t i = 1; i < filteredConfigs.size(); i++)
    {
        const egl::Config &prevConfig = *filteredConfigs[i - 1];
        size_t prevBitCount = prevConfig.redSize +
                              prevConfig.greenSize +
                              prevConfig.blueSize +
                              prevConfig.alphaSize;

        const egl::Config &curConfig = *filteredConfigs[i];
        size_t curBitCount = curConfig.redSize +
                             curConfig.greenSize +
                             curConfig.blueSize +
                             curConfig.alphaSize;

        EXPECT_GE(prevBitCount, curBitCount);
    }
}
