// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

#pragma once

#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "arrow/util/macros.h"
#include "gandiva/annotator.h"
#include "gandiva/compiled_expr.h"
#include "gandiva/configuration.h"
#include "gandiva/dex_visitor.h"
#include "gandiva/engine.h"
#include "gandiva/execution_context.h"
#include "gandiva/expr_decomposer.h"
#include "gandiva/expression_cache_key.h"
#include "gandiva/function_registry.h"
#include "gandiva/gandiva_aliases.h"
#include "gandiva/llvm_types.h"
#include "gandiva/lvalue.h"
#include "gandiva/selection_vector.h"
#include "gandiva/value_validity_pair.h"
#include "gandiva/visibility.h"

namespace gandiva {

class FunctionHolder;

/// Builds an LLVM module and generates code for the specified set of expressions.
class GANDIVA_EXPORT LLVMGenerator {
 public:
  /// \brief Factory method to initialize the generator.
  static Result<std::unique_ptr<LLVMGenerator>> Make(
      const std::shared_ptr<Configuration>& config, bool cached,
      std::optional<std::reference_wrapper<GandivaObjectCache>> object_cache =
          std::nullopt);

  /// \brief Get the cache to be used for LLVM ObjectCache.
  static std::shared_ptr<Cache<ExpressionCacheKey, std::shared_ptr<llvm::MemoryBuffer>>>
  GetCache();

  /// \brief Set LLVM ObjectCache.
  Status SetLLVMObjectCache(GandivaObjectCache& object_cache);

  /// \brief Build the code for the expression trees for default mode with a LLVM
  /// ObjectCache. Each element in the vector represents an expression tree
  Status Build(const ExpressionVector& exprs, SelectionVector::Mode mode);

  /// \brief Build the code for the expression trees for default mode. Each
  /// element in the vector represents an expression tree
  Status Build(const ExpressionVector& exprs);

  /// \brief Execute the built expression against the provided arguments for
  /// default mode.
  Status Execute(const arrow::RecordBatch& record_batch,
                 const ArrayDataVector& output_vector) const;

  /// \brief Execute the built expression against the provided arguments for
  /// all modes. Only works on the records specified in the selection_vector.
  Status Execute(const arrow::RecordBatch& record_batch,
                 const SelectionVector* selection_vector,
                 const ArrayDataVector& output_vector) const;

  SelectionVector::Mode selection_vector_mode() { return selection_vector_mode_; }
  LLVMTypes* types() { return engine_->types(); }
  llvm::Module* module() { return engine_->module(); }
  const std::string& ir() { return engine_->ir(); }

 private:
  explicit LLVMGenerator(bool cached,
                         std::shared_ptr<FunctionRegistry> function_registry);

  FRIEND_TEST(TestLLVMGenerator, VerifyPCFunctions);
  FRIEND_TEST(TestLLVMGenerator, TestAdd);
  FRIEND_TEST(TestLLVMGenerator, TestNullInternal);
  friend class TestLLVMGenerator;

  llvm::LLVMContext* context() { return engine_->context(); }
  llvm::IRBuilder<>* ir_builder() { return engine_->ir_builder(); }
  llvm::Constant* CreateGlobalStringPtr(const std::string& string) {
    return engine_->CreateGlobalStringPtr(string);
  }

  /// Visitor to generate the code for a decomposed expression.
  class Visitor : public DexVisitor {
   public:
    Visitor(LLVMGenerator* generator, llvm::Function* function,
            llvm::BasicBlock* entry_block, llvm::Value* arg_addrs,
            llvm::Value* arg_local_bitmaps, llvm::Value* arg_holder_ptrs,
            std::vector<llvm::Value*> slice_offsets, llvm::Value* arg_context_ptr,
            llvm::Value* loop_var);

