/**
 * @file   arrow_io_impl.h
 *
 * @section LICENSE
 *
 * The MIT License
 *
 * @copyright Copyright (c) 2020-2021 TileDB, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @section DESCRIPTION
 *
 * This file defines TileDB interoperation functionality with Apache Arrow.
 */

#ifndef TILEDB_ARROW_H
#define TILEDB_ARROW_H

/* ************************************************************************ */
/*
 * Arrow C Data Interface
 * Apache License 2.0
 * source: https://arrow.apache.org/docs/format/CDataInterface.html
 */

#define ARROW_FLAG_DICTIONARY_ORDERED 1
#define ARROW_FLAG_NULLABLE 2
#define ARROW_FLAG_MAP_KEYS_SORTED 4

struct ArrowSchema {
    // Array type description
    const char* format;
    const char* name;
    const char* metadata;
    int64_t flags;
    int64_t n_children;
    struct ArrowSchema** children;
    struct ArrowSchema* dictionary;

    // Release callback
    void (*release)(struct ArrowSchema*);
    // Opaque producer-specific data
    void* private_data;
};

struct ArrowArray {
    // Array data description
    int64_t length;
    int64_t null_count;
    int64_t offset;
    int64_t n_buffers;
    int64_t n_children;
    const void** buffers;
    struct ArrowArray** children;
    struct ArrowArray* dictionary;

    // Release callback
    void (*release)(struct ArrowArray*);
    // Opaque producer-specific data
    void* private_data;
};
/* End Arrow C API */
/* ************************************************************************ */

/* ************************************************************************ */
/* Begin TileDB Arrow IO internal implementation */

#include <memory>
#include <optional>

/* ****************************** */
/*      Error context helper      */
/* ****************************** */

using _TileDBError = tiledb::TileDBError;
#ifndef NDEBUG
#define TDB_LERROR(m)                                                       \
    _TileDBError(                                                           \
        std::string(m) + " (" + __FILE__ + ":" + std::to_string(__LINE__) + \
        ")");
#else
#define TDB_LERROR tiledb::TileDBError
#endif

namespace tiledb {
namespace arrow {

/* ****************************** */
/*       Helper types             */
/* ****************************** */

// Arrow format and representation
struct ArrowInfo {
    ArrowInfo(std::string fmt, const std::string& rep = std::string())
        : fmt_(fmt)
        , rep_(rep) {};

    std::string fmt_;
    std::string rep_;
};

// TileDB type information
struct TypeInfo {
    tiledb_datatype_t type;
    uint64_t elem_size;
    uint32_t cell_val_num;

