/*
 * 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/arithmetic/ir_mutator_with_analyzer.h
 * \brief IR mutator base-class with an analyzer context.
 */
#ifndef TVM_ARITH_IR_MUTATOR_WITH_ANALYZER_H_
#define TVM_ARITH_IR_MUTATOR_WITH_ANALYZER_H_

#include <tvm/arith/analyzer.h>
#include <tvm/ffi/cast.h>
#include <tvm/ir/scope_stack.h>
#include <tvm/ir/with_context.h>
#include <tvm/tirx/analysis.h>
#include <tvm/tirx/stmt_functor.h>

#include <unordered_set>
#include <utility>

namespace tvm {
namespace arith {

/*!
 * \brief IRMutator with an analyzer context.
 *
 * This class can sub-classed by ir mutators that need an analyzer.
 * It will populates scope-related info such as bounds of loop-variables and constraints
 * for the analyzer, so that the child class can do accurate context-dependent analysis.
 *
 * \sa src/arithmetic/ir_mutator_with_analyzer.cc
 */
class IRMutatorWithAnalyzer : public tirx::StmtExprMutator {
 public:
  explicit IRMutatorWithAnalyzer(const Analyzer& analyzer) : analyzer_(analyzer.get()) {}
  explicit IRMutatorWithAnalyzer(AnalyzerObj* analyzer) : analyzer_(analyzer) {}

  using StmtExprMutator::VisitExpr_;
  using StmtExprMutator::VisitStmt_;

  // override functions that need to populate the context information.
  tirx::Stmt VisitStmt_(const tirx::ForNode* op) override;
  tirx::Stmt VisitStmt_(const tirx::SBlockNode* op) override;
  tirx::Stmt VisitStmt_(const tirx::BindNode* op) override;
  tirx::Stmt VisitStmt_(const tirx::IfThenElseNode* op) override;
  tirx::Stmt VisitStmt_(const tirx::AttrStmtNode* op) override;
  tirx::Stmt VisitStmt_(const tirx::AssertStmtNode* op) override;
  tirx::Stmt VisitStmt_(const tirx::SeqStmtNode* op) override;
  Expr VisitExpr_(const tirx::LetNode* op) override;
  Expr VisitExpr_(const tirx::SelectNode* op) override;
  Expr VisitExpr_(const CallNode* op) override;
  Expr VisitExpr_(const tirx::ReduceNode* op) override;

 protected:
  /*!
   * \brief Mark all buffer-parameter shape values as positive values.
   *
   * \note call this function before Visit function's body to maximize
   *       simplification efficiency
   */
  void MarkBufferParamShapes(const tirx::PrimFunc& func);

  /*!
   * \brief Use internal bound information to perform inter map simplification of indices.
   * \note Only do this during layout remapping
   */
  ffi::Array<PrimExpr> IterMapSimplifyWithContext(const ffi::Array<PrimExpr>& indices,
                                                  bool non_trivial_only);

  /*! \brief internal analyzer field. */
  AnalyzerObj* analyzer_;
  /*! \brief Scope stack for accumulated assert constraints. */
  ScopeStack<WithGroup<ConstraintContext>> constraint_scope_;
  // the following two fields are useful in case we want
  // note however that iter map analysis are usually more
  // expensive and we only encourage doing them during
  // necessary cases like layout remapping
  /*! \brief Recorded loop iterators */
  ffi::Map<tirx::PrimVar, Range> iter_vars_;
  /*! \brief iterator predicates */
  ffi::Array<PrimExpr> iter_predicates_;
  /*!
   * \brief Run callback while trying to record iter predicate
   * \param conditon Condition to be checked.
   * \param callback The callback to be called.
   */
  template <typename FLambda>
  void WithRecordIterPredicate(PrimExpr condition, FLambda callback) {
    std::unordered_set<const tirx::VarNode*> iter_var_nodes;
    for (const auto& [var, _] : iter_vars_) {
      iter_var_nodes.insert(var.get());
    }
    auto f_use_itervar = [&iter_var_nodes](const tirx::VarNode* v) {
      return iter_var_nodes.count(v);
    };
    // simple heuristics for detecting predicate
    if (tirx::UsesVar(condition, f_use_itervar)) {
      iter_predicates_.push_back(condition);
      callback();
      iter_predicates_.pop_back();
    } else {
      callback();
    }
  }
};
}  // namespace arith
}  // namespace tvm
#endif  // TVM_ARITH_IR_MUTATOR_WITH_ANALYZER_H_
