/*
   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 TypedDataProxyFactory<NodeInputDataProxy>_input_dataproxy_factory;
    static TypedDataProxyFactory<NodeOutputDataProxy>_output_dataproxy_factory;

    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();
    }

    Node::Node(NodeDef *nodedef, dispatch::BaseDispatcher *dispatcher):
        BaseNode(nodedef, dispatcher, _input_dataproxy_factory, _output_dataproxy_factory)
    {
        this->build_proxies(nodedef);
    }

    Node::Node(NodeDef *nodedef, dispatch::BaseDispatcher *dispatcher, const std::string &name):
        BaseNode(nodedef, dispatcher, name, _input_dataproxy_factory, _output_dataproxy_factory)
    {
        this->build_proxies(nodedef);
    }

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

    void
    Node::build_proxies(const NodeDef *nodedef)
    {
        auto &input_terminals = nodedef->input_terminals();
        for(const auto &it: input_terminals){
            this->_input_dataproxies->add(it.first);

            if(it.second.has_default_value()){
                DataProxy &proxy = this->_input_dataproxies->query(it.first);
                proxy.set_static(it.second.default_value());
            }
        }

        auto &output_terminals = nodedef->output_terminals();
        for(const auto &it: output_terminals){
            this->_output_dataproxies->add(it.first);
        }
    }

    Node &
    operator << (Node &node, Input &&input){
        node.add_input(std::move(input));
        return node;
    }

    Node &
    operator << (Node &node, LoopCondition &&input){
        node.loop_condition(input.source);
        return node;
    }

    bool
    Node::try_process()
    {
        bool is_input_ready = this->is_input_data_ready();
        bool is_output_ready = this->is_output_queue_ready();
#if defined(VERBOSE_DEBUG)
            {
                std::stringstream ss;
                ss << "try_process: " << ((is_input_ready && is_output_ready) ? "true" : "false") << ": " << this->name() << ", input-ready=" << is_input_ready << ", output-ready=" << is_output_ready << std::endl;
                std::cout << ss.str();
            }
#endif
        if(is_input_ready && is_output_ready){
            NodeDef &nodedef = *this->_nodedef;

#if defined(VERBOSE_DEBUG)
            {
                std::stringstream ss;
                ss << this->_name << "::operator(): start: count=" << this->_cursor_count << std::endl;
                std::cout << ss.str();
            }
#endif
            bool processed = nodedef(this->_cursor_count, this->_input_dataproxies->as_datasource_map(), this->_output_dataproxies->as_datasink_map());
#if defined(VERBOSE_DEBUG)
            {
                std::stringstream ss;
                ss << this->_name << "::operator(): end: count=" << this->_cursor_count << ", processed=" << processed << std::endl;
                std::cout << ss.str();
            }
#endif

            if(processed){
                if(this->_dispatcher && this->_dispatcher->is_synchronous()){
                    for(auto &input_name: this->_input_dataproxies->names()){
                        auto &proxy = this->_input_dataproxies->query(input_name);
                        if(proxy.is_synchronous()){
#if defined(VERBOSE_DEBUG)
                            {
                                std::stringstream ss;
                                ss << "pop: " << this->name() << "." << input_name << ", dispatcher=" << typeid(*this->_dispatcher.get()).name() << std::endl;
                                std::cout << ss.str();
                            }
#endif
                            proxy.pop();
                        }
                    }
                }
                this->_cursor_count++;
            }

            return true; //processed;

        }else{
// #if defined(VERBOSE_DEBUG)
//             {
//                 std::stringstream ss;
//                 ss << "try_process: false: " << this->name() << ", input-ready=" << is_input_ready << ", output-ready=" << is_output_ready << std::endl;
//                 std::cout << ss.str();
//             }
// #endif
            return false;
        }
    }

    bool
    Node::is_ready() const
    {
        return is_input_data_ready();
    }

    bool
    Node::is_input_data_ready() const
    {
        size_t s1 = this->_input_dataproxies->count_ready_source();
        size_t s2 = this->_input_dataproxies->count();
#if defined(VERBOSE_DEBUG)
        {
            std::stringstream ss;
            ss << this->name() << ": is_input_data_ready: s1=" << s1 << ", s2=" << s2 << std::endl;
            std::cout << ss.str();
        }
#endif
        return s1 == s2;
    }

    bool
    Node::is_output_queue_ready() const
    {
        size_t s1 = this->_output_dataproxies->count_ready_sink();
        size_t s2 = this->_output_dataproxies->count();
#if defined(VERBOSE_DEBUG)
        {
            std::stringstream ss;
            ss << this->name() << ": is_output_queue_ready: s1=" << s1 << ", s2=" << s2 << std::endl;
            std::cout << ss.str();
        }
#endif
        return s1 == s2;
    }

    void
    Node::initialize()
    {
        this->_cursor_count = 0;
        for(auto &input_name: this->_input_dataproxies->names()){
            auto &input = this->_input_dataproxies->query(input_name);
            input.clear();
        }
        for(auto &output_name: this->_output_dataproxies->names()){
            auto &output = this->_output_dataproxies->query(output_name);
            output.clear();
        }
    }

    void
    Node::dump_graph(std::ostream &os, bool with_headers) const
    {
        os << this->name() << std::endl;

        for(auto &output_name: this->_output_dataproxies->names()){
            auto children = this->_output_dataproxies->query(output_name).down_streams();
            // std::cout << "child-count: " << children.size() << std::endl;
            for(auto *sink: children){
                auto child = sink->parent();
                os << this->name() << " -> " << child->name() << std::endl;
                // child->dump_graph(os);
            }
        }
    }

}

/* end of file */
