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

/*!
 * \file tvm/tirx/var.h
 * \brief Variables in the TIR.
 */
#ifndef TVM_TIR_VAR_H_
#define TVM_TIR_VAR_H_

#include <tvm/ffi/dtype.h>
#include <tvm/ir/cow.h>
#include <tvm/ir/expr.h>
#include <tvm/ir/type.h>

#include <functional>
#include <string>

namespace tvm {
namespace tirx {

/*!
 * \brief A variable node in the IR.
 *
 * A variable is uniquely identified by its address.
 *
 * Each variable is only bound once in the following nodes:
 * - Allocate
 * - For
 * - Let
 * - Bind
 */
class VarNode : public ExprNode {
 public:
  /*!
   * \brief The hint to the variable name.
   * \note Each variable is uniquely identified by its address.
   */
  ffi::String name_hint;
  static void RegisterReflection() {
    namespace refl = tvm::ffi::reflection;
    refl::ObjectDef<VarNode>().def_ro("name", &VarNode::name_hint,
                                      refl::AttachFieldFlag::SEqHashIgnore());
  }

  static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindFreeVar;
  static constexpr const uint32_t _type_child_slots = 1;
  TVM_FFI_DECLARE_OBJECT_INFO("tirx.Var", VarNode, ExprNode);
};

/*! \brief a named variable in TIR */
class Var : public Expr {
 public:
  explicit Var(ffi::UnsafeInit tag) : Expr(tag) {}
  explicit Var(ffi::ObjectPtr<VarNode> n) : Expr(n) {}
  /*!
   * \brief Constructor
   * \param name_hint variable name
   * \param dtype data type
   * \param span The location of this object in the source code.
   */
  TVM_DLL explicit Var(ffi::String name_hint = "v", PrimType dtype = PrimType::Int(32),
                       Span span = Span());
  /*!
   * \brief Constructor which provides a more detailed type annotation.
   * \param name_hint variable name.
   * \param type_annotation The type annotation.
   * \param span The location of this object in the source code.
   */
  TVM_DLL explicit Var(ffi::String name_hint, Type type_annotation, Span span = Span());
  /*!
   * \brief Make a new copy of var with same type, but a different nam
   * \param name The new name to be used.
   * \return the new Var copy
   */
  TVM_DLL Var copy_with_name(const ffi::String& name) const;
  /*!
   * \brief Make a new copy of var with same type, append suffix
   * \param suffix The suffix to be appended.
   * \return the new Var copy
   */
  TVM_DLL Var CopyWithSuffix(const ffi::String& suffix) const;
  /*!
   * \brief Make a new copy of the variable with specified dtype
   * \param dtype The specified dtype
   * \return The new variable
   */
  TVM_DLL Var copy_with_dtype(PrimType dtype) const;

  /*!
   * \brief Get pointer to the internal value.
   * \return the corresponding Variable.
   */
  const VarNode* operator->() const { return get(); }
  /*!
   * \brief Get pointer to the internal value.
   * \return the corresponding Variable.
   */
  const VarNode* get() const { return static_cast<const VarNode*>(data_.get()); }
  /*! \brief type indicate the container type */
  using ContainerType = VarNode;
  static constexpr bool _type_container_is_exact = true;
};

/*!
 * \brief Checked scalar view over a VarNode.
 *
 * PrimVar is a zero-state reference view over the same VarNode as Var.  It additionally
 * guarantees that the inherited ExprNode::ty is PrimType.
 */
class PrimVar : public PrimExpr {
 public:
  /*! \brief Construct a scalar variable directly from a primitive type. */
  explicit PrimVar(ffi::String name_hint, PrimType dtype = PrimType::Int(32), Span span = Span())
      : PrimExpr(
            Var(std::move(name_hint), std::move(dtype), std::move(span)).as_or_throw<PrimExpr>()) {}

  /*! \brief Construct a scalar variable directly from a checked type annotation. */
  explicit PrimVar(ffi::String name_hint, Type type_annotation, Span span = Span())
      : PrimExpr(Var(std::move(name_hint), std::move(type_annotation), std::move(span))
                     .as_or_throw<PrimExpr>()) {}

