/*
 * SPDX-FileCopyrightText: All Contributors to the PyTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#include "precompiled_header.hpp"
#include "device_pipe.h"
#include "tgutils.h"
#include "pytgutils.h"
#include "tango_numpy.h"
#include "fast_from_py.h"

#include "to_py_numpy.hpp"

namespace PyTango
{
namespace DevicePipe
{
template <long tangoTypeConst>
bopy::object __update_scalar_values(Tango::DevicePipe &self, size_t elt_idx)
{
    typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType;
    TangoScalarType val;
    bopy::str name(self.get_data_elt_name(elt_idx));
    self >> val;
    bopy::object data(val);
    return bopy::make_tuple(name, data);
}

template <>
bopy::object __update_scalar_values<Tango::DEV_VOID>(Tango::DevicePipe &self, size_t elt_idx)
{
    bopy::str name(self.get_data_elt_name(elt_idx));
    return bopy::make_tuple(name, bopy::object());
}

template <>
bopy::object __update_scalar_values<Tango::DEV_STRING>(Tango::DevicePipe &self, size_t elt_idx)
{
    typedef std::string TangoScalarType;
    TangoScalarType val;
    bopy::str name(self.get_data_elt_name(elt_idx));
    self >> val;
    bopy::object data(val);
    return bopy::make_tuple(name, data);
}

template <>
bopy::object __update_scalar_values<Tango::DEV_PIPE_BLOB>(Tango::DevicePipe &self, size_t elt_idx)
{
    Tango::DevicePipeBlob val;
    bopy::str name(self.get_data_elt_name(elt_idx));
    self >> val;
    bopy::object data = extract(val, PyTango::ExtractAsNumpy);
    return bopy::make_tuple(name, data);
}

template <long tangoArrayTypeConst>
bopy::object
    __update_array_values(Tango::DevicePipe &self, bopy::object &py_self, size_t elt_idx, PyTango::ExtractAs extract_as)
{
    typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType;

    TangoArrayType tmp_arr;
    self >> (&tmp_arr);
    bopy::object data;
    switch(extract_as)
    {
    default:
    case PyTango::ExtractAsNumpy:
        data = to_py_numpy<tangoArrayTypeConst>(&tmp_arr, py_self);
        tmp_arr.get_buffer(1);
        break;
    case PyTango::ExtractAsList:
    case PyTango::ExtractAsPyTango3:
        data = to_py_list(&tmp_arr);
        break;
    case PyTango::ExtractAsTuple:
        data = to_py_tuple(&tmp_arr);
        break;
    case PyTango::ExtractAsString: /// @todo
    case PyTango::ExtractAsNothing:
        data = bopy::object();
        break;
    }

    bopy::str name(self.get_data_elt_name(elt_idx));
    return bopy::make_tuple(name, data);
}

template <>
bopy::object __update_array_values<Tango::DEVVAR_LONGSTRINGARRAY>(Tango::DevicePipe &self,
                                                                  bopy::object &py_self,
                                                                  size_t elt_idx,
                                                                  PyTango::ExtractAs extract_as)
{
    assert(false);
    return bopy::object();
}

template <>
bopy::object __update_array_values<Tango::DEVVAR_DOUBLESTRINGARRAY>(Tango::DevicePipe &self,
                                                                    bopy::object &py_self,
                                                                    size_t elt_idx,
                                                                    PyTango::ExtractAs extract_as)
{
    assert(false);
    return bopy::object();
}

bopy::object update_value(Tango::DevicePipe &self, bopy::object &py_self, size_t elt_idx, PyTango::ExtractAs extract_as)
{
    const int elt_type = self.get_data_elt_type(elt_idx);

    TANGO_DO_ON_DEVICE_DATA_TYPE_ID(elt_type, return __update_scalar_values<tangoTypeConst>(self, elt_idx);
                                    ,
                                    return __update_array_values<tangoTypeConst>(self, py_self, elt_idx, extract_as););
    return bopy::object();
}

void update_values(Tango::DevicePipe &self,
                   bopy::object &py_self,
                   PyTango::ExtractAs extract_as /*=PyTango::ExtractAsNumpy*/)
{
    // We do not want is_empty to launch an exception!!
    // self.reset_exceptions(Tango::DevicePipe::isempty_flag);

    // py_self.attr("name") = self.get_name();
    bopy::list data;
    py_self.attr("data") = data;

    size_t elt_nb = self.get_data_elt_nb();
    for(size_t elt_idx = 0; elt_idx < elt_nb; ++elt_idx)
    {
        data.append(update_value(self, py_self, elt_idx, extract_as));
    }
}

