/*
 * 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/array-builder.hpp>
#include <arrow-glib/error.hpp>
#include <arrow-glib/record-batch.hpp>
#include <arrow-glib/schema.hpp>
#include <arrow-glib/table-builder.hpp>

G_BEGIN_DECLS

/**
 * SECTION: table-builder
 * @section_id: table-builder-classes
 * @title: Table builder classes
 * @include: arrow-glib/arrow-glib.h
 *
 * #GArrowRecordBatchBuilder is a class to create
 * new #GArrowRecordBatch.
 */

typedef struct GArrowRecordBatchBuilderPrivate_
{
  arrow::RecordBatchBuilder *record_batch_builder;
  GPtrArray *column_builders;
} GArrowRecordBatchBuilderPrivate;

enum {
  PROP_0,
  PROP_RECORD_BATCH_BUILDER
};

G_DEFINE_TYPE_WITH_PRIVATE(GArrowRecordBatchBuilder,
                           garrow_record_batch_builder,
                           G_TYPE_OBJECT)

#define GARROW_RECORD_BATCH_BUILDER_GET_PRIVATE(object)                                  \
  static_cast<GArrowRecordBatchBuilderPrivate *>(                                        \
    garrow_record_batch_builder_get_instance_private(                                    \
      GARROW_RECORD_BATCH_BUILDER(object)))

static void
garrow_record_batch_builder_constructed(GObject *object)
{
  auto priv = GARROW_RECORD_BATCH_BUILDER_GET_PRIVATE(object);
  auto arrow_builder = priv->record_batch_builder;
  auto n_columns = arrow_builder->num_fields();
  priv->column_builders = g_ptr_array_new_full(n_columns, g_object_unref);
  for (int i = 0; i < n_columns; ++i) {
    auto arrow_array_builder = arrow_builder->GetField(i);
    auto array_builder = garrow_array_builder_new_raw(arrow_array_builder);
    g_ptr_array_add(priv->column_builders, array_builder);
  }

  G_OBJECT_CLASS(garrow_record_batch_builder_parent_class)->constructed(object);
}

