#include <pybind11/pybind11.h>
#include <pybind11/pytypes.h>
#include <pybind11/iostream.h>
#include <pybind11/numpy.h>
#include <pybind11/stl_bind.h>
#include <pybind11/chrono.h>
#include <pybind11/stl.h>
#include <pybind11/complex.h>
#include <pybind11/functional.h>
// kinoshita-san version need eigen.h
//#include <pybind11/eigen.h>

#include <hark-lib/node.h>
#include <hark-lib/network.h>
#include <hark-lib/datastream.h>
#include <hark-lib/dispatch.h>
#include <hark-lib/internal/thread.h>

#include <hark-lib/matrix.h>
#include <hark-lib/tensor.h>
#include <hark-lib/source.h>
#include <hark-lib/timepoint.h>
#include <hark-lib/tf.h>

#include <hark-lib/node/AND.h>
#include <hark-lib/node/Constant.h>
#include <hark-lib/node/Delay.h>
#include <hark-lib/node/Equal.h>
#include <hark-lib/node/Greater.h>
#include <hark-lib/node/GreaterEqual.h>
#include <hark-lib/node/IF.h>
#include <hark-lib/node/InputStream.h>
#include <hark-lib/node/IterCount.h>
#include <hark-lib/node/Iterate.h>
#include <hark-lib/node/NOP.h>
#include <hark-lib/node/NOT.h>
#include <hark-lib/node/OR.h>
//#include <hark-lib/node/Sleep.h>
#include <hark-lib/node/Smaller.h>
#include <hark-lib/node/SmallerEqual.h>

#include <hark-lib/node/AudioStreamFromMemory.h>
#include <hark-lib/node/AudioStreamFromWave.h>
//#include <hark-lib/node/BF.h>
#include <hark-lib/node/BGNEstimator.h>
#include <hark-lib/node/CMChannelSelector.h>
#include <hark-lib/node/CMConjEachElement.h>
#include <hark-lib/node/CMDivideEachElement.h>
#include <hark-lib/node/CMIdentityMatrix.h>
#include <hark-lib/node/CMInverseMatrix.h>
#include <hark-lib/node/CMLoad.h>
#include <hark-lib/node/CMMakerFromFFT.h>
#include <hark-lib/node/CMMakerFromFFTwithFlag.h>
#include <hark-lib/node/CMMultiplyEachElement.h>
#include <hark-lib/node/CMMultiplyMatrix.h>
#include <hark-lib/node/CMSave.h>
//#include <hark-lib/node/CSP.h>
#include <hark-lib/node/CalcSpecAddPower.h>
#include <hark-lib/node/CalcSpecSubGain.h>
#include <hark-lib/node/ChannelSelector.h>
#include <hark-lib/node/CombineMap.h>
#include <hark-lib/node/CombineSource.h>
#include <hark-lib/node/ConstantLocalization.h>
//#include <hark-lib/node/DataLogger.h>
#include <hark-lib/node/Delta.h>
//#include <hark-lib/node/DeltaMask.h>
//#include <hark-lib/node/DeltaPowerMask.h>
#include <hark-lib/node/DisplayLocalization.h>
#include <hark-lib/node/EstimateLeak.h>
//#include <hark-lib/node/EstimateTF.h>
#include <hark-lib/node/FeatureRemover.h>
#include <hark-lib/node/GHDSS.h>
//#include <hark-lib/node/HRLE.h>
//#include <hark-lib/node/HarkDataStreamSender.h>
//#include <hark-lib/node/HarkParamDynReconf.h>
//#include <hark-lib/node/LoadMapFrames.h>
//#include <hark-lib/node/LoadSourceFrames.h>
//#include <hark-lib/node/LoadVectorFrames.h>
//#include <hark-lib/node/LocalizeBFDS.h>
#include <hark-lib/node/LocalizationMUSIC.hpp>
#include <hark-lib/node/LocalizeMUSIC.h>
//#include <hark-lib/node/MFCCExtraction.h>
//#include <hark-lib/node/MFMGeneration.h>
//#include <hark-lib/node/ML.h>
#include <hark-lib/node/MSLSExtraction.h>
//#include <hark-lib/node/MSNR.h>
//#include <hark-lib/node/MVDR.h>
//#include <hark-lib/node/MapIDOffset.h>
//#include <hark-lib/node/MapMatrixValueOverwrite.h>
//#include <hark-lib/node/MapOperator.h>
//#include <hark-lib/node/MapSelectorBySource.h>
//#include <hark-lib/node/MapToMap.h>
//#include <hark-lib/node/MapToMatrix.h>
//#include <hark-lib/node/MapToVector.h>
//#include <hark-lib/node/MapVectorValueOverwrite.h>
//#include <hark-lib/node/MatrixToMap.h>
//#include <hark-lib/node/MatrixToVector.h>
//#include <hark-lib/node/MatrixValueOverwrite.h>
#include <hark-lib/node/MelFilterBank.h>
//#include <hark-lib/node/MultiDownSampler.h>
#include <hark-lib/node/MultiFFT.h>
#include <hark-lib/node/MultiGain.h>
//#include <hark-lib/node/MultiLocalizationIntegrator.h>
//#include <hark-lib/node/NormalizeMUSIC.h>
//#include <hark-lib/node/PlayAudio.h>
//#include <hark-lib/node/PostFilter.h>
#include <hark-lib/node/PowerCalcForMap.h>
#include <hark-lib/node/PowerCalcForMatrix.h>
#include <hark-lib/node/PreEmphasis.h>
//#include <hark-lib/node/ResizeMapMatrixValue.h>
//#include <hark-lib/node/ResizeMapVectorValue.h>
#include <hark-lib/node/SaveFeatures.h>
#include <hark-lib/node/SaveHTKFeatures.h>
//#include <hark-lib/node/SaveMapFrames.h>
//#include <hark-lib/node/SaveMatrixFrames.h>
//#include <hark-lib/node/SaveRawPCM.h>
//#include <hark-lib/node/SaveSourceLocation.h>
//#include <hark-lib/node/SaveVectorFrames.h>
#include <hark-lib/node/SaveWavePCM.h>
//#include <hark-lib/node/SegmentAudioStreamByID.h>
#include <hark-lib/node/SemiBlindICA.h>
#include <hark-lib/node/SourceIntervalExtender.h>
//#include <hark-lib/node/SourceSelectorByDirection.h>
//#include <hark-lib/node/SourceSelectorByID.h>
//#include <hark-lib/node/SourceSelectorBySourceInfo.h>
#include <hark-lib/node/SourceTracker.h>
//#include <hark-lib/node/SourceTrackerPF.h>
//#include <hark-lib/node/SourceTransformer.h>
#include <hark-lib/node/SpectralGainFilter.h>
//#include <hark-lib/node/SpectralMeanNormalization.h>
#include <hark-lib/node/SpectralMeanNormalizationIncremental.h>
#include <hark-lib/node/SpeechRecognitionClient.h>
//#include <hark-lib/node/SpeechRecognitionSMNClient.h>
#include <hark-lib/node/SubscriberFLAC.h>
#include <hark-lib/node/Synthesize.h>
//#include <hark-lib/node/TextConcatnate.h>
//#include <hark-lib/node/TextConverter.h>
#include <hark-lib/node/VADZC.h>
//#include <hark-lib/node/VectorToMap.h>
//#include <hark-lib/node/VectorToMatrix.h>
//#include <hark-lib/node/VectorToVector.h>
//#include <hark-lib/node/VectorValueOverwrite.h>
#include <hark-lib/node/WhiteNoiseAdder.h>

