// 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 "gandiva/selection_vector.h"

#include <memory>
#include <utility>
#include <vector>

#include <gtest/gtest.h>

#include "arrow/testing/gtest_util.h"

namespace gandiva {

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

  arrow::MemoryPool* pool_;
};

static inline uint32_t RoundUpNumi64(uint32_t value) { return (value + 63) >> 6; }

TEST_F(TestSelectionVector, TestInt16Make) {
  int max_slots = 10;

  // Test with pool allocation
  std::shared_ptr<SelectionVector> selection;
  auto status = SelectionVector::MakeInt16(max_slots, pool_, &selection);
  EXPECT_EQ(status.ok(), true) << status.message();
  EXPECT_EQ(selection->GetMaxSlots(), max_slots);
  EXPECT_EQ(selection->GetNumSlots(), 0);

  // Test with pre-alloced buffer
  std::shared_ptr<SelectionVector> selection2;
  auto buffer_len = max_slots * sizeof(int16_t);
  ASSERT_OK_AND_ASSIGN(auto buffer, arrow::AllocateBuffer(buffer_len, pool_));

  status = SelectionVector::MakeInt16(max_slots, std::move(buffer), &selection2);
  EXPECT_EQ(status.ok(), true) << status.message();
  EXPECT_EQ(selection2->GetMaxSlots(), max_slots);
  EXPECT_EQ(selection2->GetNumSlots(), 0);
}

TEST_F(TestSelectionVector, TestInt16MakeNegative) {
  int max_slots = 10;

  std::shared_ptr<SelectionVector> selection;
  auto buffer_len = max_slots * sizeof(int16_t);

  // alloc a buffer that's insufficient.
  ASSERT_OK_AND_ASSIGN(auto buffer, arrow::AllocateBuffer(buffer_len - 16, pool_));

  auto status = SelectionVector::MakeInt16(max_slots, std::move(buffer), &selection);
  EXPECT_EQ(status.IsInvalid(), true);
}

TEST_F(TestSelectionVector, TestInt16Set) {
  int max_slots = 10;

  std::shared_ptr<SelectionVector> selection;
  auto status = SelectionVector::MakeInt16(max_slots, pool_, &selection);
  EXPECT_EQ(status.ok(), true) << status.message();

  selection->SetIndex(0, 100);
  EXPECT_EQ(selection->GetIndex(0), 100);

  selection->SetIndex(1, 200);
  EXPECT_EQ(selection->GetIndex(1), 200);

  selection->SetNumSlots(2);
  EXPECT_EQ(selection->GetNumSlots(), 2);

  // TopArray() should return an array with 100,200
  auto array_raw = selection->ToArray();
  const auto& array = dynamic_cast<const arrow::UInt16Array&>(*array_raw);
  EXPECT_EQ(array.length(), 2) << array_raw->ToString();
  EXPECT_EQ(array.Value(0), 100) << array_raw->ToString();
  EXPECT_EQ(array.Value(1), 200) << array_raw->ToString();
}

TEST_F(TestSelectionVector, TestInt16PopulateFromBitMap) {
  int max_slots = 200;

  std::shared_ptr<SelectionVector> selection;
  auto status = SelectionVector::MakeInt16(max_slots, pool_, &selection);
  EXPECT_EQ(status.ok(), true) << status.message();

  int bitmap_size = RoundUpNumi64(max_slots) * 8;
  std::vector<uint8_t> bitmap(bitmap_size);

  arrow::bit_util::SetBit(&bitmap[0], 0);
  arrow::bit_util::SetBit(&bitmap[0], 5);
  arrow::bit_util::SetBit(&bitmap[0], 121);
  arrow::bit_util::SetBit(&bitmap[0], 220);

  status = selection->PopulateFromBitMap(&bitmap[0], bitmap_size, max_slots - 1);
  EXPECT_EQ(status.ok(), true) << status.message();

  EXPECT_EQ(selection->GetNumSlots(), 3);
  EXPECT_EQ(selection->GetIndex(0), 0);
  EXPECT_EQ(selection->GetIndex(1), 5);
  EXPECT_EQ(selection->GetIndex(2), 121);
}

TEST_F(TestSelectionVector, TestInt16PopulateFromBitMapNegative) {
  int max_slots = 2;

  std::shared_ptr<SelectionVector> selection;
  auto status = SelectionVector::MakeInt16(max_slots, pool_, &selection);
  EXPECT_EQ(status.ok(), true) << status.message();

  int bitmap_size = 16;
  std::vector<uint8_t> bitmap(bitmap_size);

  arrow::bit_util::SetBit(&bitmap[0], 0);
  arrow::bit_util::SetBit(&bitmap[0], 1);
  arrow::bit_util::SetBit(&bitmap[0], 2);

  // The bitmap has three set bits, whereas the selection vector has capacity for only 2.
  status = selection->PopulateFromBitMap(&bitmap[0], bitmap_size, 2);
  EXPECT_EQ(status.IsInvalid(), true);
}

