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

#include <gtest/gtest.h>
#include "arrow/memory_pool.h"
#include "arrow/status.h"

#include "gandiva/projector.h"
#include "gandiva/tests/test_util.h"
#include "gandiva/tree_expr_builder.h"

namespace gandiva {

using arrow::boolean;
using arrow::int32;

class TestBooleanExpr : public ::testing::Test {
 public:
  void SetUp() { pool_ = arrow::default_memory_pool(); }

 protected:
  arrow::MemoryPool* pool_;
};

TEST_F(TestBooleanExpr, SimpleAnd) {
  // schema for input fields
  auto fielda = field("a", int32());
  auto fieldb = field("b", int32());
  auto schema = arrow::schema({fielda, fieldb});

  // output fields
  auto field_result = field("res", boolean());

  // build expression.
  // (a > 0) && (b > 0)
  auto node_a = TreeExprBuilder::MakeField(fielda);
  auto node_b = TreeExprBuilder::MakeField(fieldb);
  auto literal_0 = TreeExprBuilder::MakeLiteral((int32_t)0);
  auto a_gt_0 =
      TreeExprBuilder::MakeFunction("greater_than", {node_a, literal_0}, boolean());
  auto b_gt_0 =
      TreeExprBuilder::MakeFunction("greater_than", {node_b, literal_0}, boolean());

  auto node_and = TreeExprBuilder::MakeAnd({a_gt_0, b_gt_0});
  auto expr = TreeExprBuilder::MakeExpression(node_and, field_result);

  // Build a projector for the expressions.
  std::shared_ptr<Projector> projector;
  auto status = Projector::Make(schema, {expr}, TestConfiguration(), &projector);
  EXPECT_TRUE(status.ok());

  // FALSE_VALID && ?  => FALSE_VALID
  int num_records = 4;
  auto arraya = MakeArrowArrayInt32({-2, -2, -2, -2}, {true, true, true, true});
  auto arrayb = MakeArrowArrayInt32({-2, -2, 2, 2}, {true, false, true, false});
  auto exp = MakeArrowArrayBool({false, false, false, false}, {true, true, true, true});
  auto in_batch = arrow::RecordBatch::Make(schema, num_records, {arraya, arrayb});

  arrow::ArrayVector outputs;
  status = projector->Evaluate(*in_batch, pool_, &outputs);
  EXPECT_TRUE(status.ok());
  EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));

  // FALSE_INVALID && ?
  num_records = 4;
  arraya = MakeArrowArrayInt32({-2, -2, -2, -2}, {false, false, false, false});
  arrayb = MakeArrowArrayInt32({-2, -2, 2, 2}, {true, false, true, false});
  exp = MakeArrowArrayBool({false, false, false, false}, {true, false, false, false});
  in_batch = arrow::RecordBatch::Make(schema, num_records, {arraya, arrayb});
  outputs.clear();
  status = projector->Evaluate(*in_batch, pool_, &outputs);
  EXPECT_TRUE(status.ok());
  EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));

  // TRUE_VALID && ?
  num_records = 4;
  arraya = MakeArrowArrayInt32({2, 2, 2, 2}, {true, true, true, true});
  arrayb = MakeArrowArrayInt32({-2, -2, 2, 2}, {true, false, true, false});
  exp = MakeArrowArrayBool({false, false, true, false}, {true, false, true, false});
  in_batch = arrow::RecordBatch::Make(schema, num_records, {arraya, arrayb});
  outputs.clear();
  status = projector->Evaluate(*in_batch, pool_, &outputs);
  EXPECT_TRUE(status.ok());
  EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));

  // TRUE_INVALID && ?
  num_records = 4;
  arraya = MakeArrowArrayInt32({2, 2, 2, 2}, {false, false, false, false});
  arrayb = MakeArrowArrayInt32({-2, -2, 2, 2}, {true, false, true, false});
  exp = MakeArrowArrayBool({false, false, false, false}, {true, false, false, false});
  in_batch = arrow::RecordBatch::Make(schema, num_records, {arraya, arrayb});
  outputs.clear();
  status = projector->Evaluate(*in_batch, pool_, &outputs);
  EXPECT_TRUE(status.ok());
  EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));
}