#include <hark-lib/node/PublishData.h>
#include <hark-lib/node/SubscribeData.h>

//#include <hark-lib/node/PlotQuickSourceKivy.h>

#include <hark-lib/node/Print.h>

#include "offline.h"

namespace py = pybind11;

template <typename T>
py::object
vector_to_list_object(hark::data &value)
{
    std::vector<T> &v = value.cast<std::vector<T> >();
    py::list l;
    for(auto it: v){ l.append(py::cast(it)); }

    return l;
}

template <typename T>
py::object
vector_to_array_t_object(hark::data &value)
{
    std::vector<T> &v = value.cast<std::vector<T> >();
    py::array_t<T> a(v.size(), v.data());

    return a;
}

template <typename T>
py::object
matrix_to_array_t_object(hark::data &value)
{
    hark::Matrix<T> &m = value.cast<hark::Matrix<T> >();
    bool row_major = m.native_is_row_major();
    py::buffer_info bi = {
        m.data(),
        sizeof(T),
        py::format_descriptor<T>::format(),
        2,
        { m.rows(), m.cols() },
        { sizeof(T) * (row_major ? m.cols() : 1),
          sizeof(T) * (row_major ? 1 : m.rows())}
    };
    return py::array_t<T>(bi);
}

template <typename T>
py::object
tensor_to_array_t_object(hark::data &value)
{
    hark::Tensor<T> &t = value.cast<hark::Tensor<T> >();
    py::array_t<T> a(t.vsize, t.data.data());

    return a;
}

static py::object
data_to_object(hark::data value)
{
    // container data elements are hark::data (recursive)
    if(value.is_type<std::map<int, hark::data> >()){
        auto &s = value.cast<std::map<int, hark::data> >();
        py::dict d;
        for(auto it: s){
            d[py::int_(it.first)] = data_to_object(it.second);
        }
        return d;
    }
    else if(value.is_type<std::vector<hark::data> >()){
        auto &s = value.cast<std::vector<hark::data> >();
        py::list l = py::list(s.size());
        for(size_t i = 0; i < s.size(); i++){
            l[i] = data_to_object(s[i]);
        }
        return l;
    }
    // std::vector<T> -> list or array_t
    else if(value.is_type<std::vector<bool> >()){
        return vector_to_list_object<bool>(value);
    }
    else if(value.is_type<std::vector<int8_t> >()){
        return vector_to_array_t_object<int8_t>(value);
    }
    else if(value.is_type<std::vector<int16_t> >()){
        return vector_to_array_t_object<int16_t>(value);
    }
    else if(value.is_type<std::vector<int32_t> >()){
        return vector_to_array_t_object<int32_t>(value);
    }
    else if(value.is_type<std::vector<int64_t> >()){
        return vector_to_array_t_object<int64_t>(value);
    }
    else if(value.is_type<std::vector<float> >()){
        return vector_to_array_t_object<float>(value);
    }
    else if(value.is_type<std::vector<double> >()){
        return vector_to_array_t_object<double>(value);
    }
    else if(value.is_type<std::vector<std::complex<float> > >()){
        return vector_to_array_t_object<std::complex<float> >(value);
    }
    else if(value.is_type<std::vector<std::complex<double> > >()){
        return vector_to_array_t_object<std::complex<double> >(value);
    }
    // kinoshita-san version
    /*
    else if(value.is_type<hark::Matrix<float> >()){
        hark::Matrix<float> &m = value.cast<hark::Matrix<float>>();

        bool row_major = hark::Matrix<float>::Flags & Eigen::RowMajorBit;

        py::buffer_info bi = {
            m.data(),
            sizeof(float),
            py::format_descriptor<float>::format(),
            2,
            { m.rows(), m.cols() },
            { sizeof(float) * (row_major ? m.cols() : 1),
              sizeof(float) * (row_major ? 1 : m.rows())}
        };

        return py::array_t<float>(bi);
    }
    else if(value.is_type<hark::Matrix<std::complex<float> > >()){
        hark::Matrix<std::complex<float> > &m = value.cast<hark::Matrix<std::complex<float> > >();

        bool row_major = hark::Matrix<std::complex<float> >::Flags & Eigen::RowMajorBit;

        py::buffer_info bi = {
            m.data(),
            sizeof(float),
            py::format_descriptor<float>::format(),
            2,
            { m.rows(), m.cols() },
            { sizeof(float) * (row_major ? m.cols() : 1),
              sizeof(float) * (row_major ? 1 : m.rows())}
        };

        return py::array_t<float>(bi);
    }
    */
    // hark::Matrix<T> -> array_t
    else if(value.is_type<hark::Matrix<float> >()){
        return matrix_to_array_t_object<float>(value);
    }
    else if(value.is_type<hark::Matrix<double> >()){
        return matrix_to_array_t_object<double>(value);
    }
    else if(value.is_type<hark::Matrix<std::complex<float> > >()){
        return matrix_to_array_t_object<std::complex<float> >(value);
    }
    else if(value.is_type<hark::Matrix<std::complex<double> > >()){
        return matrix_to_array_t_object<std::complex<double> >(value);
    }
    // hark::Tensor<T> -> array_t
    else if(value.is_type<hark::Tensor<float> >()){
        return tensor_to_array_t_object<float>(value);
    }
    else if(value.is_type<hark::Tensor<double> >()){
        return tensor_to_array_t_object<double>(value);
    }
    else if(value.is_type<hark::Tensor<std::complex<float> > >()){
        return tensor_to_array_t_object<std::complex<float> >(value);
    }
    else if(value.is_type<hark::Tensor<std::complex<double> > >()){
        return tensor_to_array_t_object<std::complex<double> >(value);
    }
    // hark:: namespace classes
    else if(value.is_type<hark::Source>()){
        return py::cast(value.cast<hark::Source>());
    }
    else if(value.is_type<hark::TransferFunction>()){
        return py::cast(value.cast<hark::TransferFunction>());
    }
    else if(value.is_type<hark::Time>()){
        return py::cast(value.cast<hark::Time>());
    }
    else if(value.is_type<hark::TimeStamp>()){
        return py::cast(value.cast<hark::TimeStamp>());
    }
    else{
        return py::str("Unexpected data");
    }

    return py::none();
}

