#include <tiledb/tiledb>
#include <tiledb/tiledb_experimental>

#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/pytypes.h>
#include <pybind11/stl.h>

#include "common.h"

namespace libtiledbcpp {

using namespace tiledb;
using namespace tiledbpy::common;
namespace py = pybind11;

void set_fill_value(Attribute& attr, py::array value) {
    attr.set_fill_value(value.data(), value.nbytes());
}

py::array get_fill_value(Attribute& attr) {
    // Get the fill value from the C++ API as a void* value.
    const void* value;
    uint64_t size;
    attr.get_fill_value(&value, &size);

    // If this is a string type, we want to return each value as a single cell.
    if (is_tdb_str(attr.type())) {
        auto value_type = py::dtype("|S1");
        return py::array(value_type, size, value);
    }

    // If this is a record type (void), return a single cell.
    // If this is a blob-like type, we want to return each value as a single
    // byte cell.
    auto tdb_type = attr.type();
    if (tdb_type == TILEDB_BLOB
#if TILEDB_VERSION_MAJOR >= 2 && TILEDB_VERSION_MINOR >= 21
        || tdb_type == TILEDB_GEOM_WKB || tdb_type == TILEDB_GEOM_WKT
#endif
    ) {
        auto value_type = py::dtype("S");
        return py::array(value_type, size, value);
    }

    // Get the number of values in a cell and the Python datatype.
    auto value_num = attr.cell_val_num();
    auto value_type = tdb_to_np_dtype(attr.type(), value_num);

    if (py::getattr(value_type, "kind").is(py::str("V"))) {
        return py::array(value_type, 1, value);
    }

    // If this is a complex type both cell values fit in a single complex
    // element.
    if (value_type.is(py::dtype("complex64")) ||
        value_type.is(py::dtype("complex128"))) {
        return py::array(value_type, 1, value);
    }

    return py::array(value_type, value_num, value);
}

void set_enumeration_name(
    Attribute& attr, const Context& ctx, const std::string& enumeration_name) {
    AttributeExperimental::set_enumeration_name(ctx, attr, enumeration_name);
}

std::optional<std::string> get_enumeration_name(
    Attribute& attr, const Context& ctx) {
    return AttributeExperimental::get_enumeration_name(ctx, attr);
}

void init_attribute(py::module& m) {
    py::class_<tiledb::Attribute>(m, "Attribute")
        .def(py::init<Attribute>())

        .def(
            py::init<Context&, std::string&, tiledb_datatype_t>(),
            py::keep_alive<1, 2>())

        .def(
            py::init<Context&, std::string&, tiledb_datatype_t, FilterList&>(),
            py::keep_alive<1, 2>())

        .def(py::init<const Context&, py::capsule>(), py::keep_alive<1, 2>())

        .def(
            "__capsule__",
            [](Attribute& attr) {
                return py::capsule(attr.ptr().get(), "attr");
            })

        .def_property_readonly("_name", &Attribute::name)

        .def_property_readonly("_tiledb_dtype", &Attribute::type)

        .def_property(
            "_nullable", &Attribute::nullable, &Attribute::set_nullable)

        .def_property(
            "_ncell", &Attribute::cell_val_num, &Attribute::set_cell_val_num)

        .def_property_readonly("_var", &Attribute::variable_sized)

        .def_property(
            "_filters", &Attribute::filter_list, &Attribute::set_filter_list)

        .def_property_readonly("_cell_size", &Attribute::cell_size)

        .def_property("_fill", get_fill_value, set_fill_value)

        .def("_get_enumeration_name", get_enumeration_name)

        .def("_set_enumeration_name", set_enumeration_name)

        .def("_dump", [](Attribute& attr) {
#if TILEDB_VERSION_MAJOR >= 2 && TILEDB_VERSION_MINOR >= 26
            std::stringstream ss;
            ss << attr;
            return ss.str();
#else
        attr.dump();
#endif
        });
}

}  // namespace libtiledbcpp