///////////////////////////////////////////////////////////////////////////////////////////

template <typename T, long tangoTypeConst>
bopy::object __extract_scalar(T &obj, size_t elt_idx)
{
    typedef typename TANGO_const2type(tangoTypeConst) TangoScalarType;
    TangoScalarType val;
    obj >> val;
    return bopy::object(val);
}

template <>
bopy::object __extract_scalar<Tango::DevicePipe, Tango::DEV_VOID>(Tango::DevicePipe &obj, size_t elt_idx)
{
    return bopy::object();
}

template <>
bopy::object __extract_scalar<Tango::DevicePipe, Tango::DEV_STRING>(Tango::DevicePipe &obj, size_t elt_idx)
{
    std::string val;
    obj >> val;
    return bopy::object(val);
}

template <>
bopy::object __extract_scalar<Tango::DevicePipe, Tango::DEV_PIPE_BLOB>(Tango::DevicePipe &obj, size_t elt_idx)
{
    Tango::DevicePipeBlob val;
    obj >> val;
    // TODO: propagate extract_as
    return extract(val, PyTango::ExtractAsNumpy);
}

template <>
bopy::object __extract_scalar<Tango::DevicePipeBlob, Tango::DEV_VOID>(Tango::DevicePipeBlob &obj, size_t elt_idx)
{
    return bopy::object();
}

template <>
bopy::object __extract_scalar<Tango::DevicePipeBlob, Tango::DEV_STRING>(Tango::DevicePipeBlob &obj, size_t elt_idx)
{
    std::string val;
    obj >> val;
    return bopy::object(val);
}

template <>
bopy::object __extract_scalar<Tango::DevicePipeBlob, Tango::DEV_PIPE_BLOB>(Tango::DevicePipeBlob &obj, size_t elt_idx)
{
    Tango::DevicePipeBlob val;
    obj >> val;
    // TODO: propagate extract_as
    return extract(val, PyTango::ExtractAsNumpy);
}

template <long tangoTypeConst>
bopy::object extract_scalar(Tango::DevicePipe &self, size_t elt_idx)
{
    return __extract_scalar<Tango::DevicePipe, tangoTypeConst>(self, elt_idx);
}

template <long tangoTypeConst>
bopy::object extract_scalar(Tango::DevicePipeBlob &self, size_t elt_idx)
{
    return __extract_scalar<Tango::DevicePipeBlob, tangoTypeConst>(self, elt_idx);
}

template <typename T, long tangoArrayTypeConst>
bopy::object __extract_array(T &obj, size_t elt_idx, PyTango::ExtractAs extract_as)
{
    typedef typename TANGO_const2type(tangoArrayTypeConst) TangoArrayType;

    TangoArrayType tmp_arr;
    obj >> (&tmp_arr);
    bopy::object data;
    switch(extract_as)
    {
    default:
    case PyTango::ExtractAsNumpy:
        data = to_py_numpy<tangoArrayTypeConst>(&tmp_arr, 1);
        break;
    case PyTango::ExtractAsList:
    case PyTango::ExtractAsPyTango3:
        data = to_py_list(&tmp_arr);
        break;
    case PyTango::ExtractAsTuple:
        data = to_py_tuple(&tmp_arr);
        break;
    case PyTango::ExtractAsString: /// @todo
    case PyTango::ExtractAsNothing:
        data = bopy::object();
        break;
    }
    return data;
}

template <>
bopy::object __extract_array<Tango::DevicePipe, Tango::DEVVAR_LONGSTRINGARRAY>(Tango::DevicePipe &pipe,
                                                                               size_t elt_idx,
                                                                               PyTango::ExtractAs extract_as)
{
    assert(false);
    return bopy::object();
}

template <>
bopy::object __extract_array<Tango::DevicePipe, Tango::DEVVAR_DOUBLESTRINGARRAY>(Tango::DevicePipe &pipe,
                                                                                 size_t elt_idx,
                                                                                 PyTango::ExtractAs extract_as)
{
    assert(false);
    return bopy::object();
}

template <>
bopy::object __extract_array<Tango::DevicePipeBlob, Tango::DEVVAR_LONGSTRINGARRAY>(Tango::DevicePipeBlob &blob,
                                                                                   size_t elt_idx,
                                                                                   PyTango::ExtractAs extract_as)
{
    assert(false);
    return bopy::object();
}