TEST_F(TestBooleanExpr, SimpleOr) {
  // schema for input fields
  auto fielda = field("a", int32());
  auto fieldb = field("b", int32());
  auto schema = arrow::schema({fielda, fieldb});

  // output fields
  auto field_result = field("res", boolean());

  // build expression.
  // (a > 0) || (b > 0)
  auto node_a = TreeExprBuilder::MakeField(fielda);
  auto node_b = TreeExprBuilder::MakeField(fieldb);
  auto literal_0 = TreeExprBuilder::MakeLiteral((int32_t)0);
  auto a_gt_0 =
      TreeExprBuilder::MakeFunction("greater_than", {node_a, literal_0}, boolean());
  auto b_gt_0 =
      TreeExprBuilder::MakeFunction("greater_than", {node_b, literal_0}, boolean());

  auto node_or = TreeExprBuilder::MakeOr({a_gt_0, b_gt_0});
  auto expr = TreeExprBuilder::MakeExpression(node_or, field_result);

  // Build a projector for the expressions.
  std::shared_ptr<Projector> projector;
  auto status = Projector::Make(schema, {expr}, TestConfiguration(), &projector);
  EXPECT_TRUE(status.ok());

  // TRUE_VALID && ?  => TRUE_VALID
  int num_records = 4;
  auto arraya = MakeArrowArrayInt32({2, 2, 2, 2}, {true, true, true, true});
  auto arrayb = MakeArrowArrayInt32({-2, -2, 2, 2}, {true, false, true, false});
  auto exp = MakeArrowArrayBool({true, true, true, true}, {true, true, true, true});
  auto in_batch = arrow::RecordBatch::Make(schema, num_records, {arraya, arrayb});

  arrow::ArrayVector outputs;
  status = projector->Evaluate(*in_batch, pool_, &outputs);
  EXPECT_TRUE(status.ok());
  EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));

  // TRUE_INVALID && ?
  num_records = 4;
  arraya = MakeArrowArrayInt32({2, 2, 2, 2}, {false, false, false, false});
  arrayb = MakeArrowArrayInt32({-2, -2, 2, 2}, {true, false, true, false});
  exp = MakeArrowArrayBool({false, false, true, false}, {false, false, true, false});
  in_batch = arrow::RecordBatch::Make(schema, num_records, {arraya, arrayb});
  outputs.clear();
  status = projector->Evaluate(*in_batch, pool_, &outputs);
  EXPECT_TRUE(status.ok());
  EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));

  // FALSE_VALID && ?
  num_records = 4;
  arraya = MakeArrowArrayInt32({-2, -2, -2, -2}, {true, true, true, true});
  arrayb = MakeArrowArrayInt32({-2, -2, 2, 2}, {true, false, true, false});
  exp = MakeArrowArrayBool({false, false, true, false}, {true, false, true, false});
  in_batch = arrow::RecordBatch::Make(schema, num_records, {arraya, arrayb});
  outputs.clear();
  status = projector->Evaluate(*in_batch, pool_, &outputs);
  EXPECT_TRUE(status.ok());
  EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));

  // FALSE_INVALID && ?
  num_records = 4;
  arraya = MakeArrowArrayInt32({-2, -2, -2, -2}, {false, false, false, false});
  arrayb = MakeArrowArrayInt32({-2, -2, 2, 2}, {true, false, true, false});
  exp = MakeArrowArrayBool({false, false, true, false}, {false, false, true, false});
  in_batch = arrow::RecordBatch::Make(schema, num_records, {arraya, arrayb});
  outputs.clear();
  status = projector->Evaluate(*in_batch, pool_, &outputs);
  EXPECT_TRUE(status.ok());
  EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));
}

TEST_F(TestBooleanExpr, AndThree) {
  // schema for input fields
  auto fielda = field("a", int32());
  auto fieldb = field("b", int32());
  auto fieldc = field("c", int32());
  auto schema = arrow::schema({fielda, fieldb, fieldc});

  // output fields
  auto field_result = field("res", boolean());

  // build expression.
  // (a > 0) && (b > 0) && (c > 0)
  auto node_a = TreeExprBuilder::MakeField(fielda);
  auto node_b = TreeExprBuilder::MakeField(fieldb);
  auto node_c = TreeExprBuilder::MakeField(fieldc);
  auto literal_0 = TreeExprBuilder::MakeLiteral((int32_t)0);
  auto a_gt_0 =
      TreeExprBuilder::MakeFunction("greater_than", {node_a, literal_0}, boolean());
  auto b_gt_0 =
      TreeExprBuilder::MakeFunction("greater_than", {node_b, literal_0}, boolean());
  auto c_gt_0 =
      TreeExprBuilder::MakeFunction("greater_than", {node_c, literal_0}, boolean());

  auto node_and = TreeExprBuilder::MakeAnd({a_gt_0, b_gt_0, c_gt_0});
  auto expr = TreeExprBuilder::MakeExpression(node_and, field_result);

  // Build a projector for the expressions.
  std::shared_ptr<Projector> projector;
  auto status = Projector::Make(schema, {expr}, TestConfiguration(), &projector);
  EXPECT_TRUE(status.ok());

  int num_records = 8;
  std::vector<bool> validity({true, true, true, true, true, true, true, true});
  auto arraya = MakeArrowArrayInt32({2, 2, 2, 0, 2, 0, 0, 0}, validity);
  auto arrayb = MakeArrowArrayInt32({2, 2, 0, 2, 0, 2, 0, 0}, validity);
  auto arrayc = MakeArrowArrayInt32({2, 0, 2, 2, 0, 0, 2, 0}, validity);
  auto exp = MakeArrowArrayBool({true, false, false, false, false, false, false, false},
                                validity);

  auto in_batch = arrow::RecordBatch::Make(schema, num_records, {arraya, arrayb, arrayc});

  arrow::ArrayVector outputs;
  status = projector->Evaluate(*in_batch, pool_, &outputs);
  EXPECT_TRUE(status.ok());
  EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));
}

