//
// Copyright 2018 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.
//
// RewriteStructSamplers: Extract samplers from structs.
//

#include "compiler/translator/tree_ops/vulkan/RewriteStructSamplers.h"

#include "compiler/translator/ImmutableStringBuilder.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/IntermTraverse.h"

namespace sh
{
namespace
{

// Used to map one structure type to another (one where the samplers are removed).
struct StructureData
{
    // The structure this was replaced with.  If nullptr, it means the structure is removed (because
    // it had all samplers).
    const TStructure *modified;
    // Indexed by the field index of original structure, to get the field index of the modified
    // structure.  For example:
    //
    //     struct Original
    //     {
    //         sampler2D s1;
    //         vec4 f1;
    //         sampler2D s2;
    //         sampler2D s3;
    //         vec4 f2;
    //     };
    //
    //     struct Modified
    //     {
    //         vec4 f1;
    //         vec4 f2;
    //     };
    //
    //     fieldMap:
    //         0 -> Invalid
    //         1 -> 0
    //         2 -> Invalid
    //         3 -> Invalid
    //         4 -> 1
    //
    TVector<int> fieldMap;
};

using StructureMap        = angle::HashMap<const TStructure *, StructureData>;
using StructureUniformMap = angle::HashMap<const TVariable *, const TVariable *>;
using ExtractedSamplerMap = angle::HashMap<std::string, const TVariable *>;

TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
    TCompiler *compiler,
    TIntermBinary *node,
    const StructureMap &structureMap,
    const StructureUniformMap &structureUniformMap,
    const ExtractedSamplerMap &extractedSamplers);

TIntermTyped *RewriteExpressionVisitBinaryHelper(TCompiler *compiler,
                                                 TIntermBinary *node,
                                                 const StructureMap &structureMap,
                                                 const StructureUniformMap &structureUniformMap,
                                                 const ExtractedSamplerMap &extractedSamplers)
{
    // Only interested in EOpIndexDirectStruct binary nodes.
    if (node->getOp() != EOpIndexDirectStruct)
    {
        return nullptr;
    }

    const TStructure *structure = node->getLeft()->getType().getStruct();
    ASSERT(structure);

    // If the result of the index is not a sampler and the struct is not replaced, there's nothing
    // to do.
    if (!node->getType().isSampler() && structureMap.find(structure) == structureMap.end())
    {
        return nullptr;
    }

    // Otherwise, replace the whole expression such that:
    //
    // - if sampler, it's indexed with whatever indices the parent structs were indexed with,
    // - otherwise, the chain of field selections is rewritten by modifying the base uniform so all
    //   the intermediate nodes would have the correct type (and therefore fields).
    ASSERT(structureMap.find(structure) != structureMap.end());

    return RewriteModifiedStructFieldSelectionExpression(compiler, node, structureMap,
                                                         structureUniformMap, extractedSamplers);
}

// Given an expression, this traverser calculates a new expression where sampler-in-structs are
// replaced with their extracted ones, and field indices are adjusted for the rest of the fields.
// In particular, this is run on the right node of EOpIndexIndirect binary nodes, so that the
// expression in the index gets a chance to go through this transformation.
class RewriteExpressionTraverser final : public TIntermTraverser
{
  public:
    explicit RewriteExpressionTraverser(TCompiler *compiler,
                                        const StructureMap &structureMap,
                                        const StructureUniformMap &structureUniformMap,
                                        const ExtractedSamplerMap &extractedSamplers)
        : TIntermTraverser(true, false, false),
          mCompiler(compiler),
          mStructureMap(structureMap),
          mStructureUniformMap(structureUniformMap),
          mExtractedSamplers(extractedSamplers)
    {}

    bool visitBinary(Visit visit, TIntermBinary *node) override
    {
        TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper(
            mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers);

        if (rewritten == nullptr)
        {
            return true;
        }

        queueReplacement(rewritten, OriginalNode::IS_DROPPED);

        // Don't iterate as the expression is rewritten.
        return false;
    }

    void visitSymbol(TIntermSymbol *node) override
    {
        // It's impossible to reach here with a symbol that needs replacement.
        // MonomorphizeUnsupportedFunctionsInVulkanGLSL makes sure that whole structs containing
        // samplers are not passed to functions, so any instance of the struct uniform is
        // necessarily indexed right away.  visitBinary should have already taken care of it.
        ASSERT(mStructureUniformMap.find(&node->variable()) == mStructureUniformMap.end());
    }

