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

#include "hark-lib/node.h"
#include "hark-lib/datastream.h"
#include "hark-lib/dispatch.h"

namespace hark
{
    static std::string
    _create_default_name(const NodeDef *nodedef)
    {
        static size_t _node_count = 0;
        std::stringstream ss;
        ss << nodedef->name() << "_" << ++_node_count;
        return ss.str();
    }

    BaseNode::BaseNode(NodeDef *nodedef, dispatch::BaseDispatcher *dispatcher, DataProxyFactory &input_dataproxy_factory, DataProxyFactory &output_dataproxy_factory):
        BaseNode(nodedef, dispatcher, _create_default_name(nodedef), input_dataproxy_factory, output_dataproxy_factory)
    {
    }

    BaseNode::BaseNode(NodeDef *nodedef, dispatch::BaseDispatcher *dispatcher, const std::string &name, DataProxyFactory &input_dataproxy_factory, DataProxyFactory &output_dataproxy_factory):
        _nodedef(nodedef),
        _dispatcher(dispatcher),
        _name(name),
        _input_dataproxies(DataProxyMap::create(input_dataproxy_factory, this)),
        _output_dataproxies(DataProxyMap::create(output_dataproxy_factory, this))
    {
        if(this->_dispatcher){
            this->_dispatcher->add_node(this);
        }
        // this->build_proxies(nodedef);
    }

    BaseNode::~BaseNode()
    {
    }

    size_t
    BaseNode::cursor_count() const
    {
        return this->_cursor_count;
    }

    const std::string &
    BaseNode::type() const
    {
        return this->_nodedef->name();
    }

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

    DataSource &
    BaseNode::get_output(const std::string &name)
    {
        return this->_output_dataproxies->as_datasource_map().query(name);
    }

    DataSink &
    BaseNode::get_input(const std::string &name)
    {
        return this->_input_dataproxies->as_datasink_map().query(name);
    }

    DataProxy &
    BaseNode::get_output_proxy(const std::string &name)
    {
        return this->_output_dataproxies->query(name);
    }

    DataProxy &
    BaseNode::get_input_proxy(const std::string &name)
    {
        return this->_input_dataproxies->query(name);
    }

    std::vector<std::string>
    BaseNode::get_output_names() const
    {
        return this->_output_dataproxies->names();
    }

    std::vector<std::string>
    BaseNode::get_input_names() const
    {
        return this->_input_dataproxies->names();
    }

    NodeDef *
    BaseNode::get_nodedef() const
    {
        return this->_nodedef.get();
    }

    BaseNode &
    BaseNode::add_input(Input &&input)
    {
        DataProxy &proxy = this->_input_dataproxies->query(input.name());

        DataSource *source = nullptr;
//        const std::string *value = nullptr;
        if((source = input.source()) != nullptr){
            source->connect_to(&proxy);
            proxy.connect_from(source);

        }else if(input.is_static()){
            proxy.set_static(input.value());

        }else{
            throw "";
        }

        return *this;
    }

    BaseNode &
    BaseNode::loop_condition(LoopCondition &&input)
    {
        this->_dispatcher->set_loop_condition(input.source);
        return *this;
    }

    // void
    // BaseNode::assign_dispatcher(dispatch::BaseDispatcher *dispatcher)
    // {
    //     this->_dispatcher = dispatcher;
    // }

    void
    BaseNode::notify_data_arrival()
    {
        // std::cout << "notify_data_arrival: " << this->name() << std::endl;
        if(this->_dispatcher){
            this->_dispatcher->notify_data_arrival();
        }else{
            // std::cout << "no dispatcher" << std::endl;
        }
    }

    void
    BaseNode::notify_queue_vacancy()
    {
        // std::cout << "notify_dispatcher: " << this->name() << std::endl;
        if(this->_dispatcher){
            this->_dispatcher->notify_queue_vacancy();
        }
    }

    bool
    BaseNode::is_ready() const
    {
        return true;
    }

    void
    BaseNode::update_delay()
    {
        // std::cout << "Node: " << this->name() << ": update_delay: " << std::endl;
        size_t delay_max = 0;
        for(auto name: this->_input_dataproxies->names()){
            DataSource &ds = this->_input_dataproxies->query(name);

            size_t delay = ds.get_delay();
            delay_max = std::max(delay_max, delay);
        }
        for(auto name: this->_input_dataproxies->names()){
            DataSource &ds = this->_input_dataproxies->query(name);

            size_t delay = ds.get_delay();
            ds.set_extended_window(delay_max - delay);
            // std::cout << "Node: " << this->name() << ": update_delay: extended-delay: " << name << ": " << (delay_max - delay) << std::endl;
        }
        for(auto name: this->_output_dataproxies->names()){
            DataSink &ds = this->_output_dataproxies->query(name);

            ds.set_delay(delay_max);
            // std::cout << "Node: " << this->name() << ": update_delay: daka-sink: " << name << ": set-delay: " << name << ": " << delay_max << std::endl;
        }
    }

    void
    BaseNode::attach_userdata(UserData *value)
    {
        this->_userdata.reset(value);
    }

    const BaseNode::UserData *
    BaseNode::get_userdata() const
    {
        return this->_userdata.get();
    }

    BaseNode::UserData *
    BaseNode::detach_userdata()
    {
        BaseNode::UserData *ud = this->_userdata.release();
        return ud;
    }

    void
    BaseNode::reset()
    {
        this->_nodedef->reset();
    }

}

/* end of file */
