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

#include <arrow/array.h>
#include <arrow/array/concatenate.h>
#include <arrow/record_batch.h>
#include <arrow/util/bitmap_reader.h>
#include <arrow/util/byte_size.h>

namespace cpp11 {

const char* r6_class_name<arrow::Array>::get(const std::shared_ptr<arrow::Array>& array) {
  auto type = array->type_id();
  switch (type) {
    case arrow::Type::DICTIONARY:
      return "DictionaryArray";
    case arrow::Type::STRUCT:
      return "StructArray";
    case arrow::Type::LIST:
      return "ListArray";
    case arrow::Type::LARGE_LIST:
      return "LargeListArray";
    case arrow::Type::FIXED_SIZE_LIST:
      return "FixedSizeListArray";
    case arrow::Type::MAP:
      return "MapArray";
    case arrow::Type::EXTENSION:
      return "ExtensionArray";

    default:
      return "Array";
  }
}

}  // namespace cpp11

void arrow::r::validate_slice_offset(R_xlen_t offset, int64_t len) {
  if (offset == NA_INTEGER) {
    cpp11::stop("Slice 'offset' cannot be NA");
  }
  if (offset < 0) {
    cpp11::stop("Slice 'offset' cannot be negative");
  }
  if (offset > len) {
    cpp11::stop("Slice 'offset' greater than array length");
  }
}