    // is this represented as "Arrow large"
    bool arrow_large;
};

struct BufferInfo {
    TypeInfo tdbtype;
    bool is_var;               // is var-length
    uint64_t data_num;         // number of data elements
    void* data;                // data pointer
    uint64_t data_elem_size;   // bytes per data element
    uint64_t offsets_num;      // number of offsets
    void* offsets;             // offsets pointer
    size_t offsets_elem_size;  // bytes per offset element
};

/* ****************************** */
/*        Type conversions        */
/* ****************************** */

// Get Arrow format from TileDB BufferInfo
ArrowInfo tiledb_buffer_arrow_fmt(BufferInfo bufferinfo, bool use_list = true) {
    auto typeinfo = bufferinfo.tdbtype;
    auto cell_val_num = typeinfo.cell_val_num;

    // TODO support List<T> for simple scalar T
    (void)use_list;
    /*
    if (use_list && cell_val_num == TILEDB_VAR_NUM) {
      switch(typeinfo.type) {
        case TILEDB_STRING_UTF8:
        case TILEDB_STRING_ASCII:
          break;
        case TILEDB_BLOB:
        case TILEDB_INT8:
        case TILEDB_INT16:
        case TILEDB_INT32:
        case TILEDB_INT64:
        case TILEDB_UINT8:
        case TILEDB_UINT16:
        case TILEDB_UINT32:
        case TILEDB_UINT64:
        case TILEDB_FLOAT32:
        case TILEDB_FLOAT64:
          return ArrowInfo("+l");
        default:
          throw TDB_LERROR(
            "TileDB-Arrow: List<T> translation not yet supported for var-length
    TileDB type ('"
            + tiledb::impl::type_to_str(typeinfo.type) + "')");
      }
    }
    */

    switch (typeinfo.type) {
        ////////////////////////////////////////////////////////////////////////
        case TILEDB_STRING_ASCII:
        case TILEDB_STRING_UTF8:
            if (bufferinfo.offsets_elem_size == 4) {
                return ArrowInfo("u");
            } else {
                return ArrowInfo("U");
            }
        case TILEDB_CHAR:
            if (!bufferinfo.is_var) {
                return ArrowInfo("w:" + std::to_string(cell_val_num));
            } else if (bufferinfo.offsets_elem_size == 4) {
                return ArrowInfo("z");
            } else {
                return ArrowInfo("Z");
            }
        case TILEDB_INT32:
            return ArrowInfo("i");
        case TILEDB_INT64:
            return ArrowInfo("l");
        case TILEDB_FLOAT32:
            return ArrowInfo("f");
        case TILEDB_FLOAT64:
            return ArrowInfo("g");
        case TILEDB_BLOB:
#if TILEDB_VERSION_MAJOR >= 2 && TILEDB_VERSION_MINOR >= 21
        case TILEDB_GEOM_WKB:
        case TILEDB_GEOM_WKT:
#endif
            return ArrowInfo("B");
        case TILEDB_INT8:
            return ArrowInfo("c");
        case TILEDB_UINT8:
            return ArrowInfo("C");
        case TILEDB_INT16:
            return ArrowInfo("s");
        case TILEDB_UINT16:
            return ArrowInfo("S");
        case TILEDB_UINT32:
            return ArrowInfo("I");
        case TILEDB_UINT64:
            return ArrowInfo("L");

        case TILEDB_TIME_SEC:
            return ArrowInfo("tts");
        case TILEDB_TIME_MS:
            return ArrowInfo("ttm");
        case TILEDB_TIME_US:
            return ArrowInfo("ttu");
        case TILEDB_TIME_NS:
            return ArrowInfo("ttn");
        case TILEDB_DATETIME_SEC:
            return ArrowInfo("tss:");
        case TILEDB_DATETIME_MS:
            return ArrowInfo("tsm:");
        case TILEDB_DATETIME_US:
            return ArrowInfo("tsu:");
        case TILEDB_DATETIME_NS:
            return ArrowInfo("tsn:");
        case TILEDB_DATETIME_DAY:
            return ArrowInfo("tdD");
        // TILEDB_BOOL is stored as a uint8_t but arrow::Type::BOOL is 1 bit
        case TILEDB_BOOL:
            return ArrowInfo("C");

        // TODO: these could potentially be rep'd w/ additional
        //       language-specific metadata
        case TILEDB_DATETIME_YEAR:
        case TILEDB_DATETIME_MONTH:
        case TILEDB_DATETIME_WEEK:
        case TILEDB_DATETIME_HR:
        case TILEDB_DATETIME_MIN:
        case TILEDB_DATETIME_PS:
        case TILEDB_DATETIME_FS:
        case TILEDB_DATETIME_AS:
        case TILEDB_TIME_MIN:
        case TILEDB_TIME_PS:
        case TILEDB_TIME_FS:
        case TILEDB_TIME_AS:
        case TILEDB_STRING_UTF16:
        case TILEDB_STRING_UTF32:
        case TILEDB_STRING_UCS2:
        case TILEDB_STRING_UCS4:
        case TILEDB_ANY:
        default:
            break;
    }
    throw TDB_LERROR(
        "TileDB-Arrow: tiledb datatype not understood ('" +
        tiledb::impl::type_to_str(typeinfo.type) +
        "', cell_val_num: " + std::to_string(cell_val_num) + ")");
}

TypeInfo arrow_type_to_tiledb(ArrowSchema* arw_schema) {
    auto fmt = std::string(arw_schema->format);
    bool large = false;
    if (fmt == "+l") {
        large = false;
        assert(arw_schema->n_children == 1);
        arw_schema = arw_schema->children[0];
    } else if (fmt == "+L") {
        large = true;
        assert(arw_schema->n_children == 1);
        arw_schema = arw_schema->children[0];
    }

    if (fmt == "i")
        return {TILEDB_INT32, 4, 1, large};
    else if (fmt == "l")
        return {TILEDB_INT64, 8, 1, large};
    else if (fmt == "f")
        return {TILEDB_FLOAT32, 4, 1, large};
    else if (fmt == "g")
        return {TILEDB_FLOAT64, 8, 1, large};
    else if (fmt == "B")
        return {TILEDB_BLOB, 1, 1, large};
    else if (fmt == "c")
        return {TILEDB_INT8, 1, 1, large};
    else if (fmt == "C")
        return {TILEDB_UINT8, 1, 1, large};
    else if (fmt == "s")
        return {TILEDB_INT16, 2, 1, large};
    else if (fmt == "S")
        return {TILEDB_UINT16, 2, 1, large};
    else if (fmt == "I")
        return {TILEDB_UINT32, 4, 1, large};
    else if (fmt == "L")
        return {TILEDB_UINT64, 8, 1, large};
    // this is kind of a hack
    // technically 'tsn:' is timezone-specific, which we don't support
    // however, the blank (no suffix) base is interconvertible w/ np.datetime64
    else if (fmt == "tsn:")
        return {TILEDB_DATETIME_NS, 8, 1, large};
    else if (fmt == "z" || fmt == "Z")
        return {TILEDB_CHAR, 1, TILEDB_VAR_NUM, fmt == "Z"};
    else if (fmt.rfind("w:", 0) == 0) {
        uint32_t cell_val_num = atoi(fmt.substr(2).c_str());
        return {TILEDB_CHAR, 1, cell_val_num, fmt == "w"};
    } else if (fmt == "u" || fmt == "U")
        return {TILEDB_STRING_UTF8, 1, TILEDB_VAR_NUM, fmt == "U"};
    else if (fmt == "b")
        return {TILEDB_BOOL, 1, 1, large};
    else
        throw tiledb::TileDBError(
            "[TileDB-Arrow]: Unknown or unsupported Arrow format string '" +
            fmt + "'");
}

TypeInfo tiledb_dt_info(const ArraySchema& schema, const std::string& name) {
    if (schema.has_attribute(name)) {
        auto attr = schema.attribute(name);
        auto retval = TypeInfo();
        retval.type = attr.type(),
        retval.elem_size = tiledb::impl::type_size(attr.type()),
        retval.cell_val_num = attr.cell_val_num(), retval.arrow_large = false;
        return retval;
    } else if (schema.domain().has_dimension(name)) {
        auto dom = schema.domain();
        auto dim = dom.dimension(name);

        auto retval = TypeInfo();
        retval.type = dim.type();
        retval.elem_size = tiledb::impl::type_size(dim.type());
        retval.cell_val_num = dim.cell_val_num();
        retval.arrow_large = false;
        return retval;
    } else {
        throw TDB_LERROR("Schema does not have attribute named '" + name + "'");
    }
}

/* ****************************** */
/*        Helper functions        */
/* ****************************** */

void check_arrow_schema(const ArrowSchema* arw_schema) {
    if (arw_schema == nullptr)
        TDB_LERROR("[ArrowIO]: Invalid ArrowSchema object!");

    // sanity check the arrow schema
    if (arw_schema->release == nullptr)
        TDB_LERROR(
            "[ArrowIO]: Invalid ArrowSchema: cannot import released schema.");
    if (arw_schema->format != std::string("+s"))
        TDB_LERROR("[ArrowIO]: Unsupported ArrowSchema: must be struct (+s).");
    if (arw_schema->n_children < 1)
        TDB_LERROR("[ArrowIO]: Unsupported ArrowSchema with 0 children.");
    if (arw_schema->children == nullptr)
        TDB_LERROR(
            "[ArrowIO]: Invalid ArrowSchema with n_children>0 and "
            "children==NULL");
}

/* ****************************** */
/*  Arrow C API Struct wrappers   */
/* ****************************** */

// NOTE: These structs manage the lifetime of the contained C structs.
// CAUTION: they do *not* manage the lifetime of the underlying buffers.

struct CPPArrowSchema {
    /*
     * Initialize a CPPArrowSchema object
     *
     * The lifetime of this object is controlled by the
     * release callback set in the ArrowSchema.
     *
     * Note that an ArrowSchema is *movable*, provided
     * the release callback of the source is set to null.
     */
    CPPArrowSchema(
        std::string name,
        std::string format,
        std::optional<std::string> metadata,
        int64_t flags,
        std::vector<ArrowSchema*> children,
        std::shared_ptr<CPPArrowSchema> dictionary)
        : format_(format)
        , name_(name)
        , metadata_(metadata)
        , children_(children)
        , dictionary_(dictionary) {
        flags_ = flags;
        n_children_ = children.size();

        schema_ = static_cast<ArrowSchema*>(std::malloc(sizeof(ArrowSchema)));
        if (schema_ == nullptr)
            throw tiledb::TileDBError("Failed to allocate ArrowSchema");

        // Initialize ArrowSchema with data *owned by this object*
        // Type description
        schema_->format = format_.c_str();
        schema_->name = name_.c_str();
        schema_->metadata = metadata ? metadata.value().c_str() : nullptr;
        schema_->flags = flags;
        schema_->n_children = n_children_;

        // Cross-refs
        schema_->children = nullptr;
        schema_->dictionary = nullptr;

        // Release callback
        schema_->release = ([](ArrowSchema* schema_p) {
            assert(schema_p->release != nullptr);

            // Release children
            for (int64_t i = 0; i < schema_p->n_children; i++) {
                ArrowSchema* child_schema = schema_p->children[i];
                child_schema->release(child_schema);
                assert(child_schema->release == nullptr);
            }
            // Release dictionary struct
            struct ArrowSchema* dict = schema_p->dictionary;
            if (dict != nullptr && dict->release != nullptr) {
                dict->release(dict);
                assert(dict->release == nullptr);
            }

            // mark the ArrowSchema struct as released
            schema_p->release = nullptr;

            delete static_cast<CPPArrowSchema*>(schema_p->private_data);
        });

        // Private data for release callback
        schema_->private_data = this;

        if (n_children_ > 0) {
            schema_->children = static_cast<ArrowSchema**>(children.data());
        }

        if (dictionary) {
            schema_->dictionary = dictionary.get()->ptr();
        }
    }

