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

/// \file iceberg/expression/expression_visitor.h
/// Visitor pattern implementation for traversing Iceberg expression trees.

#include <concepts>
#include <memory>
#include <typeinfo>

#include "iceberg/expression/aggregate.h"
#include "iceberg/expression/expression.h"
#include "iceberg/expression/literal.h"
#include "iceberg/expression/predicate.h"
#include "iceberg/expression/term.h"
#include "iceberg/iceberg_export.h"
#include "iceberg/result.h"
#include "iceberg/util/checked_cast.h"
#include "iceberg/util/macros.h"

namespace iceberg {

/// \brief Base visitor for traversing expression trees.
///
/// This visitor traverses expression trees in postorder traversal and calls appropriate
/// visitor methods for each node. Subclasses can override specific methods to implement
/// custom behavior.
///
/// \tparam R The return type produced by visitor methods
template <typename R>
class ICEBERG_EXPORT ExpressionVisitor {
  using ParamType = std::conditional_t<std::is_fundamental_v<R>, R, const R&>;

 public:
  virtual ~ExpressionVisitor() = default;

  /// \brief Visit a True expression (always evaluates to true).
  virtual Result<R> AlwaysTrue() = 0;

  /// \brief Visit a False expression (always evaluates to false).
  virtual Result<R> AlwaysFalse() = 0;

  /// \brief Visit a Not expression.
  /// \param child_result The result from visiting the child expression
  virtual Result<R> Not(ParamType child_result) = 0;

  /// \brief Visit an And expression.
  /// \param left_result The result from visiting the left child
  /// \param right_result The result from visiting the right child
  virtual Result<R> And(ParamType left_result, ParamType right_result) = 0;

  /// \brief Visit an Or expression.
  /// \param left_result The result from visiting the left child
  /// \param right_result The result from visiting the right child
  virtual Result<R> Or(ParamType left_result, ParamType right_result) = 0;

  /// \brief Visit a bound predicate.
  /// \param pred The bound predicate to visit
  virtual Result<R> Predicate(const std::shared_ptr<BoundPredicate>& pred) = 0;

  /// \brief Visit an unbound predicate.
  /// \param pred The unbound predicate to visit
  virtual Result<R> Predicate(const std::shared_ptr<UnboundPredicate>& pred) = 0;

  /// \brief Visit a bound aggregate.
  /// \param aggregate The bound aggregate to visit.
  virtual Result<R> Aggregate(const std::shared_ptr<BoundAggregate>& aggregate) {
    ICEBERG_DCHECK(aggregate != nullptr, "Bound aggregate cannot be null");
    return NotSupported("Visitor {} does not support bound aggregate",
                        typeid(*this).name());
  }

  /// \brief Visit an unbound aggregate.
  /// \param aggregate The unbound aggregate to visit.
  virtual Result<R> Aggregate(const std::shared_ptr<UnboundAggregate>& aggregate) {
    ICEBERG_DCHECK(aggregate != nullptr, "Unbound aggregate cannot be null");
    return NotSupported("Visitor {} does not support unbound aggregate",
                        typeid(*this).name());
  }
};

/// \brief Visitor for bound expressions.
///
/// This visitor is for traversing bound expression trees.
///
/// \tparam R The return type produced by visitor methods
template <typename R>
class ICEBERG_EXPORT BoundVisitor : public ExpressionVisitor<R> {
 public:
  ~BoundVisitor() override = default;

  /// \brief Visit an IS_NULL bound expression.
  /// \param expr The bound expression being tested
  virtual Result<R> IsNull(const std::shared_ptr<Bound>& expr) = 0;

  /// \brief Visit a NOT_NULL bound expression.
  /// \param expr The bound expression being tested
  virtual Result<R> NotNull(const std::shared_ptr<Bound>& expr) = 0;

  /// \brief Visit an IS_NAN bound expression.
  /// \param expr The bound expression being tested
  virtual Result<R> IsNaN(const std::shared_ptr<Bound>& expr) {
    return NotSupported("IsNaN operation is not supported by this visitor");
  }

  /// \brief Visit a NOT_NAN bound expression.
  /// \param expr The bound expression being tested
  virtual Result<R> NotNaN(const std::shared_ptr<Bound>& expr) {
    return NotSupported("NotNaN operation is not supported by this visitor");
  }

  /// \brief Visit a less-than bound expression.
  /// \param expr The bound expression being tested
  /// \param lit The literal value to compare against
  virtual Result<R> Lt(const std::shared_ptr<Bound>& expr, const Literal& lit) = 0;