  private:
    TCompiler *mCompiler;

    // See RewriteStructSamplersTraverser.
    const StructureMap &mStructureMap;
    const StructureUniformMap &mStructureUniformMap;
    const ExtractedSamplerMap &mExtractedSamplers;
};

// Rewrite the index of an EOpIndexIndirect expression.  The root can never need replacing, because
// it cannot be a sampler itself or of a struct type.
void RewriteIndexExpression(TCompiler *compiler,
                            TIntermTyped *expression,
                            const StructureMap &structureMap,
                            const StructureUniformMap &structureUniformMap,
                            const ExtractedSamplerMap &extractedSamplers)
{
    RewriteExpressionTraverser traverser(compiler, structureMap, structureUniformMap,
                                         extractedSamplers);
    expression->traverse(&traverser);
    bool valid = traverser.updateTree(compiler, expression);
    ASSERT(valid);
}

// Given an expression such as the following:
//
//                                                    EOpIndexDirectStruct (sampler)
//                                                    /                  \
//                                               EOpIndex*           field index
//                                              /        \
//                                EOpIndexDirectStruct   index 2
//                                /                  \
//                           EOpIndex*           field index
//                          /        \
//            EOpIndexDirectStruct   index 1
//            /                  \
//     Uniform Struct           field index
//
// produces:
//
//                                EOpIndex*
//                                /      \
//                           EOpIndex*  index 2
//                          /        \
//                      sampler    index 1
//
// Alternatively, if the expression is as such:
//
//                                                    EOpIndexDirectStruct
//                                                    /                  \
//                        (modified struct type) EOpIndex*           field index
//                                              /        \
//                                EOpIndexDirectStruct   index 2
//                                /                  \
//                           EOpIndex*           field index
//                          /        \
//            EOpIndexDirectStruct   index 1
//            /                  \
//     Uniform Struct           field index
//
// produces:
//
//                                                    EOpIndexDirectStruct
//                                                    /                  \
//                                               EOpIndex*     mapped field index
//                                              /        \
//                                EOpIndexDirectStruct   index 2
//                                /                  \
//                           EOpIndex*      mapped field index
//                          /        \
//            EOpIndexDirectStruct   index 1
//            /                  \
//     Uniform Struct     mapped field index
//
TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
    TCompiler *compiler,
    TIntermBinary *node,
    const StructureMap &structureMap,
    const StructureUniformMap &structureUniformMap,
    const ExtractedSamplerMap &extractedSamplers)
{
    ASSERT(node->getOp() == EOpIndexDirectStruct);

    const bool isSampler = node->getType().isSampler();

    TIntermSymbol *baseUniform = nullptr;
    std::string samplerName;

    TVector<TIntermBinary *> indexNodeStack;

    // Iterate once and build the name of the sampler.
    TIntermBinary *iter = node;
    while (baseUniform == nullptr)
    {
        indexNodeStack.push_back(iter);
        baseUniform = iter->getLeft()->getAsSymbolNode();

        if (isSampler)
        {
            if (iter->getOp() == EOpIndexDirectStruct)
            {
                // When indexed into a struct, get the field name instead and construct the sampler
                // name.
                samplerName.insert(0, iter->getIndexStructFieldName().data());
                samplerName.insert(0, "_");
            }

            if (baseUniform)
            {
                // If left is a symbol, we have reached the end of the chain.  Use the struct name
                // to finish building the name of the sampler.
                samplerName.insert(0, baseUniform->variable().name().data());
            }
        }

        iter = iter->getLeft()->getAsBinaryNode();
    }

    TIntermTyped *rewritten = nullptr;

    if (isSampler)
    {
        ASSERT(extractedSamplers.find(samplerName) != extractedSamplers.end());
        rewritten = new TIntermSymbol(extractedSamplers.at(samplerName));
    }
    else
    {
        const TVariable *baseUniformVar = &baseUniform->variable();
        ASSERT(structureUniformMap.find(baseUniformVar) != structureUniformMap.end());
        rewritten = new TIntermSymbol(structureUniformMap.at(baseUniformVar));
    }

    // Iterate again and build the expression from bottom up.
    for (auto it = indexNodeStack.rbegin(); it != indexNodeStack.rend(); ++it)
    {
        TIntermBinary *indexNode = *it;

        switch (indexNode->getOp())
        {
            case EOpIndexDirectStruct:
                if (!isSampler)
                {
                    // Remap the field.
                    const TStructure *structure = indexNode->getLeft()->getType().getStruct();
                    ASSERT(structureMap.find(structure) != structureMap.end());

                    TIntermConstantUnion *asConstantUnion =
                        indexNode->getRight()->getAsConstantUnion();
                    ASSERT(asConstantUnion);

                    const int fieldIndex = asConstantUnion->getIConst(0);
                    ASSERT(fieldIndex <
                           static_cast<int>(structureMap.at(structure).fieldMap.size()));

                    const int mappedFieldIndex = structureMap.at(structure).fieldMap[fieldIndex];

                    rewritten = new TIntermBinary(EOpIndexDirectStruct, rewritten,
                                                  CreateIndexNode(mappedFieldIndex));
                }
                break;

            case EOpIndexDirect:
                rewritten = new TIntermBinary(EOpIndexDirect, rewritten, indexNode->getRight());
                break;

            case EOpIndexIndirect:
            {
                // Run RewriteExpressionTraverser on the right node.  It may itself be an expression
                // with a sampler inside that needs to be rewritten, or simply use a field of a
                // struct that's remapped.
                TIntermTyped *indexExpression = indexNode->getRight();
                RewriteIndexExpression(compiler, indexExpression, structureMap, structureUniformMap,
                                       extractedSamplers);
                rewritten = new TIntermBinary(EOpIndexIndirect, rewritten, indexExpression);
                break;
            }

            default:
                UNREACHABLE();
                break;
        }
    }

    return rewritten;
}

