/*========================== begin_copyright_notice ============================

Copyright (C) 2017-2021 Intel Corporation

SPDX-License-Identifier: MIT

============================= end_copyright_notice ===========================*/

#pragma once

#include "Compiler/CodeGenPublic.h"
#include "Compiler/MetaDataApi/MetaDataApi.h"
#include "Compiler/Optimizer/ValueTracker.h"
#include "common/LLVMWarningsPush.hpp"
#include <llvm/ADT/MapVector.h>
#include <llvm/ADT/StringRef.h>
#include <llvmWrapper/IR/Intrinsics.h>
#include "common/LLVMWarningsPop.hpp"
#include "GenISAIntrinsics/GenIntrinsics.h"
#include <map>
#include "Probe/Assertion.h"

namespace llvm
{
    class CallInst;
}

namespace IGC
{
    class CCommand
    {
    public:
        /// @brief  gets llvm function declaration for GenISA_ISA Intrinsic
        /// @param  Id          the wanted GenISA_ISA intrinsic
        /// @param  ptrTy       for intrinsic with overloaded types.
        /// @returns    llvm Function
        llvm::Function* getFunctionDeclaration(llvm::GenISAIntrinsic::ID id, llvm::ArrayRef<llvm::Type*> Tys = llvm::ArrayRef<llvm::Type*>());

        /// @brief  gets llvm function declaration for LLVM Intrinsic
        /// @param  Id          the wanted LLVM intrinsic
        /// @param  ptrTy       for intrinsic with overloaded types.
        /// @returns    llvm Function
        llvm::Function* getFunctionDeclaration(IGCLLVM::Intrinsic id, llvm::ArrayRef<llvm::Type*> Tys = llvm::ArrayRef<llvm::Type*>());

        /// @brief  Creates llvm function call for LLVM Intrinsic and replace the
        ///         current instruction with the new function call.
        /// @param  Id          the wanted LLVM intrinsic
        /// @param  ptrTy       for intrinsic with overloaded types.
        void replaceCallInst(IGCLLVM::Intrinsic intrinsicName, llvm::ArrayRef<llvm::Type*> Tys = llvm::ArrayRef<llvm::Type*>());


        /// @brief  Creates llvm function call for GenISA_ISA Intrinsic and replace the
        ///         current instruction with the new function call.
        /// @param  Id          the wanted GenISA_ISA intrinsic
        /// @param  ptrTy       for intrinsic with overloaded types.
        void replaceGenISACallInst(llvm::GenISAIntrinsic::ID intrinsicName, llvm::ArrayRef<llvm::Type*> Tys = llvm::ArrayRef<llvm::Type*>());

        /// @brief  reap init() and createIntrinsic().
        /// @param  Inst            the call instruction that need to be replaced.
        /// @param  CodeGenContext  context to return errors
        void execute(llvm::CallInst* Inst, IGC::CodeGenContext* CodeGenContext);

        /// @brief  replace the old __builtin_IB function call with the match llvm/GenISA_ISA
        ///         instructions.
        /// @param  Inst        the call instruction that need to be replaced.
        virtual void createIntrinsic() = 0;

        /// @brief  verify that there are no user errors
        virtual void verifyCommand() {}

        /// @brief  initialize the callInst,  function, context, constants and clear the arg list.
        /// @param  Inst            the call instruction that need to be replaced.
        /// @param  CodeGenContext  context to return errors
        void init(llvm::CallInst* Inst, IGC::CodeGenContext* CodeGenContext);
        void clearArgsList(void);

        /// @brief returns true if command executed with errors
        bool hasError() const {return m_bHasError;}

        enum CoordType
        {
            FloatType,
            IntType
        };

        CCommand(void) :
            m_pFloatZero(nullptr),
            m_pIntZero(nullptr),
            m_pIntOne(nullptr),
            m_pCallInst(nullptr),
            m_pFunc(nullptr),
            m_pCtx(nullptr),
            m_pCodeGenContext(nullptr),
            m_pFloatType(nullptr),
            m_pIntType(nullptr),
            m_bHasError(false)
        {}
        virtual ~CCommand(void) {};