  /// \brief Visit a less-than-or-equal bound expression.
  /// \param expr The bound expression being tested
  /// \param lit The literal value to compare against
  virtual Result<R> LtEq(const std::shared_ptr<Bound>& expr, const Literal& lit) = 0;

  /// \brief Visit a greater-than bound expression.
  /// \param expr The bound expression being tested
  /// \param lit The literal value to compare against
  virtual Result<R> Gt(const std::shared_ptr<Bound>& expr, const Literal& lit) = 0;

  /// \brief Visit a greater-than-or-equal bound expression.
  /// \param expr The bound expression being tested
  /// \param lit The literal value to compare against
  virtual Result<R> GtEq(const std::shared_ptr<Bound>& expr, const Literal& lit) = 0;

  /// \brief Visit an equality bound expression.
  /// \param expr The bound expression being tested
  /// \param lit The literal value to compare against
  virtual Result<R> Eq(const std::shared_ptr<Bound>& expr, const Literal& lit) = 0;

  /// \brief Visit a not-equal bound expression.
  /// \param expr The bound expression being tested
  /// \param lit The literal value to compare against
  virtual Result<R> NotEq(const std::shared_ptr<Bound>& expr, const Literal& lit) = 0;

  /// \brief Visit a starts-with bound expression.
  /// \param expr The bound expression being tested
  /// \param lit The literal value to check for prefix match
  virtual Result<R> StartsWith([[maybe_unused]] const std::shared_ptr<Bound>& expr,
                               [[maybe_unused]] const Literal& lit) {
    return NotSupported("StartsWith operation is not supported by this visitor");
  }

  /// \brief Visit a not-starts-with bound expression.
  /// \param expr The bound expression being tested
  /// \param lit The literal value to check for prefix match
  virtual Result<R> NotStartsWith([[maybe_unused]] const std::shared_ptr<Bound>& expr,
                                  [[maybe_unused]] const Literal& lit) {
    return NotSupported("NotStartsWith operation is not supported by this visitor");
  }

  /// \brief Visit an IN set bound expression.
  /// \param expr The bound expression being tested
  /// \param literal_set The set of literal values to test membership
  virtual Result<R> In(
      [[maybe_unused]] const std::shared_ptr<Bound>& expr,
      [[maybe_unused]] const BoundSetPredicate::LiteralSet& literal_set) {
    return NotSupported("In operation is not supported by this visitor");
  }

  /// \brief Visit a NOT_IN set bound expression.
  /// \param expr The bound expression being tested
  /// \param literal_set The set of literal values to test membership
  virtual Result<R> NotIn(
      [[maybe_unused]] const std::shared_ptr<Bound>& expr,
      [[maybe_unused]] const BoundSetPredicate::LiteralSet& literal_set) {
    return NotSupported("NotIn operation is not supported by this visitor");
  }

  /// \brief Visit a bound predicate.
  ///
  /// This method dispatches to specific visitor methods based on the predicate
  /// type and operation.
  ///
  /// \param pred The bound predicate to visit
  Result<R> Predicate(const std::shared_ptr<BoundPredicate>& pred) override {
    ICEBERG_DCHECK(pred != nullptr, "BoundPredicate cannot be null");

    switch (pred->kind()) {
      case BoundPredicate::Kind::kUnary: {
        switch (pred->op()) {
          case Expression::Operation::kIsNull:
            return IsNull(pred->term());
          case Expression::Operation::kNotNull:
            return NotNull(pred->term());
          case Expression::Operation::kIsNan:
            return IsNaN(pred->term());
          case Expression::Operation::kNotNan:
            return NotNaN(pred->term());
          default:
            return InvalidExpression("Invalid operation for BoundUnaryPredicate: {}",
                                     ToString(pred->op()));
        }
      }
      case BoundPredicate::Kind::kLiteral: {
        const auto& literal_pred =
            internal::checked_cast<const BoundLiteralPredicate&>(*pred);
        switch (pred->op()) {
          case Expression::Operation::kLt:
            return Lt(pred->term(), literal_pred.literal());
          case Expression::Operation::kLtEq:
            return LtEq(pred->term(), literal_pred.literal());
          case Expression::Operation::kGt:
            return Gt(pred->term(), literal_pred.literal());
          case Expression::Operation::kGtEq:
            return GtEq(pred->term(), literal_pred.literal());
          case Expression::Operation::kEq:
            return Eq(pred->term(), literal_pred.literal());
          case Expression::Operation::kNotEq:
            return NotEq(pred->term(), literal_pred.literal());
          case Expression::Operation::kStartsWith:
            return StartsWith(pred->term(), literal_pred.literal());
          case Expression::Operation::kNotStartsWith:
            return NotStartsWith(pred->term(), literal_pred.literal());
          default:
            return InvalidExpression("Invalid operation for BoundLiteralPredicate: {}",
                                     ToString(pred->op()));
        }
      }
      case BoundPredicate::Kind::kSet: {
        const auto& set_pred = internal::checked_cast<const BoundSetPredicate&>(*pred);
        switch (pred->op()) {
          case Expression::Operation::kIn:
            return In(pred->term(), set_pred.literal_set());
          case Expression::Operation::kNotIn:
            return NotIn(pred->term(), set_pred.literal_set());
          default:
            return InvalidExpression("Invalid operation for BoundSetPredicate: {}",
                                     ToString(pred->op()));
        }
      }
    }

    return InvalidExpression("Unsupported bound predicate: {}", pred->ToString());
  }

