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

#if !defined(_HARKLIB_NODE_H_INCLUDED_)
#define _HARKLIB_NODE_H_INCLUDED_ 1

#include "hark-lib/hark-lib.h"
#include "hark-lib/object.h"
#include "hark-lib/error.h"
// #include "hark-lib/datastream.h"
#include "hark-lib/terminal.h"
#include "hark-lib/input.h"

#include <map>
#include <vector>
#include <sstream>

namespace hark
{
    class BaseNode: public object
    {
    public:
        class UserData
        {
        public:
            UserData() = default;
            virtual ~UserData() = default;
        };

    public:
        BaseNode(NodeDef *nodedef, dispatch::BaseDispatcher *dispatcher, DataProxyFactory &input_dataproxy_factory, DataProxyFactory &output_dataproxy_factory);
        BaseNode(NodeDef *nodedef, dispatch::BaseDispatcher *dispatcher, const std::string &name, DataProxyFactory &input_dataproxy_factory, DataProxyFactory &output_dataproxy_factory);

        virtual ~BaseNode();

    public:
        inline const std::string&name() const {
            return this->_name;
        }
        inline const std::string&type() const;

        void attach_userdata(UserData *value);
        const UserData *get_userdata() const;
        UserData *detach_userdata();

        NodeDef *get_nodedef() const;

        virtual DataSource &operator [] (const std::string &name) = 0;
        virtual DataSource &get(const std::string &name);

        virtual DataSource &get_output(const std::string &name);
        virtual DataSink &get_input(const std::string &name);

        virtual DataProxy &get_output_proxy(const std::string &name);
        virtual DataProxy &get_input_proxy(const std::string &name);

        virtual std::vector<std::string> get_input_names() const;
        virtual std::vector<std::string> get_output_names() const;

        virtual BaseNode&add_input(Input &&input);
        virtual BaseNode&loop_condition(LoopCondition &&loop_condition);
        virtual void reset();
        virtual void initialize() = 0;
        virtual void dump_graph(std::ostream &os, bool with_headers = false) const = 0;

        virtual void update_delay();

        // void assign_dispatcher(dispatch::BaseDispatcher *dispatcher);
        virtual void notify_data_arrival();
        virtual void notify_queue_vacancy();
        virtual bool try_process() = 0;

        virtual size_t cursor_count() const;
        virtual bool is_ready() const;

        dispatch::BaseDispatcher*get_dispatcher() const {
            return this->_dispatcher.get();
        }

    protected:
        std::string _name;
        std::shared_ptr<NodeDef>_nodedef;

        std::unique_ptr<DataProxyMap>_input_dataproxies;
        std::unique_ptr<DataProxyMap>_output_dataproxies;

        std::shared_ptr<dispatch::BaseDispatcher>_dispatcher;

        size_t _cursor_count;

        std::unique_ptr<UserData> _userdata;
    };

    class Node: public BaseNode
    {
    public:
        Node(NodeDef *nodedef, dispatch::BaseDispatcher *dispatch);
        Node(NodeDef *nodedef, dispatch::BaseDispatcher *dispatch, const std::string &name);
        virtual ~Node();

    private:
        void build_proxies(const NodeDef *nodedef);

    public:
        // Node &add_input(Input &&input);
        virtual void initialize();

        virtual DataSource &operator [] (const std::string &name) {
            return this->get_output(name);
        }

        bool is_ready() const;
        bool is_input_data_ready() const;
        bool is_output_queue_ready() const;
        virtual bool try_process();

        virtual void dump_graph(std::ostream &os, bool with_headers = false) const;

    protected:
        static size_t _node_count;

        friend Node &operator << (Node &node, LoopCondition &&input);
    };

    Node &operator << (Node &node, Input &&input);
    Node &operator << (Node &node, LoopCondition &&input);

    class NodeDef: public object {
    public:
        struct LookupWindow
        {
            size_t lookAhead, lookBack;
        };

    public:
        NodeDef();
        NodeDef(const std::string &node_name);
        virtual ~NodeDef();

        NodeDef(const NodeDef &) = delete;
        NodeDef(const NodeDef &&) = delete;

    public:
        NodeDef&addInput(const Terminal &terminal);
        NodeDef&addOutput(const Terminal &terminal);
        void setInputLookupWindow(const std::string &terminal_name, size_t lookBack, size_t lookAhead);

        virtual void initialize();
        virtual void finalize();
        virtual bool operator () (size_t count, InputDataBundle &input, OutputDataBundle &output) = 0;
        virtual void reset() = 0;

        const std::string&name() const;

        // NodeDef &operator << (const Terminal &terminal);
        // NodeDef &operator >> (const Terminal &terminal);

        const std::map<std::string, Terminal>&input_terminals() const;
        const std::map<std::string, Terminal>&output_terminals() const;

    private:
        std::string _name;
        std::map<std::string, Terminal>_input_terminals, _output_terminals;
        std::map<std::string, LookupWindow>_input_lookup_windows;

    };

    NodeDef &operator << (NodeDef &nodedef, const Terminal &terminal);
    NodeDef &operator >> (NodeDef &nodedef, const Terminal &terminal);
}

#include "hark-lib/datastream.h"

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

/* end of file */