    protected:
        /// @brief emits error using CodeGenContext, sets error state of CCommand to true
        /// @param ErrorStr error string to emit
        /// @param Context Value to get debug location to print out the relevant info.
        void emitError(const char* ErrorStr, const llvm::Value *Context);

        llvm::Value* m_pFloatZero;
        llvm::Value* m_pIntZero;
        llvm::Value* m_pIntOne;
        llvm::CallInst* m_pCallInst;
        llvm::Function* m_pFunc;
        llvm::LLVMContext* m_pCtx;
        IGC::CodeGenContext* m_pCodeGenContext;
        llvm::SmallVector<llvm::Value*, 10> m_args;
        llvm::DebugLoc m_DL;
        llvm::Type* m_pFloatType;
        llvm::IntegerType* m_pIntType;
        bool m_bHasError;
    };

    static inline BufferType ResourceTypeMap(ResourceTypeEnum type)
    {
        switch (type)
        {
        case IGC::UAVResourceType:
            return UAV;
        case IGC::BindlessUAVResourceType:
            return BINDLESS;
        case IGC::SRVResourceType:
            return RESOURCE;
        case IGC::SamplerResourceType:
            return SAMPLER;
        case IGC::BindlessSamplerResourceType:
            return BINDLESS_SAMPLER;
        case IGC::OtherResourceType:
            return BUFFER_TYPE_UNKNOWN;
        default:
            IGC_ASSERT_MESSAGE(0, "unknown type!");
            return BUFFER_TYPE_UNKNOWN;
        }
    };

    class CImagesBI : public CCommand
    {
    public:
        enum Dimension
        {
            DIM_UNKNOWN,
            DIM_1D,
            DIM_1D_ARRAY,
            DIM_2D,
            DIM_2D_ARRAY,
            DIM_3D
        };

        enum CoordComponents
        {
            COORD_X,
            COORD_Y,
            COORD_Z,
            COORD_W
        };

        enum ImageProperty
        {
            WIDTH,
            HEIGHT,
            DEPTH,
            ARRAY_SIZE_1D,
            ARRAY_SIZE_2D
        };

        struct ParamInfo
        {
            ParamInfo(int i, ResourceTypeEnum t, ResourceExtensionTypeEnum extension) :
                index(i), type(ResourceTypeMap(t)), extension(extension) {}
            ParamInfo() :
                index(0), type(BUFFER_TYPE_UNKNOWN), extension(NonExtensionType) {}

            int index;
            BufferType type;
            ResourceExtensionTypeEnum extension;
        };

        typedef llvm::MapVector<llvm::Value*, ParamInfo> ParamMap;
        typedef std::map<int, int> InlineMap;

        class CImagesUtils
        {
        public:
            /// @brief  find the BTI of the image argumant
            /// @param  paramIndex  the index of the image paramtere in the call isntruciton
            /// @returns  the image index
            static llvm::ConstantInt* getImageIndex(ParamMap* pParamMap, llvm::CallInst* pCallInst, unsigned int paramIndex, llvm::Value*& imageParam);

            /// @brief  find the type (UAV/RESOURCE) of the image argumant
            /// @returns  the image type
            static BufferType getImageType(ParamMap* pParamMap, llvm::CallInst* pCallInst, unsigned int paramIndex);

            /// @brief  find the image argument associated with the given bufType and idx
            /// @returns the image argument
            static llvm::Argument* findImageFromBufferPtr(const IGC::IGCMD::MetaDataUtils& MdUtils, llvm::Function* F, BufferType bufType, uint64_t idx, const IGC::ModuleMetaData* modMD);
        };

        CImagesBI(ParamMap* paramMap, InlineMap* inlineMap, int* nextSampler, Dimension Dim) :
            m_pParamMap(paramMap), m_pInlineMap(inlineMap), m_pNextSampler(nextSampler), m_dim(Dim),
            CoordX(nullptr), CoordY(nullptr), CoordZ(nullptr), m_IncorrectBti(false) {}