    void Visit(const VectorReadValidityDex& dex) override;
    void Visit(const VectorReadFixedLenValueDex& dex) override;
    void Visit(const VectorReadVarLenValueDex& dex) override;
    void Visit(const LocalBitMapValidityDex& dex) override;
    void Visit(const TrueDex& dex) override;
    void Visit(const FalseDex& dex) override;
    void Visit(const LiteralDex& dex) override;
    void Visit(const NonNullableFuncDex& dex) override;
    void Visit(const NullableNeverFuncDex& dex) override;
    void Visit(const NullableInternalFuncDex& dex) override;
    void Visit(const IfDex& dex) override;
    void Visit(const BooleanAndDex& dex) override;
    void Visit(const BooleanOrDex& dex) override;
    void Visit(const InExprDexBase<int32_t>& dex) override;
    void Visit(const InExprDexBase<int64_t>& dex) override;
    void Visit(const InExprDexBase<float>& dex) override;
    void Visit(const InExprDexBase<double>& dex) override;
    void Visit(const InExprDexBase<gandiva::DecimalScalar128>& dex) override;
    void Visit(const InExprDexBase<std::string>& dex) override;
    template <typename Type>
    void VisitInExpression(const InExprDexBase<Type>& dex);

    LValuePtr result() { return result_; }

    bool has_arena_allocs() { return has_arena_allocs_; }

   private:
    enum BufferType { kBufferTypeValidity = 0, kBufferTypeData, kBufferTypeOffsets };

    llvm::IRBuilder<>* ir_builder() { return generator_->ir_builder(); }
    llvm::Module* module() { return generator_->module(); }
    llvm::Constant* CreateGlobalStringPtr(const std::string& string) {
      return generator_->CreateGlobalStringPtr(string);
    }

    // Generate the code to build the combined validity (bitwise and) from the
    // vector of validities.
    llvm::Value* BuildCombinedValidity(const DexVector& validities);

    // Generate the code to build the validity and the value for the given pair.
    LValuePtr BuildValueAndValidity(const ValueValidityPair& pair);

    // Generate code to build the params.
    std::vector<llvm::Value*> BuildParams(int holder_idx,
                                          const ValueValidityPairVector& args,
                                          bool with_validity, bool with_context);

    // Generate code to invoke a function call.
    LValuePtr BuildFunctionCall(const NativeFunction* func, DataTypePtr arrow_return_type,
                                std::vector<llvm::Value*>* params);

    // Generate code for an if-else condition.
    LValuePtr BuildIfElse(llvm::Value* condition, std::function<LValuePtr()> then_func,
                          std::function<LValuePtr()> else_func,
                          DataTypePtr arrow_return_type);

    // Switch to the entry_block and get reference of the validity/value/offsets buffer
    llvm::Value* GetBufferReference(int idx, BufferType buffer_type, FieldPtr field);

    // Get the slice offset of the validity/value/offsets buffer
    llvm::Value* GetSliceOffset(int idx);

    // Switch to the entry_block and get reference to the local bitmap.
    llvm::Value* GetLocalBitMapReference(int idx);

    // Clear the bit in the local bitmap, if is_valid is 'false'
    void ClearLocalBitMapIfNotValid(int local_bitmap_idx, llvm::Value* is_valid);

    LLVMGenerator* generator_;
    LValuePtr result_;
    llvm::Function* function_;
    llvm::BasicBlock* entry_block_;
    llvm::Value* arg_addrs_;
    llvm::Value* arg_local_bitmaps_;
    llvm::Value* arg_holder_ptrs_;
    std::vector<llvm::Value*> slice_offsets_;
    llvm::Value* arg_context_ptr_;
    llvm::Value* loop_var_;
    bool has_arena_allocs_;
  };

  // Generate the code for one expression for default mode, with the output of
  // the expression going to 'output'.
  Status Add(const ExpressionPtr expr, const FieldDescriptorPtr output);

