//
// Copyright (c) 2002-2013 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 "angle_gl.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/VariableInfo.h"
#include "compiler/translator/util.h"
#include "common/utilities.h"

namespace sh
{

namespace
{

BlockLayoutType GetBlockLayoutType(TLayoutBlockStorage blockStorage)
{
    switch (blockStorage)
    {
        case EbsPacked:
            return BLOCKLAYOUT_PACKED;
        case EbsShared:
            return BLOCKLAYOUT_SHARED;
        case EbsStd140:
            return BLOCKLAYOUT_STANDARD;
        default:
            UNREACHABLE();
            return BLOCKLAYOUT_SHARED;
    }
}

void ExpandUserDefinedVariable(const ShaderVariable &variable,
                               const std::string &name,
                               const std::string &mappedName,
                               bool markStaticUse,
                               std::vector<ShaderVariable> *expanded)
{
    ASSERT(variable.isStruct());

    const std::vector<ShaderVariable> &fields = variable.fields;

    for (size_t fieldIndex = 0; fieldIndex < fields.size(); fieldIndex++)
    {
        const ShaderVariable &field = fields[fieldIndex];
        ExpandVariable(field, name + "." + field.name, mappedName + "." + field.mappedName,
                       markStaticUse, expanded);
    }
}

template <class VarT>
VarT *FindVariable(const TString &name, std::vector<VarT> *infoList)
{
    // TODO(zmo): optimize this function.
    for (size_t ii = 0; ii < infoList->size(); ++ii)
    {
        if ((*infoList)[ii].name.c_str() == name)
            return &((*infoList)[ii]);
    }

    return nullptr;
}
}

CollectVariables::CollectVariables(std::vector<sh::Attribute> *attribs,
                                   std::vector<sh::OutputVariable> *outputVariables,
                                   std::vector<sh::Uniform> *uniforms,
                                   std::vector<sh::Varying> *varyings,
                                   std::vector<sh::InterfaceBlock> *interfaceBlocks,
                                   ShHashFunction64 hashFunction,
                                   const TSymbolTable &symbolTable,
                                   const TExtensionBehavior &extensionBehavior)
    : TIntermTraverser(true, false, false),
      mAttribs(attribs),
      mOutputVariables(outputVariables),
      mUniforms(uniforms),
      mVaryings(varyings),
      mInterfaceBlocks(interfaceBlocks),
      mDepthRangeAdded(false),
      mPointCoordAdded(false),
      mFrontFacingAdded(false),
      mFragCoordAdded(false),
      mInstanceIDAdded(false),
      mVertexIDAdded(false),
      mPositionAdded(false),
      mPointSizeAdded(false),
      mLastFragDataAdded(false),
      mFragColorAdded(false),
      mFragDataAdded(false),
      mFragDepthEXTAdded(false),
      mFragDepthAdded(false),
      mSecondaryFragColorEXTAdded(false),
      mSecondaryFragDataEXTAdded(false),
      mHashFunction(hashFunction),
      mSymbolTable(symbolTable),
      mExtensionBehavior(extensionBehavior)
{
}

// We want to check whether a uniform/varying is statically used
// because we only count the used ones in packing computing.
// Also, gl_FragCoord, gl_PointCoord, and gl_FrontFacing count
// toward varying counting if they are statically used in a fragment
// shader.
void CollectVariables::visitSymbol(TIntermSymbol *symbol)
{
    ASSERT(symbol != nullptr);
    ShaderVariable *var       = nullptr;
    const TString &symbolName = symbol->getSymbol();

    if (IsVarying(symbol->getQualifier()))
    {
        var = FindVariable(symbolName, mVaryings);
    }
    else if (symbol->getType().getBasicType() == EbtInterfaceBlock)
    {
        UNREACHABLE();
    }
    else if (symbolName == "gl_DepthRange")
    {
        ASSERT(symbol->getQualifier() == EvqUniform);

        if (!mDepthRangeAdded)
        {
            Uniform info;
            const char kName[] = "gl_DepthRange";
            info.name          = kName;
            info.mappedName    = kName;
            info.type          = GL_STRUCT_ANGLEX;
            info.arraySize     = 0;
            info.precision     = GL_NONE;
            info.staticUse     = true;

            ShaderVariable nearInfo;
            const char kNearName[] = "near";
            nearInfo.name          = kNearName;
            nearInfo.mappedName    = kNearName;
            nearInfo.type          = GL_FLOAT;
            nearInfo.arraySize     = 0;
            nearInfo.precision     = GL_HIGH_FLOAT;
            nearInfo.staticUse     = true;

            ShaderVariable farInfo;
            const char kFarName[] = "far";
            farInfo.name          = kFarName;
            farInfo.mappedName    = kFarName;
            farInfo.type          = GL_FLOAT;
            farInfo.arraySize     = 0;
            farInfo.precision     = GL_HIGH_FLOAT;
            farInfo.staticUse     = true;

            ShaderVariable diffInfo;
            const char kDiffName[] = "diff";
            diffInfo.name          = kDiffName;
            diffInfo.mappedName    = kDiffName;
            diffInfo.type          = GL_FLOAT;
            diffInfo.arraySize     = 0;
            diffInfo.precision     = GL_HIGH_FLOAT;
            diffInfo.staticUse     = true;

            info.fields.push_back(nearInfo);
            info.fields.push_back(farInfo);
            info.fields.push_back(diffInfo);

            mUniforms->push_back(info);
            mDepthRangeAdded = true;
        }
    }
    else
    {
        switch (symbol->getQualifier())
        {
            case EvqAttribute:
            case EvqVertexIn:
                var = FindVariable(symbolName, mAttribs);
                break;
            case EvqFragmentOut:
                var = FindVariable(symbolName, mOutputVariables);
                break;
            case EvqUniform:
            {
                const TInterfaceBlock *interfaceBlock = symbol->getType().getInterfaceBlock();
                if (interfaceBlock)
                {
                    InterfaceBlock *namedBlock =
                        FindVariable(interfaceBlock->name(), mInterfaceBlocks);
                    ASSERT(namedBlock);
                    var = FindVariable(symbolName, &namedBlock->fields);

                    // Set static use on the parent interface block here
                    namedBlock->staticUse = true;
                }
                else
                {
                    var = FindVariable(symbolName, mUniforms);
                }

                // It's an internal error to reference an undefined user uniform
                ASSERT(symbolName.compare(0, 3, "gl_") != 0 || var);
            }
            break;
            case EvqFragCoord:
                if (!mFragCoordAdded)
                {
                    Varying info;
                    const char kName[] = "gl_FragCoord";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_FLOAT_VEC4;
                    info.arraySize     = 0;
                    info.precision     = GL_MEDIUM_FLOAT;  // Defined by spec.
                    info.staticUse     = true;
                    info.isInvariant   = mSymbolTable.isVaryingInvariant(kName);
                    mVaryings->push_back(info);
                    mFragCoordAdded = true;
                }
                return;
            case EvqFrontFacing:
                if (!mFrontFacingAdded)
                {
                    Varying info;
                    const char kName[] = "gl_FrontFacing";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_BOOL;
                    info.arraySize     = 0;
                    info.precision     = GL_NONE;
                    info.staticUse     = true;
                    info.isInvariant   = mSymbolTable.isVaryingInvariant(kName);
                    mVaryings->push_back(info);
                    mFrontFacingAdded = true;
                }
                return;
            case EvqPointCoord:
                if (!mPointCoordAdded)
                {
                    Varying info;
                    const char kName[] = "gl_PointCoord";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_FLOAT_VEC2;
                    info.arraySize     = 0;
                    info.precision     = GL_MEDIUM_FLOAT;  // Defined by spec.
                    info.staticUse     = true;
                    info.isInvariant   = mSymbolTable.isVaryingInvariant(kName);
                    mVaryings->push_back(info);
                    mPointCoordAdded = true;
                }
                return;
            case EvqInstanceID:
                if (!mInstanceIDAdded)
                {
                    Attribute info;
                    const char kName[] = "gl_InstanceID";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_INT;
                    info.arraySize     = 0;
                    info.precision     = GL_HIGH_INT;  // Defined by spec.
                    info.staticUse     = true;
                    info.location      = -1;
                    mAttribs->push_back(info);
                    mInstanceIDAdded = true;
                }
                return;
            case EvqVertexID:
                if (!mVertexIDAdded)
                {
                    Attribute info;
                    const char kName[] = "gl_VertexID";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_INT;
                    info.arraySize     = 0;
                    info.precision     = GL_HIGH_INT;  // Defined by spec.
                    info.staticUse     = true;
                    info.location      = -1;
                    mAttribs->push_back(info);
                    mVertexIDAdded = true;
                }
                return;
            case EvqPosition:
                if (!mPositionAdded)
                {
                    Varying info;
                    const char kName[] = "gl_Position";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_FLOAT_VEC4;
                    info.arraySize     = 0;
                    info.precision     = GL_HIGH_FLOAT;  // Defined by spec.
                    info.staticUse     = true;
                    info.isInvariant   = mSymbolTable.isVaryingInvariant(kName);
                    mVaryings->push_back(info);
                    mPositionAdded = true;
                }
                return;
            case EvqPointSize:
                if (!mPointSizeAdded)
                {
                    Varying info;
                    const char kName[] = "gl_PointSize";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_FLOAT;
                    info.arraySize     = 0;
                    info.precision     = GL_MEDIUM_FLOAT;  // Defined by spec.
                    info.staticUse     = true;
                    info.isInvariant   = mSymbolTable.isVaryingInvariant(kName);
                    mVaryings->push_back(info);
                    mPointSizeAdded = true;
                }
                return;
            case EvqLastFragData:
                if (!mLastFragDataAdded)
                {
                    Varying info;
                    const char kName[] = "gl_LastFragData";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_FLOAT_VEC4;
                    info.arraySize     = static_cast<const TVariable *>(
                                         mSymbolTable.findBuiltIn("gl_MaxDrawBuffers", 100))
                                         ->getConstPointer()
                                         ->getIConst();
                    info.precision   = GL_MEDIUM_FLOAT;  // Defined by spec.
                    info.staticUse   = true;
                    info.isInvariant = mSymbolTable.isVaryingInvariant(kName);
                    mVaryings->push_back(info);
                    mLastFragDataAdded = true;
                }
                return;
            case EvqFragColor:
                if (!mFragColorAdded)
                {
                    OutputVariable info;
                    const char kName[] = "gl_FragColor";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_FLOAT_VEC4;
                    info.arraySize     = 0;
                    info.precision     = GL_MEDIUM_FLOAT;  // Defined by spec.
                    info.staticUse     = true;
                    mOutputVariables->push_back(info);
                    mFragColorAdded = true;
                }
                return;
            case EvqFragData:
                if (!mFragDataAdded)
                {
                    OutputVariable info;
                    const char kName[] = "gl_FragData";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_FLOAT_VEC4;
                    if (::IsExtensionEnabled(mExtensionBehavior, "GL_EXT_draw_buffers"))
                    {
                        info.arraySize = static_cast<const TVariable *>(
                                             mSymbolTable.findBuiltIn("gl_MaxDrawBuffers", 100))
                                             ->getConstPointer()
                                             ->getIConst();
                    }
                    else
                    {
                        info.arraySize = 1;
                    }
                    info.precision = GL_MEDIUM_FLOAT;  // Defined by spec.
                    info.staticUse = true;
                    mOutputVariables->push_back(info);
                    mFragDataAdded = true;
                }
                return;
            case EvqFragDepthEXT:
                if (!mFragDepthEXTAdded)
                {
                    OutputVariable info;
                    const char kName[] = "gl_FragDepthEXT";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_FLOAT;
                    info.arraySize     = 0;
                    info.precision =
                        GLVariablePrecision(static_cast<const TVariable *>(
                                                mSymbolTable.findBuiltIn("gl_FragDepthEXT", 100))
                                                ->getType());
                    info.staticUse = true;
                    mOutputVariables->push_back(info);
                    mFragDepthEXTAdded = true;
                }
                return;
            case EvqFragDepth:
                if (!mFragDepthAdded)
                {
                    OutputVariable info;
                    const char kName[] = "gl_FragDepth";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_FLOAT;
                    info.arraySize     = 0;
                    info.precision     = GL_HIGH_FLOAT;
                    info.staticUse     = true;
                    mOutputVariables->push_back(info);
                    mFragDepthAdded = true;
                }
                return;
            case EvqSecondaryFragColorEXT:
                if (!mSecondaryFragColorEXTAdded)
                {
                    OutputVariable info;
                    const char kName[] = "gl_SecondaryFragColorEXT";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_FLOAT_VEC4;
                    info.arraySize     = 0;
                    info.precision     = GL_MEDIUM_FLOAT;  // Defined by spec.
                    info.staticUse     = true;
                    mOutputVariables->push_back(info);
                    mSecondaryFragColorEXTAdded = true;
                }
                return;
            case EvqSecondaryFragDataEXT:
                if (!mSecondaryFragDataEXTAdded)
                {
                    OutputVariable info;
                    const char kName[] = "gl_SecondaryFragDataEXT";
                    info.name          = kName;
                    info.mappedName    = kName;
                    info.type          = GL_FLOAT_VEC4;

                    const TVariable *maxDualSourceDrawBuffersVar = static_cast<const TVariable *>(
                        mSymbolTable.findBuiltIn("gl_MaxDualSourceDrawBuffersEXT", 100));
                    info.arraySize = maxDualSourceDrawBuffersVar->getConstPointer()->getIConst();
                    info.precision = GL_MEDIUM_FLOAT;  // Defined by spec.
                    info.staticUse = true;
                    mOutputVariables->push_back(info);
                    mSecondaryFragDataEXTAdded = true;
                }
                return;
            default:
                break;
        }
    }
    if (var)
    {
        var->staticUse = true;
    }
}

void CollectVariables::setCommonVariableProperties(const TType &type,
                                                   const TString &name,
                                                   ShaderVariable *variableOut) const
{
    ASSERT(variableOut);

    const TStructure *structure = type.getStruct();

    if (!structure)
    {
        variableOut->type      = GLVariableType(type);
        variableOut->precision = GLVariablePrecision(type);
    }
    else
    {
        // Note: this enum value is not exposed outside ANGLE
        variableOut->type       = GL_STRUCT_ANGLEX;
        variableOut->structName = structure->name().c_str();

        const TFieldList &fields = structure->fields();

        for (TField *field : fields)
        {
            // Regardless of the variable type (uniform, in/out etc.) its fields are always plain
            // ShaderVariable objects.
            ShaderVariable fieldVariable;
            setCommonVariableProperties(*field->type(), field->name(), &fieldVariable);
            variableOut->fields.push_back(fieldVariable);
        }
    }
    variableOut->name       = name.c_str();
    variableOut->mappedName = TIntermTraverser::hash(name, mHashFunction).c_str();
    variableOut->arraySize  = type.getArraySize();
}

Attribute CollectVariables::recordAttribute(const TIntermSymbol &variable) const
{
    const TType &type = variable.getType();
    ASSERT(!type.getStruct());

    Attribute attribute;
    setCommonVariableProperties(type, variable.getSymbol(), &attribute);

    attribute.location = type.getLayoutQualifier().location;
    return attribute;
}

OutputVariable CollectVariables::recordOutputVariable(const TIntermSymbol &variable) const
{
    const TType &type = variable.getType();
    ASSERT(!type.getStruct());

    OutputVariable outputVariable;
    setCommonVariableProperties(type, variable.getSymbol(), &outputVariable);

    outputVariable.location = type.getLayoutQualifier().location;
    return outputVariable;
}

Varying CollectVariables::recordVarying(const TIntermSymbol &variable) const
{
    const TType &type = variable.getType();

    Varying varying;
    setCommonVariableProperties(type, variable.getSymbol(), &varying);

    switch (type.getQualifier())
    {
        case EvqVaryingIn:
        case EvqVaryingOut:
        case EvqVertexOut:
        case EvqSmoothOut:
        case EvqFlatOut:
        case EvqCentroidOut:
            if (mSymbolTable.isVaryingInvariant(std::string(variable.getSymbol().c_str())) ||
                type.isInvariant())
            {
                varying.isInvariant = true;
            }
            break;
        default:
            break;
    }

    varying.interpolation = GetInterpolationType(type.getQualifier());
    return varying;
}

InterfaceBlock CollectVariables::recordInterfaceBlock(const TIntermSymbol &variable) const
{
    const TInterfaceBlock *blockType = variable.getType().getInterfaceBlock();
    ASSERT(blockType);

    InterfaceBlock interfaceBlock;
    interfaceBlock.name = blockType->name().c_str();
    interfaceBlock.mappedName =
        TIntermTraverser::hash(blockType->name().c_str(), mHashFunction).c_str();
    interfaceBlock.instanceName =
        (blockType->hasInstanceName() ? blockType->instanceName().c_str() : "");
    interfaceBlock.arraySize        = variable.getArraySize();
    interfaceBlock.isRowMajorLayout = (blockType->matrixPacking() == EmpRowMajor);
    interfaceBlock.binding          = blockType->blockBinding();
    interfaceBlock.layout           = GetBlockLayoutType(blockType->blockStorage());

    // Gather field information
    for (const TField *field : blockType->fields())
    {
        const TType &fieldType = *field->type();

        InterfaceBlockField fieldVariable;
        setCommonVariableProperties(fieldType, field->name(), &fieldVariable);
        fieldVariable.isRowMajorLayout =
            (fieldType.getLayoutQualifier().matrixPacking == EmpRowMajor);
        interfaceBlock.fields.push_back(fieldVariable);
    }
    return interfaceBlock;
}

Uniform CollectVariables::recordUniform(const TIntermSymbol &variable) const
{
    Uniform uniform;
    setCommonVariableProperties(variable.getType(), variable.getSymbol(), &uniform);
    uniform.binding = variable.getType().getLayoutQualifier().binding;
    uniform.location = variable.getType().getLayoutQualifier().location;
    return uniform;
}

bool CollectVariables::visitDeclaration(Visit, TIntermDeclaration *node)
{
    const TIntermSequence &sequence = *(node->getSequence());
    ASSERT(!sequence.empty());

    const TIntermTyped &typedNode = *(sequence.front()->getAsTyped());
    TQualifier qualifier          = typedNode.getQualifier();

    bool isShaderVariable = qualifier == EvqAttribute || qualifier == EvqVertexIn ||
                            qualifier == EvqFragmentOut || qualifier == EvqUniform ||
                            IsVarying(qualifier);

    if (typedNode.getBasicType() != EbtInterfaceBlock && !isShaderVariable)
    {
        return true;
    }

    for (TIntermNode *variableNode : sequence)
    {
        // The only case in which the sequence will not contain a TIntermSymbol node is
        // initialization. It will contain a TInterBinary node in that case. Since attributes,
        // uniforms, varyings, outputs and interface blocks cannot be initialized in a shader, we
        // must have only TIntermSymbol nodes in the sequence in the cases we are interested in.
        const TIntermSymbol &variable = *variableNode->getAsSymbolNode();
        if (typedNode.getBasicType() == EbtInterfaceBlock)
        {
            mInterfaceBlocks->push_back(recordInterfaceBlock(variable));
        }
        else
        {
            switch (qualifier)
            {
                case EvqAttribute:
                case EvqVertexIn:
                    mAttribs->push_back(recordAttribute(variable));
                    break;
                case EvqFragmentOut:
                    mOutputVariables->push_back(recordOutputVariable(variable));
                    break;
                case EvqUniform:
                    mUniforms->push_back(recordUniform(variable));
                    break;
                default:
                    mVaryings->push_back(recordVarying(variable));
                    break;
            }
        }
    }

    // None of the recorded variables can have initializers, so we don't need to traverse the
    // declarators.
    return false;
}

bool CollectVariables::visitBinary(Visit, TIntermBinary *binaryNode)
{
    if (binaryNode->getOp() == EOpIndexDirectInterfaceBlock)
    {
        // NOTE: we do not determine static use for individual blocks of an array
        TIntermTyped *blockNode = binaryNode->getLeft()->getAsTyped();
        ASSERT(blockNode);

        TIntermConstantUnion *constantUnion = binaryNode->getRight()->getAsConstantUnion();
        ASSERT(constantUnion);

        const TInterfaceBlock *interfaceBlock = blockNode->getType().getInterfaceBlock();
        InterfaceBlock *namedBlock = FindVariable(interfaceBlock->name(), mInterfaceBlocks);
        ASSERT(namedBlock);
        namedBlock->staticUse = true;

        unsigned int fieldIndex = constantUnion->getUConst(0);
        ASSERT(fieldIndex < namedBlock->fields.size());
        namedBlock->fields[fieldIndex].staticUse = true;
        return false;
    }

    return true;
}

void ExpandVariable(const ShaderVariable &variable,
                    const std::string &name,
                    const std::string &mappedName,
                    bool markStaticUse,
                    std::vector<ShaderVariable> *expanded)
{
    if (variable.isStruct())
    {
        if (variable.isArray())
        {
            for (unsigned int elementIndex = 0; elementIndex < variable.elementCount();
                 elementIndex++)
            {
                std::string lname       = name + ::ArrayString(elementIndex);
                std::string lmappedName = mappedName + ::ArrayString(elementIndex);
                ExpandUserDefinedVariable(variable, lname, lmappedName, markStaticUse, expanded);
            }
        }
        else
        {
            ExpandUserDefinedVariable(variable, name, mappedName, markStaticUse, expanded);
        }
    }
    else
    {
        ShaderVariable expandedVar = variable;

        expandedVar.name       = name;
        expandedVar.mappedName = mappedName;

        // Mark all expanded fields as used if the parent is used
        if (markStaticUse)
        {
            expandedVar.staticUse = true;
        }

        if (expandedVar.isArray())
        {
            expandedVar.name += "[0]";
            expandedVar.mappedName += "[0]";
        }

        expanded->push_back(expandedVar);
    }
}

void ExpandUniforms(const std::vector<Uniform> &compact, std::vector<ShaderVariable> *expanded)
{
    for (size_t variableIndex = 0; variableIndex < compact.size(); variableIndex++)
    {
        const ShaderVariable &variable = compact[variableIndex];
        ExpandVariable(variable, variable.name, variable.mappedName, variable.staticUse, expanded);
    }
}
}
