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

#ifndef ORC_SRC_SEARCHARGUMENT_HH
#define ORC_SRC_SEARCHARGUMENT_HH

#include "ExpressionTree.hh"
#include "orc/sargs/SearchArgument.hh"
#include "sargs/PredicateLeaf.hh"
#include "wrap/orc-proto-wrapper.hh"

#include <deque>
#include <stdexcept>
#include <unordered_map>

namespace orc {

  /**
   * Primary interface for a search argument, which are the subset of predicates
   * that can be pushed down to the RowReader. Each SearchArgument consists
   * of a series of search clauses that must each be true for the row to be
   * accepted by the filter.
   *
   * This requires that the filter be normalized into conjunctive normal form
   * (<a href="https://en.wikipedia.org/wiki/Conjunctive_normal_form">CNF</a>).
   */
  class SearchArgumentImpl : public SearchArgument {
   public:
    SearchArgumentImpl(TreeNode root, const std::vector<PredicateLeaf>& leaves);

    /**
     * Get the leaf predicates that are required to evaluate the predicate. The
     * list will have the duplicates removed.
     * @return the list of leaf predicates
     */
    const std::vector<PredicateLeaf>& getLeaves() const;

    /**
     * Get the expression tree. This should only needed for file formats that
     * need to translate the expression to an internal form.
     */
    const ExpressionTree* getExpression() const;

    /**
     * Evaluate the entire predicate based on the values for the leaf predicates.
     * @param leaves the value of each leaf predicate
     * @return the value of the entire predicate
     */
    TruthValue evaluate(const std::vector<TruthValue>& leaves) const override;

    std::string toString() const override;

   private:
    std::shared_ptr<ExpressionTree> expressionTree_;
    std::vector<PredicateLeaf> leaves_;
  };

  /**
   * A builder object to create a SearchArgument from expressions. The user
   * must call startOr, startAnd, or startNot before adding any leaves.
   */
  class SearchArgumentBuilderImpl : public SearchArgumentBuilder {
   public:
    SearchArgumentBuilderImpl();

    /**
     * Start building an or operation and push it on the stack.
     * @return this
     */
    SearchArgumentBuilder& startOr() override;

    /**
     * Start building an and operation and push it on the stack.
     * @return this
     */
    SearchArgumentBuilder& startAnd() override;

    /**
     * Start building a not operation and push it on the stack.
     * @return this
     */
    SearchArgumentBuilder& startNot() override;

    /**
     * Finish the current operation and pop it off of the stack. Each start
     * call must have a matching end.
     * @return this
     */
    SearchArgumentBuilder& end() override;

    /**
     * Add a less than leaf to the current item on the stack.
     * @param column the field name of the column
     * @param type the type of the expression
     * @param literal the literal
     * @return this
     */
    SearchArgumentBuilder& lessThan(const std::string& column, PredicateDataType type,
                                    Literal literal) override;

    /**
     * Add a less than leaf to the current item on the stack.
     * @param columnId the column id of the column
     * @param type the type of the expression
     * @param literal the literal
     * @return this
     */
    SearchArgumentBuilder& lessThan(uint64_t columnId, PredicateDataType type,
                                    Literal literal) override;

    /**
     * Add a less than equals leaf to the current item on the stack.
     * @param column the field name of the column
     * @param type the type of the expression
     * @param literal the literal
     * @return this
     */
    SearchArgumentBuilder& lessThanEquals(const std::string& column, PredicateDataType type,
                                          Literal literal) override;

    /**
     * Add a less than equals leaf to the current item on the stack.
     * @param columnId the column id of the column
     * @param type the type of the expression
     * @param literal the literal
     * @return this
     */
    SearchArgumentBuilder& lessThanEquals(uint64_t columnId, PredicateDataType type,
                                          Literal literal) override;

    /**
     * Add an equals leaf to the current item on the stack.
     * @param column the field name of the column
     * @param type the type of the expression
     * @param literal the literal
     * @return this
     */
    SearchArgumentBuilder& equals(const std::string& column, PredicateDataType type,
                                  Literal literal) override;

    /**
     * Add an equals leaf to the current item on the stack.
     * @param columnId the column id of the column
     * @param type the type of the expression
     * @param literal the literal
     * @return this
     */
    SearchArgumentBuilder& equals(uint64_t columnId, PredicateDataType type,
                                  Literal literal) override;

    /**
     * Add a null safe equals leaf to the current item on the stack.
     * @param column the field name of the column
     * @param type the type of the expression
     * @param literal the literal
     * @return this
     */
    SearchArgumentBuilder& nullSafeEquals(const std::string& column, PredicateDataType type,
                                          Literal literal) override;

