//
// Copyright 2002 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.
//

// Config.cpp: Implements the egl::Config class, describing the format, type
// and size for an egl::Surface. Implements EGLConfig and related functionality.
// [EGL 1.5] section 3.4 page 19.

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

#include <algorithm>
#include <vector>

#include <EGL/eglext.h>
#include "angle_gl.h"

#include "common/debug.h"

namespace egl
{

Config::Config()
    : renderTargetFormat(GL_NONE),
      depthStencilFormat(GL_NONE),
      bufferSize(0),
      redSize(0),
      greenSize(0),
      blueSize(0),
      luminanceSize(0),
      alphaSize(0),
      alphaMaskSize(0),
      bindToTextureRGB(EGL_FALSE),
      bindToTextureRGBA(EGL_FALSE),
      bindToTextureTarget(EGL_TEXTURE_2D),
      colorBufferType(EGL_RGB_BUFFER),
      configCaveat(EGL_NONE),
      configID(0),
      conformant(0),
      depthSize(0),
      level(0),
      matchNativePixmap(EGL_FALSE),
      maxPBufferWidth(0),
      maxPBufferHeight(0),
      maxPBufferPixels(0),
      maxSwapInterval(0),
      minSwapInterval(0),
      nativeRenderable(EGL_FALSE),
      nativeVisualID(0),
      nativeVisualType(0),
      renderableType(0),
      sampleBuffers(0),
      samples(0),
      stencilSize(0),
      surfaceType(0),
      transparentType(EGL_NONE),
      transparentRedValue(0),
      transparentGreenValue(0),
      transparentBlueValue(0),
      optimalOrientation(0),
      colorComponentType(EGL_COLOR_COMPONENT_TYPE_FIXED_EXT),
      recordable(EGL_FALSE),
      framebufferTarget(EGL_FALSE),  // TODO: http://anglebug.com/4208
      yInverted(EGL_FALSE)
{}

Config::~Config() {}

Config::Config(const Config &other) = default;

Config &Config::operator=(const Config &other) = default;

ConfigSet::ConfigSet() = default;

ConfigSet::ConfigSet(const ConfigSet &other) = default;

ConfigSet &ConfigSet::operator=(const ConfigSet &other) = default;

ConfigSet::~ConfigSet() = default;

EGLint ConfigSet::add(const Config &config)
{
    // Set the config's ID to a small number that starts at 1 ([EGL 1.5] section 3.4)
    EGLint id = static_cast<EGLint>(mConfigs.size()) + 1;

    Config copyConfig(config);
    copyConfig.configID = id;
    mConfigs.insert(std::make_pair(id, copyConfig));

    return id;
}

const Config &ConfigSet::get(EGLint id) const
{
    ASSERT(mConfigs.find(id) != mConfigs.end());
    return mConfigs.find(id)->second;
}

void ConfigSet::clear()
{
    mConfigs.clear();
}

size_t ConfigSet::size() const
{
    return mConfigs.size();
}

bool ConfigSet::contains(const Config *config) const
{
    for (auto i = mConfigs.begin(); i != mConfigs.end(); i++)
    {
        const Config &item = i->second;
        if (config == &item)
        {
            return true;
        }
    }

    return false;
}

// Function object used by STL sorting routines for ordering Configs according to [EGL 1.5]
// section 3.4.1.2 page 28.
class ConfigSorter
{
  public:
    explicit ConfigSorter(const AttributeMap &attributeMap)
        : mWantRed(false),
          mWantGreen(false),
          mWantBlue(false),
          mWantAlpha(false),
          mWantLuminance(false)
    {
        scanForWantedComponents(attributeMap);
    }

    bool operator()(const Config *x, const Config *y) const { return (*this)(*x, *y); }

    bool operator()(const Config &x, const Config &y) const
    {
#define SORT(attribute)                       \
    do                                        \
    {                                         \
        if (x.attribute != y.attribute)       \
            return x.attribute < y.attribute; \
    } while (0)

        static_assert(EGL_NONE < EGL_SLOW_CONFIG && EGL_SLOW_CONFIG < EGL_NON_CONFORMANT_CONFIG,
                      "Unexpected EGL enum value.");
        SORT(configCaveat);

        static_assert(EGL_COLOR_COMPONENT_TYPE_FIXED_EXT < EGL_COLOR_COMPONENT_TYPE_FLOAT_EXT,
                      "Unexpected order of EGL enums.");
        SORT(colorComponentType);

        static_assert(EGL_RGB_BUFFER < EGL_LUMINANCE_BUFFER, "Unexpected EGL enum value.");
        SORT(colorBufferType);

        // By larger total number of color bits, only considering those that are requested to be >
        // 0.
        EGLint xComponentsSize = wantedComponentsSize(x);
        EGLint yComponentsSize = wantedComponentsSize(y);
        if (xComponentsSize != yComponentsSize)
        {
            return xComponentsSize > yComponentsSize;
        }

        SORT(bufferSize);
        SORT(sampleBuffers);
        SORT(samples);
        SORT(depthSize);
        SORT(stencilSize);
        SORT(alphaMaskSize);
        SORT(nativeVisualType);
        SORT(configID);

#undef SORT

        return false;
    }

