#include <sst/sit/sit.hpp>

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

class {{module_name}} : 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:
    // constructor for component
    {{module_name}}(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_name}}_din", new SST::Event::Handler<{{module_name}}>(this, &{{module_name}}::handle_event)
          )),
          m_dout_link(configureLink("{{module_name}}_dout")) {

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

        registerClock(m_clock, new SST::Clock::Handler<{{module_name}}>(this, &{{module_name}}::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) {

            char *args[] = {&m_proc[0u], &m_ipc_port[0u], nullptr};
            m_output.verbose(CALL_INFO, 1, 0, "Forking process \"%s\"...\n", m_proc.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 SystemC 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 SystemC 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_name}}, // class
        "{{lib}}", // element library
        "{{module_name}}", // component
        SST_ELI_ELEMENT_VERSION(1, 0, 0),
        "{{desc}}",
        COMPONENT_CATEGORY_UNCATEGORIZED
    )

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

};
