//
// 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 <algorithm>

#include "compiler/translator/TranslatorMetalDirect/AstHelpers.h"
#include "compiler/translator/TranslatorMetalDirect/SeparateCompoundStructDeclarations.h"
#include "compiler/translator/tree_ops/SeparateDeclarations.h"
#include "compiler/translator/tree_util/IntermTraverse.h"

using namespace sh;

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

namespace
{

class Separator : public TIntermTraverser
{
  public:
    std::unordered_map<int, TIntermSymbol *> replacementMap;
    Separator(TSymbolTable &symbolTable, IdGen &idGen)
        : TIntermTraverser(false, false, true, &symbolTable), mIdGen(idGen)
    {}
    IdGen &mIdGen;
    bool visitDeclaration(Visit, TIntermDeclaration *declNode) override
    {
        ASSERT(declNode->getChildCount() == 1);
        Declaration declaration = ViewDeclaration(*declNode);

        const TVariable &var        = declaration.symbol.variable();
        const TType &type           = var.getType();
        const SymbolType symbolType = var.symbolType();
        if (type.isStructSpecifier() && symbolType != SymbolType::Empty)
        {
            const TStructure *structure = type.getStruct();
            TVariable *structVar        = nullptr;
            TType *instanceType         = nullptr;
            // Name unnamed inline structs
            if (structure->symbolType() == SymbolType::Empty)
            {
                const TStructure *structDefn =
                    new TStructure(mSymbolTable, mIdGen.createNewName().rawName(),
                                   &structure->fields(), SymbolType::AngleInternal);
                structVar    = new TVariable(mSymbolTable, ImmutableString(""),
                                          new TType(structDefn, true), SymbolType::Empty);
                instanceType = new TType(structDefn, false);
            }
            else
            {
                structVar    = new TVariable(mSymbolTable, ImmutableString(""),
                                          new TType(structure, true), SymbolType::Empty);
                instanceType = new TType(structure, false);
            }
            if (type.isArray())
            {
                instanceType->makeArrays(type.getArraySizes());
            }
            instanceType->setQualifier(type.getQualifier());
            auto *instanceVar =
                new TVariable(mSymbolTable, var.name(), instanceType, symbolType, var.extensions());

            TIntermSequence replacements;
            replacements.push_back(new TIntermDeclaration({structVar}));

            TIntermSymbol *instanceSymbol    = new TIntermSymbol(instanceVar);
            TIntermDeclaration *instanceDecl = new TIntermDeclaration;
            if (declaration.initExpr)
            {
                instanceDecl->appendDeclarator(new TIntermBinary(
                    TOperator::EOpInitialize, instanceSymbol, declaration.initExpr));
            }
            else
            {
                instanceDecl->appendDeclarator(instanceSymbol);
            }
            replacements.push_back(instanceDecl);

            replacementMap[declaration.symbol.uniqueId().get()] = instanceSymbol;
            ASSERT(getParentNode() != nullptr);
            ASSERT(getParentNode()->getAsBlock() != nullptr);
            mMultiReplacements.push_back(NodeReplaceWithMultipleEntry(
                getParentNode()->getAsBlock(), declNode, std::move(replacements)));
        }

        return false;
    }

    void visitSymbol(TIntermSymbol *decl) override
    {
        auto symbol = replacementMap.find(decl->uniqueId().get());
        if (symbol != replacementMap.end())
        {
            queueReplacement(symbol->second->deepCopy(), OriginalNode::IS_DROPPED);
        }
    }
};

}  // anonymous namespace

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

bool sh::SeparateCompoundStructDeclarations(TCompiler &compiler,
                                            IdGen &idGen,
                                            TIntermBlock &root,
                                            TSymbolTable *symbolTable)
{
    Separator separator(compiler.getSymbolTable(), idGen);
    root.traverse(&separator);
    if (!separator.updateTree(&compiler, &root))
    {
        return false;
    }

    if (!SeparateDeclarations(&compiler, &root, symbolTable))
    {
        return false;
    }

    return true;
}