TEST_F(TestBooleanExpr, OrThree) {
  // schema for input fields
  auto fielda = field("a", int32());
  auto fieldb = field("b", int32());
  auto fieldc = field("c", int32());
  auto schema = arrow::schema({fielda, fieldb, fieldc});

  // output fields
  auto field_result = field("res", boolean());

  // build expression.
  // (a > 0) || (b > 0) || (c > 0)
  auto node_a = TreeExprBuilder::MakeField(fielda);
  auto node_b = TreeExprBuilder::MakeField(fieldb);
  auto node_c = TreeExprBuilder::MakeField(fieldc);
  auto literal_0 = TreeExprBuilder::MakeLiteral((int32_t)0);
  auto a_gt_0 =
      TreeExprBuilder::MakeFunction("greater_than", {node_a, literal_0}, boolean());
  auto b_gt_0 =
      TreeExprBuilder::MakeFunction("greater_than", {node_b, literal_0}, boolean());
  auto c_gt_0 =
      TreeExprBuilder::MakeFunction("greater_than", {node_c, literal_0}, boolean());

  auto node_or = TreeExprBuilder::MakeOr({a_gt_0, b_gt_0, c_gt_0});
  auto expr = TreeExprBuilder::MakeExpression(node_or, field_result);

  // Build a projector for the expressions.
  std::shared_ptr<Projector> projector;
  auto status = Projector::Make(schema, {expr}, TestConfiguration(), &projector);
  EXPECT_TRUE(status.ok());

  int num_records = 8;
  std::vector<bool> validity({true, true, true, true, true, true, true, true});
  auto arraya = MakeArrowArrayInt32({2, 2, 2, 0, 2, 0, 0, 0}, validity);
  auto arrayb = MakeArrowArrayInt32({2, 2, 0, 2, 0, 2, 0, 0}, validity);
  auto arrayc = MakeArrowArrayInt32({2, 0, 2, 2, 0, 0, 2, 0}, validity);
  auto exp =
      MakeArrowArrayBool({true, true, true, true, true, true, true, false}, validity);

  auto in_batch = arrow::RecordBatch::Make(schema, num_records, {arraya, arrayb, arrayc});

  arrow::ArrayVector outputs;
  status = projector->Evaluate(*in_batch, pool_, &outputs);
  EXPECT_TRUE(status.ok());
  EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));
}

