#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("\033[32mblackbox-" + getName() + "\033[0m -> ", 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}
    )

}};

