#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 <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/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/Grater.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/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/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 "offline.h"

namespace py = pybind11;

static py::object
data_to_object(hark::data value)
{
    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<float>>()){
        std::vector<float> &s = value.cast<std::vector<float>>();

        py::array_t<float> d(s.size(), s.data());

        return d;

    }else if(value.is_type<hark::Source>()){
        return py::cast(value.cast<hark::Source>());

    }else{
        return py::str("Unexpected 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
        );
    }
};

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::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(Grater);
    //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(AudioStreamFromMemory);
    DEFINE_NODEDEF(AudioStreamFromWave);
    //DEFINE_NODEDEF(BF);
    //DEFINE_NODEDEF(BGNEstimator);
    //DEFINE_NODEDEF(CMChannelSelector);
    //DEFINE_NODEDEF(CMConjEachElement);
    //DEFINE_NODEDEF(CMDivideEachElement);
    //DEFINE_NODEDEF(CMIdentityMatirx);
    //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);

    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<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("subid", &hark::Source::sub_id)
        .def_readwrite("micid", &hark::Source::mic_id)
        .def_readwrite("count", &hark::Source::count);

    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::register_exception<hark::RuntimeError>(m, "RuntimeError");
}