TEST_F(TestSelectionVector, TestInt32Set) {
  int max_slots = 10;

  std::shared_ptr<SelectionVector> selection;
  auto status = SelectionVector::MakeInt32(max_slots, pool_, &selection);
  EXPECT_EQ(status.ok(), true) << status.message();

  selection->SetIndex(0, 100);
  EXPECT_EQ(selection->GetIndex(0), 100);

  selection->SetIndex(1, 200);
  EXPECT_EQ(selection->GetIndex(1), 200);

  selection->SetIndex(2, 100000);
  EXPECT_EQ(selection->GetIndex(2), 100000);

  selection->SetNumSlots(3);
  EXPECT_EQ(selection->GetNumSlots(), 3);

  // TopArray() should return an array with 100,200,100000
  auto array_raw = selection->ToArray();
  const auto& array = dynamic_cast<const arrow::UInt32Array&>(*array_raw);
  EXPECT_EQ(array.length(), 3) << array_raw->ToString();
  EXPECT_EQ(array.Value(0), 100) << array_raw->ToString();
  EXPECT_EQ(array.Value(1), 200) << array_raw->ToString();
  EXPECT_EQ(array.Value(2), 100000) << array_raw->ToString();
}

TEST_F(TestSelectionVector, TestInt32PopulateFromBitMap) {
  int max_slots = 200;

  std::shared_ptr<SelectionVector> selection;
  auto status = SelectionVector::MakeInt32(max_slots, pool_, &selection);
  EXPECT_EQ(status.ok(), true) << status.message();

  int bitmap_size = RoundUpNumi64(max_slots) * 8;
  std::vector<uint8_t> bitmap(bitmap_size);

  arrow::bit_util::SetBit(&bitmap[0], 0);
  arrow::bit_util::SetBit(&bitmap[0], 5);
  arrow::bit_util::SetBit(&bitmap[0], 121);
  arrow::bit_util::SetBit(&bitmap[0], 220);

  status = selection->PopulateFromBitMap(&bitmap[0], bitmap_size, max_slots - 1);
  EXPECT_EQ(status.ok(), true) << status.message();

  EXPECT_EQ(selection->GetNumSlots(), 3);
  EXPECT_EQ(selection->GetIndex(0), 0);
  EXPECT_EQ(selection->GetIndex(1), 5);
  EXPECT_EQ(selection->GetIndex(2), 121);
}

TEST_F(TestSelectionVector, TestInt32MakeNegative) {
  int max_slots = 10;

  std::shared_ptr<SelectionVector> selection;
  auto buffer_len = max_slots * sizeof(int32_t);

  // alloc a buffer that's insufficient.
  ASSERT_OK_AND_ASSIGN(auto buffer, arrow::AllocateBuffer(buffer_len - 1, pool_));

  auto status = SelectionVector::MakeInt32(max_slots, std::move(buffer), &selection);
  EXPECT_EQ(status.IsInvalid(), true);
}

TEST_F(TestSelectionVector, TestInt64Set) {
  int max_slots = 10;

  std::shared_ptr<SelectionVector> selection;
  auto status = SelectionVector::MakeInt64(max_slots, pool_, &selection);
  EXPECT_EQ(status.ok(), true) << status.message();

  selection->SetIndex(0, 100);
  EXPECT_EQ(selection->GetIndex(0), 100);

  selection->SetIndex(1, 200);
  EXPECT_EQ(selection->GetIndex(1), 200);

  selection->SetIndex(2, 100000);
  EXPECT_EQ(selection->GetIndex(2), 100000);

  selection->SetNumSlots(3);
  EXPECT_EQ(selection->GetNumSlots(), 3);

  // TopArray() should return an array with 100,200,100000
  auto array_raw = selection->ToArray();
  const auto& array = dynamic_cast<const arrow::UInt64Array&>(*array_raw);
  EXPECT_EQ(array.length(), 3) << array_raw->ToString();
  EXPECT_EQ(array.Value(0), 100) << array_raw->ToString();
  EXPECT_EQ(array.Value(1), 200) << array_raw->ToString();
  EXPECT_EQ(array.Value(2), 100000) << array_raw->ToString();
}

TEST_F(TestSelectionVector, TestInt64PopulateFromBitMap) {
  int max_slots = 200;

  std::shared_ptr<SelectionVector> selection;
  auto status = SelectionVector::MakeInt64(max_slots, pool_, &selection);
  EXPECT_EQ(status.ok(), true) << status.message();

  int bitmap_size = RoundUpNumi64(max_slots) * 8;
  std::vector<uint8_t> bitmap(bitmap_size);

  arrow::bit_util::SetBit(&bitmap[0], 0);
  arrow::bit_util::SetBit(&bitmap[0], 5);
  arrow::bit_util::SetBit(&bitmap[0], 121);
  arrow::bit_util::SetBit(&bitmap[0], 220);

  status = selection->PopulateFromBitMap(&bitmap[0], bitmap_size, max_slots - 1);
  EXPECT_EQ(status.ok(), true) << status.message();

  EXPECT_EQ(selection->GetNumSlots(), 3);
  EXPECT_EQ(selection->GetIndex(0), 0);
  EXPECT_EQ(selection->GetIndex(1), 5);
  EXPECT_EQ(selection->GetIndex(2), 121);
}

TEST_F(TestSelectionVector, TestInt64MakeNegative) {
  int max_slots = 10;

  std::shared_ptr<SelectionVector> selection;
  auto buffer_len = max_slots * sizeof(int64_t);

  // alloc a buffer that's insufficient.
  ASSERT_OK_AND_ASSIGN(auto buffer, arrow::AllocateBuffer(buffer_len - 1, pool_));

  auto status = SelectionVector::MakeInt64(max_slots, std::move(buffer), &selection);
  EXPECT_EQ(status.IsInvalid(), true);
}

}  // namespace gandiva
