/*
 * 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/io/memory.h>
#include <arrow/ipc/writer.h>

#include <arrow-glib/buffer.hpp>
#include <arrow-glib/codec.hpp>
#include <arrow-glib/error.hpp>
#include <arrow-glib/file.hpp>
#include <arrow-glib/ipc-options.hpp>
#include <arrow-glib/output-stream.hpp>
#include <arrow-glib/record-batch.hpp>
#include <arrow-glib/tensor.hpp>
#include <arrow-glib/writable.hpp>

#include <iostream>
#include <sstream>

G_BEGIN_DECLS

/**
 * SECTION: output-stream
 * @section_id: output-stream-classes
 * @title: Output stream classes
 * @include: arrow-glib/arrow-glib.h
 *
 * #GArrowOutputStream is an interface for stream output. Stream
 * output is file based and writable
 *
 * #GArrowFileOutputStream is a class for file output stream.
 *
 * #GArrowBufferOutputStream is a class for buffer output stream.
 *
 * #GArrowGIOOutputStream is a class for `GOutputStream` based output
 * stream.
 *
 * #GArrowCompressedOutputStream is a class to write compressed data to
 * output stream.
 */

typedef struct GArrowOutputStreamPrivate_ {
  std::shared_ptr<arrow::io::OutputStream> output_stream;
} GArrowOutputStreamPrivate;

enum {
  PROP_0,
  PROP_OUTPUT_STREAM
};

static std::shared_ptr<arrow::io::FileInterface>
garrow_output_stream_get_raw_file_interface(GArrowFile *file)
{
  auto output_stream = GARROW_OUTPUT_STREAM(file);
  auto arrow_output_stream = garrow_output_stream_get_raw(output_stream);
  return arrow_output_stream;
}

static void
garrow_output_stream_file_interface_init(GArrowFileInterface *iface)
{
  iface->get_raw = garrow_output_stream_get_raw_file_interface;
}

static std::shared_ptr<arrow::io::Writable>
garrow_output_stream_get_raw_writable_interface(GArrowWritable *writable)
{
  auto output_stream = GARROW_OUTPUT_STREAM(writable);
  auto arrow_output_stream = garrow_output_stream_get_raw(output_stream);
  return arrow_output_stream;
}

static void
garrow_output_stream_writable_interface_init(GArrowWritableInterface *iface)
{
  iface->get_raw = garrow_output_stream_get_raw_writable_interface;
}

G_DEFINE_TYPE_WITH_CODE(GArrowOutputStream,
                        garrow_output_stream,
                        G_TYPE_OBJECT,
                        G_ADD_PRIVATE(GArrowOutputStream)
                        G_IMPLEMENT_INTERFACE(GARROW_TYPE_FILE,
                                              garrow_output_stream_file_interface_init)
                        G_IMPLEMENT_INTERFACE(GARROW_TYPE_WRITABLE,
                                              garrow_output_stream_writable_interface_init));

#define GARROW_OUTPUT_STREAM_GET_PRIVATE(obj)         \
  static_cast<GArrowOutputStreamPrivate *>(           \
     garrow_output_stream_get_instance_private(       \
       GARROW_OUTPUT_STREAM(obj)))