static void
garrow_record_batch_builder_finalize(GObject *object)
{
  auto priv = GARROW_RECORD_BATCH_BUILDER_GET_PRIVATE(object);

  g_ptr_array_free(priv->column_builders, TRUE);
  delete priv->record_batch_builder;

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

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

  switch (prop_id) {
  case PROP_RECORD_BATCH_BUILDER:
    priv->record_batch_builder =
      static_cast<arrow::RecordBatchBuilder *>(g_value_get_pointer(value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    break;
  }
}

static void
garrow_record_batch_builder_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_record_batch_builder_init(GArrowRecordBatchBuilder *builder)
{
}

static void
garrow_record_batch_builder_class_init(GArrowRecordBatchBuilderClass *klass)
{
  auto gobject_class = G_OBJECT_CLASS(klass);

  gobject_class->constructed = garrow_record_batch_builder_constructed;
  gobject_class->finalize = garrow_record_batch_builder_finalize;
  gobject_class->set_property = garrow_record_batch_builder_set_property;
  gobject_class->get_property = garrow_record_batch_builder_get_property;

  GParamSpec *spec;
  spec = g_param_spec_pointer(
    "record-batch-builder",
    "RecordBatch builder",
    "The raw arrow::RecordBatchBuilder *",
    static_cast<GParamFlags>(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
  g_object_class_install_property(gobject_class, PROP_RECORD_BATCH_BUILDER, spec);
}

/**
 * garrow_record_batch_builder_new:
 * @schema: A #GArrowSchema.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: A newly created #GArrowRecordBatchBuilder on success,
 *   %NULL on error.
 *
 * Since: 0.8.0
 */
GArrowRecordBatchBuilder *
garrow_record_batch_builder_new(GArrowSchema *schema, GError **error)
{
  auto arrow_schema = garrow_schema_get_raw(schema);
  auto memory_pool = arrow::default_memory_pool();
  auto builder_result = arrow::RecordBatchBuilder::Make(arrow_schema, memory_pool);

  if (garrow::check(error, builder_result, "[record-batch-builder][new]")) {
    std::unique_ptr<arrow::RecordBatchBuilder> arrow_builder =
      std::move(builder_result).ValueOrDie();
    return garrow_record_batch_builder_new_raw(arrow_builder.release());
  } else {
    return NULL;
  }
}

/**
 * garrow_record_batch_builder_get_initial_capacity:
 * @builder: A #GArrowRecordBatchBuilder.
 *
 * Returns: The initial capacity for array builders.
 *
 * Since: 0.8.0
 */
gint64
garrow_record_batch_builder_get_initial_capacity(GArrowRecordBatchBuilder *builder)
{
  auto arrow_builder = garrow_record_batch_builder_get_raw(builder);
  return arrow_builder->initial_capacity();
}

/**
 * garrow_record_batch_builder_set_initial_capacity:
 * @builder: A #GArrowRecordBatchBuilder.
 * @capacity: The new initial capacity for array builders.
 *
 * Since: 0.8.0
 */
void
garrow_record_batch_builder_set_initial_capacity(GArrowRecordBatchBuilder *builder,
                                                 gint64 capacity)
{
  auto arrow_builder = garrow_record_batch_builder_get_raw(builder);
  arrow_builder->SetInitialCapacity(capacity);
}

/**
 * garrow_record_batch_builder_get_schema:
 * @builder: A #GArrowRecordBatchBuilder.
 *
 * Returns: (transfer full): The #GArrowSchema of the record batch builder.
 *
 * Since: 0.8.0
 */
GArrowSchema *
garrow_record_batch_builder_get_schema(GArrowRecordBatchBuilder *builder)
{
  auto arrow_builder = garrow_record_batch_builder_get_raw(builder);
  auto arrow_schema = arrow_builder->schema();
  return garrow_schema_new_raw(&arrow_schema);
}

/**
 * garrow_record_batch_builder_get_n_fields:
 * @builder: A #GArrowRecordBatchBuilder.
 *
 * Returns: The number of fields.
 *
 * Since: 0.8.0
 *
 * Deprecated: 0.13.0:
 *   Use garrow_record_batch_builder_get_n_columns() instead.
 */
gint
garrow_record_batch_builder_get_n_fields(GArrowRecordBatchBuilder *builder)
{
  return garrow_record_batch_builder_get_n_columns(builder);
}

/**
 * garrow_record_batch_builder_get_n_columns:
 * @builder: A #GArrowRecordBatchBuilder.
 *
 * Returns: The number of columns.
 *
 * Since: 0.13.0
 */
gint
garrow_record_batch_builder_get_n_columns(GArrowRecordBatchBuilder *builder)
{
  auto arrow_builder = garrow_record_batch_builder_get_raw(builder);
  return arrow_builder->num_fields();
}

/**
 * garrow_record_batch_builder_get_field:
 * @builder: A #GArrowRecordBatchBuilder.
 * @i: The field index. If it's negative, index is counted backward
 *   from the end of the fields. `-1` means the last field.
 *
 * Returns: (transfer none) (nullable): The #GArrowArrayBuilder for
 *   the `i`-th field on success, %NULL on out of index.
 *
 * Since: 0.8.0
 *
 * Deprecated: 0.13.0:
 *   Use garrow_record_batch_builder_get_column_builder() instead.
 */
GArrowArrayBuilder *
garrow_record_batch_builder_get_field(GArrowRecordBatchBuilder *builder, gint i)
{
  return garrow_record_batch_builder_get_column_builder(builder, i);
}

/**
 * garrow_record_batch_builder_get_column_builder:
 * @builder: A #GArrowRecordBatchBuilder.
 * @i: The column index. If it's negative, index is counted backward
 *   from the end of the columns. `-1` means the last column.
 *
 * Returns: (transfer none) (nullable): The #GArrowArrayBuilder for
 *   the `i`-th column on success, %NULL on out of index.
 *
 * Since: 0.13.0
 */
GArrowArrayBuilder *
garrow_record_batch_builder_get_column_builder(GArrowRecordBatchBuilder *builder, gint i)
{
  auto priv = GARROW_RECORD_BATCH_BUILDER_GET_PRIVATE(builder);
  if (i < 0) {
    i += priv->column_builders->len;
  }
  if (i < 0) {
    return NULL;
  }
  if (static_cast<guint>(i) >= priv->column_builders->len) {
    return NULL;
  }

  return GARROW_ARRAY_BUILDER(g_ptr_array_index(priv->column_builders, i));
}

/**
 * garrow_record_batch_builder_flush:
 * @builder: A #GArrowRecordBatchBuilder.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: (transfer full): The built #GArrowRecordBatch on success,
 *   %NULL on error.
 *
 * Since: 0.8.0
 */
GArrowRecordBatch *
garrow_record_batch_builder_flush(GArrowRecordBatchBuilder *builder, GError **error)
{
  auto arrow_builder = garrow_record_batch_builder_get_raw(builder);
  auto batch_result = arrow_builder->Flush();

  if (garrow::check(error, batch_result, "[record-batch-builder][flush]")) {
    std::shared_ptr<arrow::RecordBatch> arrow_record_batch = *batch_result;
    return garrow_record_batch_new_raw(&arrow_record_batch);
  } else {
    return NULL;
  }
}

G_END_DECLS

GArrowRecordBatchBuilder *
garrow_record_batch_builder_new_raw(arrow::RecordBatchBuilder *arrow_builder)
{
  auto builder = g_object_new(GARROW_TYPE_RECORD_BATCH_BUILDER,
                              "record-batch-builder",
                              arrow_builder,
                              NULL);
  return GARROW_RECORD_BATCH_BUILDER(builder);
}

arrow::RecordBatchBuilder *
garrow_record_batch_builder_get_raw(GArrowRecordBatchBuilder *builder)
{
  auto priv = GARROW_RECORD_BATCH_BUILDER_GET_PRIVATE(builder);
  return priv->record_batch_builder;
}