    /*
     * CPPArrowSchema destructor
     *
     * This destructor is invoked via the ArrowSchema.release
     * callback. Owned member data is released via default destructors.
     */
    ~CPPArrowSchema() {
        if (schema_ != nullptr)
            std::free(schema_);
    };

    /*
     * Exports the ArrowSchema to a pre-allocated target struct
     *
     * This function frees the array_.
     * The lifetime of all other member variables is controlled
     * by the ArrowSchema.release callback, which frees the
     * CPPArrowSchema structure (via ArrowSchema.private_data).
     */
    void export_ptr(ArrowSchema* out_schema) {
        assert(out_schema != nullptr);
        memcpy(out_schema, schema_, sizeof(ArrowSchema));
        std::free(schema_);
        schema_ = nullptr;
    }

    ArrowSchema* mutable_ptr() {
        assert(schema_ != nullptr);
        return schema_;
    }

    ArrowSchema* ptr() const {
        assert(schema_ != nullptr);
        return schema_;
    }

   private:
    ArrowSchema* schema_;
    std::string format_;
    std::string name_;
    std::optional<std::string> metadata_;
    int64_t flags_;
    int64_t n_children_;
    std::vector<ArrowSchema*> children_;
    std::shared_ptr<CPPArrowSchema> dictionary_;
};

struct CPPArrowArray {
    /*
     * Initialize a CPPArrowSchema object
     *
     * The lifetime of this object is controlled by the
     * release callback set in the ArrowSchema.
     *
     * Note that an ArrowSchema is *movable*, provided
     * the release callback of the source is set to null.
     */
    CPPArrowArray(
        int64_t elem_num,
        int64_t null_num,
        int64_t offset,
        std::vector<std::shared_ptr<CPPArrowArray>> children,
        std::vector<void*> buffers,
        ArrowAdapter::release_cb cb,
        void* cb_data)
        : owner_cb(cb)
        , owner_cb_data(cb_data) {
        array_ = static_cast<ArrowArray*>(std::malloc(sizeof(ArrowArray)));
        if (array_ == nullptr)
            throw tiledb::TileDBError("Failed to allocate ArrowArray");

        // Data description
        array_->length = elem_num;
        array_->null_count = null_num;
        array_->offset = offset;
        array_->n_buffers = static_cast<int64_t>(buffers.size());
        array_->n_children = static_cast<int64_t>(children.size());
        array_->buffers = nullptr;
        array_->children = nullptr;
        array_->dictionary = nullptr;
        // Bookkeeping
        array_->release = ([](ArrowArray* array_p) {
            assert(array_p->release != nullptr);

            // Release children
            for (int64_t i = 0; i < array_p->n_children; i++) {
                ArrowArray* child_array = array_p->children[i];
                child_array->release(child_array);
                assert(child_array->release == nullptr);
            }

            // Release dictionary
            struct ArrowArray* dict = array_p->dictionary;
            if (dict != nullptr && dict->release != nullptr) {
                dict->release(dict);
                assert(dict->release == nullptr);
            }

            // mark the ArrowArray struct as released
            array_p->release = nullptr;

            // free the data owner
            auto self = static_cast<CPPArrowArray*>(array_p->private_data);
            if (self->owner_cb) {
                self->owner_cb(self->owner_cb_data);
            }

            delete self;
        });
        array_->private_data = this;

        buffers_ = buffers;
        array_->buffers = const_cast<const void**>(buffers_.data());
    }

