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

#ifndef COMPILER_TRANSLATOR_MSL_SYMBOLENV_H_
#define COMPILER_TRANSLATOR_MSL_SYMBOLENV_H_

#include <unordered_set>

#include "compiler/translator/Compiler.h"
#include "compiler/translator/Name.h"
#include "compiler/translator/Types.h"
#include "compiler/translator/msl/Reference.h"

namespace sh
{

enum class AddressSpace
{
    Constant,
    Device,
    Thread,
};

char const *toString(AddressSpace space);

class VarField
{
  public:
    VarField(const TVariable &var) : mVariable(&var) {}
    VarField(const TField &field) : mField(&field) {}

    ANGLE_INLINE const TVariable *variable() const { return mVariable; }

    ANGLE_INLINE const TField *field() const { return mField; }

    ANGLE_INLINE bool operator==(const VarField &other) const
    {
        const int thisVarId  = mVariable ? mVariable->uniqueId().get() : -1;
        const int otherVarId = other.mVariable ? mVariable->uniqueId().get() : -1;
        return thisVarId == otherVarId && mField == other.mField;
    }

  private:
    const TVariable *mVariable = nullptr;
    const TField *mField       = nullptr;
};

}  // namespace sh

namespace std
{

template <>
struct hash<sh::VarField>
{
    size_t operator()(const sh::VarField &x) const
    {
        const sh::TVariable *var = x.variable();
        if (var)
        {
            return std::hash<const sh::TVariable *>()(var);
        }
        const sh::TField *field = x.field();
        return std::hash<const sh::TField *>()(field);
    }
};

}  // namespace std

namespace sh
{

class TemplateArg
{
  public:
    enum class Kind
    {
        Bool,
        Int,
        UInt,
        Type,
    };

    union Value
    {
        Value(bool value) : b(value) {}
        Value(int value) : i(value) {}
        Value(unsigned value) : u(value) {}
        Value(const TType &value) : t(&value) {}

        bool b;
        int i;
        unsigned u;
        const TType *t;
    };

    TemplateArg(bool value);
    TemplateArg(int value);
    TemplateArg(unsigned value);
    TemplateArg(const TType &value);

    bool operator==(const TemplateArg &other) const;
    bool operator<(const TemplateArg &other) const;

    Kind kind() const { return mKind; }
    Value value() const { return mValue; }

  public:
    Kind mKind;
    Value mValue;
};

// An environment for creating and uniquely sharing TSymbol objects.
class SymbolEnv
{
    class TemplateName
    {
        Name baseName;
        std::vector<TemplateArg> templateArgs;

      public:
        bool operator==(const TemplateName &other) const;
        bool operator<(const TemplateName &other) const;

        bool empty() const;
        void clear();

        Name fullName(std::string &buffer) const;

        void assign(const Name &name, size_t argCount, const TemplateArg *args);
    };

    using TypeRef        = CRef<TType>;
    using Sig            = std::vector<TypeRef>;  // Param0, Param1, ... ParamN, Return
    using SigToFunc      = std::map<Sig, TFunction *>;
    using Overloads      = std::map<TemplateName, SigToFunc>;
    using AngleStructs   = std::map<ImmutableString, TStructure *>;
    using TextureStructs = std::map<TBasicType, TStructure *>;

  public:
    SymbolEnv(TCompiler &compiler, TIntermBlock &root);

    TSymbolTable &symbolTable() { return mSymbolTable; }

    // There exist Angle rewrites that can lead to incoherent structs
    //
    // Example
    //    struct A { ... }
    //    struct B { A' a; } // A' has same name as A but is not identical.
    // becomes
    //    struct A { ... }
    //    struct B { A a; }
    //
    // This notably happens when A contains a sampler in the original GLSL code but is rewritten to
    // not have a sampler, yet the A' struct field still contains the sampler field.
    const TStructure &remap(const TStructure &s) const;

    // Like `TStructure &remap(const TStructure &s)` but additionally maps null to null.
    const TStructure *remap(const TStructure *s) const;

    const TFunction &getFunctionOverload(const Name &name,
                                         const TType &returnType,
                                         size_t paramCount,
                                         const TType **paramTypes,
                                         size_t templateArgCount         = 0,
                                         const TemplateArg *templateArgs = nullptr);

    TIntermAggregate &callFunctionOverload(const Name &name,
                                           const TType &returnType,
                                           TIntermSequence &args,
                                           size_t templateArgCount         = 0,
                                           const TemplateArg *templateArgs = nullptr);

    const TStructure &newStructure(const Name &name, TFieldList &fields);

    const TStructure &getTextureEnv(TBasicType samplerType);
    const TStructure &getSamplerStruct();

    void markAsPointer(VarField x, AddressSpace space);
    void removePointer(VarField x);
    const AddressSpace *isPointer(VarField x) const;

    void markAsReference(VarField x, AddressSpace space);
    void removeAsReference(VarField x);
    const AddressSpace *isReference(VarField x) const;

    void markAsPacked(const TField &field);
    bool isPacked(const TField &field) const;

    void markAsUBO(VarField x);
    bool isUBO(VarField x) const;

  private:
    const TFunction &getFunctionOverloadImpl();

    void markSpace(VarField x, AddressSpace space, std::unordered_map<VarField, AddressSpace> &map);
    void removeSpace(VarField x, std::unordered_map<VarField, AddressSpace> &map);
    const AddressSpace *isSpace(VarField x,
                                const std::unordered_map<VarField, AddressSpace> &map) const;

  private:
    TSymbolTable &mSymbolTable;

    std::map<Name, const TStructure *> mNameToStruct;
    Overloads mOverloads;
    Sig mReusableSigBuffer;
    TemplateName mReusableTemplateNameBuffer;
    std::string mReusableStringBuffer;

    AngleStructs mAngleStructs;
    std::unordered_map<TBasicType, const TStructure *> mTextureEnvs;
    const TStructure *mSampler = nullptr;

    std::unordered_map<VarField, AddressSpace> mPointers;
    std::unordered_map<VarField, AddressSpace> mReferences;
    std::unordered_set<const TField *> mPackedFields;
    std::unordered_set<VarField> mUboFields;
};

Name GetTextureTypeName(TBasicType samplerType);

}  // namespace sh

#endif  // COMPILER_TRANSLATOR_MSL_SYMBOLENV_H_