    /**
     * Add a null safe equals leaf to the current item on the stack.
     * @param columnId the column id of the column
     * @param type the type of the expression
     * @param literal the literal
     * @return this
     */
    SearchArgumentBuilder& nullSafeEquals(uint64_t columnId, PredicateDataType type,
                                          Literal literal) override;

    /**
     * Add an in leaf to the current item on the stack.
     * @param column the field name of the column
     * @param type the type of the expression
     * @param literals the literals
     * @return this
     */
    SearchArgumentBuilder& in(const std::string& column, PredicateDataType type,
                              const std::initializer_list<Literal>& literals) override;

    /**
     * Add an in leaf to the current item on the stack.
     * @param columnId the column id of the column
     * @param type the type of the expression
     * @param literals the literals
     * @return this
     */
    SearchArgumentBuilder& in(uint64_t columnId, PredicateDataType type,
                              const std::initializer_list<Literal>& literals) override;

    /**
     * Add an in leaf to the current item on the stack.
     * @param column the field name of the column
     * @param type the type of the expression
     * @param literals the literals
     * @return this
     */
    SearchArgumentBuilder& in(const std::string& column, PredicateDataType type,
                              const std::vector<Literal>& literals) override;

    /**
     * Add an in leaf to the current item on the stack.
     * @param columnId the column id of the column
     * @param type the type of the expression
     * @param literals the literals
     * @return this
     */
    SearchArgumentBuilder& in(uint64_t columnId, PredicateDataType type,
                              const std::vector<Literal>& literals) override;

    /**
     * Add an is null leaf to the current item on the stack.
     * @param column the field name of the column
     * @param type the type of the expression
     * @return this
     */
    SearchArgumentBuilder& isNull(const std::string& column, PredicateDataType type) override;

    /**
     * Add an is null leaf to the current item on the stack.
     * @param columnId the column id of the column
     * @param type the type of the expression
     * @return this
     */
    SearchArgumentBuilder& isNull(uint64_t columnId, PredicateDataType type) override;

    /**
     * Add a between leaf to the current item on the stack.
     * @param column the field name of the column
     * @param type the type of the expression
     * @param lower the literal
     * @param upper the literal
     * @return this
     */
    SearchArgumentBuilder& between(const std::string& column, PredicateDataType type, Literal lower,
                                   Literal upper) override;

    /**
     * Add a between leaf to the current item on the stack.
     * @param columnId the column id of the column
     * @param type the type of the expression
     * @param lower the literal
     * @param upper the literal
     * @return this
     */
    SearchArgumentBuilder& between(uint64_t columnId, PredicateDataType type, Literal lower,
                                   Literal upper) override;

    /**
     * Add a truth value to the expression.
     * @param truth truth value
     * @return this
     */
    SearchArgumentBuilder& literal(TruthValue truth) override;

    /**
     * Build and return the SearchArgument that has been defined. All of the
     * starts must have been ended before this call.
     * @return the new SearchArgument
     */
    std::unique_ptr<SearchArgument> build() override;

    /**
     * Add a maybe leaf to the current item on the stack.
     * @return this
     */
    SearchArgumentBuilder& maybe() override;

   private:
    SearchArgumentBuilder& start(ExpressionTree::Operator op);
    size_t addLeaf(PredicateLeaf leaf);

    static bool isInvalidColumn(const std::string& column);
    static bool isInvalidColumn(uint64_t columnId);

    template <typename T>
    SearchArgumentBuilder& compareOperator(PredicateLeaf::Operator op, T column,
                                           PredicateDataType type, Literal literal);

    template <typename T, typename CONTAINER>
    SearchArgumentBuilder& addChildForIn(T column, PredicateDataType type,
                                         const CONTAINER& literals);

    template <typename T>
    SearchArgumentBuilder& addChildForIsNull(T column, PredicateDataType type);

    template <typename T>
    SearchArgumentBuilder& addChildForBetween(T column, PredicateDataType type, Literal lower,
                                              Literal upper);

   public:
    static TreeNode pushDownNot(TreeNode root);
    static TreeNode foldMaybe(TreeNode expr);
    static TreeNode flatten(TreeNode root);
    static TreeNode convertToCNF(TreeNode root);

   private:
    std::deque<TreeNode> currTree_;
    std::unordered_map<PredicateLeaf, size_t, PredicateLeafHash, PredicateLeafComparator> leaves_;
    std::shared_ptr<ExpressionTree> root_;
  };

}  // namespace orc

#endif  // ORC_SRC_SEARCHARGUMENT_HH