template <>
bopy::object __extract_array<Tango::DevicePipeBlob, Tango::DEVVAR_DOUBLESTRINGARRAY>(Tango::DevicePipeBlob &blob,
                                                                                     size_t elt_idx,
                                                                                     PyTango::ExtractAs extract_as)
{
    assert(false);
    return bopy::object();
}

template <long tangoArrayTypeConst>
bopy::object extract_array(Tango::DevicePipe &self, size_t elt_idx, PyTango::ExtractAs extract_as)
{
    return __extract_array<Tango::DevicePipe, tangoArrayTypeConst>(self, elt_idx, extract_as);
}

template <long tangoArrayTypeConst>
bopy::object extract_array(Tango::DevicePipeBlob &self, size_t elt_idx, PyTango::ExtractAs extract_as)
{
    return __extract_array<Tango::DevicePipeBlob, tangoArrayTypeConst>(self, elt_idx, extract_as);
}

template <typename T>
bopy::object __extract_item(T &obj, size_t elt_idx, PyTango::ExtractAs extract_as)
{
    const int elt_type = obj.get_data_elt_type(elt_idx);
    TANGO_DO_ON_DEVICE_DATA_TYPE_ID(elt_type, return extract_scalar<tangoTypeConst>(obj, elt_idx);
                                    , return extract_array<tangoTypeConst>(obj, elt_idx, extract_as););
    return bopy::object();
}

template <typename T>
bopy::object __extract(T &obj, PyTango::ExtractAs extract_as)
{
    bopy::list data;
    size_t elt_nb = obj.get_data_elt_nb();
    for(size_t elt_idx = 0; elt_idx < elt_nb; ++elt_idx)
    {
        bopy::dict elem;
        elem["name"] = obj.get_data_elt_name(elt_idx);
        elem["dtype"] = static_cast<Tango::CmdArgType>(obj.get_data_elt_type(elt_idx));
        elem["value"] = __extract_item(obj, elt_idx, extract_as);
        data.append(elem);
    }
    return data;
}

bopy::object extract(Tango::DevicePipeBlob &blob, PyTango::ExtractAs extract_as)
{
    bopy::object name = bopy::str(blob.get_name());
    bopy::object value = __extract<Tango::DevicePipeBlob>(blob, extract_as);
    return bopy::make_tuple(name, value);
}

bopy::object extract(Tango::DevicePipe &device_pipe, PyTango::ExtractAs extract_as = PyTango::ExtractAsNumpy)
{
    bopy::object name = bopy::str(device_pipe.get_root_blob_name());
    bopy::object value = __extract<Tango::DevicePipe>(device_pipe, extract_as);
    return bopy::make_tuple(name, value);
}
} // namespace DevicePipe
} // namespace PyTango

void export_device_pipe()
{
    bopy::class_<Tango::DevicePipe> DevicePipe("DevicePipe");

    bopy::scope dp_scope = DevicePipe;

    DevicePipe.def(bopy::init<>())
        .def(bopy::init<const std::string &>())
        .def(bopy::init<const std::string &, const std::string &>())
        .def(bopy::init<const Tango::DevicePipe &>())
        .add_property(
            "name",
            bopy::make_function(&Tango::DevicePipe::get_name, bopy::return_value_policy<bopy::copy_const_reference>()),
            &Tango::DevicePipe::set_name)
        .add_property("root_blob_name",
                      bopy::make_function(&Tango::DevicePipe::get_root_blob_name,
                                          bopy::return_value_policy<bopy::copy_const_reference>()),
                      &Tango::DevicePipe::set_root_blob_name)
        .add_property("data_elt_nb", &Tango::DevicePipe::get_data_elt_nb, &Tango::DevicePipe::set_data_elt_nb)
        .add_property("data_elt_names", &Tango::DevicePipe::get_data_elt_names, &Tango::DevicePipe::set_data_elt_names)
        .def("get_data_elt_name", &Tango::DevicePipe::get_data_elt_name)
        .def("get_data_elt_type", &Tango::DevicePipe::get_data_elt_type)

        .def("extract", (bopy::object(*)(Tango::DevicePipe &, PyTango::ExtractAs)) PyTango::DevicePipe::extract)

        .def("extract", (bopy::object(*)(Tango::DevicePipeBlob &, PyTango::ExtractAs)) PyTango::DevicePipe::extract);
}