        CImagesBI(ParamMap* paramMap) :
            m_pParamMap(paramMap), m_pInlineMap(nullptr), m_pNextSampler(nullptr), m_dim(Dimension::DIM_UNKNOWN),
            CoordX(nullptr), CoordY(nullptr), CoordZ(nullptr), m_IncorrectBti(false) {}

        /// @brief  push 3 zeros offset to the function argumant list
        void prepareZeroOffsets(void);

        /// @brief  initilaize CoordX/Y/Z with the given coordinates.
        /// @param  Dim         the image dimension
        /// @param  Coord       the coords from the original intrinsic.
        /// @param  Zero        const zero with the correct type (i.e. float or int)
        ///                     it been use only for image_1d and image_2d
        void prepareCoords(Dimension Dim, llvm::Value* Coord, llvm::Value* Zero);

        /// @brief  push the colors to the function argument list
        /// @param  Color       the Color value
        void prepareColor(llvm::Value* Color);

        /// @brief  set LOD to Zero and push the function argument list
        /// @param  Coord       the type of zero (int or float)
        void prepareLOD(CoordType Coord);


        /// @brief  push the sampler value into the function argument list
        void prepareSamplerValue(void);

        /// @brief  create a call to the GetBufferPtr intrinsic pseudo-instruction
        /// @brief  push the image index into the function argument list
        void createGetBufferPtr(void);

        /// @brief  returns "true" if the "val" is integer or float with fractional part = 0.
        static bool derivedFromInt(const llvm::Value* pVal);

        void verifyCommand();

    protected:
        /// @brief  push the image index into the function argument list
        void prepareImageBTI(void);

        /// @brief  push the paired resource pointer into the function argument list
        void preparePairedResource(void);

        // m_pParamMap - maps image and sampler kernel parameters to BTIs
        //               and sampler array indexes, respecitvely
        ParamMap* m_pParamMap;

        // m_pInlineMap - maps inline sampler values (CLK_...) to sampler
        //                array indexes.
        InlineMap* m_pInlineMap;

        // The next sampler index to allocate a newly encountered
        // inline sampler
        int* m_pNextSampler;

        Dimension m_dim;
        llvm::Value* CoordX;
        llvm::Value* CoordY;
        llvm::Value* CoordZ;
        bool m_IncorrectBti;
    };

    class CBuiltinsResolver
    {
    private:
        std::map<llvm::StringRef, std::unique_ptr<CCommand>> m_CommandMap;

        // the list of known builtins not to be resolved
        std::vector<llvm::StringRef> m_KnownBuiltins;
        CodeGenContext* m_CodeGenContext;
    public:
        CBuiltinsResolver(CImagesBI::ParamMap* paramMap, CImagesBI::InlineMap* inlineMap, int* nextSampler, CodeGenContext* ctx);
        ~CBuiltinsResolver(void) {}

        // resolve __builtin_IB_* calls.
        // return values:
        //              true - when CallInst was __builtin_IB_* and successfully repleced
        //              false - when CallInst wasn't __builtin_IB*
        bool resolveBI(llvm::CallInst* Inst);
    };

    union InlineSamplerState
    {
        struct _state
        {
            uint64_t TCXAddressMode : 3;
            uint64_t TCYAddressMode : 3;
            uint64_t TCZAddressMode : 3;
            uint64_t MagFilterMode : 2;
            uint64_t MinFilterMode : 2;
            uint64_t MipFilterMode : 2;
            uint64_t NormalizedCoords : 1;
            uint64_t CompareFunction : 4;
            uint64_t _reserved : 44;
        } state;
        uint64_t _Value;

        InlineSamplerState() : _Value(0) {}
        InlineSamplerState(uint64_t samplerValue) : _Value(samplerValue) {}

