/*
   HARK Library
   Copyright (C) 2021 Honda Research Institute Japan Inc.
 */

#if !defined(_HARKLIB_DATASTREAM_H_INCLUDED_)
#define _HARKLIB_DATASTREAM_H_INCLUDED_ 1

#include "hark-lib/hark-lib.h"
#include "hark-lib/object.h"
#include "hark-lib/node.h"
#include "hark-lib/data.h"

#include <map>
#include <vector>
#include <deque>
#include <algorithm>
#include <sstream>
//#include <mutex>
//#include <condition_variable>
#include <functional>

namespace hark
{
    // class data: public object
    // {
    // public:
    //     data value;
    //
    // public:
    //     data() = default;
    //     virtual ~data() = default;
    //
    //     data(const data &src) = default;
    //     data(data &&src) = default;
    //     data &operator = (const data &src) = default;
    //     data &operator = (data &&src) = default;
    //
    //     data(const data &src);
    //     data(data &&src);
    //
    //     // template<typename T> data(const T &value): value(value) {}
    //     // template<typename T> data(T &&value): value(std::forward<T>(value)) {}
    //
    //     template<typename T> T as() const {
    //         return this->value.cast<T>();
    //     }
    //
    //     virtual std::ostream &write(std::ostream &os) const;
    // };

    // class DataStream: public object {
    // public:
    //     DataStream(const std::string &value){}
    //     DataStream(int value){}
    //     DataStream() {}
    //     virtual ~DataStream() {}
    //
    //     DataStream(const DataStream &&src);
    //     DataStream(const DataStream &src) = delete;
    //
    // public:
    //     // DataStream &operator << (DataStream &source);
    //     DataStream &operator << (data &&packet)
    //     {
    //         return *this;
    //     }
    //
    //     data last() const;
    //     void push(const data &packet);
    //
    // };

    // class DataBundle: public object
    // {
    // public:
    //     DataStream &operator [] (const std::string &name);
    //     void add_datastream(const std::string &name, DataStream *stream);
    //     DataStream &query_datastream(const std::string &name) const;
    //
    //     std::vector<std::string> names() const;
    //     size_t stream_count() const;
    //
    // private:
    //     std::map<std::string, std::unique_ptr<DataStream>> _named_datastreams;
    // };

    // class DataQueue: public object
    // {
    // public:
    //     DataQueue();
    //     virtual ~DataQueue();
    //
    // public:
    //     bool is_empty() const;
    //
    //     void push(const data &packet);
    //     data shift();
    //
    // private:
    //     std::deque<data> _queue;
    // };

    // class DataDispatcherControl: public object
    // {
    // public:
    //     DataDispatcherControl(): _to_quit(false) {}
    //     virtual ~DataDispatcherControl() {}
    //
    // public:
    //     void register_dispatcher(DataDispatcher *dispatcher);
    //     void unregister_dispatcher(DataDispatcher *dispatcher);
    //
    //     void request_quit(bool to_quit=true);
    //     bool to_quit() const;
    //
    // private:
    //     std::vector<DataDispatcher *> _dispatchers;
    //     bool _to_quit;
    //     std::mutex _mutex;
    // };


    class DataSource: public object
    {
    public:
        DataSource(): _down_streams(), _parent(nullptr){
        }
        DataSource(BaseNode *parent, const std::string &name): _down_streams(), _parent(parent), _name(name) {
        }
        // DataSource(const std::string &value): DataSource() {}
        // DataSource(int value): DataSource() {}

        virtual ~DataSource();

        static DataSource*create_static(const std::string &value);

    public:
        void connect_to(DataSink *sink);

        virtual void clear() = 0;
        virtual bool is_source_ready() const = 0;
        virtual bool is_available_at(size_t count) const = 0;

        virtual void set_look_window(size_t lookBack, size_t lookAhead, size_t look_back_required = -1, size_t look_ahead_required = -1) = 0;
        virtual void set_extended_window(size_t count) = 0;
        virtual void set_delay(size_t count) = 0;
        virtual size_t get_delay() const = 0;
        // virtual void get_look_window(size_t *lookBack, size_t *lookAhead) const = 0;

        virtual size_t count() const = 0;
        virtual data last() = 0;
        virtual data at(size_t count) = 0;
        virtual data relative(int offset) = 0;
        virtual data pop() = 0;
        virtual std::vector<data>window(int offset_from, int offset_to) = 0;

        virtual void notify_send_data() = 0;

        inline std::vector<DataSink *>&down_streams(){
            return this->_down_streams;
        }
        inline std::vector<DataSink *>down_streams() const {
            return this->_down_streams;
        }

        inline const BaseNode*parent() const {
            return this->_parent;
        }
        inline BaseNode*parent(){
            return this->_parent;
        }

        std::string name() const;