  private:
    static bool wantsComponent(const AttributeMap &attributeMap, EGLAttrib component)
    {
        // [EGL 1.5] section 3.4.1.2 page 30
        // Sorting rule #3: by larger total number of color bits, not considering
        // components that are 0 or don't-care.
        EGLAttrib value = attributeMap.get(component, 0);
        return value != 0 && value != EGL_DONT_CARE;
    }

    void scanForWantedComponents(const AttributeMap &attributeMap)
    {
        mWantRed       = wantsComponent(attributeMap, EGL_RED_SIZE);
        mWantGreen     = wantsComponent(attributeMap, EGL_GREEN_SIZE);
        mWantBlue      = wantsComponent(attributeMap, EGL_BLUE_SIZE);
        mWantAlpha     = wantsComponent(attributeMap, EGL_ALPHA_SIZE);
        mWantLuminance = wantsComponent(attributeMap, EGL_LUMINANCE_SIZE);
    }

    EGLint wantedComponentsSize(const Config &config) const
    {
        EGLint total = 0;

        if (mWantRed)
            total += config.redSize;
        if (mWantGreen)
            total += config.greenSize;
        if (mWantBlue)
            total += config.blueSize;
        if (mWantAlpha)
            total += config.alphaSize;
        if (mWantLuminance)
            total += config.luminanceSize;

        return total;
    }