void arrow::r::validate_slice_length(R_xlen_t length, int64_t available) {
  if (length == NA_INTEGER) {
    cpp11::stop("Slice 'length' cannot be NA");
  }
  if (length < 0) {
    cpp11::stop("Slice 'length' cannot be negative");
  }
  if (length > available) {
    // For an unknown reason, cpp11::warning() crashes here; however, this
    // should throw an exception if Rf_warning() jumps, so we need
    // cpp11::safe[]().
    cpp11::safe[Rf_warning]("Slice 'length' greater than available length");
  }
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> Array__Slice1(const std::shared_ptr<arrow::Array>& array,
                                            R_xlen_t offset) {
  arrow::r::validate_slice_offset(offset, array->length());
  return array->Slice(offset);
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> Array__Slice2(const std::shared_ptr<arrow::Array>& array,
                                            R_xlen_t offset, R_xlen_t length) {
  arrow::r::validate_slice_offset(offset, array->length());
  arrow::r::validate_slice_length(length, array->length() - offset);
  return array->Slice(offset, length);
}

void arrow::r::validate_index(int64_t i, int64_t len) {
  if (i == NA_INTEGER) {
    cpp11::stop("'i' cannot be NA");
  }
  if (i < 0 || i >= len) {
    cpp11::stop("subscript out of bounds");
  }
}

// [[arrow::export]]
bool Array__IsNull(const std::shared_ptr<arrow::Array>& x, R_xlen_t i) {
  arrow::r::validate_index(i, x->length());
  return x->IsNull(i);
}

// [[arrow::export]]
bool Array__IsValid(const std::shared_ptr<arrow::Array>& x, R_xlen_t i) {
  arrow::r::validate_index(i, x->length());
  return x->IsValid(i);
}

// [[arrow::export]]
r_vec_size Array__length(const std::shared_ptr<arrow::Array>& x) {
  return r_vec_size(x->length());
}

// [[arrow::export]]
r_vec_size Array__offset(const std::shared_ptr<arrow::Array>& x) {
  return r_vec_size(x->offset());
}

// [[arrow::export]]
r_vec_size Array__null_count(const std::shared_ptr<arrow::Array>& x) {
  return r_vec_size(x->null_count());
}

// [[arrow::export]]
std::shared_ptr<arrow::DataType> Array__type(const std::shared_ptr<arrow::Array>& x) {
  return x->type();
}

// [[arrow::export]]
std::string Array__ToString(const std::shared_ptr<arrow::Array>& x) {
  return x->ToString();
}

// [[arrow::export]]
arrow::Type::type Array__type_id(const std::shared_ptr<arrow::Array>& x) {
  return x->type_id();
}

// [[arrow::export]]
bool Array__Equals(const std::shared_ptr<arrow::Array>& lhs,
                   const std::shared_ptr<arrow::Array>& rhs) {
  return lhs->Equals(rhs);
}

// [[arrow::export]]
bool Array__ApproxEquals(const std::shared_ptr<arrow::Array>& lhs,
                         const std::shared_ptr<arrow::Array>& rhs) {
  return lhs->ApproxEquals(rhs);
}

// [[arrow::export]]
std::string Array__Diff(const std::shared_ptr<arrow::Array>& lhs,
                        const std::shared_ptr<arrow::Array>& rhs) {
  return lhs->Diff(*rhs);
}

// [[arrow::export]]
std::shared_ptr<arrow::ArrayData> Array__data(
    const std::shared_ptr<arrow::Array>& array) {
  return array->data();
}

// [[arrow::export]]
bool Array__RangeEquals(const std::shared_ptr<arrow::Array>& self,
                        const std::shared_ptr<arrow::Array>& other, R_xlen_t start_idx,
                        R_xlen_t end_idx, R_xlen_t other_start_idx) {
  if (start_idx == NA_INTEGER) {
    cpp11::stop("'start_idx' cannot be NA");
  }
  if (end_idx == NA_INTEGER) {
    cpp11::stop("'end_idx' cannot be NA");
  }
  if (other_start_idx == NA_INTEGER) {
    cpp11::stop("'other_start_idx' cannot be NA");
  }
  return self->RangeEquals(*other, start_idx, end_idx, other_start_idx);
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> Array__View(const std::shared_ptr<arrow::Array>& array,
                                          const std::shared_ptr<arrow::DataType>& type) {
  return ValueOrStop(array->View(type));
}

// [[arrow::export]]
void Array__Validate(const std::shared_ptr<arrow::Array>& array) {
  StopIfNotOk(array->Validate());
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> DictionaryArray__indices(
    const std::shared_ptr<arrow::DictionaryArray>& array) {
  return array->indices();
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> DictionaryArray__dictionary(
    const std::shared_ptr<arrow::DictionaryArray>& array) {
  return array->dictionary();
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> StructArray__field(
    const std::shared_ptr<arrow::StructArray>& array, int i) {
  return array->field(i);
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> StructArray__GetFieldByName(
    const std::shared_ptr<arrow::StructArray>& array, const std::string& name) {
  return array->GetFieldByName(name);
}

// [[arrow::export]]
std::shared_ptr<arrow::StructArray> StructArray__from_RecordBatch(
    const std::shared_ptr<arrow::RecordBatch>& batch) {
  return ValueOrStop(
      arrow::StructArray::Make(batch->columns(), batch->schema()->field_names()));
}

// [[arrow::export]]
cpp11::list StructArray__Flatten(const std::shared_ptr<arrow::StructArray>& array) {
  return arrow::r::to_r_list(ValueOrStop(array->Flatten()));
}

// [[arrow::export]]
std::shared_ptr<arrow::DataType> ListArray__value_type(
    const std::shared_ptr<arrow::ListArray>& array) {
  return array->value_type();
}

// [[arrow::export]]
std::shared_ptr<arrow::DataType> LargeListArray__value_type(
    const std::shared_ptr<arrow::LargeListArray>& array) {
  return array->value_type();
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> ListArray__values(
    const std::shared_ptr<arrow::ListArray>& array) {
  return array->values();
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> LargeListArray__values(
    const std::shared_ptr<arrow::LargeListArray>& array) {
  return array->values();
}

// [[arrow::export]]
int32_t ListArray__value_length(const std::shared_ptr<arrow::ListArray>& array,
                                int64_t i) {
  return array->value_length(i);
}

// [[arrow::export]]
r_vec_size LargeListArray__value_length(
    const std::shared_ptr<arrow::LargeListArray>& array, int64_t i) {
  return r_vec_size(array->value_length(i));
}

// [[arrow::export]]
int FixedSizeListArray__value_length(
    const std::shared_ptr<arrow::FixedSizeListArray>& array, int64_t i) {
  return array->value_length(i);
}

// [[arrow::export]]
int32_t ListArray__value_offset(const std::shared_ptr<arrow::ListArray>& array,
                                int64_t i) {
  return array->value_offset(i);
}

// [[arrow::export]]
r_vec_size LargeListArray__value_offset(
    const std::shared_ptr<arrow::LargeListArray>& array, int64_t i) {
  return r_vec_size(array->value_offset(i));
}

// [[arrow::export]]
r_vec_size FixedSizeListArray__value_offset(
    const std::shared_ptr<arrow::FixedSizeListArray>& array, int64_t i) {
  return r_vec_size(array->value_offset(i));
}

// [[arrow::export]]
cpp11::writable::integers ListArray__raw_value_offsets(
    const std::shared_ptr<arrow::ListArray>& array) {
  auto offsets = array->raw_value_offsets();
  return cpp11::writable::integers(offsets, offsets + array->length());
}

// [[arrow::export]]
cpp11::writable::doubles LargeListArray__raw_value_offsets(
    const std::shared_ptr<arrow::LargeListArray>& array) {
  auto offsets = array->raw_value_offsets();
  return cpp11::writable::doubles(offsets, offsets + array->length());
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> MapArray__keys(
    const std::shared_ptr<arrow::MapArray>& array) {
  return array->keys();
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> MapArray__items(
    const std::shared_ptr<arrow::MapArray>& array) {
  return array->items();
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> MapArray__keys_nested(
    const std::shared_ptr<arrow::MapArray>& array) {
  return ValueOrStop(arrow::ListArray::FromArrays(*(array->offsets()), *(array->keys())));
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> MapArray__items_nested(
    const std::shared_ptr<arrow::MapArray>& array) {
  return ValueOrStop(
      arrow::ListArray::FromArrays(*(array->offsets()), *(array->items())));
}

// [[arrow::export]]
bool Array__Same(const std::shared_ptr<arrow::Array>& x,
                 const std::shared_ptr<arrow::Array>& y) {
  return x.get() == y.get();
}

// [[arrow::export]]
r_vec_size Array__ReferencedBufferSize(const std::shared_ptr<arrow::Array>& x) {
  return r_vec_size(ValueOrStop(arrow::util::ReferencedBufferSize(*x)));
}

// [[arrow::export]]
std::shared_ptr<arrow::Array> arrow__Concatenate(cpp11::list dots) {
  arrow::ArrayVector vector;
  vector.reserve(dots.size());

  for (const cpp11::sexp& item : dots) {
    vector.push_back(cpp11::as_cpp<std::shared_ptr<arrow::Array>>(item));
  }

  return ValueOrStop(arrow::Concatenate(vector));
}