template <typename T>
hark::data
py_array_t_to_data(py::handle &object)
{
    auto value = object.cast<py::array_t<T> >();
    const auto &bi = value.request();

    if(bi.ndim == 1){
        std::vector<T> v(value.data(), value.data() + value.size());
        return hark::data(std::move(v));
    }
    else if(bi.ndim == 2){
        hark::Matrix<T> m = hark::Matrix<T>::from_native_image(static_cast<T*>(bi.ptr), bi.shape[0], bi.shape[1]);
        return hark::data(std::move(m));
    }
    else{
        hark::Tensor<T> t = hark::Tensor<T>();
        size_t valid_size = 1;

        t.vsize.resize(bi.shape.size());
        for(size_t i = 0; i < t.vsize.size(); i++){
            t.vsize[i] = bi.shape[i];
            valid_size *= t.vsize[i];
        }

        std::vector<T> v(value.data(), value.data() + value.size());
        std::swap(t.data, v);

        if(valid_size != t.data.size()){
            std::stringstream ss;
            ss << "Unexpected data size: The expected Tensor data size is " << valid_size
               << ", but it was actually " << t.data.size() << ".\n";
            throw hark::RuntimeError(ss.str());
        }
        return hark::data(std::move(t));
    }
    return hark::data();
}

template <typename T>
hark::data
py_list_to_data(py::handle &object)
{
    auto value = object.cast<py::list>();

    std::vector<T> v;
    for(size_t i = 0; i < py::len(value); i++){
        T e = value[i].cast<T>();
        v.emplace_back(e);
    }
    return hark::data(v);
}

static hark::data
object_to_data(py::handle object)
{
    /*
    if(py::isinstance<py::array_t<int16_t> >(object)){
        auto value = object.cast<py::array_t<int16_t> >();
        std::vector<short int> v(value.data(), value.data() + value.size());
        return hark::data(v);
    }
    else if(py::isinstance<py::array_t<float> >(object)){
        auto value = object.cast<py::array_t<float> >();
        const auto &bi = value.request();

        if(bi.ndim == 1){
            std::vector<float> v(value.data(), value.data() + value.size());
            return hark::data(v);
        }else if(bi.ndim == 2){
            hark::Matrix<float> m = hark::Matrix<float>::from_col_major_image(static_cast<float *>(bi.ptr), bi.shape[0], bi.shape[1]);
            return hark::data(m);
        }else{
            throw hark::RuntimeError("Unexpected data type: array<float> with dimensionality more than 2.");
        }
    }
    */
    // array_t -> std::vector<T> , hark::Matrix<T> or hark::Tensor<T>
    if(py::isinstance<py::array_t<int8_t> >(object)){
        return py_array_t_to_data<int8_t>(object);
    }
    else if(py::isinstance<py::array_t<int16_t> >(object)){
        return py_array_t_to_data<int16_t>(object);
    }
    else if(py::isinstance<py::array_t<int32_t> >(object)){
        return py_array_t_to_data<int32_t>(object);
    }
    else if(py::isinstance<py::array_t<int64_t> >(object)){
        return py_array_t_to_data<int64_t>(object);
    }
    else if(py::isinstance<py::array_t<uint8_t> >(object)){
        return py_array_t_to_data<uint8_t>(object);
    }
    else if(py::isinstance<py::array_t<uint16_t> >(object)){
        return py_array_t_to_data<uint16_t>(object);
    }
    else if(py::isinstance<py::array_t<uint32_t> >(object)){
        return py_array_t_to_data<uint32_t>(object);
    }
    else if(py::isinstance<py::array_t<uint64_t> >(object)){
        return py_array_t_to_data<uint64_t>(object);
    }
    else if(py::isinstance<py::array_t<float> >(object)){
        return py_array_t_to_data<float>(object);
    }
    else if(py::isinstance<py::array_t<double> >(object)){
        return py_array_t_to_data<double>(object);
    }
    else if(py::isinstance<py::array_t<std::complex<float> > >(object)){
        return py_array_t_to_data<std::complex<float> >(object);
    }
    else if(py::isinstance<py::array_t<std::complex<double> > >(object)){
        return py_array_t_to_data<std::complex<double> >(object);
    }
    // list -> std::vector<hark::data>
    else if(py::isinstance<py::list>(object)){
        if(py::len(object.cast<py::list>()) == 0){
            std::vector<hark::data> v;
            return hark::data(v);
        }
        else if(py::isinstance<hark::Source>(object.cast<py::list>()[0])){
            auto value = object.cast<py::list>();

            std::vector<hark::data> v;
            for(size_t i = 0; i < py::len(value); i++){
                hark::Source src = value[i].cast<hark::Source>();
                v.emplace_back(hark::data(src));
            }
            return hark::data(v);
        }
        else if(py::isinstance<py::bool_>(object.cast<py::list>()[0])){
            return py_list_to_data<bool>(object);
        }
        else if(py::isinstance<py::int_>(object.cast<py::list>()[0])){
            return py_list_to_data<int64_t>(object);
        }
        else if(py::isinstance<py::float_>(object.cast<py::list>()[0])){
            return py_list_to_data<double>(object);
        }
        /*
        else if(py::isinstance<bool>(object.cast<py::list>()[0])){
            return py_list_to_data<bool>(object);
        }
        else if(py::isinstance<int8_t>(object.cast<py::list>()[0])){
            return py_list_to_data<int8_t>(object);
        }
        else if(py::isinstance<int16_t>(object.cast<py::list>()[0])){
            return py_list_to_data<int16_t>(object);
        }
        else if(py::isinstance<int32_t>(object.cast<py::list>()[0])){
            return py_list_to_data<int32_t>(object);
        }
        else if(py::isinstance<int64_t>(object.cast<py::list>()[0])){
            return py_list_to_data<int64_t>(object);
        }
        else if(py::isinstance<uint8_t>(object.cast<py::list>()[0])){
            return py_list_to_data<uint8_t>(object);
        }
        else if(py::isinstance<uint16_t>(object.cast<py::list>()[0])){
            return py_list_to_data<uint16_t>(object);
        }
        else if(py::isinstance<uint32_t>(object.cast<py::list>()[0])){
            return py_list_to_data<uint32_t>(object);
        }
        else if(py::isinstance<uint64_t>(object.cast<py::list>()[0])){
            return py_list_to_data<uint64_t>(object);
        }
        else if(py::isinstance<float>(object.cast<py::list>()[0])){
            return py_list_to_data<float>(object);
        }
        else if(py::isinstance<double>(object.cast<py::list>()[0])){
            return py_list_to_data<double>(object);
        }
        else if(py::isinstance<std::complex<float> >(object.cast<py::list>()[0])){
            return py_list_to_data<std::complex<float> >(object);
        }
        else if(py::isinstance<std::complex<double> >(object.cast<py::list>()[0])){
            return py_list_to_data<std::complex<double> >(object);
        }
        */
        else{
            throw hark::RuntimeError("Unexpected data type.  To support, implement method harklib::object_to_data(). py::list");
        }
    }
    else if(py::isinstance<py::dict>(object)){
        if(py::len(object.cast<py::dict>()) == 0){
            std::map<int, hark::data> m;
            return hark::data(std::move(m));
        }
        std::map<int, hark::data> m;
        for(std::pair<py::handle, py::handle> item: object.cast<py::dict>()){
            auto key = item.first.cast<int>();
            m[key] = object_to_data(item.second);
        }
        return hark::data(std::move(m));
    }
    else if(py::isinstance<py::str>(object)){
        if(py::len(object.cast<py::str>()) == 0){
            std::string s;
            return hark::data(std::move(s));
        }
        std::string s(object.cast<std::string>());
        return hark::data(std::move(s));
    }
    else if(py::isinstance<py::bool_>(object)){
        return hark::data(std::move(object.cast<bool>()));
    }
    else if(py::isinstance<py::int_>(object)){
        return hark::data(std::move(object.cast<int64_t>()));
    }
    else if(py::isinstance<py::float_>(object)){
        return hark::data(std::move(object.cast<double>()));
    }
    else if(py::isinstance<hark::Source>(object)){
        hark::Source src = object.cast<hark::Source>();
        return hark::data(std::move(src));
    }
    else if(py::isinstance<hark::TransferFunction>(object)){
        hark::TransferFunction tf = object.cast<hark::TransferFunction>();
        return hark::data(std::move(tf));
    }
    else if(py::isinstance<hark::Time>(object)){
        hark::Time t = object.cast<hark::Time>();
        return hark::data(std::move(t));
    }
    else if(py::isinstance<hark::TimeStamp>(object)){
        hark::TimeStamp ts = object.cast<hark::TimeStamp>();
        return hark::data(std::move(ts));
    }
    else if(py::isinstance<hark::data>(object)){
        return object.cast<hark::data>();
    }
    else{
        throw hark::RuntimeError("Unexpected data type.  To support, implement method harklib::object_to_data().");
    }
    return hark::data();
}

