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

#include "hark-lib/network.h"

#include "hark-lib/datastream.h"
#include "hark-lib/dispatch.h"
#include <thread>

namespace hark
{
    static TypedDataProxyFactory<NetworkInputDataProxy> _network_input_dataproxy_factory;
    static TypedDataProxyFactory<NetworkOutputDataProxy> _network_output_dataproxy_factory;

    NetworkDef::NetworkDef(): NodeDef("Network")
    {
        // std::cout << "NetworkDef::NetworkDef()" << std::endl;
    }

    NetworkDef::NetworkDef(const std::string &name): NodeDef(name)
    {
        // std::cout << "NetworkDef::NetworkDef(\"" << name << "\")" << std::endl;
    }

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

    void
    NetworkDef::reset()
    {
    }

    bool
    NetworkDef::operator () (size_t count, InputDataBundle &input, OutputDataBundle &output)
    {
        throw RuntimeError("NetworkDef::operator() is unexpectedly called.");
    }

    Network::Network(NetworkDef *network_def, const std::string &name):
        Network(network_def, new dispatch::Default(), name)
    {
        // std::cout << "hark::Network::Network(): 1" << std::endl;
    }

    Network::Network(NetworkDef *network_def, dispatch::BaseDispatcher *dispatcher, const std::string &name):
        _networkdef(network_def),
        BaseNode(network_def, dispatcher, name, _network_input_dataproxy_factory, _network_output_dataproxy_factory)
    {
        this->_networkdef->build(*this,
                                 this->_input_dataproxies->as_datasource_map(),
                                 this->_output_dataproxies->as_datasink_map());
    }

    BaseNode &
    Network::create_node(NodeDef *def, dispatch::BaseDispatcher *dispatcher, const std::string &name)
    {
        BaseNode *nv = new Node(def, dispatcher, name);
        this->_nodes.push_back(std::unique_ptr<BaseNode>(nv));
        return *nv;
    }

    Network &
    Network::create_network(NetworkDef *def, dispatch::BaseDispatcher *dispatcher, const std::string &name)
    {
        Network *nv = new Network(def, dispatcher, name);
        this->_nodes.push_back(std::unique_ptr<BaseNode>(nv));
        return *nv;
    }

    void
    Network::execute()
    {
        this->initialize();
        this->reset();

        this->schedule(this->_scheduler);
        this->_scheduler.start_dispatchers();

        this->_scheduler.wait_for_idle();

        this->_scheduler.request_quit();
        this->_scheduler.join();
    }

    void
    Network::stop()
    {
        this->_scheduler.request_quit();
    }

    void
    Network::initialize()
    {
        // std::cout << "Network::initialize(), this=" << (void *)this << std::endl;
        this->_scheduler.initialize();
        for(auto &n: this->_nodes){
            n->initialize();
        }
    }

    void
    Network::schedule(dispatch::Scheduler &scheduler)
    {
        // std::cout << "Network::schedule(), this=" << (void *)this << std::endl;
        for(auto &base_node: this->_nodes){
            auto *network = dynamic_cast<Network *>(base_node.get());
            auto *node = dynamic_cast<Node *>(base_node.get());
            if(network){
                network->schedule(scheduler);

            }else if(node){
                scheduler.add_dispatcher(node->get_dispatcher());

            }else{
                throw RuntimeError("Unexpected BaseNode object.");
            }
        }
        // std::cout << "Network::schedule(): end: this=" << (void *)this << std::endl;
    }

    DataSource &
    Network::operator [] (const std::string &name)
    {
        return this->_output_dataproxies->as_datasource_map().query(name);
    }

    Network &
    Network::operator << (Input &&input){
        this->add_input(std::move(input));
        return *this;
    }

    BaseNode *
    Network::query_node(const std::string &name) const
    {
        // std::cout << "Network::query_node: this=" << this->name() << ", name=" << name << ", node-count: " << this->_nodes.size() << std::endl;
        for(auto &node: this->_nodes){
            // std::cout << "Network::query_node: this=" << this->name() << ", checking: " << node->name() << std::endl;
            Network *network = dynamic_cast<Network *>(node.get());
            if(network){
                BaseNode *cnode = network->query_node(name);
                if(cnode){
                    return cnode;
                }
            }else if(node->name() == name){
                // std::cout << "found" << std::endl;
                return node.get();
            }
        }
        return nullptr;
    }

    void
    Network::reset()
    {
        for(auto &node: this->_nodes){
            node->reset();
        }
    }

    void
    Network::dispatch()
    {

    }

    void
    Network::dump_graph(std::ostream &os, bool with_headers) const
    {
        if(with_headers){
            os << "digraph {" << std::endl;
        }
        for(auto &n: this->_nodes){
            n->dump_graph(os);
        }
        if(with_headers){
            os << "}" << std::endl;
        }
    }

}

/* end of file */