        enum ADDRESS_MODE
        {
            ADDRESS_MODE_WRAP,
            ADDRESS_MODE_CLAMP,
            ADDRESS_MODE_MIRROR,
            ADDRESS_MODE_BORDER,
            ADDRESS_MODE_MIRRORONCE,
            ADDRESS_MODE_MIRROR101,
            NUM_ADDRESS_MODE
        };

        enum MAP_FILTER_MODE    // Applies for MagFilterMode and MinFilterMode
        {
            MAP_FILTER_MODE_POINT,
            MAP_FILTER_MODE_LINEAR,
            MAP_FILTER_MODE_ANISOTROPIC,
            MAP_FILTER_MODE_GAUSSIANQUAD,
            MAP_FILTER_MODE_PYRAMIDALQUAD,
            MAP_FILTER_MODE_MONO,
            NUM_MAP_FILTER_MODE
        };

        enum MIP_FILTER_MODE
        {
            MIP_FILTER_MODE_POINT,
            MIP_FILTER_MODE_LINEAR,
            MIP_FILTER_MODE_NONE,
            NUM_MIP_FILTER_MODE
        };

        enum NORMALIZED_MODE
        {
            NORMALIZED_MODE_FALSE,
            NORMALIZED_MODE_TRUE,
        };

        enum COMPARE_FUNC
        {
            COMPARE_FUNC_NONE,
            COMPARE_FUNC_LESS,
            COMPARE_FUNC_LESS_EQUAL = 2,
            COMPARE_FUNC_GREATER = 3,
            COMPARE_FUNC_GREATER_EQUAL = 4,
            COMPARE_FUNC_EQUAL = 5,
            COMPARE_FUNC_NOT_EQUAL = 6,
            COMPARE_FUNC_ALWAYS = 7,
            COMPARE_FUNC_NEVER = 8,
            NUM_COMPARE_FUNC
        };
    };

    // Built-in image functions
    // These values need to match the runtime equivalent
#define LEGACY_CLK_ADDRESS_NONE                         0x00
#define LEGACY_CLK_ADDRESS_CLAMP                        0x01
#define LEGACY_CLK_ADDRESS_CLAMP_TO_EDGE                0x02
#define LEGACY_CLK_ADDRESS_REPEAT                       0x03
#define LEGACY_CLK_ADDRESS_MIRRORED_REPEAT              0x04
#define LEGACY_CLK_ADDRESS_MIRRORED_REPEAT_101_INTEL     0x05

#define LEGACY_CLK_NORMALIZED_COORDS_FALSE   0x00
#define LEGACY_CLK_NORMALIZED_COORDS_TRUE    0x08

#define LEGACY_CLK_FILTER_NEAREST            0x00
#define LEGACY_CLK_FILTER_LINEAR             0x10

    // Mask values for the various components
#define LEGACY_SAMPLER_ADDRESS_MASK          0x07
#define LEGACY_SAMPLER_NORMALIZED_MASK       0x08
#define LEGACY_SAMPLER_FILTER_MASK           0x10

    // These values need to match SPIR sampler enum
#define SPIR_CLK_ADDRESS_NONE                       0x00
#define SPIR_CLK_ADDRESS_CLAMP_TO_EDGE              0x02
#define SPIR_CLK_ADDRESS_CLAMP                        0x04
#define SPIR_CLK_ADDRESS_REPEAT                     0x06
#define SPIR_CLK_ADDRESS_MIRRORED_REPEAT            0x08
#define SPIR_CLK_ADDRESS_MIRRORED_REPEAT_101_INTEL     0x0A

#define SPIR_CLK_NORMALIZED_COORDS_FALSE   0x00
#define SPIR_CLK_NORMALIZED_COORDS_TRUE    0x01

#define SPIR_CLK_FILTER_NEAREST            0x0010
#define SPIR_CLK_FILTER_LINEAR             0x0020

    // Mask values for the various components
#define SPIR_SAMPLER_NORMALIZED_MASK       0x01
#define SPIR_SAMPLER_ADDRESS_MASK          0x0e
#define SPIR_SAMPLER_FILTER_MASK           0x30

} // namespace IGC