  /*! \brief Safe widening to a general Var view over the same node. */
  operator Var() const { return this->as_or_throw<Var>(); }

  PrimVar CopyWithSuffix(const ffi::String& suffix) const {
    return this->as_or_throw<Var>().CopyWithSuffix(suffix).as_or_throw<PrimVar>();
  }
  PrimVar copy_with_dtype(PrimType dtype) const {
    return this->as_or_throw<Var>().copy_with_dtype(dtype).as_or_throw<PrimVar>();
  }

  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(PrimVar, PrimExpr, VarNode);
  static constexpr bool _type_container_is_exact = true;
};

using Region = ffi::Array<Range>;

/*!
 * \brief Type of iteration variable.
 *  Each IterVar have a specific type.
 *
 *  The type of iter var can be overriden via
 *  stage.iter_var_attrs given they are compatible.
 */
enum IterVarType : int {
  /*!
   * \brief Data parallel iteration.
   *  This normally corresponds to axis of Tensor.
   *  Allow all IterVar manipulations.
   *
   * \note This does not mean the loop
   *  have to be executed in parallel fashion.
   */
  kDataPar = 0,
  /*!
   * \brief The IterVar itself is a thread-index
   *  of a fixed thread launching group.
   *  Note that this is already assumed to be parallelized.
   *
   *  Disallow: split/fuse/vectorize/parallel
   */
  kThreadIndex = 1,
  /*!
   * \brief Communicative reduction.
   *  Cannot be directly parallelized.
   *
   *  Disallow: parallel/vectorize
   */
  kCommReduce = 2,
  /*!
   * \brief Serial loops with loop carry dependency,
   *  the iteration must execute in order.
   *  Cannot be re-ordered.
   *
   *  Disallow: reorder/parallel/vectorize
   */
  kOrdered = 3,
  /*!
   * \brief IterVar is opaque,
   *
   *  May not corresponds to any generated loop
   *  Disallow all IterVar manipulations and compute_at
   *
   * \note This is usually used to implement composite op
   *  or external op, where the
   */
  kOpaque = 4,
  // The following are possible additional
  // types that are provided during schedule
  /*!
   * \brief The execution is unrolled.
   */
  kUnrolled = 5,
  /*!
   * \brief The loop is vectorized.
   */
  kVectorized = 6,
  /*!
   * \brief The loop is parallelized.
   */
  kParallelized = 7,
  /*!
   * \brief Marks boundary of tensorization intrinsic.
   */
  kTensorized = 8
};

/*!
 * \brief An iteration variable representing an iteration
 *  over a one dimensional interval.
 *
 *  The dtype of the extent of the `dom` of the IterVar must match the dtype of the internal Var.
 */
class IterVarNode : public PrimExprConvertibleNode {
 public:
  /*!
   * \brief the domain of iteration, if known, can be None
   *  For the intermediate schedule node, before schedule.
   */
  Range dom;
  /*! \brief The looping variable */
  PrimVar var;
  /*! \brief The type of the IterVar */
  IterVarType iter_type;
  /*!
   * \brief additional tag on the iteration variable,
   *  set this if this is bound already to a known thread tag.
   */
  ffi::String thread_tag;
  /*!
   * \brief Span that points to the original source code.
   *        Reserved debug information.
   */
  mutable Span span;

  PrimExpr ToPrimExpr() const final { return var; }

  static void RegisterReflection() {
    namespace refl = tvm::ffi::reflection;
    refl::ObjectDef<IterVarNode>()
        .def_ro("dom", &IterVarNode::dom)
        .def_ro("var", &IterVarNode::var, refl::AttachFieldFlag::SEqHashDefRecursive())
        .def_ro("iter_type", &IterVarNode::iter_type)
        .def_ro("thread_tag", &IterVarNode::thread_tag);
  }

