/*
 * 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-glib/buffer.hpp>
#include <arrow-glib/error.hpp>
#include <arrow-glib/input-stream.hpp>
#include <arrow-glib/ipc-options.hpp>
#include <arrow-glib/output-stream.hpp>
#include <arrow-glib/readable.hpp>
#include <arrow-glib/record-batch.hpp>
#include <arrow-glib/schema.hpp>

#include <arrow-cuda-glib/cuda.hpp>

G_BEGIN_DECLS

/**
 * SECTION: cuda
 * @section_id: cuda-classes
 * @title: CUDA related classes
 * @include: arrow-cuda-glib/arrow-cuda-glib.h
 *
 * The following classes provide CUDA support for Apache Arrow data.
 *
 * #GArrowCUDADeviceManager is the starting point. You need at
 * least one #GArrowCUDAContext to process Apache Arrow data on
 * NVIDIA GPU.
 *
 * #GArrowCUDAContext is a class to keep context for one GPU. You
 * need to create #GArrowCUDAContext for each GPU that you want to
 * use. You can create #GArrowCUDAContext by
 * garrow_cuda_device_manager_get_context().
 *
 * #GArrowCUDABuffer is a class for data on GPU. You can copy data
 * on GPU to/from CPU by garrow_cuda_buffer_copy_to_host() and
 * garrow_cuda_buffer_copy_from_host(). You can share data on GPU
 * with other processes by garrow_cuda_buffer_export() and
 * garrow_cuda_buffer_new_ipc().
 *
 * #GArrowCUDAHostBuffer is a class for data on CPU that is
 * directly accessible from GPU.
 *
 * #GArrowCUDAIPCMemoryHandle is a class to share data on GPU with
 * other processes. You can export your data on GPU to other processes
 * by garrow_cuda_buffer_export() and
 * garrow_cuda_ipc_memory_handle_new(). You can import other
 * process data on GPU by garrow_cuda_ipc_memory_handle_new() and
 * garrow_cuda_buffer_new_ipc().
 *
 * #GArrowCUDABufferInputStream is a class to read data in
 * #GArrowCUDABuffer.
 *
 * #GArrowCUDABufferOutputStream is a class to write data into
 * #GArrowCUDABuffer.
 */

G_DEFINE_TYPE(GArrowCUDADeviceManager,
              garrow_cuda_device_manager,
              G_TYPE_OBJECT)

static void
garrow_cuda_device_manager_init(GArrowCUDADeviceManager *object)
{
}

static void
garrow_cuda_device_manager_class_init(GArrowCUDADeviceManagerClass *klass)
{
}

/**
 * garrow_cuda_device_manager_new:
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: A newly created #GArrowCUDADeviceManager on success,
 *   %NULL on error.
 *
 * Since: 0.8.0
 */
GArrowCUDADeviceManager *
garrow_cuda_device_manager_new(GError **error)
{
  auto arrow_manager = arrow::cuda::CudaDeviceManager::Instance();
  if (garrow::check(error, arrow_manager, "[cuda][device-manager][new]")) {
    auto manager = g_object_new(GARROW_CUDA_TYPE_DEVICE_MANAGER,
                                NULL);
    return GARROW_CUDA_DEVICE_MANAGER(manager);
  } else {
    return NULL;
  }
}

/**
 * garrow_cuda_device_manager_get_context:
 * @manager: A #GArrowCUDADeviceManager.
 * @gpu_number: A GPU device number for the target context.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: (transfer full): A newly created #GArrowCUDAContext on
 *   success, %NULL on error. Contexts for the same GPU device number
 *   share the same data internally.
 *
 * Since: 0.8.0
 */
GArrowCUDAContext *
garrow_cuda_device_manager_get_context(GArrowCUDADeviceManager *manager,
                                       gint gpu_number,
                                       GError **error)
{
  auto arrow_manager = arrow::cuda::CudaDeviceManager::Instance();
  auto arrow_cuda_context = (*arrow_manager)->GetContext(gpu_number);
  if (garrow::check(error, arrow_cuda_context,
                    "[cuda][device-manager][get-context]]")) {
    return garrow_cuda_context_new_raw(&(*arrow_cuda_context));
  } else {
    return NULL;
  }
}