    protected:
        std::vector<DataSink *>_down_streams;
        BaseNode *_parent;
        std::string _name;

        friend void operator << (DataSink &sink, DataSource &source);
    };

    class DataSink: public object
    {
    public:
        DataSink(): _up_stream(nullptr), _parent(nullptr){
        }
        DataSink(BaseNode *parent, const std::string &name): _up_stream(nullptr), _parent(parent), _name(name){
        }
        // DataSink(const std::string &value): DataSink() {}
        // DataSink(int value): DataSink() {}

        virtual ~DataSink();

    public:
        virtual void connect_from(DataSource *source);

        virtual void clear() = 0;
        virtual void set_delay(size_t count) = 0;
        virtual bool is_sink_ready() const = 0;
        virtual void push(const data &packet) = 0;

        virtual void notify_receive_data() = 0;

        inline DataSource*up_stream(){
            return this->_up_stream;
        }
        inline const DataSource*up_stream() const {
            return this->_up_stream;
        }

        inline const BaseNode*parent() const {
            return this->_parent;
        }
        inline BaseNode*parent(){
            return this->_parent;
        }

        std::string name() const;

    protected:
        DataSource *_up_stream;
        BaseNode *_parent;
        std::string _name;

        friend void operator << (DataSink &sink, DataSource &source);
    };

    class DataProxy: public DataSink, public DataSource
    {
    public:
        // DataProxy(): DataSource(), DataSink(), _is_static(false) {}
        DataProxy(BaseNode *parent, const std::string &name);
        virtual ~DataProxy();

    public:
        virtual void set_static(const data &value) = 0;
        virtual bool is_static() const = 0;
        virtual bool is_synchronous() const = 0;
        virtual bool is_available_at(size_t count) const = 0;

        virtual void set_look_window(size_t lookBack, size_t lookAhead, size_t look_back_required = -1, size_t look_ahead_required = -1) = 0;
        virtual void set_extended_window(size_t count) = 0;
        virtual void set_delay(size_t count) = 0;
        virtual size_t get_delay() const = 0;
        // virtual void get_look_window(size_t *lookBack, size_t *lookAhead) const = 0;

        virtual void clear() = 0;
        virtual void push(const data &packet) = 0;
        virtual size_t count() const = 0;
        virtual data last() = 0;
        virtual data at(size_t count) = 0;
        virtual data relative(int offset) = 0;
        virtual data pop() = 0;
        virtual std::vector<data>window(int offset_from, int offset_to) = 0;

        virtual bool is_source_ready() const = 0;
        virtual bool is_sink_ready() const = 0;

    protected:
        //std::unique_ptr<Mutex> _mutex;
        //std::unique_ptr<ConditionVariable> _condvar;
        //std::mutex _mutex;
        //std::condition_variable _condvar;
    };

    class FunctionalDataSink: public DataSink
    {
    public:
        FunctionalDataSink(std::function<void(const data &)> &&f);

    public:
        virtual void clear();
        virtual bool is_sink_ready() const;
        virtual void push(const data &packet);
        virtual void set_delay(size_t count);
        virtual void notify_receive_data();

    private:
        std::function<void(const data &)> _f;

    };

    class QueueingDataProxy: public DataProxy
    {
    public:
        QueueingDataProxy(BaseNode *parent, const std::string &name);
        virtual ~QueueingDataProxy();

    public:
        virtual void set_static(const data &value);
        virtual bool is_static() const;
        virtual bool is_synchronous() const;

        void set_synchronous(bool is_synchronous);
        virtual void set_look_window(size_t lookBack, size_t lookAhead, size_t look_back_required = -1, size_t look_ahead_required = -1);
        virtual void set_extended_window(size_t count);
        virtual void set_delay(size_t count);
        virtual size_t get_delay() const;
        // virtual void get_look_window(size_t *lookBack, size_t *lookAhead) const;

        virtual void clear();
        virtual void connect_from(DataSource* source);
        virtual void push(const data &packet);
        virtual size_t count() const;
        virtual data last();
        virtual data at(size_t count);
        virtual data relative(int offset);
        virtual data pop();
        virtual std::vector<data>window(int offset_from, int offset_to);

        virtual bool is_available_at(size_t count) const;
        virtual bool is_source_ready() const;
        virtual bool is_sink_ready() const;

        virtual void notify_send_data();
        virtual void notify_receive_data();

    private:
        bool _is_static;
        data _static_value;

        std::unique_ptr<Mutex> _mutex;
        std::unique_ptr<ConditionVariable> _condvar;
        //mutable std::mutex _mutex;
        std::deque<data>_queue;
        size_t _look_back, _look_ahead, _look_back_required, _look_ahead_required, _extended_window, _queue_margin, _upstream_delay;
        bool _is_synchronous;
        size_t _count, _head_cursor;

    };

