#include <sst/sit/sit.hpp>

#include <sst/core/component.h>
#include <sst/core/interfaces/stringEvent.h>
#include <sst/core/link.h>

class {{module}} : public SST::Component {
   private:
    // Prepare the signal handler
    {{sig_type}} m_signal_io;

    // SST parameters
    SST::Output m_output;
    std::string m_clock, m_proc, m_ipc_port;
    SST::Link *m_din_link, *m_dout_link;

   public:
    {{module}}(SST::ComponentId_t id, SST::Params &params)
        : SST::Component(id),
          m_signal_io({{buf_size}}),
          m_clock(params.find<std::string>("clock", "")),
          m_proc(params.find<std::string>("proc", "")),
          m_ipc_port(params.find<std::string>("ipc_port", "")),
          m_din_link(configureLink(
              "{{module}}_din", new SST::Event::Handler<{{module}}>(this, &{{module}}::handle_event)
          )),
          m_dout_link(configureLink("{{module}}_dout")) {

        m_output.init("blackbox-" + getName() + " -> ", 1, 0, SST::Output::STDOUT);
        m_output.setVerboseLevel(params.find<bool>("OUTPUT", true));

        registerClock(m_clock, new SST::Clock::Handler<{{module}}>(this, &{{module}}::tick));

        if (!(m_din_link && m_dout_link)) {
            m_output.fatal(CALL_INFO, -1, "Failed to configure port\n");
        }

    }

    void setup() {

        int child_pid = fork();

        if (!child_pid) {

            std::string cmd = m_proc + m_ipc_port;

            char *args[8];
            int i = 0;
            args[i] = std::strtok(&cmd[0u], " ");

            while (args[i] != nullptr) {
                args[++i] = strtok(nullptr, " ");
            }
            args[i] = nullptr;

            m_output.verbose(CALL_INFO, 1, 0, "Forking process \"%s\"...\n", cmd.c_str());
            execvp(args[0], args);

        } else {

            m_signal_io.set_addr(m_ipc_port);
            {{receiver}}.recv();
            if (child_pid == std::stoi({{receiver}}.get())) {
                m_output.verbose(CALL_INFO, 1, 0, "Process \"%s\" successfully synchronized\n",
                                 m_proc.c_str());
            }

        }

    }

    bool tick(SST::Cycle_t) { return false; };

    void handle_event(SST::Event *ev) {

        auto *se = dynamic_cast<SST::Interfaces::StringEvent *>(ev);

        if (se) {

            std::string _data_in = se->getString();
            bool keep_send = _data_in[0] != '0';
            bool keep_recv = _data_in[1] != '0';
            _data_in = 'X' + _data_in.substr(2);

            // inputs from parent SST model, outputs to PyRTL child process
            {{sender}}.set(_data_in);

            if (keep_send) {
                {{sender}}.set_state(keep_recv);
                {{sender}}.send();
            }
            if (keep_recv) {
                {{receiver}}.recv();
            }

            // inputs to parent SST model, outputs from PyRTL child process
            std::string _data_out = {{receiver}}.get();
            m_dout_link->send(new SST::Interfaces::StringEvent(_data_out));

        }

    }

    // Register the component
    SST_ELI_REGISTER_COMPONENT(
        {{module}}, // class
        "{{lib}}", // element library
        "{{module}}", // component
        SST_ELI_ELEMENT_VERSION(1, 0, 0),
        "{{desc}}",
        COMPONENT_CATEGORY_UNCATEGORIZED
    )

    // Port name, description, event type
    SST_ELI_DOCUMENT_PORTS(
        {{ports}}
    )

};

