//
// Copyright 2020 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 "compiler/translator/TranslatorMetalDirect/Pipeline.h"
#include "compiler/translator/tree_util/BuiltIn.h"

using namespace sh;

////////////////////////////////////////////////////////////////////////////////

#define VARIANT_NAME(variant, base) (variant == Variant::Modified ? base "Mod" : base)

bool Pipeline::uses(const TVariable &var) const
{
    if (var.symbolType() == SymbolType::Empty)
    {
        return false;
    }

    if (globalInstanceVar)
    {
        return &var == globalInstanceVar;
    }

    const TType &nodeType      = var.getType();
    const TQualifier qualifier = nodeType.getQualifier();

    switch (type)
    {
        case Type::VertexIn:
            switch (qualifier)
            {
                case TQualifier::EvqAttribute:
                case TQualifier::EvqVertexIn:
                    return true;
                default:
                    return false;
            }

        case Type::VertexOut:
            switch (qualifier)
            {
                case TQualifier::EvqVertexOut:
                case TQualifier::EvqPosition:
                case TQualifier::EvqFlatOut:
                case TQualifier::EvqPointSize:
                case TQualifier::EvqSmoothOut:
                case TQualifier::EvqCentroidOut:
                case TQualifier::EvqNoPerspectiveOut:
                case TQualifier::EvqVaryingOut:
                    return true;
                default:
                    return false;
            }

        case Type::FragmentIn:
            switch (qualifier)
            {
                case TQualifier::EvqFragmentIn:
                case TQualifier::EvqFlatIn:
                case TQualifier::EvqSmoothIn:
                case TQualifier::EvqCentroidIn:
                case TQualifier::EvqNoPerspectiveIn:
                case TQualifier::EvqVaryingIn:
                    return true;
                default:
                    return false;
            }

        case Type::FragmentOut:
            switch (qualifier)
            {
                case TQualifier::EvqFragmentOut:
                case TQualifier::EvqFragColor:
                case TQualifier::EvqFragData:
                case TQualifier::EvqFragDepth:
                case TQualifier::EvqSampleMask:
                    return true;
                default:
                    return false;
            }

        case Type::UserUniforms:
            switch (qualifier)
            {
                case TQualifier::EvqUniform:
                    return true;
                default:
                    return false;
            }

        case Type::NonConstantGlobals:
            switch (qualifier)
            {
                case TQualifier::EvqGlobal:
                    return true;
                default:
                    return false;
            }

        case Type::InvocationVertexGlobals:
            switch (qualifier)
            {
                case TQualifier::EvqVertexID:
                    return true;
                default:
                    return false;
            }

        case Type::InvocationFragmentGlobals:
            switch (qualifier)
            {
                case TQualifier::EvqFragCoord:
                case TQualifier::EvqPointCoord:
                case TQualifier::EvqFrontFacing:
                    return true;
                default:
                    return false;
            }

        case Type::UniformBuffer:
            switch (qualifier)
            {
                case TQualifier::EvqBuffer:
                    return true;
                default:
                    return false;
            }
        case Type::AngleUniforms:
            UNREACHABLE();  // globalInstanceVar should be non-null and thus never reach here.
            return false;

        case Type::Texture:
            return IsSampler(nodeType.getBasicType());

        case Type::InstanceId:
            return Name(var) == Name(*BuiltInVariable::gl_InstanceID());
    }
}

Name Pipeline::getStructTypeName(Variant variant) const
{
    const char *name;
    switch (type)
    {
        case Type::VertexIn:
            name = VARIANT_NAME(variant, "VertexIn");
            break;
        case Type::VertexOut:
            name = VARIANT_NAME(variant, "VertexOut");
            break;
        case Type::FragmentIn:
            name = VARIANT_NAME(variant, "FragmentIn");
            break;
        case Type::FragmentOut:
            name = VARIANT_NAME(variant, "FragmentOut");
            break;
        case Type::UserUniforms:
            name = VARIANT_NAME(variant, "UserUniforms");
            break;
        case Type::AngleUniforms:
            name = VARIANT_NAME(variant, "AngleUniforms");
            break;
        case Type::NonConstantGlobals:
            name = VARIANT_NAME(variant, "NonConstGlobals");
            break;
        case Type::InvocationVertexGlobals:
            name = VARIANT_NAME(variant, "InvocationVertexGlobals");
            break;
        case Type::InvocationFragmentGlobals:
            name = VARIANT_NAME(variant, "InvocationFragmentGlobals");
            break;
        case Type::Texture:
            name = VARIANT_NAME(variant, "TextureEnvs");
            break;
        case Type::InstanceId:
            name = VARIANT_NAME(variant, "InstanceId");
            break;
        case Type::UniformBuffer:
            name = VARIANT_NAME(variant, "UniformBuffer");
    }
    return Name(name);
}