    class ForwardingDataProxy: public DataProxy
    {
    public:
        using DataProxy::DataProxy;
        virtual ~ForwardingDataProxy();

    public:
        virtual void set_static(const data &value);
        virtual bool is_static() const;
        virtual bool is_synchronous() const;
        virtual void set_look_window(size_t lookBack, size_t lookAhead, size_t look_back_required = -1, size_t look_ahead_required = -1);
        virtual void set_extended_window(size_t count);
        virtual void set_delay(size_t count);
        virtual size_t get_delay() const;
        // virtual void get_look_window(size_t *lookBack, size_t *lookAhead) const;

        virtual void clear();
        virtual void push(const data &packet);
        virtual size_t count() const;
        virtual data last();
        virtual data at(size_t count);
        virtual data relative(int offset);
        virtual data pop();
        virtual std::vector<data>window(int offset_from, int offset_to);

        virtual bool is_available_at(size_t count) const;
        virtual bool is_source_ready() const;
        virtual bool is_sink_ready() const;

        virtual void notify_send_data();
        virtual void notify_receive_data();

    };

    using NodeInputDataProxy = QueueingDataProxy;
    using NodeOutputDataProxy = ForwardingDataProxy;
    using NetworkInputDataProxy = ForwardingDataProxy;
    using NetworkOutputDataProxy = ForwardingDataProxy;

    void operator << (DataSink &sink, DataSource &source);

    class DataSourceMap
    {
    public:
        DataSourceMap(){
        }
        virtual ~DataSourceMap(){
        }
        // static DataSourceMap *create(Node *parent);

    public:
        virtual size_t count() const = 0;
        virtual std::vector<std::string>names() const = 0;

        virtual DataSource&add(const std::string &name) = 0;

        virtual bool has(const std::string &name) const = 0;
        virtual DataSource &operator [] (const std::string &name) {
            return this->get(name);
        }
        virtual DataSource &get(const std::string &name) = 0;
        virtual DataSource&query(const std::string &name) = 0;

        virtual size_t count_ready() const = 0;
    };

    class DataSinkMap
    {
    public:
        DataSinkMap(){
        }
        virtual ~DataSinkMap(){
        }
        // static DataSinkMap *create(Node *parent);

    public:
        virtual size_t count() const = 0;
        virtual std::vector<std::string>names() const = 0;

        virtual DataSink&add(const std::string &name) = 0;

        virtual bool has(const std::string &name) const = 0;
        virtual DataSink &operator [] (const std::string &name) {
            return this->get(name);
        }
        virtual DataSink &get(const std::string &name) = 0;
        virtual DataSink&query(const std::string &name) = 0;

        virtual size_t count_ready() const = 0;
    };

    class DataProxyFactory: public object
    {
    public:
        virtual DataProxy*create(BaseNode *parent, const std::string &name) = 0;
    };

    template<typename T>
    class TypedDataProxyFactory: public DataProxyFactory
    {
    public:
        virtual DataProxy*create(BaseNode *parent, const std::string &name){
            return new T(parent, name);
        }
    };

    class DataProxyMap
    {
    public:
        DataProxyMap(DataProxyFactory &factory): _factory(factory){
        }
        virtual ~DataProxyMap(){
        }
        static DataProxyMap*create(DataProxyFactory &factory, BaseNode *parent);

    public:
        virtual size_t count() const = 0;
        virtual std::vector<std::string>names() const = 0;

        virtual DataProxy&add(const std::string &name) = 0;

        virtual bool has(const std::string &name) const = 0;
        virtual DataProxy &operator [] (const std::string &name) = 0;
        virtual DataProxy&query(const std::string &name) = 0;

        virtual DataSinkMap&as_datasink_map() = 0;
        virtual DataSourceMap&as_datasource_map() = 0;

        virtual size_t count_ready_source() const = 0;
        virtual size_t count_ready_sink() const = 0;

        DataProxyFactory &_factory;
    };

    using InputDataBundle = DataSourceMap;
    using OutputDataBundle = DataSinkMap;

    template<typename T, typename B>
    class DataMapImpl: public B, public object
    {
    public:
        DataMapImpl(DataProxyFactory &factory, BaseNode *parent): B(factory), _parent(parent)
        {
        }

        virtual ~DataMapImpl()
        {
            // std::cout << "DataMapImpl: dtor" << std::endl;
            for(auto v: this->_values){
                delete v.second;
            }
        }

    public:
        bool
        has(const std::string &name) const
        {
            return this->_values.find(name) != this->_values.end();
        }

        T &
        add(const std::string &name)
        {
            return *this->_values.insert({name, this->_factory.create(this->_parent, name)}).first->second;
        }