static void
garrow_output_stream_finalize(GObject *object)
{
  auto priv = GARROW_OUTPUT_STREAM_GET_PRIVATE(object);

  priv->output_stream.~shared_ptr();

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

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

  switch (prop_id) {
  case PROP_OUTPUT_STREAM:
    priv->output_stream =
      *static_cast<std::shared_ptr<arrow::io::OutputStream> *>(g_value_get_pointer(value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    break;
  }
}

static void
garrow_output_stream_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_output_stream_init(GArrowOutputStream *object)
{
  auto priv = GARROW_OUTPUT_STREAM_GET_PRIVATE(object);
  new(&priv->output_stream) std::shared_ptr<arrow::io::OutputStream>;
}

static void
garrow_output_stream_class_init(GArrowOutputStreamClass *klass)
{
  auto gobject_class = G_OBJECT_CLASS(klass);

  gobject_class->finalize     = garrow_output_stream_finalize;
  gobject_class->set_property = garrow_output_stream_set_property;
  gobject_class->get_property = garrow_output_stream_get_property;

  GParamSpec *spec;
  spec = g_param_spec_pointer("output-stream",
                              "io::OutputStream",
                              "The raw std::shared<arrow::io::OutputStream> *",
                              static_cast<GParamFlags>(G_PARAM_WRITABLE |
                                                       G_PARAM_CONSTRUCT_ONLY));
  g_object_class_install_property(gobject_class, PROP_OUTPUT_STREAM, spec);
}

/**
 * garrow_output_stream_align:
 * @stream: A #GArrowOutputStream.
 * @alignment: The byte multiple for the metadata prefix, usually 8
 *   or 64, to ensure the body starts on a multiple of that alignment.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: %TRUE on success, %FALSE on error.
 *
 * Since: 0.11.0
 */
gboolean
garrow_output_stream_align(GArrowOutputStream *stream,
                           gint32 alignment,
                           GError **error)
{
  auto arrow_stream = garrow_output_stream_get_raw(stream);
  auto status = arrow::ipc::AlignStream(arrow_stream.get(), alignment);
  return garrow::check(error, status, "[output-stream][align]");
}

/**
 * garrow_output_stream_write_tensor:
 * @stream: A #GArrowOutputStream.
 * @tensor: A #GArrowTensor to be written.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: The number of written bytes on success, -1 on error.
 *
 * Since: 0.4.0
 */
gint64
garrow_output_stream_write_tensor(GArrowOutputStream *stream,
                                  GArrowTensor *tensor,
                                  GError **error)
{
  auto arrow_stream = garrow_output_stream_get_raw(stream);
  auto arrow_tensor = garrow_tensor_get_raw(tensor);
  int32_t metadata_length;
  int64_t body_length;
  auto status = arrow::ipc::WriteTensor(*arrow_tensor,
                                        arrow_stream.get(),
                                        &metadata_length,
                                        &body_length);
  if (garrow::check(error, status, "[output-stream][write-tensor]")) {
    return metadata_length + body_length;
  } else {
    return -1;
  }
}

/**
 * garrow_output_stream_write_record_batch:
 * @stream: A #GArrowOutputStream.
 * @record_batch: A #GArrowRecordBatch to be written.
 * @options: (nullable): A #GArrowWriteOptions.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: The number of written bytes on success, -1 on error.
 *
 * Since: 1.0.0
 */
gint64
garrow_output_stream_write_record_batch(GArrowOutputStream *stream,
                                        GArrowRecordBatch *record_batch,
                                        GArrowWriteOptions *options,
                                        GError **error)
{
  auto arrow_stream = garrow_output_stream_get_raw(stream);
  auto arrow_record_batch = garrow_record_batch_get_raw(record_batch);
  int64_t buffer_start_offset = 0;
  int32_t metadata_length;
  int64_t body_length;
  arrow::Status status;
  if (options) {
    auto arrow_options = garrow_write_options_get_raw(options);
    status = arrow::ipc::WriteRecordBatch(*arrow_record_batch,
                                          buffer_start_offset,
                                          arrow_stream.get(),
                                          &metadata_length,
                                          &body_length,
                                          *arrow_options);
  } else {
    auto arrow_options = arrow::ipc::IpcWriteOptions::Defaults();
    status = arrow::ipc::WriteRecordBatch(*arrow_record_batch,
                                          buffer_start_offset,
                                          arrow_stream.get(),
                                          &metadata_length,
                                          &body_length,
                                          arrow_options);
  }
  if (garrow::check(error, status, "[output-stream][write-record-batch]")) {
    return metadata_length + body_length;
  } else {
    return -1;
  }
}


G_DEFINE_TYPE(GArrowFileOutputStream,
              garrow_file_output_stream,
              GARROW_TYPE_OUTPUT_STREAM);

static void
garrow_file_output_stream_init(GArrowFileOutputStream *file_output_stream)
{
}

static void
garrow_file_output_stream_class_init(GArrowFileOutputStreamClass *klass)
{
}

/**
 * garrow_file_output_stream_new:
 * @path: The path of the file output stream.
 * @append: Whether the path is opened as append mode or recreate mode.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: (nullable): A newly opened #GArrowFileOutputStream or
 *   %NULL on error.
 */
GArrowFileOutputStream *
garrow_file_output_stream_new(const gchar *path,
                              gboolean append,
                              GError **error)
{
  auto arrow_file_output_stream_result =
    arrow::io::FileOutputStream::Open(std::string(path), append);
  if (arrow_file_output_stream_result.ok()) {
    auto arrow_file_output_stream =
      arrow_file_output_stream_result.ValueOrDie();
    return garrow_file_output_stream_new_raw(&arrow_file_output_stream);
  } else {
    std::string context("[io][file-output-stream][open]: <");
    context += path;
    context += ">";
    garrow::check(error, arrow_file_output_stream_result, context.c_str());
    return NULL;
  }
}


G_DEFINE_TYPE(GArrowBufferOutputStream,
              garrow_buffer_output_stream,
              GARROW_TYPE_OUTPUT_STREAM);

static void
garrow_buffer_output_stream_init(GArrowBufferOutputStream *buffer_output_stream)
{
}

static void
garrow_buffer_output_stream_class_init(GArrowBufferOutputStreamClass *klass)
{
}

/**
 * garrow_buffer_output_stream_new:
 * @buffer: The resizable buffer to be output.
 *
 * Returns: (transfer full): A newly created #GArrowBufferOutputStream.
 */
GArrowBufferOutputStream *
garrow_buffer_output_stream_new(GArrowResizableBuffer *buffer)
{
  auto arrow_buffer = garrow_buffer_get_raw(GARROW_BUFFER(buffer));
  auto arrow_resizable_buffer =
    std::static_pointer_cast<arrow::ResizableBuffer>(arrow_buffer);
  auto arrow_buffer_output_stream =
    std::make_shared<arrow::io::BufferOutputStream>(arrow_resizable_buffer);
  return garrow_buffer_output_stream_new_raw(&arrow_buffer_output_stream);
}

G_END_DECLS


namespace garrow {
  class GIOOutputStream : public arrow::io::OutputStream {
  public:
    GIOOutputStream(GOutputStream *output_stream) :
      output_stream_(output_stream),
      position_(0) {
      g_object_ref(output_stream_);
    }

    ~GIOOutputStream() {
      g_object_unref(output_stream_);
    }

    GOutputStream *get_output_stream() {
      return output_stream_;
    }

    bool closed() const override {
      return static_cast<bool>(g_output_stream_is_closed(output_stream_));
    }

    arrow::Status Close() override {
      GError *error = NULL;
      if (g_output_stream_close(output_stream_, NULL, &error)) {
        return arrow::Status::OK();
      } else {
        return garrow_error_to_status(error,
                                      arrow::StatusCode::IOError,
                                      "[gio-output-stream][close]");
      }
    }

    arrow::Result<int64_t> Tell() const override {
      if (G_IS_SEEKABLE(output_stream_)) {
        return g_seekable_tell(G_SEEKABLE(output_stream_));
      } else {
        return position_;
      }
    }

    arrow::Status Write(const void *data,
                        int64_t n_bytes) override {
      GError *error = NULL;
      gsize n_written_bytes;
      auto successed = g_output_stream_write_all(output_stream_,
                                                 data,
                                                 n_bytes,
                                                 &n_written_bytes,
                                                 NULL,
                                                 &error);
      if (successed) {
        position_ += n_written_bytes;
        return arrow::Status::OK();
      } else {
        std::stringstream message("[gio-output-stream][write]");
        message << "[" << n_written_bytes << "/" << n_bytes << "]";
        return garrow_error_to_status(error,
                                      arrow::StatusCode::IOError,
                                      message.str().c_str());
      }
    }

    arrow::Status Flush() override {
      GError *error = NULL;
      auto successed = g_output_stream_flush(output_stream_, NULL, &error);
      if (successed) {
        return arrow::Status::OK();
      } else {
        return garrow_error_to_status(error,
                                      arrow::StatusCode::IOError,
                                      "[gio-output-stream][flush]");
      }
    }

  private:
    GOutputStream *output_stream_;
    int64_t position_;
  };
};

G_BEGIN_DECLS

typedef struct GArrowGIOOutputStreamPrivate_ {
  GOutputStream *raw;
} GArrowGIOOutputStreamPrivate;

enum {
  PROP_GIO_RAW = 1
};

G_DEFINE_TYPE_WITH_PRIVATE(GArrowGIOOutputStream,
                           garrow_gio_output_stream,
                           GARROW_TYPE_OUTPUT_STREAM);

#define GARROW_GIO_OUTPUT_STREAM_GET_PRIVATE(object)    \
  static_cast<GArrowGIOOutputStreamPrivate *>(          \
    garrow_gio_output_stream_get_instance_private(      \
      GARROW_GIO_OUTPUT_STREAM(object)))

static void
garrow_gio_output_stream_dispose(GObject *object)
{
  auto priv = GARROW_GIO_OUTPUT_STREAM_GET_PRIVATE(object);

  if (priv->raw) {
    g_object_unref(priv->raw);
    priv->raw = nullptr;
  }

  G_OBJECT_CLASS(garrow_gio_output_stream_parent_class)->dispose(object);
}

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

  switch (prop_id) {
  case PROP_GIO_RAW:
    priv->raw = G_OUTPUT_STREAM(g_value_dup_object(value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    break;
  }
}

static void
garrow_gio_output_stream_get_property(GObject *object,
                                      guint prop_id,
                                      GValue *value,
                                      GParamSpec *pspec)
{
  auto priv = GARROW_GIO_OUTPUT_STREAM_GET_PRIVATE(object);

  switch (prop_id) {
  case PROP_GIO_RAW:
    g_value_set_object(value, priv->raw);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    break;
  }
}

static void
garrow_gio_output_stream_init(GArrowGIOOutputStream *object)
{
}

static void
garrow_gio_output_stream_class_init(GArrowGIOOutputStreamClass *klass)
{
  auto gobject_class = G_OBJECT_CLASS(klass);

  gobject_class->dispose      = garrow_gio_output_stream_dispose;
  gobject_class->set_property = garrow_gio_output_stream_set_property;
  gobject_class->get_property = garrow_gio_output_stream_get_property;

  GParamSpec *spec;
  spec = g_param_spec_object("raw",
                             "Raw",
                             "The raw GOutputStream *",
                             G_TYPE_OUTPUT_STREAM,
                             static_cast<GParamFlags>(G_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT_ONLY));
  g_object_class_install_property(gobject_class, PROP_GIO_RAW, spec);
}

/**
 * garrow_gio_output_stream_new:
 * @gio_output_stream: The stream to be output.
 *
 * Returns: (transfer full): A newly created #GArrowGIOOutputStream.
 */
GArrowGIOOutputStream *
garrow_gio_output_stream_new(GOutputStream *gio_output_stream)
{
  auto arrow_output_stream =
    std::make_shared<garrow::GIOOutputStream>(gio_output_stream);
  auto object = g_object_new(GARROW_TYPE_GIO_OUTPUT_STREAM,
                             "output-stream", &arrow_output_stream,
                             "raw", gio_output_stream,
                             NULL);
  auto output_stream = GARROW_GIO_OUTPUT_STREAM(object);
  return output_stream;
}

/**
 * garrow_gio_output_stream_get_raw:
 * @output_stream: A #GArrowGIOOutputStream.
 *
 * Returns: (transfer none): The wrapped #GOutputStream.
 *
 * Since: 0.5.0
 *
 * Deprecated: 0.12.0: Use GArrowGIOOutputStream::raw property instead.
 */
GOutputStream *
garrow_gio_output_stream_get_raw(GArrowGIOOutputStream *output_stream)
{
  auto priv = GARROW_GIO_OUTPUT_STREAM_GET_PRIVATE(output_stream);
  return priv->raw;
}

typedef struct GArrowCompressedOutputStreamPrivate_ {
  GArrowCodec *codec;
  GArrowOutputStream *raw;
} GArrowCompressedOutputStreamPrivate;

enum {
  PROP_CODEC = 1,
  PROP_RAW
};

G_DEFINE_TYPE_WITH_PRIVATE(GArrowCompressedOutputStream,
                           garrow_compressed_output_stream,
                           GARROW_TYPE_OUTPUT_STREAM)

#define GARROW_COMPRESSED_OUTPUT_STREAM_GET_PRIVATE(object)     \
  static_cast<GArrowCompressedOutputStreamPrivate *>(           \
    garrow_compressed_output_stream_get_instance_private(       \
      GARROW_COMPRESSED_OUTPUT_STREAM(object)))

static void
garrow_compressed_output_stream_dispose(GObject *object)
{
  auto priv = GARROW_COMPRESSED_OUTPUT_STREAM_GET_PRIVATE(object);

  if (priv->codec) {
    g_object_unref(priv->codec);
    priv->codec = NULL;
  }

  if (priv->raw) {
    g_object_unref(priv->raw);
    priv->raw = NULL;
  }

  G_OBJECT_CLASS(garrow_compressed_output_stream_parent_class)->dispose(object);
}

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

  switch (prop_id) {
  case PROP_CODEC:
    priv->codec = GARROW_CODEC(g_value_dup_object(value));
    break;
  case PROP_RAW:
    priv->raw = GARROW_OUTPUT_STREAM(g_value_dup_object(value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    break;
  }
}

static void
garrow_compressed_output_stream_get_property(GObject *object,
                                             guint prop_id,
                                             GValue *value,
                                             GParamSpec *pspec)
{
  auto priv = GARROW_COMPRESSED_OUTPUT_STREAM_GET_PRIVATE(object);

  switch (prop_id) {
  case PROP_CODEC:
    g_value_set_object(value, priv->codec);
    break;
  case PROP_RAW:
    g_value_set_object(value, priv->raw);
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
    break;
  }
}

static void
garrow_compressed_output_stream_init(GArrowCompressedOutputStream *object)
{
}

static void
garrow_compressed_output_stream_class_init(GArrowCompressedOutputStreamClass *klass)
{
  auto gobject_class = G_OBJECT_CLASS(klass);

  gobject_class->dispose      = garrow_compressed_output_stream_dispose;
  gobject_class->set_property = garrow_compressed_output_stream_set_property;
  gobject_class->get_property = garrow_compressed_output_stream_get_property;

  GParamSpec *spec;
  spec = g_param_spec_object("codec",
                             "Codec",
                             "The codec for the stream",
                             GARROW_TYPE_CODEC,
                             static_cast<GParamFlags>(G_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT_ONLY));
  g_object_class_install_property(gobject_class, PROP_CODEC, spec);

  spec = g_param_spec_object("raw",
                             "Raw",
                             "The underlying raw output stream",
                             GARROW_TYPE_OUTPUT_STREAM,
                             static_cast<GParamFlags>(G_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT_ONLY));
  g_object_class_install_property(gobject_class, PROP_RAW, spec);
}

/**
 * garrow_compressed_output_stream_new:
 * @codec: A #GArrowCodec for compressed data in the @raw.
 * @raw: A #GArrowOutputStream that is a sink for compressed data.
 * @error: (nullable): Return location for a #GError or %NULL.
 *
 * Returns: A newly created #GArrowCompressedOutputStream.
 *
 * Since: 0.12.0
 */
GArrowCompressedOutputStream *
garrow_compressed_output_stream_new(GArrowCodec *codec,
                                    GArrowOutputStream *raw,
                                    GError **error)
{
  auto arrow_codec = garrow_codec_get_raw(codec).get();
  auto arrow_raw = garrow_output_stream_get_raw(raw);
  auto arrow_stream = arrow::io::CompressedOutputStream::Make(arrow_codec,
                                                              arrow_raw);
  if (garrow::check(error, arrow_stream, "[compressed-output-stream][new]")) {
    return garrow_compressed_output_stream_new_raw(&(arrow_stream.ValueOrDie()),
                                                   codec,
                                                   raw);
  } else {
    return NULL;
  }
}

G_END_DECLS


GArrowOutputStream *
garrow_output_stream_new_raw(std::shared_ptr<arrow::io::OutputStream> *arrow_output_stream)
{
  auto output_stream =
    GARROW_OUTPUT_STREAM(g_object_new(GARROW_TYPE_OUTPUT_STREAM,
                                      "output-stream", arrow_output_stream,
                                      NULL));
  return output_stream;
}

std::shared_ptr<arrow::io::OutputStream>
garrow_output_stream_get_raw(GArrowOutputStream *output_stream)
{
  auto priv = GARROW_OUTPUT_STREAM_GET_PRIVATE(output_stream);
  return priv->output_stream;
}


GArrowFileOutputStream *
garrow_file_output_stream_new_raw(std::shared_ptr<arrow::io::FileOutputStream> *arrow_file_output_stream)
{
  auto file_output_stream =
    GARROW_FILE_OUTPUT_STREAM(g_object_new(GARROW_TYPE_FILE_OUTPUT_STREAM,
                                           "output-stream", arrow_file_output_stream,
                                           NULL));
  return file_output_stream;
}

GArrowBufferOutputStream *
garrow_buffer_output_stream_new_raw(std::shared_ptr<arrow::io::BufferOutputStream> *arrow_buffer_output_stream)
{
  auto buffer_output_stream =
    GARROW_BUFFER_OUTPUT_STREAM(g_object_new(GARROW_TYPE_BUFFER_OUTPUT_STREAM,
                                             "output-stream", arrow_buffer_output_stream,
                                             NULL));
  return buffer_output_stream;
}

GArrowCompressedOutputStream *
garrow_compressed_output_stream_new_raw(std::shared_ptr<arrow::io::CompressedOutputStream> *arrow_raw,
                                        GArrowCodec *codec,
                                        GArrowOutputStream *raw)
{
  auto compressed_output_stream =
    g_object_new(GARROW_TYPE_COMPRESSED_OUTPUT_STREAM,
                 "output-stream", arrow_raw,
                 "codec", codec,
                 "raw", raw,
                 NULL);
  return GARROW_COMPRESSED_OUTPUT_STREAM(compressed_output_stream);
}

std::shared_ptr<arrow::io::OutputStream>
garrow_compressed_output_stream_get_raw(GArrowCompressedOutputStream *compressed_output_stream)
{
  auto output_stream = GARROW_OUTPUT_STREAM(compressed_output_stream);
  auto arrow_output_stream = garrow_output_stream_get_raw(output_stream);
  auto arrow_compressed_output_stream =
    std::static_pointer_cast<arrow::io::CompressedOutputStream>(arrow_output_stream);
  return arrow_compressed_output_stream->raw();
}