/**
 * garrow_cuda_device_manager_get_n_devices:
 * @manager: A #GArrowCUDADeviceManager.
 *
 * Returns: The number of GPU devices.
 *
 * Since: 0.8.0
 */
gsize
garrow_cuda_device_manager_get_n_devices(GArrowCUDADeviceManager *manager)
{
  auto arrow_manager = arrow::cuda::CudaDeviceManager::Instance();
  return (*arrow_manager)->num_devices();
}


typedef struct GArrowCUDAContextPrivate_ {
  std::shared_ptr<arrow::cuda::CudaContext> context;
} GArrowCUDAContextPrivate;

enum {
  PROP_CONTEXT = 1
};

G_DEFINE_TYPE_WITH_PRIVATE(GArrowCUDAContext,
                           garrow_cuda_context,
                           G_TYPE_OBJECT)

#define GARROW_CUDA_CONTEXT_GET_PRIVATE(object) \
  static_cast<GArrowCUDAContextPrivate *>(      \
    garrow_cuda_context_get_instance_private(   \
      GARROW_CUDA_CONTEXT(object)))

static void
garrow_cuda_context_finalize(GObject *object)
{
  auto priv = GARROW_CUDA_CONTEXT_GET_PRIVATE(object);

  priv->context.~shared_ptr();

  G_OBJECT_CLASS(garrow_cuda_context_parent_class)->finalize(object);
}