Name Pipeline::getStructInstanceName(Variant variant) const
{
    const char *name;
    switch (type)
    {
        case Type::VertexIn:
            name = VARIANT_NAME(variant, "vertexIn");
            break;
        case Type::VertexOut:
            name = VARIANT_NAME(variant, "vertexOut");
            break;
        case Type::FragmentIn:
            name = VARIANT_NAME(variant, "fragmentIn");
            break;
        case Type::FragmentOut:
            name = VARIANT_NAME(variant, "fragmentOut");
            break;
        case Type::UserUniforms:
            name = VARIANT_NAME(variant, "userUniforms");
            break;
        case Type::AngleUniforms:
            name = VARIANT_NAME(variant, "angleUniforms");
            break;
        case Type::NonConstantGlobals:
            name = VARIANT_NAME(variant, "nonConstGlobals");
            break;
        case Type::InvocationVertexGlobals:
            name = VARIANT_NAME(variant, "invocationVertexGlobals");
            break;
        case Type::InvocationFragmentGlobals:
            name = VARIANT_NAME(variant, "invocationFragmentGlobals");
            break;
        case Type::Texture:
            name = VARIANT_NAME(variant, "textureEnvs");
            break;
        case Type::InstanceId:
            name = VARIANT_NAME(variant, "instanceId");
            break;
        case Type::UniformBuffer:
            name = VARIANT_NAME(variant, "uniformBuffer");
    }
    return Name(name);
}

static bool AllowPacking(Pipeline::Type type)
{
    using Type = Pipeline::Type;

    switch (type)
    {
        case Type::UniformBuffer:
        case Type::UserUniforms:
            return true;

        case Type::VertexIn:
        case Type::VertexOut:
        case Type::FragmentIn:
        case Type::FragmentOut:
        case Type::AngleUniforms:
        case Type::NonConstantGlobals:
        case Type::InvocationVertexGlobals:
        case Type::InvocationFragmentGlobals:
        case Type::Texture:
        case Type::InstanceId:
            return false;
    }
}

static bool AllowPadding(Pipeline::Type type)
{
    using Type = Pipeline::Type;

    switch (type)
    {
        case Type::UserUniforms:
        case Type::VertexIn:
        case Type::VertexOut:
        case Type::FragmentIn:
        case Type::FragmentOut:
        case Type::AngleUniforms:
        case Type::NonConstantGlobals:
        case Type::InvocationVertexGlobals:
        case Type::InvocationFragmentGlobals:
        case Type::UniformBuffer:
            return true;

        case Type::Texture:
        case Type::InstanceId:
            return false;
    }
}
enum Compare
{
    LT,
    LTE,
    EQ,
    GTE,
    GT,
};

template <typename T>
static bool CompareBy(Compare op, const T &x, const T &y)
{
    switch (op)
    {
        case LT:
            return x < y;
        case LTE:
            return x <= y;
        case EQ:
            return x == y;
        case GTE:
            return x >= y;
        case GT:
            return x > y;
    }
}

template <TBasicType BT, Compare Cmp, int MatchDim, int NewDim>
static int SaturateVectorOf(const TField &field)
{
    static_assert(NewDim >= MatchDim, "");

    const TType &type = *field.type();
    ASSERT(type.isScalar() || type.isVector());

    const bool cond = type.getBasicType() == BT && !type.isArray() &&
                      CompareBy(Cmp, type.getNominalSize(), MatchDim) &&
                      type.getQualifier() != TQualifier::EvqFragDepth;

    if (cond)
    {
        return NewDim;
    }
    return 0;
}

