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

// ImageIndex.cpp: Implementation for ImageIndex methods.

#include "libANGLE/ImageIndex.h"

#include "common/utilities.h"
#include "libANGLE/Constants.h"
#include "libANGLE/angletypes.h"

#include <tuple>

namespace gl
{
namespace
{
GLint TextureTargetToLayer(TextureTarget target)
{
    switch (target)
    {
        case TextureTarget::CubeMapPositiveX:
            return 0;
        case TextureTarget::CubeMapNegativeX:
            return 1;
        case TextureTarget::CubeMapPositiveY:
            return 2;
        case TextureTarget::CubeMapNegativeY:
            return 3;
        case TextureTarget::CubeMapPositiveZ:
            return 4;
        case TextureTarget::CubeMapNegativeZ:
            return 5;
        case TextureTarget::External:
            return ImageIndex::kEntireLevel;
        case TextureTarget::Rectangle:
            return ImageIndex::kEntireLevel;
        case TextureTarget::_2D:
            return ImageIndex::kEntireLevel;
        case TextureTarget::VideoImage:
            return ImageIndex::kEntireLevel;
        case TextureTarget::_2DArray:
            return ImageIndex::kEntireLevel;
        case TextureTarget::_2DMultisample:
            return ImageIndex::kEntireLevel;
        case TextureTarget::_2DMultisampleArray:
            return ImageIndex::kEntireLevel;
        case TextureTarget::_3D:
            return ImageIndex::kEntireLevel;
        case TextureTarget::CubeMapArray:
            return ImageIndex::kEntireLevel;
        default:
            UNREACHABLE();
            return 0;
    }
}

bool IsArrayTarget(TextureTarget target)
{
    switch (target)
    {
        case TextureTarget::_2DArray:
        case TextureTarget::_2DMultisampleArray:
            return true;
        default:
            return false;
    }
}
}  // anonymous namespace

TextureTarget TextureTypeToTarget(TextureType type, GLint layerIndex)
{
    if (type == TextureType::CubeMap)
    {
        // As GL_TEXTURE_CUBE_MAP cannot be a texture target in texImage*D APIs, so we don't allow
        // an entire cube map to have a texture target.
        ASSERT(layerIndex != ImageIndex::kEntireLevel);
        return CubeFaceIndexToTextureTarget(layerIndex);
    }
    else
    {
        return NonCubeTextureTypeToTarget(type);
    }
}

ImageIndex::ImageIndex()
    : mType(TextureType::InvalidEnum), mLevelIndex(0), mLayerIndex(0), mLayerCount(kEntireLevel)
{}

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

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

bool ImageIndex::hasLayer() const
{
    return mLayerIndex != kEntireLevel;
}

bool ImageIndex::isLayered() const
{
    switch (mType)
    {
        case TextureType::_2DArray:
        case TextureType::_2DMultisampleArray:
        case TextureType::CubeMap:
        case TextureType::_3D:
        case TextureType::CubeMapArray:
            return mLayerIndex == kEntireLevel;
        default:
            return false;
    }
}

bool ImageIndex::has3DLayer() const
{
    // It's quicker to check != CubeMap than calling usesTex3D, which checks multiple types. This
    // ASSERT validates the check gives the same result.
    ASSERT(!hasLayer() || ((mType != TextureType::CubeMap) == usesTex3D()));
    return (hasLayer() && mType != TextureType::CubeMap);
}

bool ImageIndex::usesTex3D() const
{
    return mType == TextureType::_3D || mType == TextureType::_2DArray ||
           mType == TextureType::_2DMultisampleArray || mType == TextureType::CubeMapArray;
}

TextureTarget ImageIndex::getTarget() const
{
    return TextureTypeToTarget(mType, mLayerIndex);
}

gl::TextureTarget ImageIndex::getTargetOrFirstCubeFace() const
{
    if (isEntireLevelCubeMap())
    {
        return gl::kCubeMapTextureTargetMin;
    }
    else
    {
        return getTarget();
    }
}

GLint ImageIndex::cubeMapFaceIndex() const
{
    ASSERT(mType == TextureType::CubeMap);
    ASSERT(mLayerIndex == kEntireLevel || mLayerIndex < static_cast<GLint>(kCubeFaceCount));
    return mLayerIndex;
}

bool ImageIndex::valid() const
{
    return mType != TextureType::InvalidEnum;
}

bool ImageIndex::isEntireLevelCubeMap() const
{
    return mType == TextureType::CubeMap && mLayerIndex == ImageIndex::kEntireLevel;
}

ImageIndex ImageIndex::Make2D(GLint levelIndex)
{
    return ImageIndex(TextureType::_2D, levelIndex, kEntireLevel, 1);
}

ImageIndex ImageIndex::MakeRectangle(GLint levelIndex)
{
    return ImageIndex(TextureType::Rectangle, levelIndex, kEntireLevel, 1);
}

ImageIndex ImageIndex::MakeCubeMapFace(TextureTarget target, GLint levelIndex)
{
    ASSERT(IsCubeMapFaceTarget(target));
    return ImageIndex(TextureType::CubeMap, levelIndex, TextureTargetToLayer(target), 1);
}

ImageIndex ImageIndex::Make2DArray(GLint levelIndex, GLint layerIndex)
{
    return ImageIndex(TextureType::_2DArray, levelIndex, layerIndex, 1);
}

ImageIndex ImageIndex::Make2DArrayRange(GLint levelIndex, GLint layerIndex, GLint numLayers)
{
    return ImageIndex(TextureType::_2DArray, levelIndex, layerIndex, numLayers);
}

ImageIndex ImageIndex::Make3D(GLint levelIndex, GLint layerIndex)
{
    return ImageIndex(TextureType::_3D, levelIndex, layerIndex, 1);
}

ImageIndex ImageIndex::MakeFromTarget(TextureTarget target, GLint levelIndex, GLint depth)
{
    return ImageIndex(TextureTargetToType(target), levelIndex, TextureTargetToLayer(target),
                      IsArrayTarget(target) ? depth : 1);
}

ImageIndex ImageIndex::MakeFromType(TextureType type,
                                    GLint levelIndex,
                                    GLint layerIndex,
                                    GLint layerCount)
{
    GLint overrideLayerCount =
        (type == TextureType::CubeMap && layerIndex == kEntireLevel ? kCubeFaceCount : layerCount);
    return ImageIndex(type, levelIndex, layerIndex, overrideLayerCount);
}

ImageIndex ImageIndex::Make2DMultisample()
{
    return ImageIndex(TextureType::_2DMultisample, 0, kEntireLevel, 1);
}

ImageIndex ImageIndex::Make2DMultisampleArray(GLint layerIndex)
{
    return ImageIndex(TextureType::_2DMultisampleArray, 0, layerIndex, 1);
}

ImageIndex ImageIndex::Make2DMultisampleArrayRange(GLint layerIndex, GLint numLayers)
{
    return ImageIndex(TextureType::_2DMultisampleArray, 0, layerIndex, numLayers);
}

bool ImageIndex::operator<(const ImageIndex &b) const
{
    return std::tie(mType, mLevelIndex, mLayerIndex, mLayerCount) <
           std::tie(b.mType, b.mLevelIndex, b.mLayerIndex, b.mLayerCount);
}

bool ImageIndex::operator==(const ImageIndex &b) const
{
    return std::tie(mType, mLevelIndex, mLayerIndex, mLayerCount) ==
           std::tie(b.mType, b.mLevelIndex, b.mLayerIndex, b.mLayerCount);
}

bool ImageIndex::operator!=(const ImageIndex &b) const
{
    return !(*this == b);
}

ImageIndex::ImageIndex(TextureType type, GLint levelIndex, GLint layerIndex, GLint layerCount)
    : mType(type), mLevelIndex(levelIndex), mLayerIndex(layerIndex), mLayerCount(layerCount)
{}

ImageIndexIterator ImageIndex::getLayerIterator(GLint layerCount) const
{
    ASSERT(mType != TextureType::_2D && !hasLayer());
    return ImageIndexIterator::MakeGeneric(mType, mLevelIndex, mLevelIndex + 1, 0, layerCount);
}

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

ImageIndexIterator ImageIndexIterator::Make2D(GLint minMip, GLint maxMip)
{
    return ImageIndexIterator(TextureType::_2D, Range<GLint>(minMip, maxMip),
                              Range<GLint>(ImageIndex::kEntireLevel, ImageIndex::kEntireLevel),
                              nullptr);
}

ImageIndexIterator ImageIndexIterator::MakeRectangle(GLint minMip, GLint maxMip)
{
    return ImageIndexIterator(TextureType::Rectangle, Range<GLint>(minMip, maxMip),
                              Range<GLint>(ImageIndex::kEntireLevel, ImageIndex::kEntireLevel),
                              nullptr);
}

ImageIndexIterator ImageIndexIterator::MakeCube(GLint minMip, GLint maxMip)
{
    return ImageIndexIterator(TextureType::CubeMap, Range<GLint>(minMip, maxMip),
                              Range<GLint>(0, 6), nullptr);
}

ImageIndexIterator ImageIndexIterator::Make3D(GLint minMip,
                                              GLint maxMip,
                                              GLint minLayer,
                                              GLint maxLayer)
{
    return ImageIndexIterator(TextureType::_3D, Range<GLint>(minMip, maxMip),
                              Range<GLint>(minLayer, maxLayer), nullptr);
}

ImageIndexIterator ImageIndexIterator::Make2DArray(GLint minMip,
                                                   GLint maxMip,
                                                   const GLsizei *layerCounts)
{
    return ImageIndexIterator(TextureType::_2DArray, Range<GLint>(minMip, maxMip),
                              Range<GLint>(0, IMPLEMENTATION_MAX_2D_ARRAY_TEXTURE_LAYERS),
                              layerCounts);
}

ImageIndexIterator ImageIndexIterator::Make2DMultisample()
{
    return ImageIndexIterator(TextureType::_2DMultisample, Range<GLint>(0, 1),
                              Range<GLint>(ImageIndex::kEntireLevel, ImageIndex::kEntireLevel),
                              nullptr);
}

ImageIndexIterator ImageIndexIterator::Make2DMultisampleArray(const GLsizei *layerCounts)
{
    return ImageIndexIterator(TextureType::_2DMultisampleArray, Range<GLint>(0, 1),
                              Range<GLint>(0, IMPLEMENTATION_MAX_2D_ARRAY_TEXTURE_LAYERS),
                              layerCounts);
}

ImageIndexIterator ImageIndexIterator::MakeGeneric(TextureType type,
                                                   GLint minMip,
                                                   GLint maxMip,
                                                   GLint minLayer,
                                                   GLint maxLayer)
{
    if (type == TextureType::CubeMap)
    {
        return MakeCube(minMip, maxMip);
    }

    return ImageIndexIterator(type, Range<GLint>(minMip, maxMip), Range<GLint>(minLayer, maxLayer),
                              nullptr);
}

ImageIndexIterator::ImageIndexIterator(TextureType type,
                                       const Range<GLint> &mipRange,
                                       const Range<GLint> &layerRange,
                                       const GLsizei *layerCounts)
    : mMipRange(mipRange),
      mLayerRange(layerRange),
      mLayerCounts(layerCounts),
      mCurrentIndex(type, mipRange.low(), layerRange.low(), 1)
{}

GLint ImageIndexIterator::maxLayer() const
{
    if (mLayerCounts)
    {
        ASSERT(mCurrentIndex.hasLayer());
        return (mCurrentIndex.getLevelIndex() < mMipRange.high())
                   ? mLayerCounts[mCurrentIndex.getLevelIndex()]
                   : 0;
    }
    return mLayerRange.high();
}

ImageIndex ImageIndexIterator::next()
{
    ASSERT(hasNext());

    // Make a copy of the current index to return
    ImageIndex previousIndex = mCurrentIndex;

    // Iterate layers in the inner loop for now. We can add switchable
    // layer or mip iteration if we need it.

    if (mCurrentIndex.hasLayer() && mCurrentIndex.getLayerIndex() < maxLayer() - 1)
    {
        mCurrentIndex.mLayerIndex++;
    }
    else if (mCurrentIndex.mLevelIndex < mMipRange.high() - 1)
    {
        mCurrentIndex.mLayerIndex = mLayerRange.low();
        mCurrentIndex.mLevelIndex++;
    }
    else
    {
        mCurrentIndex = ImageIndex();
    }

    return previousIndex;
}

ImageIndex ImageIndexIterator::current() const
{
    return mCurrentIndex;
}

bool ImageIndexIterator::hasNext() const
{
    return mCurrentIndex.valid();
}

}  // namespace gl