static void
garrow_cuda_context_set_property(GObject *object,
                                 guint prop_id,
                                 const GValue *value,
                                 GParamSpec *pspec)
{
  auto priv = GARROW_CUDA_CONTEXT_GET_PRIVATE(object);

  switch (prop_id) {
  case PROP_CONTEXT:
    priv->context =
      *static_cast<std::shared_ptr<arrow::cuda::CudaContext> *>(g_value_get_pointer(value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    break;
  }
}

static void
garrow_cuda_context_get_property(GObject *object,
                                 guint prop_id,
                                 GValue *value,
                                 GParamSpec *pspec)
{
  switch (prop_id) {
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    break;
  }
}

static void
garrow_cuda_context_init(GArrowCUDAContext *object)
{
  auto priv = GARROW_CUDA_CONTEXT_GET_PRIVATE(object);
  new(&priv->context) std::shared_ptr<arrow::cuda::CudaContext>;
}

static void
garrow_cuda_context_class_init(GArrowCUDAContextClass *klass)
{
  GParamSpec *spec;

  auto gobject_class = G_OBJECT_CLASS(klass);

  gobject_class->finalize     = garrow_cuda_context_finalize;
  gobject_class->set_property = garrow_cuda_context_set_property;
  gobject_class->get_property = garrow_cuda_context_get_property;

  /**
   * GArrowCUDAContext:context:
   *
   * Since: 0.8.0
   */
  spec = g_param_spec_pointer("context",
                              "Context",
                              "The raw std::shared_ptr<arrow::cuda::CudaContext>",
                              static_cast<GParamFlags>(G_PARAM_WRITABLE |
                                                       G_PARAM_CONSTRUCT_ONLY));
  g_object_class_install_property(gobject_class, PROP_CONTEXT, spec);
}

/**
 * garrow_cuda_context_get_allocated_size:
 * @context: A #GArrowCUDAContext.
 *
 * Returns: The allocated memory by this context in bytes.
 *
 * Since: 0.8.0
 */
gint64
garrow_cuda_context_get_allocated_size(GArrowCUDAContext *context)
{
  auto arrow_context = garrow_cuda_context_get_raw(context);
  return arrow_context->bytes_allocated();
}


G_DEFINE_TYPE(GArrowCUDABuffer,
              garrow_cuda_buffer,
              GARROW_TYPE_BUFFER)

static void
garrow_cuda_buffer_init(GArrowCUDABuffer *object)
{
}

static void
garrow_cuda_buffer_class_init(GArrowCUDABufferClass *klass)
{
}

/**
 * garrow_cuda_buffer_new:
 * @context: A #GArrowCUDAContext.
 * @size: The number of bytes to be allocated on GPU device for this context.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: (transfer full): A newly created #GArrowCUDABuffer on
 *   success, %NULL on error.
 *
 * Since: 0.8.0
 */
GArrowCUDABuffer *
garrow_cuda_buffer_new(GArrowCUDAContext *context,
                       gint64 size,
                       GError **error)
{
  auto arrow_context = garrow_cuda_context_get_raw(context);
  auto arrow_buffer = arrow_context->Allocate(size);
  if (garrow::check(error, arrow_buffer, "[cuda][buffer][new]")) {
    return garrow_cuda_buffer_new_raw(&(*arrow_buffer));
  } else {
    return NULL;
  }
}

/**
 * garrow_cuda_buffer_new_ipc:
 * @context: A #GArrowCUDAContext.
 * @handle: A #GArrowCUDAIPCMemoryHandle to be communicated.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: (transfer full): A newly created #GArrowCUDABuffer on
 *   success, %NULL on error. The buffer has data from the IPC target.
 *
 * Since: 0.8.0
 */
GArrowCUDABuffer *
garrow_cuda_buffer_new_ipc(GArrowCUDAContext *context,
                           GArrowCUDAIPCMemoryHandle *handle,
                           GError **error)
{
  auto arrow_context = garrow_cuda_context_get_raw(context);
  auto arrow_handle = garrow_cuda_ipc_memory_handle_get_raw(handle);
  auto arrow_buffer = arrow_context->OpenIpcBuffer(*arrow_handle);
  if (garrow::check(error, arrow_buffer, "[cuda][buffer][new-ipc]")) {
    return garrow_cuda_buffer_new_raw(&(*arrow_buffer));
  } else {
    return NULL;
  }
}

/**
 * garrow_cuda_buffer_new_record_batch:
 * @context: A #GArrowCUDAContext.
 * @record_batch: A #GArrowRecordBatch to be serialized.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: (transfer full): A newly created #GArrowCUDABuffer on
 *   success, %NULL on error. The buffer has serialized record batch
 *   data.
 *
 * Since: 0.8.0
 */
GArrowCUDABuffer *
garrow_cuda_buffer_new_record_batch(GArrowCUDAContext *context,
                                    GArrowRecordBatch *record_batch,
                                    GError **error)
{
  auto arrow_context = garrow_cuda_context_get_raw(context);
  auto arrow_record_batch = garrow_record_batch_get_raw(record_batch);
  auto arrow_buffer = arrow::cuda::SerializeRecordBatch(*arrow_record_batch,
                                                        arrow_context.get());
  if (garrow::check(error, arrow_buffer, "[cuda][buffer][new-record-batch]")) {
    return garrow_cuda_buffer_new_raw(&(*arrow_buffer));
  } else {
    return NULL;
  }
}

/**
 * garrow_cuda_buffer_copy_to_host:
 * @buffer: A #GArrowCUDABuffer.
 * @position: The offset of memory on GPU device to be copied.
 * @size: The size of memory on GPU device to be copied in bytes.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: (transfer full): A #GBytes that have copied memory on CPU
 *   host on success, %NULL on error.
 *
 * Since: 0.8.0
 */
GBytes *
garrow_cuda_buffer_copy_to_host(GArrowCUDABuffer *buffer,
                                gint64 position,
                                gint64 size,
                                GError **error)
{
  auto arrow_buffer = garrow_cuda_buffer_get_raw(buffer);
  auto data = static_cast<uint8_t *>(g_malloc(size));
  auto status = arrow_buffer->CopyToHost(position, size, data);
  if (garrow_error_check(error, status, "[cuda][buffer][copy-to-host]")) {
    return g_bytes_new_take(data, size);
  } else {
    g_free(data);
    return NULL;
  }
}

/**
 * garrow_cuda_buffer_copy_from_host:
 * @buffer: A #GArrowCUDABuffer.
 * @data: (array length=size): Data on CPU host to be copied.
 * @size: The size of data on CPU host to be copied in bytes.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: %TRUE on success, %FALSE if there was an error.
 *
 * Since: 0.8.0
 */
gboolean
garrow_cuda_buffer_copy_from_host(GArrowCUDABuffer *buffer,
                                  const guint8 *data,
                                  gint64 size,
                                  GError **error)
{
  auto arrow_buffer = garrow_cuda_buffer_get_raw(buffer);
  auto status = arrow_buffer->CopyFromHost(0, data, size);
  return garrow_error_check(error,
                            status,
                            "[cuda][buffer][copy-from-host]");
}

/**
 * garrow_cuda_buffer_export:
 * @buffer: A #GArrowCUDABuffer.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: (transfer full): A newly created
 *   #GArrowCUDAIPCMemoryHandle to handle the exported buffer on
 *   success, %NULL on error
 *
 * Since: 0.8.0
 */
GArrowCUDAIPCMemoryHandle *
garrow_cuda_buffer_export(GArrowCUDABuffer *buffer, GError **error)
{
  auto arrow_buffer = garrow_cuda_buffer_get_raw(buffer);
  auto arrow_handle = arrow_buffer->ExportForIpc();
  if (garrow::check(error, arrow_handle, "[cuda][buffer][export-for-ipc]")) {
    return garrow_cuda_ipc_memory_handle_new_raw(&(*arrow_handle));
  } else {
    return NULL;
  }
}

/**
 * garrow_cuda_buffer_get_context:
 * @buffer: A #GArrowCUDABuffer.
 *
 * Returns: (transfer full): A newly created #GArrowCUDAContext for the
 *   buffer. Contexts for the same buffer share the same data internally.
 *
 * Since: 0.8.0
 */
GArrowCUDAContext *
garrow_cuda_buffer_get_context(GArrowCUDABuffer *buffer)
{
  auto arrow_buffer = garrow_cuda_buffer_get_raw(buffer);
  auto arrow_context = arrow_buffer->context();
  return garrow_cuda_context_new_raw(&arrow_context);
}

/**
 * garrow_cuda_buffer_read_record_batch:
 * @buffer: A #GArrowCUDABuffer.
 * @schema: A #GArrowSchema for record batch.
 * @options: (nullable): A #GArrowReadOptions.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: (transfer full): A newly created #GArrowRecordBatch on
 *   success, %NULL on error. The record batch data is located on GPU.
 *
 * Since: 0.8.0
 */
GArrowRecordBatch *
garrow_cuda_buffer_read_record_batch(GArrowCUDABuffer *buffer,
                                     GArrowSchema *schema,
                                     GArrowReadOptions *options,
                                     GError **error)
{
  auto arrow_buffer = garrow_cuda_buffer_get_raw(buffer);
  auto arrow_schema = garrow_schema_get_raw(schema);

  if (options) {
    auto arrow_options = garrow_read_options_get_raw(options);
    auto arrow_dictionary_memo =
      garrow_read_options_get_dictionary_memo_raw(options);
    auto arrow_record_batch =
      arrow::cuda::ReadRecordBatch(arrow_schema,
                                   arrow_dictionary_memo,
                                   arrow_buffer,
                                   arrow_options->memory_pool);
    if (garrow::check(error, arrow_record_batch,
                      "[cuda][buffer][read-record-batch]")) {
      return garrow_record_batch_new_raw(&(*arrow_record_batch));
    } else {
      return NULL;
    }
  } else {
    auto arrow_pool = arrow::default_memory_pool();
    auto arrow_record_batch =
      arrow::cuda::ReadRecordBatch(arrow_schema,
                                   nullptr,
                                   arrow_buffer,
                                   arrow_pool);
    if (garrow::check(error, arrow_record_batch,
                      "[cuda][buffer][read-record-batch]")) {
      return garrow_record_batch_new_raw(&(*arrow_record_batch));
    } else {
      return NULL;
    }
  }
}


G_DEFINE_TYPE(GArrowCUDAHostBuffer,
              garrow_cuda_host_buffer,
              GARROW_TYPE_MUTABLE_BUFFER)

static void
garrow_cuda_host_buffer_init(GArrowCUDAHostBuffer *object)
{
}

static void
garrow_cuda_host_buffer_class_init(GArrowCUDAHostBufferClass *klass)
{
}

/**
 * garrow_cuda_host_buffer_new:
 * @gpu_number: A GPU device number for the target context.
 * @size: The number of bytes to be allocated on CPU host.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: A newly created #GArrowCUDAHostBuffer on success,
 *   %NULL on error. The allocated memory is accessible from GPU
 *   device for the @context.
 *
 * Since: 0.8.0
 */
GArrowCUDAHostBuffer *
garrow_cuda_host_buffer_new(gint gpu_number, gint64 size, GError **error)
{
  auto arrow_manager = arrow::cuda::CudaDeviceManager::Instance();
  auto arrow_buffer = (*arrow_manager)->AllocateHost(gpu_number, size);
  if (garrow::check(error, arrow_buffer, "[cuda][host-buffer][new]")) {
    return garrow_cuda_host_buffer_new_raw(&(*arrow_buffer));
  } else {
    return NULL;
  }
}


typedef struct GArrowCUDAIPCMemoryHandlePrivate_ {
  std::shared_ptr<arrow::cuda::CudaIpcMemHandle> ipc_memory_handle;
} GArrowCUDAIPCMemoryHandlePrivate;

enum {
  PROP_IPC_MEMORY_HANDLE = 1
};

G_DEFINE_TYPE_WITH_PRIVATE(GArrowCUDAIPCMemoryHandle,
                           garrow_cuda_ipc_memory_handle,
                           G_TYPE_OBJECT)

#define GARROW_CUDA_IPC_MEMORY_HANDLE_GET_PRIVATE(object)       \
  static_cast<GArrowCUDAIPCMemoryHandlePrivate *>(              \
    garrow_cuda_ipc_memory_handle_get_instance_private(         \
      GARROW_CUDA_IPC_MEMORY_HANDLE(object)))

static void
garrow_cuda_ipc_memory_handle_finalize(GObject *object)
{
  auto priv = GARROW_CUDA_IPC_MEMORY_HANDLE_GET_PRIVATE(object);

  priv->ipc_memory_handle = nullptr;

  G_OBJECT_CLASS(garrow_cuda_ipc_memory_handle_parent_class)->finalize(object);
}

static void
garrow_cuda_ipc_memory_handle_set_property(GObject *object,
                                           guint prop_id,
                                           const GValue *value,
                                           GParamSpec *pspec)
{
  auto priv = GARROW_CUDA_IPC_MEMORY_HANDLE_GET_PRIVATE(object);

  switch (prop_id) {
  case PROP_IPC_MEMORY_HANDLE:
    priv->ipc_memory_handle =
      *static_cast<std::shared_ptr<arrow::cuda::CudaIpcMemHandle> *>(g_value_get_pointer(value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    break;
  }
}

static void
garrow_cuda_ipc_memory_handle_get_property(GObject *object,
                                           guint prop_id,
                                           GValue *value,
                                           GParamSpec *pspec)
{
  switch (prop_id) {
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    break;
  }
}

static void
garrow_cuda_ipc_memory_handle_init(GArrowCUDAIPCMemoryHandle *object)
{
}

static void
garrow_cuda_ipc_memory_handle_class_init(GArrowCUDAIPCMemoryHandleClass *klass)
{
  GParamSpec *spec;

  auto gobject_class = G_OBJECT_CLASS(klass);

  gobject_class->finalize     = garrow_cuda_ipc_memory_handle_finalize;
  gobject_class->set_property = garrow_cuda_ipc_memory_handle_set_property;
  gobject_class->get_property = garrow_cuda_ipc_memory_handle_get_property;

  /**
   * GArrowCUDAIPCMemoryHandle:ipc-memory-handle:
   *
   * Since: 0.8.0
   */
  spec = g_param_spec_pointer("ipc-memory-handle",
                              "IPC Memory Handle",
                              "The raw std::shared_ptr<arrow::cuda::CudaIpcMemHandle>",
                              static_cast<GParamFlags>(G_PARAM_WRITABLE |
                                                       G_PARAM_CONSTRUCT_ONLY));
  g_object_class_install_property(gobject_class, PROP_IPC_MEMORY_HANDLE, spec);
}

/**
 * garrow_cuda_ipc_memory_handle_new:
 * @data: (array length=size): A serialized #GArrowCUDAIPCMemoryHandle.
 * @size: The size of data.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: (transfer full): A newly created #GArrowCUDAIPCMemoryHandle
 *   on success, %NULL on error.
 *
 * Since: 0.8.0
 */
GArrowCUDAIPCMemoryHandle *
garrow_cuda_ipc_memory_handle_new(const guint8 *data,
                                  gsize size,
                                  GError **error)
{
  auto arrow_handle = arrow::cuda::CudaIpcMemHandle::FromBuffer(data);
  if (garrow::check(error, arrow_handle, "[cuda][ipc-memory-handle][new]")) {
    return garrow_cuda_ipc_memory_handle_new_raw(&(*arrow_handle));
  } else {
    return NULL;
  }
}

/**
 * garrow_cuda_ipc_memory_handle_serialize:
 * @handle: A #GArrowCUDAIPCMemoryHandle.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: (transfer full): A newly created #GArrowBuffer on success,
 *   %NULL on error. The buffer has serialized @handle. The serialized
 *   @handle can be deserialized by garrow_cuda_ipc_memory_handle_new()
 *   in other process.
 *
 * Since: 0.8.0
 */
GArrowBuffer *
garrow_cuda_ipc_memory_handle_serialize(GArrowCUDAIPCMemoryHandle *handle,
                                        GError **error)
{
  auto arrow_handle = garrow_cuda_ipc_memory_handle_get_raw(handle);
  auto arrow_buffer = arrow_handle->Serialize(arrow::default_memory_pool());
  if (garrow::check(error, arrow_buffer,
                    "[cuda][ipc-memory-handle][serialize]")) {
    return garrow_buffer_new_raw(&(*arrow_buffer));
  } else {
    return NULL;
  }
}

static GArrowBuffer *
garrow_cuda_buffer_input_stream_buffer_new_raw_readable_interface(std::shared_ptr<arrow::Buffer> *arrow_buffer)
{
  auto arrow_cuda_buffer =
    reinterpret_cast<std::shared_ptr<arrow::cuda::CudaBuffer> *>(arrow_buffer);
  auto cuda_buffer = garrow_cuda_buffer_new_raw(arrow_cuda_buffer);
  return GARROW_BUFFER(cuda_buffer);
}

static std::shared_ptr<arrow::io::Readable>
garrow_cuda_buffer_input_stream_get_raw_readable_interface(GArrowReadable *readable)
{
  auto input_stream = GARROW_INPUT_STREAM(readable);
  auto arrow_input_stream = garrow_input_stream_get_raw(input_stream);
  return arrow_input_stream;
}

static void
garrow_cuda_buffer_input_stream_readable_interface_init(GArrowReadableInterface *iface)
{
  iface->buffer_new_raw =
    garrow_cuda_buffer_input_stream_buffer_new_raw_readable_interface;
  iface->get_raw =
    garrow_cuda_buffer_input_stream_get_raw_readable_interface;
}

G_DEFINE_TYPE_WITH_CODE(
  GArrowCUDABufferInputStream,
  garrow_cuda_buffer_input_stream,
  GARROW_TYPE_BUFFER_INPUT_STREAM,
  G_IMPLEMENT_INTERFACE(
    GARROW_TYPE_READABLE,
    garrow_cuda_buffer_input_stream_readable_interface_init))

static void
garrow_cuda_buffer_input_stream_init(GArrowCUDABufferInputStream *object)
{
}

static void
garrow_cuda_buffer_input_stream_class_init(GArrowCUDABufferInputStreamClass *klass)
{
}

/**
 * garrow_cuda_buffer_input_stream_new:
 * @buffer: A #GArrowCUDABuffer.
 *
 * Returns: (transfer full): A newly created
 *   #GArrowCUDABufferInputStream.
 *
 * Since: 0.8.0
 */
GArrowCUDABufferInputStream *
garrow_cuda_buffer_input_stream_new(GArrowCUDABuffer *buffer)
{
  auto arrow_buffer = garrow_cuda_buffer_get_raw(buffer);
  auto arrow_reader =
    std::make_shared<arrow::cuda::CudaBufferReader>(arrow_buffer);
  return garrow_cuda_buffer_input_stream_new_raw(&arrow_reader);
}


G_DEFINE_TYPE(GArrowCUDABufferOutputStream,
              garrow_cuda_buffer_output_stream,
              GARROW_TYPE_OUTPUT_STREAM)

static void
garrow_cuda_buffer_output_stream_init(GArrowCUDABufferOutputStream *object)
{
}

static void
garrow_cuda_buffer_output_stream_class_init(GArrowCUDABufferOutputStreamClass *klass)
{
}

/**
 * garrow_cuda_buffer_output_stream_new:
 * @buffer: A #GArrowCUDABuffer.
 *
 * Returns: (transfer full): A newly created
 *   #GArrowCUDABufferOutputStream.
 *
 * Since: 0.8.0
 */
GArrowCUDABufferOutputStream *
garrow_cuda_buffer_output_stream_new(GArrowCUDABuffer *buffer)
{
  auto arrow_buffer = garrow_cuda_buffer_get_raw(buffer);
  auto arrow_writer =
    std::make_shared<arrow::cuda::CudaBufferWriter>(arrow_buffer);
  return garrow_cuda_buffer_output_stream_new_raw(&arrow_writer);
}

/**
 * garrow_cuda_buffer_output_stream_set_buffer_size:
 * @stream: A #GArrowCUDABufferOutputStream.
 * @size: A size of CPU buffer in bytes.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: %TRUE on success, %FALSE if there was an error.
 *
 * Sets CPU buffer size. to limit `cudaMemcpy()` calls. If CPU buffer
 * size is `0`, buffering is disabled.
 *
 * The default is `0`.
 *
 * Since: 0.8.0
 */
gboolean
garrow_cuda_buffer_output_stream_set_buffer_size(GArrowCUDABufferOutputStream *stream,
                                                 gint64 size,
                                                 GError **error)
{
  auto arrow_stream = garrow_cuda_buffer_output_stream_get_raw(stream);
  auto status = arrow_stream->SetBufferSize(size);
  return garrow_error_check(error,
                            status,
                            "[cuda][buffer-output-stream][set-buffer-size]");
}

/**
 * garrow_cuda_buffer_output_stream_get_buffer_size:
 * @stream: A #GArrowCUDABufferOutputStream.
 *
 * Returns: The CPU buffer size in bytes.
 *
 * See garrow_cuda_buffer_output_stream_set_buffer_size() for CPU
 * buffer size details.
 *
 * Since: 0.8.0
 */
gint64
garrow_cuda_buffer_output_stream_get_buffer_size(GArrowCUDABufferOutputStream *stream)
{
  auto arrow_stream = garrow_cuda_buffer_output_stream_get_raw(stream);
  return arrow_stream->buffer_size();
}

/**
 * garrow_cuda_buffer_output_stream_get_buffered_size:
 * @stream: A #GArrowCUDABufferOutputStream.
 *
 * Returns: The size of buffered data in bytes.
 *
 * Since: 0.8.0
 */
gint64
garrow_cuda_buffer_output_stream_get_buffered_size(GArrowCUDABufferOutputStream *stream)
{
  auto arrow_stream = garrow_cuda_buffer_output_stream_get_raw(stream);
  return arrow_stream->num_bytes_buffered();
}


G_END_DECLS

GArrowCUDAContext *
garrow_cuda_context_new_raw(std::shared_ptr<arrow::cuda::CudaContext> *arrow_context)
{
  return GARROW_CUDA_CONTEXT(g_object_new(GARROW_CUDA_TYPE_CONTEXT,
                                          "context", arrow_context,
                                          NULL));
}

std::shared_ptr<arrow::cuda::CudaContext>
garrow_cuda_context_get_raw(GArrowCUDAContext *context)
{
  if (!context)
    return nullptr;

  auto priv = GARROW_CUDA_CONTEXT_GET_PRIVATE(context);
  return priv->context;
}

GArrowCUDAIPCMemoryHandle *
garrow_cuda_ipc_memory_handle_new_raw(std::shared_ptr<arrow::cuda::CudaIpcMemHandle> *arrow_handle)
{
  auto handle = g_object_new(GARROW_CUDA_TYPE_IPC_MEMORY_HANDLE,
                             "ipc-memory-handle", arrow_handle,
                             NULL);
  return GARROW_CUDA_IPC_MEMORY_HANDLE(handle);
}

std::shared_ptr<arrow::cuda::CudaIpcMemHandle>
garrow_cuda_ipc_memory_handle_get_raw(GArrowCUDAIPCMemoryHandle *handle)
{
  if (!handle)
    return nullptr;

  auto priv = GARROW_CUDA_IPC_MEMORY_HANDLE_GET_PRIVATE(handle);
  return priv->ipc_memory_handle;
}

GArrowCUDABuffer *
garrow_cuda_buffer_new_raw(std::shared_ptr<arrow::cuda::CudaBuffer> *arrow_buffer)
{
  return GARROW_CUDA_BUFFER(g_object_new(GARROW_CUDA_TYPE_BUFFER,
                                         "buffer", arrow_buffer,
                                         NULL));
}

std::shared_ptr<arrow::cuda::CudaBuffer>
garrow_cuda_buffer_get_raw(GArrowCUDABuffer *buffer)
{
  if (!buffer)
    return nullptr;

  auto arrow_buffer = garrow_buffer_get_raw(GARROW_BUFFER(buffer));
  return std::static_pointer_cast<arrow::cuda::CudaBuffer>(arrow_buffer);
}

GArrowCUDAHostBuffer *
garrow_cuda_host_buffer_new_raw(std::shared_ptr<arrow::cuda::CudaHostBuffer> *arrow_buffer)
{
  auto buffer = g_object_new(GARROW_CUDA_TYPE_HOST_BUFFER,
                             "buffer", arrow_buffer,
                             NULL);
  return GARROW_CUDA_HOST_BUFFER(buffer);
}

std::shared_ptr<arrow::cuda::CudaHostBuffer>
garrow_cuda_host_buffer_get_raw(GArrowCUDAHostBuffer *buffer)
{
  if (!buffer)
    return nullptr;

  auto arrow_buffer = garrow_buffer_get_raw(GARROW_BUFFER(buffer));
  return std::static_pointer_cast<arrow::cuda::CudaHostBuffer>(arrow_buffer);
}

GArrowCUDABufferInputStream *
garrow_cuda_buffer_input_stream_new_raw(std::shared_ptr<arrow::cuda::CudaBufferReader> *arrow_reader)
{
  auto input_stream = g_object_new(GARROW_CUDA_TYPE_BUFFER_INPUT_STREAM,
                                   "input-stream", arrow_reader,
                                   NULL);
  return GARROW_CUDA_BUFFER_INPUT_STREAM(input_stream);
}

std::shared_ptr<arrow::cuda::CudaBufferReader>
garrow_cuda_buffer_input_stream_get_raw(GArrowCUDABufferInputStream *input_stream)
{
  if (!input_stream)
    return nullptr;

  auto arrow_reader =
    garrow_input_stream_get_raw(GARROW_INPUT_STREAM(input_stream));
  return std::static_pointer_cast<arrow::cuda::CudaBufferReader>(arrow_reader);
}

GArrowCUDABufferOutputStream *
garrow_cuda_buffer_output_stream_new_raw(std::shared_ptr<arrow::cuda::CudaBufferWriter> *arrow_writer)
{
  auto output_stream = g_object_new(GARROW_CUDA_TYPE_BUFFER_OUTPUT_STREAM,
                                    "output-stream", arrow_writer,
                                    NULL);
  return GARROW_CUDA_BUFFER_OUTPUT_STREAM(output_stream);
}

std::shared_ptr<arrow::cuda::CudaBufferWriter>
garrow_cuda_buffer_output_stream_get_raw(GArrowCUDABufferOutputStream *output_stream)
{
  if (!output_stream)
    return nullptr;

  auto arrow_writer =
    garrow_output_stream_get_raw(GARROW_OUTPUT_STREAM(output_stream));
  return std::static_pointer_cast<arrow::cuda::CudaBufferWriter>(arrow_writer);
}