        T &
        query(const std::string &name)
        {
            auto it = this->_values.find(name);
            if(it == this->_values.end()){
                // std::cout << "::query: count=" << this->_values.size() << std::endl;
                // for(auto &it: this->_values){
                //     std::cout << "name=" << it.first << std::endl;
                // }
                std::stringstream ss;
                ss << "Data with name '" << name << "' not found in node '" << this->_parent->name() << "'.";
                throw RuntimeError(ss.str());
            }else{
                return *it->second;
            }
        }

        T &
        operator [] (const std::string &name)
        {
            return this->get(name);
        }

        T &
        get(const std::string &name)
        {
            auto it = this->_values.find(name);
            if(it == this->_values.end()){
                auto itt = this->_values.insert({name, this->_factory.create(this->_parent, name)});
                return *itt.first->second;
            }else{
                return *it->second;
            }
        }

        size_t
        count() const
        {
            return this->_values.size();
        }

        std::vector<std::string>
        names() const
        {
            std::vector<std::string>v;
            v.reserve(this->_values.size());
            for(auto &it: this->_values){
                v.push_back(it.first);
            }
            return v;
        }

        size_t
        count_ready() const
        {
            size_t count = 0;
            for(auto &it: this->_values){
                if(it.second->is_ready()){
                    count++;
                }
            }
            return count;
        }

    protected:
        BaseNode *_parent;
        std::map<std::string, T *> _values;

    };

    class DataProxyMapImpl;

    template<typename T, typename S>
    class DataProxyMapImplWrapper: public S, public object
    {
    public:
        DataProxyMapImplWrapper(DataProxyMapImpl *proxy_impl): S(), _proxy_impl(proxy_impl)
        {
        }

        virtual ~DataProxyMapImplWrapper()
        {
        }

    public:
        bool has(const std::string &name) const;
        T &add(const std::string &name);
        T &query(const std::string &name);
        T &operator [] (const std::string &name);
        T &get(const std::string &name);
        size_t count() const;
        std::vector<std::string> names() const;
        size_t count_ready() const;

    private:
        DataProxyMapImpl *_proxy_impl;
    };

    using DataSourceMapImplWrapper = DataProxyMapImplWrapper<DataSource, DataSourceMap>;
    using DataSinkMapImplWrapper = DataProxyMapImplWrapper<DataSink, DataSinkMap>;

    class DataProxyMapImpl: public DataMapImpl<DataProxy, DataProxyMap>
    {
    public:
        DataProxyMapImpl(DataProxyFactory &factory, BaseNode *parent): DataMapImpl<DataProxy, DataProxyMap>(factory, parent), _source_wrapper(this), _sink_wrapper(this)
        {
        }

        virtual DataSinkMap&as_datasink_map()
        {
            return this->_sink_wrapper;
        }

        virtual DataSourceMap&as_datasource_map()
        {
            // std::cout << "as_datasource_map()" << std::endl;
            return this->_source_wrapper;
        }

        size_t count_ready_source() const;
        size_t count_ready_sink() const;

    private:
        DataSourceMapImplWrapper _source_wrapper;
        DataSinkMapImplWrapper _sink_wrapper;
    };

    template<>
    size_t
    DataProxyMapImplWrapper<DataSource, DataSourceMap>::count_ready() const;

    template<>
    size_t
    DataProxyMapImplWrapper<DataSink, DataSinkMap>::count_ready() const;

    template<typename T, typename S>
    bool
    DataProxyMapImplWrapper<T, S>::has(const std::string &name) const
    {
        return this->_proxy_impl->has(name);
    }

    template<typename T, typename S>
    T &
    DataProxyMapImplWrapper<T, S>::add(const std::string &name)
    {
        return this->_proxy_impl->add(name);
    }

    template<typename T, typename S>
    T &
    DataProxyMapImplWrapper<T, S>::query(const std::string &name)
    {
        return this->_proxy_impl->query(name);
    }

    template<typename T, typename S>
    T &
    DataProxyMapImplWrapper<T, S>::operator [] (const std::string &name)
    {
        return this->get(name);
    }

    template<typename T, typename S>
    T &
    DataProxyMapImplWrapper<T, S>::get(const std::string &name)
    {
        return this->_proxy_impl->operator [] (name);
    }

    template<typename T, typename S>
    size_t
    DataProxyMapImplWrapper<T, S>::count() const
    {
        return this->_proxy_impl->count();
    }

    template<typename T, typename S>
    std::vector<std::string>
    DataProxyMapImplWrapper<T, S>::names() const
    {
        return this->_proxy_impl->names();
    }

    template<typename T, typename S>
    size_t
    DataProxyMapImplWrapper<T, S>::count_ready() const
    {
        return this->_proxy_impl->count_ready();
    }

    template<>
    DataSink &
    DataProxyMapImplWrapper<DataSink, DataSinkMap>::query(const std::string &name);
}

#endif /* #if !defined(_HARKLIB_DATASTREAM_H_INCLUDED_) */

/* end of file */