  /// \brief Visit an unbound predicate.
  ///
  /// \param pred The unbound predicate
  Result<R> Predicate(const std::shared_ptr<UnboundPredicate>& pred) override {
    ICEBERG_DCHECK(pred != nullptr, "UnboundPredicate cannot be null");
    return NotSupported("Not a bound predicate: {}", pred->ToString());
  }
};

/// \brief Traverse an expression tree with a visitor.
///
/// This function traverses the given expression tree in postorder traversal and calls
/// appropriate visitor methods for each node. Results from child nodes are passed to
/// parent nodes.
///
/// \tparam R The return type produced by the visitor
/// \tparam V The visitor type (must derive from ExpressionVisitor<R>)
/// \param expr The expression to traverse
/// \param visitor The visitor to use for traversal
/// \return The result produced by the visitor for the root expression node
template <typename R, typename V>
  requires std::derived_from<V, ExpressionVisitor<R>>
Result<R> Visit(const std::shared_ptr<Expression>& expr, V& visitor) {
  ICEBERG_DCHECK(expr != nullptr, "Expression cannot be null");

  if (expr->is_bound_predicate()) {
    return visitor.Predicate(std::dynamic_pointer_cast<BoundPredicate>(expr));
  }

  if (expr->is_unbound_predicate()) {
    return visitor.Predicate(std::dynamic_pointer_cast<UnboundPredicate>(expr));
  }

  if (expr->is_bound_aggregate()) {
    return visitor.Aggregate(std::dynamic_pointer_cast<BoundAggregate>(expr));
  }

  if (expr->is_unbound_aggregate()) {
    return visitor.Aggregate(std::dynamic_pointer_cast<UnboundAggregate>(expr));
  }

  switch (expr->op()) {
    case Expression::Operation::kTrue:
      return visitor.AlwaysTrue();
    case Expression::Operation::kFalse:
      return visitor.AlwaysFalse();
    case Expression::Operation::kNot: {
      const auto& not_expr = internal::checked_pointer_cast<Not>(expr);
      ICEBERG_ASSIGN_OR_RAISE(auto child_result,
                              (Visit<R, V>(not_expr->child(), visitor)));
      return visitor.Not(std::move(child_result));
    }
    case Expression::Operation::kAnd: {
      const auto& and_expr = internal::checked_pointer_cast<And>(expr);
      ICEBERG_ASSIGN_OR_RAISE(auto left_result, (Visit<R, V>(and_expr->left(), visitor)));
      ICEBERG_ASSIGN_OR_RAISE(auto right_result,
                              (Visit<R, V>(and_expr->right(), visitor)));
      return visitor.And(std::move(left_result), std::move(right_result));
    }
    case Expression::Operation::kOr: {
      const auto& or_expr = internal::checked_pointer_cast<Or>(expr);
      ICEBERG_ASSIGN_OR_RAISE(auto left_result, (Visit<R, V>(or_expr->left(), visitor)));
      ICEBERG_ASSIGN_OR_RAISE(auto right_result,
                              (Visit<R, V>(or_expr->right(), visitor)));
      return visitor.Or(std::move(left_result), std::move(right_result));
    }
    default:
      return InvalidExpression("Unknown expression operation: {}", expr->ToString());
  }
}

}  // namespace iceberg