  static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("tirx.IterVar", IterVarNode, PrimExprConvertibleNode);
};

/*!
 * \brief Iteration Variable,
 *  represents an iteration over an integer interval.
 *
 *  The dtype of the extent of the `dom` of the IterVar must match the dtype of the internal Var.
 */
class IterVar : public PrimExprConvertible {
 public:
  TVM_DLL IterVar(Range dom, PrimVar var, IterVarType iter_type, ffi::String thread_tag = "",
                  Span span = Span());
  /*!
   * \return the corresponding var in the IterVar.
   */
  inline operator PrimExpr() const;

  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IterVar, PrimExprConvertible, IterVarNode);
  TVM_DEFINE_OBJECT_REF_COW_METHOD(IterVarNode);
};

// inline implementations
inline IterVar::operator PrimExpr() const { return (*this)->var; }

inline const char* IterVarType2String(IterVarType t) {
  switch (t) {
    case kDataPar:
      return "DataPar";
    case kThreadIndex:
      return "ThreadIndex";
    case kCommReduce:
      return "CommReduce";
    case kOrdered:
      return "Ordered";
    case kOpaque:
      return "Opaque";
    case kUnrolled:
      return "Unrolled";
    case kVectorized:
      return "Vectorized";
    case kParallelized:
      return "Parallelized";
    case kTensorized:
      return "Tensorized";
  }
  return "Unknown";
}
}  // namespace tirx

}  // namespace tvm

namespace tvm::ffi {

template <>
inline constexpr bool use_default_type_traits_v<tirx::PrimVar> = false;

template <>
struct TypeTraits<tirx::PrimVar> : public ObjectRefTypeTraitsBase<tirx::PrimVar> {
  using Base = ObjectRefTypeTraitsBase<tirx::PrimVar>;
  using Base::CopyFromAnyViewAfterCheck;
  using Base::CopyToAnyView;
  using Base::GetMismatchTypeInfo;
  using Base::MoveFromAnyAfterCheck;
  using Base::MoveToAny;
  using Base::TypeSchema;
  using Base::TypeStr;

  TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) {
    if (src->type_index == TypeIndex::kTVMFFINone) {
      return tirx::PrimVar::_type_is_nullable;
    }
    if (src->type_index < TypeIndex::kTVMFFIStaticObjectBegin ||
        !details::IsObjectInstance<tirx::VarNode>(src->type_index)) {
      return false;
    }
    const auto* var = static_cast<const tirx::VarNode*>(
        details::ObjectUnsafe::ObjectPtrFromUnowned<Object>(src->v_obj).get());
    return details::AnyUnsafe::CheckAnyStrict<PrimType>(var->ExprNode::ty);
  }

  TVM_FFI_INLINE static std::optional<tirx::PrimVar> TryCastFromAnyView(const TVMFFIAny* src) {
    if (CheckAnyStrict(src)) {
      if (src->type_index == TypeIndex::kTVMFFINone) {
        return details::ObjectUnsafe::ObjectRefFromObjectPtr<tirx::PrimVar>(nullptr);
      }
      return details::ObjectUnsafe::ObjectRefFromObjectPtr<tirx::PrimVar>(
          details::ObjectUnsafe::ObjectPtrFromUnowned<tirx::VarNode>(src->v_obj));
    }
    return std::nullopt;
  }
};

}  // namespace tvm::ffi

/* \brief Allow tirx.Var as key in STL tables
 *
 * For most TIR expressions, it would be ambiguous whether the
 * expression should follow reference equality or structural equality.
 * This is not the case for variables, which do not contain nested
 * internal structure, and are frequently used as keys in lookup
 * tables.
 *
 * Providing `std::hash` and `std::equal_to` specializations for
 * `tirx::Var` allows it to be used as a key in STL tables.  For
 * `PrimExpr`, the user must specify the type of equality used
 * (e.g. `std::unordered_set<T, StructuralHash, StructuralEqual>` or
 * `std::unordered_set<T, ffi::ObjectPtrHash, ffi::ObjectPtrEqual>`).
 */
template <>
struct std::hash<tvm::tirx::Var> {
  std::size_t operator()(const tvm::tirx::Var& var) const { return tvm::ffi::ObjectPtrHash()(var); }
};

template <>
struct std::equal_to<tvm::tirx::Var> {
  bool operator()(const tvm::tirx::Var& var_a, const tvm::tirx::Var& var_b) const {
    return tvm::ffi::ObjectPtrEqual()(var_a, var_b);
  }
};

#endif  // TVM_TIR_VAR_H_