class NetworkDefTrampoline: public hark::NetworkDef
{
public:
    using NetworkDef::NetworkDef;

    virtual void build(hark::Network &network, hark::DataSourceMap &input, hark::DataSinkMap &output) override
    {
        PYBIND11_OVERRIDE_PURE(
             void, /* Return type */
             hark::NetworkDef,      /* Parent class */
             build,          /* Name of function in C++ (must match Python name) */
             &network, &input, &output   /* args */
        );
    }
};

class SubscribeData: public hark::node::SubscribeData
{
public:
    virtual void receive(hark::data output) override
    {
        py::gil_scoped_acquire guard;
        this->receive_object(data_to_object(output));
    }

    virtual void receive_object(py::object value) = 0;
};

class SubscribeDataTrampoline: public SubscribeData
{
public:
    using SubscribeData::SubscribeData;

    virtual void receive_object(py::object value) override
    {
        PYBIND11_OVERRIDE_PURE_NAME(
             void, /* Return type */
             SubscribeData,      /* Parent class */
             "receive",
             receive_object,          /* Name of function in C++ (must match Python name) */
             value
        );
    }
};

class PythonNode: public hark::NodeDef
{
public:
    PythonNode(py::list inputs, py::list outputs):
        hark::NodeDef("PythonNode")
    {
        // std::cout << "PythoNode::PythonNode(py::list, py::list)" << std::endl;
        for(const auto &i: inputs){
            const auto &name = py::str(i);
            *this << hark::Terminal::in(name);
        }
        for(const auto &o: outputs){
            const auto &name = py::str(o);
            *this >> hark::Terminal::out(name);
        }
    }

    // virtual ~PythonNode()
    // {
    //     std::cout << "PythonNode::~PythonNode()" << std::endl;
    // }

    virtual void reset()
    {
    }

    virtual bool operator () (size_t count, hark::InputDataBundle &input, hark::OutputDataBundle &output)
    {
        py::gil_scoped_acquire guard;
        py::dict input_dict;
        {
            for(const auto &iname: input.names()){
                input_dict[iname.c_str()] = data_to_object(input[iname].last());
            }
        }
        py::object output_object = this->call(input_dict);
        // std::cout << "operator(): 2" << std::endl;
        py::print(output_object);

        {
            const py::dict &output_dict = output_object.cast<py::dict>();
            for(auto it: output_dict){
                const auto &oname = py::str(it.first);
                hark::data d = object_to_data(it.second);
                // std::cout << "data: " << d << std::endl;
                output[oname].push(d);
            }
        }
        return true;
    }

    virtual py::object call(py::object input) = 0;
};

class PythonNodeTrampoline: public PythonNode
{
public:
    // using PythonNode::PythonNode;
    PythonNodeTrampoline(py::list x, py::list y):
        PythonNode(x, y)
    {
        // std::cout << "PythonNodeTrampoline::PythonNodeTrampoline(py::list, py::list), call=" << (void *)&PythonNodeTrampoline::call << std::endl;    
    }

    // virtual ~PythonNodeTrampoline()
    // {
    //     // std::cout << "PythonNodeTrampoline::~PythonNodeTrampoline()" << std::endl;
    // }

    virtual py::object call(py::object value) override
    {
        // { std::cout << "call: 1" << std::endl; }
        PYBIND11_OVERRIDE_PURE(
             py::object, /* Return type */
             PythonNode,      /* Parent class */
             call,
             value
        );
        // { std::cout << "call: 2" << std::endl; }
    }
};

template<typename MAP, typename DS>
class DataMapTrampolineTemplate: public MAP
{
public:
    using MAP::MAP;

    virtual size_t count() const override
    {
        PYBIND11_OVERRIDE_PURE(size_t, MAP, count);
    }

    virtual size_t count_ready() const override
    {
        PYBIND11_OVERRIDE_PURE(size_t, MAP, count_ready);
    }

    virtual std::vector<std::string> names() const override
    {
        PYBIND11_OVERRIDE_PURE(std::vector<std::string>, MAP, names);
    }

    virtual DS &add(const std::string &name) override
    {
        PYBIND11_OVERRIDE_PURE(DS &, MAP, add, name);
    }

    virtual bool has(const std::string &name) const override
    {
        PYBIND11_OVERRIDE_PURE(bool, MAP, has, name);
    }

    virtual DS &get(const std::string &name) override
    {
        PYBIND11_OVERRIDE_PURE(DS &, MAP, get, name);
    }

    virtual DS &query(const std::string &name) override
    {
        PYBIND11_OVERRIDE_PURE(DS &, MAP, query, name);
    }
};

using DataSinkMapTrampoline = DataMapTrampolineTemplate<hark::DataSinkMap, hark::DataSink>;
using DataSourceMapTrampoline = DataMapTrampolineTemplate<hark::DataSourceMap, hark::DataSource>;