  /// Generate code to load the vector at specified index in the 'arg_addrs' array.
  llvm::Value* LoadVectorAtIndex(llvm::Value* arg_addrs, llvm::Type* type, int idx,
                                 const std::string& name);

  /// Generate code to load the vector at specified index and cast it as bitmap.
  llvm::Value* GetValidityReference(llvm::Value* arg_addrs, int idx, FieldPtr field);

  /// Generate code to load the vector at specified index and cast it as data array.
  llvm::Value* GetDataReference(llvm::Value* arg_addrs, int idx, FieldPtr field);

  /// Generate code to load the vector at specified index and cast it as offsets array.
  llvm::Value* GetOffsetsReference(llvm::Value* arg_addrs, int idx, FieldPtr field);

  /// Generate code to load the vector at specified index and cast it as buffer pointer.
  llvm::Value* GetDataBufferPtrReference(llvm::Value* arg_addrs, int idx, FieldPtr field);

  /// Generate code for the value array of one expression.
  Status CodeGenExprValue(DexPtr value_expr, int num_buffers, FieldDescriptorPtr output,
                          int suffix_idx, std::string& fn_name,
                          SelectionVector::Mode selection_vector_mode);

  /// Generate code to load the local bitmap specified index and cast it as bitmap.
  llvm::Value* GetLocalBitMapReference(llvm::Value* arg_bitmaps, int idx);

  /// Generate code to get the bit value at 'position' in the bitmap.
  llvm::Value* GetPackedBitValue(llvm::Value* bitmap, llvm::Value* position);

  /// Generate code to get the bit value at 'position' in the validity bitmap.
  llvm::Value* GetPackedValidityBitValue(llvm::Value* bitmap, llvm::Value* position);

  /// Generate code to set the bit value at 'position' in the bitmap to 'value'.
  void SetPackedBitValue(llvm::Value* bitmap, llvm::Value* position, llvm::Value* value);

  /// Generate code to clear the bit value at 'position' in the bitmap if 'value'
  /// is false.
  void ClearPackedBitValueIfFalse(llvm::Value* bitmap, llvm::Value* position,
                                  llvm::Value* value);

  // Generate code to build a DecimalLValue with specified value/precision/scale.
  std::shared_ptr<DecimalLValue> BuildDecimalLValue(llvm::Value* value,
                                                    DataTypePtr arrow_type);

  /// Generate code to make a function call (to a pre-compiled IR function) which takes
  /// 'args' and has a return type 'ret_type'.
  llvm::Value* AddFunctionCall(const std::string& full_name, llvm::Type* ret_type,
                               const std::vector<llvm::Value*>& args);

  /// Compute the result bitmap for the expression.
  ///
  /// \param[in] compiled_expr the compiled expression (includes the bitmap indices to be
  ///            used for computing the validity bitmap of the result).
  /// \param[in] selection_vector the list of selected positions
  /// \param[in,out] eval_batch (includes input/output buffer addresses)
  void ComputeBitMapsForExpr(const CompiledExpr& compiled_expr,
                             const SelectionVector* selection_vector,
                             EvalBatch* eval_batch) const;

  /// Replace the %T in the trace msg with the correct type corresponding to 'type'
  /// eg. %d for int32, %ld for int64, ..
  std::string ReplaceFormatInTrace(const std::string& msg, llvm::Value* value,
                                   std::string* print_fn);

  /// Generate the code to print a trace msg with one optional argument (%T)
  void AddTrace(const std::string& msg, llvm::Value* value = NULLPTR);

  std::unique_ptr<Engine> engine_;
  std::vector<std::unique_ptr<CompiledExpr>> compiled_exprs_;
  bool cached_;
  std::shared_ptr<FunctionRegistry> function_registry_;
  Annotator annotator_;
  SelectionVector::Mode selection_vector_mode_;

  // used for debug
  bool enable_ir_traces_;
  std::vector<std::string> trace_strings_;
};

}  // namespace gandiva