    /*
     * CPPArrowArray destructor
     *
     * This destructor is invoked via the ArrowArray.release
     * callback. Owned member data is released via default destructors.
     */
    ~CPPArrowArray() {
        if (array_ != nullptr) {
            // did not export
            std::free(array_);
        }
    }

    void export_ptr(ArrowArray* out_array) {
        assert(out_array != nullptr);
        memcpy(out_array, array_, sizeof(ArrowArray));
        std::free(array_);
        array_ = nullptr;
    }

    ArrowArray* ptr() const {
        assert(array_ != nullptr);
        return array_;
    }

    ArrowArray* mutable_ptr() {
        assert(array_ != nullptr);
        return array_;
    }

   private:
    ArrowArray* array_;
    std::vector<void*> buffers_;
    ArrowAdapter::release_cb owner_cb;
    void* owner_cb_data;
};

/* ****************************** */
/*         Arrow Importer         */
/* ****************************** */

class ArrowImporter {
   public:
    ArrowImporter(Query* const query);
    ~ArrowImporter();

    void import_(std::string name, ArrowArray* array, ArrowSchema* schema);

   private:
    Query* const query_;
    std::vector<void*> offset_buffers_;

};  // class ArrowExporter

ArrowImporter::ArrowImporter(Query* const query)
    : query_(query) {
}

ArrowImporter::~ArrowImporter() {
    for (auto p : offset_buffers_) {
        std::free(p);
    }
}

void ArrowImporter::import_(
    std::string name, ArrowArray* arw_array, ArrowSchema* arw_schema) {
    auto typeinfo = arrow_type_to_tiledb(arw_schema);

    // buffer conversion

    if (typeinfo.cell_val_num == TILEDB_VAR_NUM) {
        assert(arw_array->n_buffers == 3);

        void* p_offsets = const_cast<void*>(arw_array->buffers[1]);
        void* p_data = const_cast<void*>(arw_array->buffers[2]);
        const uint64_t num_offsets = arw_array->length;
        uint64_t data_nbytes = 0;
        if (typeinfo.arrow_large) {
            data_nbytes = static_cast<uint64_t*>(p_offsets)[num_offsets] *
                          typeinfo.elem_size;
        } else {
            data_nbytes = static_cast<uint32_t*>(p_offsets)[num_offsets] *
                          typeinfo.elem_size;
        }

        // Set the TileDB buffer, adding `1` to `num_offsets` to account for
        // the expected, extra offset.
        query_->set_data_buffer(name, p_data, data_nbytes);
        query_->set_offsets_buffer(
            name, static_cast<uint64_t*>(p_offsets), num_offsets + 1);
    } else {
        // fixed-size attribute (not TILEDB_VAR_NUM)
        assert(arw_array->n_buffers == 2);

        void* p_data = const_cast<void*>(arw_array->buffers[1]);
        uint64_t data_num = arw_array->length;

        query_->set_data_buffer(name, static_cast<void*>(p_data), data_num);
    }
}

/* ****************************** */
/*         Arrow Exporter         */
/* ****************************** */

class ArrowExporter {
   public:
    ArrowExporter(Context* const ctx, Query* const query);