template<typename T>
using py_container = std::unique_ptr<T, py::nodelete>;

template<typename T, typename... options>
using py_class = py::class_<T, py_container<T>, options...>;

PYBIND11_MODULE(harklib, m)
{
    m.doc() = "hark: HARK-lib C++ module wrapper";

    py_class<hark::dispatch::BaseDispatcher>(m, "BaseDispatcher");
    py_class<hark::dispatch::OneShot, hark::dispatch::BaseDispatcher>(m, "OneShotDispatcher")
        .def(py::init());
    py_class<hark::dispatch::Repeat, hark::dispatch::BaseDispatcher>(m, "RepeatDispatcher")
        .def(py::init());
    py_class<hark::dispatch::TriggeredRepeat, hark::dispatch::BaseDispatcher>(m, "TriggeredRepeatDispatcher")
        .def(py::init());
    py_class<hark::dispatch::TriggeredMultiShot, hark::dispatch::BaseDispatcher>(m, "TriggeredMultiShotDispatcher")
        .def(py::init());
    py_class<hark::dispatch::TriggeredOneShot, hark::dispatch::BaseDispatcher>(m, "TriggeredOneShotDispatcher")
        .def(py::init());

    py_class<hark::NodeDef>(m, "NodeDef")
        .def("__call__", [](hark::NodeDef *_this, py::kwargs kwargs){
            return offline_process(_this, kwargs);
        });

    py_class<hark::NetworkDef, NetworkDefTrampoline>(m, "NetworkDef")
        .def(py::init())
        .def("build", &hark::NetworkDef::build);

    py_class<hark::BaseNode>(m, "BaseNode")
        .def("attach_userdata", &hark::BaseNode::attach_userdata)
        .def("detach_userdata", &hark::BaseNode::detach_userdata)
        .def("get_userdata", &hark::BaseNode::get_userdata)
        .def("add_input", [](hark::BaseNode *_this, const std::string &name, hark::DataSource *data_source){
            _this->add_input(hark::Input(name, *data_source));
            return _this;
        })
        .def("add_input", [](hark::BaseNode *_this, const std::string &name, const std::vector<float> &value){
            _this->add_input(hark::Input(name, value));
            return _this;
        })
        .def("add_input", [](hark::BaseNode *_this, const std::string &name, const std::vector<std::complex<float> > &value){
            _this->add_input(hark::Input(name, value));
            return _this;
        })
        .def("add_input", [](hark::BaseNode *_this, const std::string &name, const std::vector<double> &value){
            _this->add_input(hark::Input(name, value));
            return _this;
        })
        .def("add_input", [](hark::BaseNode *_this, const std::string &name, const std::vector<std::complex<double> > &value){
            _this->add_input(hark::Input(name, value));
            return _this;
        })
        .def("add_input", [](hark::BaseNode *_this, const std::string &name, const hark::Matrix<float> &value){
            _this->add_input(hark::Input(name, value));
            return _this;
        })
        .def("add_input", [](hark::BaseNode *_this, const std::string &name, const hark::Matrix<std::complex<float> > &value){
            _this->add_input(hark::Input(name, value));
            return _this;
        })
        .def("add_input", [](hark::BaseNode *_this, const std::string &name, const hark::Matrix<double> &value){
            _this->add_input(hark::Input(name, value));
            return _this;
        })
        .def("add_input", [](hark::BaseNode *_this, const std::string &name, const hark::Matrix<std::complex<double> > &value){
            _this->add_input(hark::Input(name, value));
            return _this;
        })
        .def("add_input", [](hark::BaseNode *_this, const std::string &name, const std::string &value){
            _this->add_input(hark::Input(name, value));
            return _this;
        })
        .def("add_input", [](hark::BaseNode *_this, const std::string &name, int value){
            _this->add_input(hark::Input(name, value));
            return _this;
        })
        .def("add_input", [](hark::BaseNode *_this, const std::string &name, double value){
            _this->add_input(hark::Input(name, value));
            return _this;
        })
        .def("add_input", [](hark::BaseNode *_this, const std::string &name, bool value){
            _this->add_input(hark::Input(name, value));
            return _this;
        })
        .def("get", [](hark::BaseNode *_this, const std::string &name){
            hark::DataSource &ds = _this->get(name);
            return &ds;
        });

    py_class<hark::BaseNode::UserData>(m, "BaseNodeUserData")
        .def(py::init());

    py_class<hark::Node, hark::BaseNode>(m, "Node")
        .def(py::init<hark::NodeDef *, hark::dispatch::BaseDispatcher *, const std::string &>())
        .def_static("create", [](hark::NodeDef *nodedef, hark::dispatch::BaseDispatcher *dispatch, const std::string &name){ //}, py::type ty, const std::string &name){ //}, hark::dispatch::BaseDispatcher &dispatch) {
            return new hark::Node(nodedef, dispatch, name);
        });
        // .def("__call__", [](hark::Node *_this, py::kwargs kwargs){
        //     return offline_process(_this, kwargs);
        // });

    py_class<hark::Network, hark::BaseNode>(m, "Network")
        .def(py::init<hark::NetworkDef *, const std::string &>())
        // .def("attach_userdata", &hark::Network::attach_userdata)
        // .def("detach_userdata", &hark::Network::detach_userdata)
        .def_static("create_network", [](hark::NetworkDef *networkdef, hark::dispatch::BaseDispatcher *dispatch, const std::string &name){ //}, py::type ty, const std::string &name){ //}, hark::dispatch::BaseDispatcher &dispatch) {
            return new hark::Network(networkdef, name);
        })
        .def("create", [](hark::Network *network, hark::NodeDef *nodedef, hark::dispatch::BaseDispatcher *dispatch, const std::string &name){ //}, py::type ty, const std::string &name){ //}, hark::dispatch::BaseDispatcher &dispatch) {
            return &network->create_node(nodedef, dispatch, name);
        })
        .def("create", [](hark::Network *network, hark::NetworkDef *nodedef, hark::dispatch::BaseDispatcher *dispatch, const std::string &name){ //}, py::type ty, const std::string &name){ //}, hark::dispatch::BaseDispatcher &dispatch) {
            return &network->create_network(nodedef, dispatch, name);
        })
        .def("execute", [](hark::Network *_this){
            py::gil_scoped_release guard;
            try{
                _this->execute();
            }catch(std::exception &ex){
                throw hark::RuntimeError(ex.what());
            }
        })
        .def("stop", [](hark::Network *_this){
            py::gil_scoped_acquire guard;
            _this->stop();
        })
        .def("query_node", &hark::Network::query_node);
        // .def("call", [](hark::Network *_this, py::kwargs kwargs){
        //     return offline_process(_this, kwargs);
        // })
        // .def("__call__", [](hark::Network *_this, py::kwargs kwargs){
        //     return offline_process(_this, kwargs);
        // });

    py_class<hark::DataSource>(m, "DataSource");
    py_class<hark::DataSink>(m, "DataSink");
    py_class<hark::DataSourceMap, DataSourceMapTrampoline>(m, "DataSourceMap")
        .def(py::init())
        .def("query", [](hark::DataSourceMap *_this, const std::string &name){
            hark::DataSource &ds = _this->query(name);
            return &ds;
        })
        .def("get", [](hark::DataSourceMap *_this, const std::string &name){
            hark::DataSource &ds = _this->get(name);
            return &ds;
        });
    py_class<hark::DataSinkMap, DataSinkMapTrampoline>(m, "DataSinkMap")
        .def(py::init())
        .def("add_input", [](hark::DataSinkMap *_this, const std::string &name, hark::DataSource *data_source){
            _this->operator [](name) << *data_source;
            return _this;
        });

    py_class<hark::DataSourceMapImplWrapper, hark::DataSourceMap>(m, "DataSourceMapImplWrapper");
    py_class<hark::DataSinkMapImplWrapper, hark::DataSinkMap>(m, "DataSinkMapImplWrapper");

#define DEFINE_NODEDEF(name) \
    py_class<hark::node::name, hark::NodeDef>(m, #name) \
        .def(py::init())

    DEFINE_NODEDEF(AND);
    DEFINE_NODEDEF(Constant);
    DEFINE_NODEDEF(Delay);
    DEFINE_NODEDEF(Equal);
    DEFINE_NODEDEF(Greater);
    DEFINE_NODEDEF(GreaterEqual);
    DEFINE_NODEDEF(IF);
    DEFINE_NODEDEF(InputStream);
    DEFINE_NODEDEF(IterCount);
    DEFINE_NODEDEF(Iterate);
    DEFINE_NODEDEF(NOP);
    DEFINE_NODEDEF(NOT);
    DEFINE_NODEDEF(OR);
    //DEFINE_NODEDEF(Sleep);
    DEFINE_NODEDEF(Smaller);
    DEFINE_NODEDEF(SmallerEqual);

    DEFINE_NODEDEF(AudioStreamFromMemory);
    DEFINE_NODEDEF(AudioStreamFromWave);
    //DEFINE_NODEDEF(BF);
    DEFINE_NODEDEF(BGNEstimator);
    DEFINE_NODEDEF(CMChannelSelector);
    DEFINE_NODEDEF(CMConjEachElement);
    DEFINE_NODEDEF(CMDivideEachElement);
    DEFINE_NODEDEF(CMIdentityMatrix);
    DEFINE_NODEDEF(CMInverseMatrix);
    DEFINE_NODEDEF(CMLoad);
    DEFINE_NODEDEF(CMMakerFromFFT);
    DEFINE_NODEDEF(CMMakerFromFFTwithFlag);
    DEFINE_NODEDEF(CMMultiplyEachElement);
    DEFINE_NODEDEF(CMMultiplyMatrix);
    DEFINE_NODEDEF(CMSave);
    //DEFINE_NODEDEF(CSP);
    DEFINE_NODEDEF(CalcSpecAddPower);
    DEFINE_NODEDEF(CalcSpecSubGain);
    DEFINE_NODEDEF(ChannelSelector);
    DEFINE_NODEDEF(CombineMap);
    DEFINE_NODEDEF(CombineSource);
    DEFINE_NODEDEF(ConstantLocalization);
    //DEFINE_NODEDEF(DataLogger);
    DEFINE_NODEDEF(Delta);
    //DEFINE_NODEDEF(DeltaMask);
    //DEFINE_NODEDEF(DeltaPowerMask);
    //DEFINE_NODEDEF(DisplayLocalization);
    DEFINE_NODEDEF(EstimateLeak);
    //DEFINE_NODEDEF(EstimateTF);
    DEFINE_NODEDEF(FeatureRemover);
    DEFINE_NODEDEF(GHDSS);
    //DEFINE_NODEDEF(HRLE);
    //DEFINE_NODEDEF(HarkDataStreamSender);
    //DEFINE_NODEDEF(HarkParamDynReconf);
    //DEFINE_NODEDEF(LoadMapFrames);
    //DEFINE_NODEDEF(LoadSourceFrames);
    //DEFINE_NODEDEF(LoadVectorFrames);
    //DEFINE_NODEDEF(LocalizeBFDS);
    DEFINE_NODEDEF(LocalizeMUSIC);
    //DEFINE_NODEDEF(MFCCExtraction);
    //DEFINE_NODEDEF(MFMGeneration);
    //DEFINE_NODEDEF(ML);
    DEFINE_NODEDEF(MSLSExtraction);
    //DEFINE_NODEDEF(MSNR);
    //DEFINE_NODEDEF(MVDR);
    //DEFINE_NODEDEF(MapIDOffset);
    //DEFINE_NODEDEF(MapMatrixValueOverwrite);
    //DEFINE_NODEDEF(MapOperator);
    //DEFINE_NODEDEF(MapSelectorBySource);
    //DEFINE_NODEDEF(MapToMap);
    //DEFINE_NODEDEF(MapToMatrix);
    //DEFINE_NODEDEF(MapToVector);
    //DEFINE_NODEDEF(MapVectorValueOverwrite);
    //DEFINE_NODEDEF(MatrixToMap);
    //DEFINE_NODEDEF(MatrixToMatrix);
    //DEFINE_NODEDEF(MatrixToVector);
    //DEFINE_NODEDEF(MatrixValueOverwrite);
    DEFINE_NODEDEF(MelFilterBank);
    //DEFINE_NODEDEF(MultiDownSampler);
    DEFINE_NODEDEF(MultiFFT);
    DEFINE_NODEDEF(MultiGain);
    //DEFINE_NODEDEF(MultiLocalizationIntegrator);
    //DEFINE_NODEDEF(NormalizeMUSIC);
    //DEFINE_NODEDEF(PlayAudio);
    //DEFINE_NODEDEF(PostFilter);
    DEFINE_NODEDEF(PowerCalcForMap);
    DEFINE_NODEDEF(PowerCalcForMatrix);
    DEFINE_NODEDEF(PreEmphasis);
    //DEFINE_NODEDEF(ResizeMapMatrixValue);
    //DEFINE_NODEDEF(ResizeMapVectorValue);
    DEFINE_NODEDEF(SaveFeatures);
    DEFINE_NODEDEF(SaveHTKFeatures);
    //DEFINE_NODEDEF(SaveMapFrames);
    //DEFINE_NODEDEF(SaveMatrixFrames);
    //DEFINE_NODEDEF(SaveRawPCM);
    //DEFINE_NODEDEF(SaveSourceLocation);
    //DEFINE_NODEDEF(SaveVectorFrames);
    DEFINE_NODEDEF(SaveWavePCM);
    //DEFINE_NODEDEF(SegmentAudioStreamByID);
    DEFINE_NODEDEF(SemiBlindICA);
    DEFINE_NODEDEF(SourceIntervalExtender);
    //DEFINE_NODEDEF(SourceSelectorByDirection);
    //DEFINE_NODEDEF(SourceSelectorByID);
    //DEFINE_NODEDEF(SourceSelectorBySourceInfo);
    DEFINE_NODEDEF(SourceTracker);
    //DEFINE_NODEDEF(SourceTrackerPF);
    //DEFINE_NODEDEF(SourceTransformer);
    DEFINE_NODEDEF(SpectralGainFilter);
    //DEFINE_NODEDEF(SpectralMeanNormalization);
    DEFINE_NODEDEF(SpectralMeanNormalizationIncremental);
    DEFINE_NODEDEF(SpeechRecognitionClient);
    //DEFINE_NODEDEF(SpeechRecognitionSMNClient);
    //DEFINE_NODEDEF(SubscribeFLAC);
    DEFINE_NODEDEF(Synthesize);
    //DEFINE_NODEDEF(TextConcatenate);
    //DEFINE_NODEDEF(TextConverter);
    DEFINE_NODEDEF(VADZC);
    //DEFINE_NODEDEF(VectorToMap);
    //DEFINE_NODEDEF(VectorToMatrix);
    //DEFINE_NODEDEF(VectorToVector);
    //DEFINE_NODEDEF(VectorValueOverwrite);
    DEFINE_NODEDEF(WhiteNoiseAdder);

    //DEFINE_NODEDEF(PlotQuickSourceKivy);

    DEFINE_NODEDEF(Print);

    py_class<hark::node::PublishData, hark::NodeDef>(m, "PublishData")
        .def(py::init())
        .def("close", &hark::node::PublishData::close)
        .def("push", [](hark::node::PublishData *_this, py::object &data){
            if(py::isinstance<py::array_t<int16_t> >(data)){
                auto value = py::array_t<int16_t>(data);
                std::vector<short int> v(value.data(), value.data() + value.size());
                _this->push(hark::data(v));
            }else{
                throw hark::RuntimeError("PublishData: Unexpected data type.  To support, implement method harklib::pyobject_to_harkdata().");
            }
        });

    py_class<SubscribeData, hark::NodeDef, SubscribeDataTrampoline>(m, "SubscribeData")
        .def(py::init())
        .def("receive", &SubscribeData::receive_object);

    py_class<PythonNode, hark::NodeDef, PythonNodeTrampoline>(m, "PythonNode")
    // py_class<PythonNode, PythonNodeTrampoline>(m, "PythonNode")
        .def(py::init<py::list, py::list>())
        .def("call", &PythonNode::call);

    py_class<hark::Source>(m, "Source")
        .def(py::init())
        .def_property("x",
            [](const hark::Source *_this){
                return std::vector<float>(_this->x, _this->x + 3);
            },
            [](hark::Source *_this, std::vector<float> &v){
                _this->x[0] = v[0];
                _this->x[1] = v[1];
                _this->x[2] = v[2];
            })
        .def_readwrite("power", &hark::Source::power)
        .def_readwrite("id", &hark::Source::id)
        .def_readwrite("sub_id", &hark::Source::sub_id)
        .def_readwrite("mic_id", &hark::Source::mic_id)
        .def_readwrite("remaining", &hark::Source::remaining)
        .def_readwrite("count", &hark::Source::count)
        .def_readwrite("first_found_count", &hark::Source::first_found_count)
        .def_readwrite("last_found_count", &hark::Source::last_found_count)
        .def_property("tfindex",
            [](const hark::Source *_this){
                return std::vector<int>(_this->tfindex, _this->tfindex + 3);
            },
            [](hark::Source *_this, std::vector<int> &v){
                _this->tfindex[0] = v[0];
                _this->tfindex[1] = v[1];
                _this->tfindex[2] = v[2];
            })
        .def_property("tfindex_max",
            [](const hark::Source *_this){
                return std::vector<int>(_this->tfindex_max, _this->tfindex_max + 3);
            },
            [](hark::Source *_this, std::vector<int> &v){
                _this->tfindex_max[0] = v[0];
                _this->tfindex_max[1] = v[1];
                _this->tfindex_max[2] = v[2];
            })
        .def_property("tfindex_min",
            [](const hark::Source *_this){
                return std::vector<int>(_this->tfindex_min, _this->tfindex_min + 3);
            },
            [](hark::Source *_this, std::vector<int> &v){
                _this->tfindex_min[0] = v[0];
                _this->tfindex_min[1] = v[1];
                _this->tfindex_min[2] = v[2];
            })
        .def_property("compare_mode",
            [](const hark::Source *_this){
                return py::cast((_this->compare_mode == hark::Source::CompareMode::DEG) ? "DEG" : "TFINDEX");
            },
            [](hark::Source *_this, py::object &m){
                _this->compare_mode = ((m.cast<std::string>() == "DEG") ? hark::Source::CompareMode::DEG : hark::Source::CompareMode::TFINDEX);
            });

    py_class<hark::UtteranceInfo>(m, "UtteranceInfo")
        .def(py::init())
        .def_readwrite("client_id", &hark::UtteranceInfo::client_id)
        .def_readwrite("mic_index", &hark::UtteranceInfo::mic_index)
        .def_readwrite("source_id", &hark::UtteranceInfo::source_id)
        .def_readwrite("sub_id", &hark::UtteranceInfo::sub_id)
        .def_readwrite("azimuth", &hark::UtteranceInfo::azimuth)
        .def_readwrite("elevation", &hark::UtteranceInfo::elevation)
        .def_property("time",
            [](const hark::UtteranceInfo &src){
                return std::chrono::system_clock::time_point{std::chrono::seconds{src.tv_sec} + std::chrono::microseconds{src.tv_usec}};
            },
            [](hark::UtteranceInfo &dst, std::chrono::system_clock::time_point value){
                auto s = std::chrono::time_point_cast<std::chrono::seconds>(value);
                if (s > value){
                    s = s - std::chrono::seconds{1};
                }
                auto us = std::chrono::duration_cast<std::chrono::microseconds>(value - s);

                dst.tv_sec = s.time_since_epoch().count();
                dst.tv_usec = us.count();
            });

    py_class<hark::Position>(m, "Position")
        .def(py::init())
        .def_readwrite("id", &hark::Position::id)
        .def_property("sys",
            [](const hark::Position *_this){
                return _this->sys == hark::TF_Coordinate::Polar ? hark::TF_Coordinate::Polar : hark::TF_Coordinate::Cartesian;
            },
            [](hark::Position *_this, hark::TF_Coordinate &sys){
                _this->sys = sys == hark::TF_Coordinate::Polar ? hark::TF_Coordinate::Polar : hark::TF_Coordinate::Cartesian;
            })
        .def_property("coord",
            [](const hark::Position *_this){
                return std::vector<float>(_this->coord, _this->coord + 3);
            },
            [](hark::Position *_this, std::vector<float> &v){
                _this->coord[0] = v[0];
                _this->coord[1] = v[1];
                _this->coord[2] = v[2];
            })
        .def_readwrite("path", &hark::Position::path)
        .def_property("data",
            [](const hark::Position *_this){
                hark::Matrix<std::complex<float> > m = _this->data;
                bool row_major = m.native_is_row_major();
                py::buffer_info bi = {
                    m.data(),
                    sizeof(std::complex<float>),
                    py::format_descriptor<std::complex<float> >::format(),
                    2,
                    { m.rows(), m.cols() },
                    { sizeof(std::complex<float>) * (row_major ? m.cols() : 1),
                      sizeof(std::complex<float>) * (row_major ? 1 : m.rows())}
                };
                return py::array_t<std::complex<float> >(bi);
            },
            [](hark::Position *_this, py::array_t<std::complex<float> > &m){
                py::buffer_info bi = m.request();

                if(bi.ndim != 2){
                    throw hark::RuntimeError("harklib.Position data.ndim must be 2.");
                }

                _this->data.noalias() = hark::Matrix<std::complex<float> >::from_native_image(static_cast<std::complex<float>*>(bi.ptr), bi.shape[0], bi.shape[1]);
            })
        .def_readwrite("channelsUse", &hark::Position::channelsUse)
        .def_property("channels",
            [](const hark::Position *_this){
                return std::vector<int>(_this->channels);
            },
            [](hark::Position *_this, std::vector<int> &v){
                std::copy(_this->channels.begin(), _this->channels.end(), v.begin());
            });

    py_class<hark::Neighbor>(m, "Neighbor")
        .def(py::init())
        .def_property("ids",
            [](const hark::Neighbor *_this){
                return std::vector<int>(_this->ids);
            },
            [](hark::Neighbor *_this, std::vector<int> &v){
                _this->ids.resize(v.size());
                std::copy(_this->ids.begin(), _this->ids.end(), v.begin());
            })
        .def_property("neighbors",
            [](const hark::Neighbor *_this){
                return std::vector<std::vector<int> >(_this->neighbors);
            },
            [](hark::Neighbor *_this, std::vector<std::vector<int> > &vv){
                _this->neighbors.resize(0);
                for(std::vector<std::vector<int> >::iterator it = vv.begin(); it != vv.end(); ++it){
                    std::vector<int> tmp;
                    tmp.resize(it->size());
                    std::copy(tmp.begin(), tmp.end(), it->begin());
                    _this->neighbors.push_back(std::move(tmp));
                }
            })
        .def_property("poses",
            [](const hark::Neighbor *_this){
                return std::vector<std::vector<hark::Position> >(_this->poses);
            },
            [](hark::Neighbor *_this, std::vector<std::vector<hark::Position> > &vv){
                _this->poses.resize(0);
                for(std::vector<std::vector<hark::Position> >::iterator it = vv.begin(); it != vv.end(); ++it){
                    std::vector<hark::Position> tmp;
                    tmp.resize(it->size());
                    std::copy(tmp.begin(), tmp.end(), it->begin());
                    _this->poses.push_back(std::move(tmp));
                }
            })
        .def_property("alg",
            [](const hark::Neighbor *_this){
                return _this->alg == hark::TF_NeighborAlgorithm::Undefined ? hark::TF_NeighborAlgorithm::Undefined : hark::TF_NeighborAlgorithm::NearestNeighbor;
            },
            [](hark::Neighbor *_this, hark::TF_NeighborAlgorithm &alg){
                _this->alg = alg == hark::TF_NeighborAlgorithm::Undefined ? hark::TF_NeighborAlgorithm::Undefined : hark::TF_NeighborAlgorithm::NearestNeighbor;
            });

    py_class<hark::Config>(m, "Config")
        .def(py::init())
        .def_readwrite("comment", &hark::Config::comment)
        .def_readwrite("synchronousAverage", &hark::Config::synchronousAverage)
        .def_readwrite("TSPpath", &hark::Config::TSPpath)
        .def_readwrite("TSPoffset", &hark::Config::TSPoffset)
        .def_readwrite("TSPLength", &hark::Config::TSPLength)
        .def_readwrite("peakSearchFrom", &hark::Config::peakSearchFrom)
        .def_readwrite("peakSearchTo", &hark::Config::peakSearchTo)
        .def_readwrite("nfft", &hark::Config::nfft)
        .def_readwrite("samplingRate", &hark::Config::samplingRate)
        .def_readwrite("signalMax", &hark::Config::signalMax);

    py_class<hark::TransferFunction>(m, "TransferFunction")
        .def(py::init())
        .def_property("is_valid",
            &hark::TransferFunction::IsValid,
            &hark::TransferFunction::SetValid)
        .def_property("m_WhatIsThis",
            &hark::TransferFunction::GetWhatIsThis,
            &hark::TransferFunction::SetWhatIsThis)
        .def_property("m_Sources",
            &hark::TransferFunction::GetSources,
            &hark::TransferFunction::SetSources)
        .def_property("m_Neighbors",
            &hark::TransferFunction::GetNeighbors,
            &hark::TransferFunction::SetNeighbors)
        .def_property("m_Microphones",
            &hark::TransferFunction::GetMicrophones,
            &hark::TransferFunction::SetMicrophones)
        .def_property("m_Config",
            &hark::TransferFunction::GetConfig,
            &hark::TransferFunction::SetConfig)
        .def_property("m_Localization",
            &hark::TransferFunction::GetLocalization,
            &hark::TransferFunction::SetLocalization)
        .def_property("m_Separtion",
            &hark::TransferFunction::GetSeparation,
            &hark::TransferFunction::SetSeparation);

    py_class<hark::Time>(m, "Time")
        .def(py::init())
        .def_property("time",
            [](const hark::Time &t){
                return std::chrono::system_clock::time_point{t.time};
            },
            [](hark::Time &t, std::chrono::system_clock::time_point value){
                t.time = value;
            });

    py_class<hark::TimeStamp>(m, "TimeStamp")
        .def(py::init())
        .def_property("time",
            [](const hark::TimeStamp &t){
                return std::chrono::system_clock::time_point{std::chrono::seconds{t.time.tv_sec} + std::chrono::microseconds{t.time.tv_usec}};
            },
            [](hark::TimeStamp &t, std::chrono::system_clock::time_point value){
                auto s = std::chrono::time_point_cast<std::chrono::seconds>(value);
                if (s > value){
                    s = s - std::chrono::seconds{1};
                }
                auto us = std::chrono::duration_cast<std::chrono::microseconds>(value - s);

                t.time.tv_sec = s.time_since_epoch().count();
                t.time.tv_usec = us.count();
            });

    py::register_exception<hark::RuntimeError>(m, "RuntimeError");
}
