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

#include <sst/sit/sit.hpp>

class {{module_name}} : public SST::Component {
   private:
    // Prepare the signal handler
    {{sig_type}} sit_buf;

    // 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),
          sit_buf(7),
          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() {
        std::string cmd = m_proc + m_ipc_port;
        int child_pid = fork();

        if (!child_pid) {

            {{exec_cmd}}
            execvp(args[0], args);

        } else {
            sit_buf.set_addr(m_ipc_port);
            {{receiver}}.recv();
            if (child_pid == std::stoi({{receiver}}.get())) {
                m_output.verbose(
                    CALL_INFO,
                    1,
                    0,
                    "\033[92m[SYNC] %s\033[0m\n",
                    cmd.c_str()
                );
                sit_buf.resize({{buf_size}});
            }
        }
    }

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

    void finish() {
        m_output.verbose(
            CALL_INFO, 1, 0, "\033[92m[DEST] %s\033[0m\n", getName().c_str()
        );
    }

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