    void export_(
        const std::string& name,
        ArrowArray* array,
        ArrowSchema* schema,
        ArrowAdapter::release_cb cb,
        void* private_data);

    BufferInfo buffer_info(const std::string& name);

   private:
    Context* const ctx_;
    Query* const query_;
};

// ArrowExporter implementation
ArrowExporter::ArrowExporter(Context* const ctx, Query* const query)
    : ctx_(ctx)
    , query_(query) {
}

BufferInfo ArrowExporter::buffer_info(const std::string& name) {
    void* data = nullptr;
    uint64_t data_nelem = 0;
    uint64_t* offsets = nullptr;
    uint64_t offsets_nelem = 0;
    uint64_t elem_size = 0;

    auto typeinfo = tiledb_dt_info(query_->array().schema(), name);

    auto result_elts = query_->result_buffer_elements();
    auto result_elt_iter = result_elts.find(name);
    if (result_elt_iter == result_elts.end()) {
        TDB_LERROR("No results found for attribute '" + name + "'");
    }

    uint8_t offsets_elem_nbytes = ctx_->config().get(
                                      "sm.var_offsets.bitsize") == "32" ?
                                      4 :
                                      8;

    bool is_var = typeinfo.cell_val_num == TILEDB_VAR_NUM;

    // NOTE: result sizes are in bytes
    if (is_var) {
        query_->get_data_buffer(name, &data, &data_nelem, &elem_size);
        query_->get_offsets_buffer(name, &offsets, &offsets_nelem);

        // The C++ API Query::get_buffer returns an incorrect `offsets_nelemn`
        // when we read 32-bit offsets from the core. As a work-around, we will
        // invoke the C-API to get the byte size of the offsets buffer and
        // divide by 4 to get the correct number of offset elements. Note that
        // the C API does not fetch the data element size, so we ignore
        // `data_nbytes` below and leave `elem_size` untouched.
        uint64_t* offsets_nbytes = nullptr;
        uint64_t* data_nbytes = nullptr;
        ctx_->handle_error(tiledb_query_get_data_buffer(
            ctx_->ptr().get(),
            query_->ptr().get(),
            name.c_str(),
            &data,
            &data_nbytes));
        ctx_->handle_error(tiledb_query_get_offsets_buffer(
            ctx_->ptr().get(),
            query_->ptr().get(),
            name.c_str(),
            &offsets,
            &offsets_nbytes));
        offsets_nelem = *offsets_nbytes / offsets_elem_nbytes;
    } else {
        query_->get_data_buffer(name, &data, &data_nelem, &elem_size);
    }

    auto retval = BufferInfo();
    retval.tdbtype = typeinfo;
    retval.is_var = is_var;
    retval.data_num = data_nelem;
    retval.data = data;
    retval.data_elem_size = elem_size;
    retval.offsets_num = (is_var ? offsets_nelem : 1);
    retval.offsets = offsets;
    retval.offsets_elem_size = offsets_elem_nbytes;

    return retval;
}

int64_t flags_for_buffer(BufferInfo binfo) {
    /*  TODO, use these defs from arrow_cdefs.h -- currently not applicable.
        #define ARROW_FLAG_DICTIONARY_ORDERED 1
        #define ARROW_FLAG_NULLABLE 2
        #define ARROW_FLAG_MAP_KEYS_SORTED 4
    */
    (void)binfo;
    return 0;
}

template <typename T>
T cast_checked(uint64_t val) {
    if (val > std::numeric_limits<T>::max()) {
        throw tiledb::TileDBError(
            "[TileDB-Arrow] Value too large to cast to requested type");
    }
    return static_cast<T>(val);
}

void ArrowExporter::export_(
    const std::string& name,
    ArrowArray* array,
    ArrowSchema* schema,
    ArrowAdapter::release_cb cb,
    void* cb_data) {
    auto bufferinfo = this->buffer_info(name);

    if (schema == nullptr || array == nullptr) {
        throw tiledb::TileDBError(
            "ArrowExporter: received invalid pointer to output array or "
            "schema.");
    }

    auto arrow_fmt = tiledb_buffer_arrow_fmt(bufferinfo);
    auto arrow_flags = flags_for_buffer(bufferinfo);

    // lifetime:
    //   - address is stored in ArrowSchema.private_data
    //   - delete is called by lambda stored in ArrowSchema.release
    CPPArrowSchema* cpp_schema = new CPPArrowSchema(
        name, arrow_fmt.fmt_, std::nullopt, arrow_flags, {}, {});

    std::vector<void*> buffers;
    if (bufferinfo.is_var) {
        buffers = {nullptr, bufferinfo.offsets, bufferinfo.data};
    } else {
        buffers = {nullptr, bufferinfo.data};
    }
    cpp_schema->export_ptr(schema);

    size_t elem_num = bufferinfo.data_num;
    if (bufferinfo.is_var) {
        // adjust for arrow offset unless empty result
        elem_num = (bufferinfo.offsets_num == 0) ? 0 :
                                                   bufferinfo.offsets_num - 1;
    } else {
        if (arrow_fmt.fmt_.rfind("w:", 0) == 0) {
            // for Arrow fixed-width binary (non-variable TILEDB_CHAR), we need
            // to take the size of the entire buffer and divide by the size of
            // each element
            elem_num = bufferinfo.data_num / bufferinfo.tdbtype.cell_val_num;
        } else if (arrow_fmt.fmt_ == "tdD") {
            // for Arrow date32 we only need the first 4 bytes of each 8-byte
            // TILEDB_DATETIME_DAY element which we keep by in-place left
            // shifting
            for (size_t i = 0; i < bufferinfo.data_num; i++) {
                uint32_t lost_data = *(reinterpret_cast<uint32_t*>(
                    static_cast<uint8_t*>(buffers[1]) + i * 8 + 4));
                if (lost_data != 0) {
                    throw tiledb::TileDBError(
                        "[TileDB-Arrow] Non-zero data detected in the memory "
                        "buffer at "
                        "position that will be overwritten");
                }

                static_cast<uint32_t*>(buffers[1])[i] = cast_checked<uint32_t>(
                    static_cast<uint64_t*>(buffers[1])[i]);
            }
        }
    }

    auto cpp_arrow_array = new CPPArrowArray(
        elem_num,  // elem_num
        0,         // null_num
        0,         // offset
        {},        // children
        buffers,
        cb,
        cb_data);
    cpp_arrow_array->export_ptr(array);
}

/* End TileDB Arrow IO internal implementation */
/* ************************************************************************ */

/* ************************************************************************ */
/* Begin TileDB Arrow IO public API implementation */

ArrowAdapter::ArrowAdapter(Context* const ctx, Query* const query)
    : importer_(nullptr)
    , exporter_(nullptr) {
    importer_ = new ArrowImporter(query);
    if (!importer_) {
        throw tiledb::TileDBError(
            "[TileDB-Arrow] Failed to allocate ArrowImporter!");
    }
    exporter_ = new ArrowExporter(ctx, query);
    if (!exporter_) {
        throw tiledb::TileDBError(
            "[TileDB-Arrow] Failed to allocate ArrowImporter!");
    }
}

void ArrowAdapter::export_buffer(
    const char* name,
    void* arrow_array,
    void* arrow_schema,
    release_cb cb,
    void* private_data) {
    exporter_->export_(
        name,
        (ArrowArray*)arrow_array,
        (ArrowSchema*)arrow_schema,
        cb,
        private_data);
}

void ArrowAdapter::import_buffer(
    const char* name, void* arrow_array, void* arrow_schema) {
    importer_->import_(
        name, (ArrowArray*)arrow_array, (ArrowSchema*)arrow_schema);
}

ArrowAdapter::~ArrowAdapter() {
    if (importer_)
        delete importer_;
    if (exporter_)
        delete exporter_;
}

void query_get_buffer_arrow_array(
    Context* const ctx,
    Query* const query,
    std::string name,
    void* v_arw_array,
    void* v_arw_schema) {
    ArrowExporter exporter(ctx, query);

    exporter.export_(
        name,
        (ArrowArray*)v_arw_array,
        (ArrowSchema*)v_arw_schema,
        nullptr,
        nullptr);
}

void query_set_buffer_arrow_array(
    Query* const query,
    std::string name,
    void* v_arw_array,
    void* v_arw_schema) {
    auto arw_schema = (ArrowSchema*)v_arw_schema;
    auto arw_array = (ArrowArray*)v_arw_array;
    check_arrow_schema(arw_schema);

    ArrowImporter importer(query);
    importer.import_(name, arw_array, arw_schema);
}

}  // end namespace arrow
}  // end namespace tiledb

/* End TileDB Arrow IO public API implementation */
/* ************************************************************************ */

#endif  // TILEDB_ARROW_H