ModifyStructConfig Pipeline::externalStructModifyConfig() const
{
    using Pred   = ModifyStructConfig::Predicate;
    using SatVec = ModifyStructConfig::SaturateVector;

    ModifyStructConfig config(
        isPipelineOut() ? ConvertType::OriginalToModified : ConvertType::ModifiedToOriginal,
        AllowPacking(type), AllowPadding(type));

    config.externalAddressSpace = externalAddressSpace();

    switch (type)
    {
        case Type::VertexIn:
            config.inlineArray        = Pred::True;
            config.splitMatrixColumns = Pred::True;
            config.inlineStruct       = Pred::True;
            break;

        case Type::VertexOut:
            config.inlineArray        = Pred::True;
            config.splitMatrixColumns = Pred::True;
            config.inlineStruct       = Pred::True;
            break;

        case Type::FragmentIn:
            config.inlineArray        = Pred::True;
            config.splitMatrixColumns = Pred::True;
            config.inlineStruct       = Pred::True;
            break;

        case Type::FragmentOut:
            config.inlineArray            = Pred::True;
            config.splitMatrixColumns     = Pred::True;
            config.inlineStruct           = Pred::True;
            config.saturateScalarOrVector = [](const TField &field) {
                if (field.type()->getQualifier() == TQualifier::EvqSampleMask)
                {
                    return 1;
                }
                if (int s = SaturateVectorOf<TBasicType::EbtInt, LT, 4, 4>(field))
                {
                    return s;
                }
                if (int s = SaturateVectorOf<TBasicType::EbtUInt, LT, 4, 4>(field))
                {
                    return s;
                }
                if (int s = SaturateVectorOf<TBasicType::EbtFloat, LT, 4, 4>(field))
                {
                    return s;
                }
                return 0;
            };
            break;
        case Type::UserUniforms:
            config.promoteBoolToUint            = Pred::True;
            config.saturateMatrixRows           = SatVec::FullySaturate;
            config.saturateScalarOrVectorArrays = SatVec::FullySaturate;
            config.recurseStruct                = Pred::True;
            break;

        case Type::AngleUniforms:
            config.initialBlockStorage = TLayoutBlockStorage::EbsStd430;  // XXX: Correct?
            break;

        case Type::NonConstantGlobals:
            break;
        case Type::UniformBuffer:
            config.promoteBoolToUint            = Pred::True;
            config.saturateMatrixRows           = SatVec::FullySaturate;
            config.saturateScalarOrVectorArrays = SatVec::FullySaturate;
            config.recurseStruct                = Pred::True;
            break;
        case Type::InvocationVertexGlobals:
        case Type::InvocationFragmentGlobals:
        case Type::Texture:
        case Type::InstanceId:
            break;
    }

    return config;
}

bool Pipeline::alwaysRequiresLocalVariableDeclarationInMain() const
{
    switch (type)
    {
        case Type::VertexIn:
        case Type::FragmentIn:
        case Type::UserUniforms:
        case Type::AngleUniforms:
        case Type::UniformBuffer:
            return false;

        case Type::VertexOut:
        case Type::FragmentOut:
        case Type::NonConstantGlobals:
        case Type::InvocationVertexGlobals:
        case Type::InvocationFragmentGlobals:
        case Type::Texture:
        case Type::InstanceId:
            return true;
    }
}

bool Pipeline::isPipelineOut() const
{
    switch (type)
    {
        case Type::VertexIn:
        case Type::FragmentIn:
        case Type::UserUniforms:
        case Type::AngleUniforms:
        case Type::NonConstantGlobals:
        case Type::InvocationVertexGlobals:
        case Type::InvocationFragmentGlobals:
        case Type::Texture:
        case Type::InstanceId:
        case Type::UniformBuffer:
            return false;

        case Type::VertexOut:
        case Type::FragmentOut:
            return true;
    }
}

AddressSpace Pipeline::externalAddressSpace() const
{
    switch (type)
    {
        case Type::VertexIn:
        case Type::FragmentIn:
        case Type::NonConstantGlobals:
        case Type::InvocationVertexGlobals:
        case Type::InvocationFragmentGlobals:
        case Type::Texture:
        case Type::InstanceId:
        case Type::FragmentOut:
        case Type::VertexOut:
            return AddressSpace::Thread;

        case Type::UserUniforms:
        case Type::AngleUniforms:
        case Type::UniformBuffer:
            return AddressSpace::Constant;
    }
}

bool PipelineStructs::matches(const TStructure &s, bool internal, bool external) const
{
    PipelineScoped<TStructure> ps[] = {
        fragmentIn,
        fragmentOut,
        vertexIn,
        vertexOut,
        userUniforms,
        /* angleUniforms, */
        nonConstantGlobals,
        invocationVertexGlobals,
        invocationFragmentGlobals,
        uniformBuffers,
        texture,
        instanceId,
    };
    for (const auto &p : ps)
    {
        if (internal && p.internal == &s)
        {
            return true;
        }
        if (external && p.external == &s)
        {
            return true;
        }
    }
    return false;
}