TEST_F(TestBooleanExpr, BooleanAndInsideIf) {
  // schema for input fields
  auto fielda = field("a", int32());
  auto fieldb = field("b", int32());
  auto schema = arrow::schema({fielda, fieldb});

  // output fields
  auto field_result = field("res", boolean());

  // build expression.
  // if (a > 2 && b > 2)
  //   a > 3 && b > 3
  // else
  //   a > 1 && b > 1
  auto node_a = TreeExprBuilder::MakeField(fielda);
  auto node_b = TreeExprBuilder::MakeField(fieldb);
  auto literal_1 = TreeExprBuilder::MakeLiteral((int32_t)1);
  auto literal_2 = TreeExprBuilder::MakeLiteral((int32_t)2);
  auto literal_3 = TreeExprBuilder::MakeLiteral((int32_t)3);
  auto a_gt_1 =
      TreeExprBuilder::MakeFunction("greater_than", {node_a, literal_1}, boolean());
  auto a_gt_2 =
      TreeExprBuilder::MakeFunction("greater_than", {node_a, literal_2}, boolean());
  auto a_gt_3 =
      TreeExprBuilder::MakeFunction("greater_than", {node_a, literal_3}, boolean());
  auto b_gt_1 =
      TreeExprBuilder::MakeFunction("greater_than", {node_b, literal_1}, boolean());
  auto b_gt_2 =
      TreeExprBuilder::MakeFunction("greater_than", {node_b, literal_2}, boolean());
  auto b_gt_3 =
      TreeExprBuilder::MakeFunction("greater_than", {node_b, literal_3}, boolean());

  auto and_1 = TreeExprBuilder::MakeAnd({a_gt_1, b_gt_1});
  auto and_2 = TreeExprBuilder::MakeAnd({a_gt_2, b_gt_2});
  auto and_3 = TreeExprBuilder::MakeAnd({a_gt_3, b_gt_3});

  auto node_if = TreeExprBuilder::MakeIf(and_2, and_3, and_1, arrow::boolean());
  auto expr = TreeExprBuilder::MakeExpression(node_if, field_result);

  // Build a projector for the expressions.
  std::shared_ptr<Projector> projector;
  auto status = Projector::Make(schema, {expr}, TestConfiguration(), &projector);
  EXPECT_TRUE(status.ok());

  int num_records = 4;
  std::vector<bool> validity({true, true, true, true});
  auto arraya = MakeArrowArrayInt32({4, 4, 2, 1}, validity);
  auto arrayb = MakeArrowArrayInt32({5, 3, 3, 1}, validity);
  auto exp = MakeArrowArrayBool({true, false, true, false}, validity);

  auto in_batch = arrow::RecordBatch::Make(schema, num_records, {arraya, arrayb});

  arrow::ArrayVector outputs;
  status = projector->Evaluate(*in_batch, pool_, &outputs);
  EXPECT_TRUE(status.ok());
  EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));
}

TEST_F(TestBooleanExpr, IfInsideBooleanAnd) {
  // schema for input fields
  auto fielda = field("a", int32());
  auto fieldb = field("b", int32());
  auto schema = arrow::schema({fielda, fieldb});

  // output fields
  auto field_result = field("res", boolean());

  // build expression.
  // (if (a > b) a > 3 else b > 3) && (if (a > b) a > 2 else b > 2)

  auto node_a = TreeExprBuilder::MakeField(fielda);
  auto node_b = TreeExprBuilder::MakeField(fieldb);
  auto literal_2 = TreeExprBuilder::MakeLiteral((int32_t)2);
  auto literal_3 = TreeExprBuilder::MakeLiteral((int32_t)3);
  auto a_gt_b =
      TreeExprBuilder::MakeFunction("greater_than", {node_a, node_b}, boolean());
  auto a_gt_2 =
      TreeExprBuilder::MakeFunction("greater_than", {node_a, literal_2}, boolean());
  auto a_gt_3 =
      TreeExprBuilder::MakeFunction("greater_than", {node_a, literal_3}, boolean());
  auto b_gt_2 =
      TreeExprBuilder::MakeFunction("greater_than", {node_b, literal_2}, boolean());
  auto b_gt_3 =
      TreeExprBuilder::MakeFunction("greater_than", {node_b, literal_3}, boolean());

  auto if_3 = TreeExprBuilder::MakeIf(a_gt_b, a_gt_3, b_gt_3, arrow::boolean());
  auto if_2 = TreeExprBuilder::MakeIf(a_gt_b, a_gt_2, b_gt_2, arrow::boolean());
  auto node_and = TreeExprBuilder::MakeAnd({if_3, if_2});
  auto expr = TreeExprBuilder::MakeExpression(node_and, field_result);

  // Build a projector for the expressions.
  std::shared_ptr<Projector> projector;
  auto status = Projector::Make(schema, {expr}, TestConfiguration(), &projector);
  EXPECT_TRUE(status.ok());

  int num_records = 4;
  std::vector<bool> validity({true, true, true, true});
  auto arraya = MakeArrowArrayInt32({4, 3, 3, 2}, validity);
  auto arrayb = MakeArrowArrayInt32({3, 4, 2, 3}, validity);
  auto exp = MakeArrowArrayBool({true, true, false, false}, validity);

  auto in_batch = arrow::RecordBatch::Make(schema, num_records, {arraya, arrayb});

  arrow::ArrayVector outputs;
  status = projector->Evaluate(*in_batch, pool_, &outputs);
  EXPECT_TRUE(status.ok());
  EXPECT_ARROW_ARRAY_EQUALS(exp, outputs.at(0));
}

}  // namespace gandiva