    bool mWantRed;
    bool mWantGreen;
    bool mWantBlue;
    bool mWantAlpha;
    bool mWantLuminance;
};

std::vector<const Config *> ConfigSet::filter(const AttributeMap &attributeMap) const
{
    std::vector<const Config *> result;
    result.reserve(mConfigs.size());

    for (auto configIter = mConfigs.begin(); configIter != mConfigs.end(); configIter++)
    {
        const Config &config = configIter->second;
        bool match           = true;

        for (auto attribIter = attributeMap.begin(); attribIter != attributeMap.end(); attribIter++)
        {
            EGLAttrib attributeKey   = attribIter->first;
            EGLAttrib attributeValue = attribIter->second;

            if (attributeValue == EGL_DONT_CARE)
            {
                continue;
            }

            switch (attributeKey)
            {
                case EGL_BUFFER_SIZE:
                    match = config.bufferSize >= attributeValue;
                    break;
                case EGL_ALPHA_SIZE:
                    match = config.alphaSize >= attributeValue;
                    break;
                case EGL_BLUE_SIZE:
                    match = config.blueSize >= attributeValue;
                    break;
                case EGL_GREEN_SIZE:
                    match = config.greenSize >= attributeValue;
                    break;
                case EGL_RED_SIZE:
                    match = config.redSize >= attributeValue;
                    break;
                case EGL_DEPTH_SIZE:
                    match = config.depthSize >= attributeValue;
                    break;
                case EGL_STENCIL_SIZE:
                    match = config.stencilSize >= attributeValue;
                    break;
                case EGL_CONFIG_CAVEAT:
                    match = config.configCaveat == static_cast<EGLenum>(attributeValue);
                    break;
                case EGL_CONFIG_ID:
                    match = config.configID == attributeValue;
                    break;
                case EGL_LEVEL:
                    match = config.level == attributeValue;
                    break;
                case EGL_NATIVE_RENDERABLE:
                    match = config.nativeRenderable == static_cast<EGLBoolean>(attributeValue);
                    break;
                case EGL_NATIVE_VISUAL_TYPE:
                    match = config.nativeVisualType == attributeValue;
                    break;
                case EGL_SAMPLES:
                    match = config.samples >= attributeValue;
                    break;
                case EGL_SAMPLE_BUFFERS:
                    match = config.sampleBuffers >= attributeValue;
                    break;
                case EGL_SURFACE_TYPE:
                    match = (config.surfaceType & attributeValue) == attributeValue;
                    break;
                case EGL_TRANSPARENT_TYPE:
                    match = config.transparentType == static_cast<EGLenum>(attributeValue);
                    break;
                case EGL_TRANSPARENT_BLUE_VALUE:
                    if (attributeMap.get(EGL_TRANSPARENT_TYPE, EGL_NONE) != EGL_NONE)
                    {
                        match = config.transparentBlueValue == attributeValue;
                    }
                    break;
                case EGL_TRANSPARENT_GREEN_VALUE:
                    if (attributeMap.get(EGL_TRANSPARENT_TYPE, EGL_NONE) != EGL_NONE)
                    {
                        match = config.transparentGreenValue == attributeValue;
                    }
                    break;
                case EGL_TRANSPARENT_RED_VALUE:
                    if (attributeMap.get(EGL_TRANSPARENT_TYPE, EGL_NONE) != EGL_NONE)
                    {
                        match = config.transparentRedValue == attributeValue;
                    }
                    break;
                case EGL_BIND_TO_TEXTURE_RGB:
                    match = config.bindToTextureRGB == static_cast<EGLBoolean>(attributeValue);
                    break;
                case EGL_BIND_TO_TEXTURE_RGBA:
                    match = config.bindToTextureRGBA == static_cast<EGLBoolean>(attributeValue);
                    break;
                case EGL_BIND_TO_TEXTURE_TARGET_ANGLE:
                    match = config.bindToTextureTarget == static_cast<EGLenum>(attributeValue);
                    break;
                case EGL_MIN_SWAP_INTERVAL:
                    match = config.minSwapInterval == attributeValue;
                    break;
                case EGL_MAX_SWAP_INTERVAL:
                    match = config.maxSwapInterval == attributeValue;
                    break;
                case EGL_LUMINANCE_SIZE:
                    match = config.luminanceSize >= attributeValue;
                    break;
                case EGL_ALPHA_MASK_SIZE:
                    match = config.alphaMaskSize >= attributeValue;
                    break;
                case EGL_COLOR_BUFFER_TYPE:
                    match = config.colorBufferType == static_cast<EGLenum>(attributeValue);
                    break;
                case EGL_RENDERABLE_TYPE:
                    match = (config.renderableType & attributeValue) == attributeValue;
                    break;
                case EGL_MATCH_NATIVE_PIXMAP:
                    match = false;
                    UNIMPLEMENTED();
                    break;
                case EGL_CONFORMANT:
                    match = (config.conformant & attributeValue) == attributeValue;
                    break;
                case EGL_MAX_PBUFFER_WIDTH:
                    match = config.maxPBufferWidth >= attributeValue;
                    break;
                case EGL_MAX_PBUFFER_HEIGHT:
                    match = config.maxPBufferHeight >= attributeValue;
                    break;
                case EGL_MAX_PBUFFER_PIXELS:
                    match = config.maxPBufferPixels >= attributeValue;
                    break;
                case EGL_OPTIMAL_SURFACE_ORIENTATION_ANGLE:
                    match = config.optimalOrientation == attributeValue;
                    break;
                case EGL_COLOR_COMPONENT_TYPE_EXT:
                    match = config.colorComponentType == static_cast<EGLenum>(attributeValue);
                    break;
                case EGL_RECORDABLE_ANDROID:
                    match = config.recordable == static_cast<EGLBoolean>(attributeValue);
                    break;
                case EGL_FRAMEBUFFER_TARGET_ANDROID:
                    match = config.framebufferTarget == static_cast<EGLBoolean>(attributeValue);
                    break;
                case EGL_Y_INVERTED_NOK:
                    match = config.yInverted == static_cast<EGLBoolean>(attributeValue);
                    break;
                case EGL_MATCH_FORMAT_KHR:
                    if (attributeValue == EGL_NONE)
                    {
                        match = (config.surfaceType & EGL_LOCK_SURFACE_BIT_KHR) == 0;
                    }
                    else
                    {
                        match = config.matchFormat == attributeValue;
                    }
                    break;
                default:
                    UNREACHABLE();
            }

            if (!match)
            {
                break;
            }
        }

        if (match)
        {
            result.push_back(&config);
        }
    }

    // Sort the result
    std::sort(result.begin(), result.end(), ConfigSorter(attributeMap));

    return result;
}

ConfigSet::ConfigMap::iterator ConfigSet::begin()
{
    return mConfigs.begin();
}

ConfigSet::ConfigMap::iterator ConfigSet::end()
{
    return mConfigs.end();
}
}  // namespace egl