class RewriteStructSamplersTraverser final : public TIntermTraverser
{
  public:
    explicit RewriteStructSamplersTraverser(TCompiler *compiler, TSymbolTable *symbolTable)
        : TIntermTraverser(true, false, false, symbolTable),
          mCompiler(compiler),
          mRemovedUniformsCount(0)
    {}

    int removedUniformsCount() const { return mRemovedUniformsCount; }

    // Each struct sampler declaration is stripped of its samplers. New uniforms are added for each
    // stripped struct sampler.
    bool visitDeclaration(Visit visit, TIntermDeclaration *decl) override
    {
        if (!mInGlobalScope)
        {
            return true;
        }

        const TIntermSequence &sequence = *(decl->getSequence());
        TIntermTyped *declarator        = sequence.front()->getAsTyped();
        const TType &type               = declarator->getType();

        if (!type.isStructureContainingSamplers())
        {
            return false;
        }

        TIntermSequence *newSequence = new TIntermSequence;

        if (type.isStructSpecifier())
        {
            // If this is just a struct definition (not a uniform variable declaration of a
            // struct type), just remove the samplers.  They are not instantiated yet.
            const TStructure *structure = type.getStruct();
            ASSERT(structure && mStructureMap.find(structure) == mStructureMap.end());

            stripStructSpecifierSamplers(structure, newSequence);
        }
        else
        {
            const TStructure *structure = type.getStruct();

            // If the structure is defined at the same time, create the mapping to the stripped
            // version first.
            if (mStructureMap.find(structure) == mStructureMap.end())
            {
                stripStructSpecifierSamplers(structure, newSequence);
            }

            // Then, extract the samplers from the struct and create global-scope variables instead.
            TIntermSymbol *asSymbol = declarator->getAsSymbolNode();
            ASSERT(asSymbol);
            const TVariable &variable = asSymbol->variable();
            ASSERT(variable.symbolType() != SymbolType::Empty);

            extractStructSamplerUniforms(variable, structure, newSequence);
        }

        mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), decl, *newSequence);

        return false;
    }

    // Same implementation as in RewriteExpressionTraverser.  That traverser cannot replace root.
    bool visitBinary(Visit visit, TIntermBinary *node) override
    {
        TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper(
            mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers);

        if (rewritten == nullptr)
        {
            return true;
        }

        queueReplacement(rewritten, OriginalNode::IS_DROPPED);

        // Don't iterate as the expression is rewritten.
        return false;
    }

    // Same implementation as in RewriteExpressionTraverser.  That traverser cannot replace root.
    void visitSymbol(TIntermSymbol *node) override
    {
        ASSERT(mStructureUniformMap.find(&node->variable()) == mStructureUniformMap.end());
    }

  private:
    // Removes all samplers from a struct specifier.
    void stripStructSpecifierSamplers(const TStructure *structure, TIntermSequence *newSequence)
    {
        TFieldList *newFieldList = new TFieldList;
        ASSERT(structure->containsSamplers());

        // Add this struct to the struct map
        ASSERT(mStructureMap.find(structure) == mStructureMap.end());
        StructureData *modifiedData = &mStructureMap[structure];

        modifiedData->modified = nullptr;
        modifiedData->fieldMap.resize(structure->fields().size(), std::numeric_limits<int>::max());

        for (size_t fieldIndex = 0; fieldIndex < structure->fields().size(); ++fieldIndex)
        {
            const TField *field    = structure->fields()[fieldIndex];
            const TType &fieldType = *field->type();

            // If the field is a sampler, or a struct that's entirely removed, skip it.
            if (!fieldType.isSampler() && !isRemovedStructType(fieldType))
            {
                TType *newType = nullptr;

                // Otherwise, if it's a struct that's replaced, create a new field of the replaced
                // type.
                if (fieldType.isStructureContainingSamplers())
                {
                    const TStructure *fieldStruct = fieldType.getStruct();
                    ASSERT(mStructureMap.find(fieldStruct) != mStructureMap.end());

                    const TStructure *modifiedStruct = mStructureMap[fieldStruct].modified;
                    ASSERT(modifiedStruct);

                    newType = new TType(modifiedStruct, true);
                    if (fieldType.isArray())
                    {
                        newType->makeArrays(fieldType.getArraySizes());
                    }
                }
                else
                {
                    // If not, duplicate the field as is.
                    newType = new TType(fieldType);
                }

                // Record the mapping of the field indices, so future EOpIndexDirectStruct's into
                // this struct can be fixed up.
                modifiedData->fieldMap[fieldIndex] = static_cast<int>(newFieldList->size());

                TField *newField =
                    new TField(newType, field->name(), field->line(), field->symbolType());
                newFieldList->push_back(newField);
            }
        }

        // Prune empty structs.
        if (newFieldList->empty())
        {
            return;
        }

        // Declare a new struct with the same name and the new fields.
        modifiedData->modified =
            new TStructure(mSymbolTable, structure->name(), newFieldList, structure->symbolType());
        TType *newStructType = new TType(modifiedData->modified, true);
        TVariable *newStructVar =
            new TVariable(mSymbolTable, kEmptyImmutableString, newStructType, SymbolType::Empty);
        TIntermSymbol *newStructRef = new TIntermSymbol(newStructVar);

        TIntermDeclaration *structDecl = new TIntermDeclaration;
        structDecl->appendDeclarator(newStructRef);

        newSequence->push_back(structDecl);
    }

    // Returns true if the type is a struct that was removed because we extracted all the members.
    bool isRemovedStructType(const TType &type) const
    {
        const TStructure *structure = type.getStruct();
        if (structure == nullptr)
        {
            // Not a struct
            return false;
        }

        // A struct is removed if it is in the map, but doesn't have a replacement struct.
        auto iter = mStructureMap.find(structure);
        return iter != mStructureMap.end() && iter->second.modified == nullptr;
    }

    // Removes samplers from struct uniforms. For each sampler removed also adds a new globally
    // defined sampler uniform.
    void extractStructSamplerUniforms(const TVariable &variable,
                                      const TStructure *structure,
                                      TIntermSequence *newSequence)
    {
        ASSERT(structure->containsSamplers());
        ASSERT(mStructureMap.find(structure) != mStructureMap.end());

        const TType &type = variable.getType();
        enterArray(type);

        for (const TField *field : structure->fields())
        {
            extractFieldSamplers(variable.name().data(), field, newSequence);
        }

        // If there's a replacement structure (because there are non-sampler fields in the struct),
        // add a declaration with that type.
        const TStructure *modified = mStructureMap[structure].modified;
        if (modified != nullptr)
        {
            TType *newType = new TType(modified, false);
            if (type.isArray())
            {
                newType->makeArrays(type.getArraySizes());
            }
            newType->setQualifier(EvqUniform);
            const TVariable *newVariable =
                new TVariable(mSymbolTable, variable.name(), newType, variable.symbolType());

            TIntermDeclaration *newDecl = new TIntermDeclaration();
            newDecl->appendDeclarator(new TIntermSymbol(newVariable));

            newSequence->push_back(newDecl);

            ASSERT(mStructureUniformMap.find(&variable) == mStructureUniformMap.end());
            mStructureUniformMap[&variable] = newVariable;
        }
        else
        {
            mRemovedUniformsCount++;
        }

        exitArray(type);
    }

    // Extracts samplers from a field of a struct. Works with nested structs and arrays.
    void extractFieldSamplers(const std::string &prefix,
                              const TField *field,
                              TIntermSequence *newSequence)
    {
        const TType &fieldType = *field->type();
        if (fieldType.isSampler() || fieldType.isStructureContainingSamplers())
        {
            std::string newPrefix = prefix + "_" + field->name().data();

            if (fieldType.isSampler())
            {
                extractSampler(newPrefix, fieldType, newSequence);
            }
            else
            {
                enterArray(fieldType);
                const TStructure *structure = fieldType.getStruct();
                for (const TField *nestedField : structure->fields())
                {
                    extractFieldSamplers(newPrefix, nestedField, newSequence);
                }
                exitArray(fieldType);
            }
        }
    }

    void GenerateArraySizesFromStack(TVector<unsigned int> *sizesOut)
    {
        sizesOut->reserve(mArraySizeStack.size());

        for (auto it = mArraySizeStack.rbegin(); it != mArraySizeStack.rend(); ++it)
        {
            sizesOut->push_back(*it);
        }
    }

    // Extracts a sampler from a struct. Declares the new extracted sampler.
    void extractSampler(const std::string &newName,
                        const TType &fieldType,
                        TIntermSequence *newSequence)
    {
        ASSERT(fieldType.isSampler());

        TType *newType = new TType(fieldType);

        // Add array dimensions accumulated so far due to struct arrays.  Note that to support
        // nested arrays, mArraySizeStack has the outermost size in the front.  |makeArrays| thus
        // expects this in reverse order.
        TVector<unsigned int> parentArraySizes;
        GenerateArraySizesFromStack(&parentArraySizes);
        newType->makeArrays(parentArraySizes);

        ImmutableStringBuilder nameBuilder(newName.size() + 1);
        nameBuilder << newName;

        newType->setQualifier(EvqUniform);
        TVariable *newVariable =
            new TVariable(mSymbolTable, nameBuilder, newType, SymbolType::AngleInternal);
        TIntermSymbol *newSymbol = new TIntermSymbol(newVariable);

        TIntermDeclaration *samplerDecl = new TIntermDeclaration;
        samplerDecl->appendDeclarator(newSymbol);

        newSequence->push_back(samplerDecl);

        // TODO: Use a temp name instead of generating a name as currently done.  There is no
        // guarantee that these generated names cannot clash.  Create a mapping from the previous
        // name to the name assigned to the temp variable so ShaderVariable::mappedName can be
        // updated post-transformation.  http://anglebug.com/4301
        ASSERT(mExtractedSamplers.find(newName) == mExtractedSamplers.end());
        mExtractedSamplers[newName] = newVariable;
    }

    void enterArray(const TType &arrayType)
    {
        const TSpan<const unsigned int> &arraySizes = arrayType.getArraySizes();
        for (auto it = arraySizes.rbegin(); it != arraySizes.rend(); ++it)
        {
            unsigned int arraySize = *it;
            mArraySizeStack.push_back(arraySize);
        }
    }

    void exitArray(const TType &arrayType)
    {
        mArraySizeStack.resize(mArraySizeStack.size() - arrayType.getNumArraySizes());
    }

    TCompiler *mCompiler;
    int mRemovedUniformsCount;

    // Map structures with samplers to ones that have their samplers removed.
    StructureMap mStructureMap;

    // Map uniform variables of structure type that are replaced with another variable.
    StructureUniformMap mStructureUniformMap;

    // Map a constructed sampler name to its variable.  Used to replace an expression that uses this
    // sampler with the extracted one.
    ExtractedSamplerMap mExtractedSamplers;

    // A stack of array sizes.  Used to figure out the array dimensions of the extracted sampler,
    // for example when it's nested in an array of structs in an array of structs.
    TVector<unsigned int> mArraySizeStack;
};
}  // anonymous namespace

bool RewriteStructSamplers(TCompiler *compiler,
                           TIntermBlock *root,
                           TSymbolTable *symbolTable,
                           int *removedUniformsCountOut)
{
    RewriteStructSamplersTraverser traverser(compiler, symbolTable);
    root->traverse(&traverser);
    *removedUniformsCountOut = traverser.removedUniformsCount();
    return traverser.updateTree(compiler, root);
}
}  // namespace sh
