/*
 * # Licensed Materials - Property of IBM
 * # Copyright IBM Corp. 2017,2018
 */

<%SPL::CodeGen::implementationPrologue($model);%>

<%
 my $cmnDir = $model->getContext()->getToolkitDirectory()."/opt/.splpy/common/";

 require "splpy_operator.pm";
 require $cmnDir."/splpy.pm";

 my @packages = splpy_Packages();
 spl_pip_packages(splpy_PyMajor(), $model, \@packages);

 my $module = splpy_Module();
 my $functionName = splpy_FunctionName();

  my $skip_set_callable = 0;
 my %cpp_tuple_types;
%>

MY_OPERATOR::MY_OPERATOR() :
    pyop_(NULL)
<%if ($model->getNumberOfInputPorts() != 0) {%>
    ,pyinputfns_(NULL),
    pyInNames_(NULL)
<%  for (my $p = 1; $p < $model->getNumberOfInputPorts(); $p++) { %>
    , pyInNames_<%=$p%>(NULL)
<%}}%>
<%  for (my $p = 0; $p < $model->getNumberOfOutputPorts(); $p++) { %>
    , pyOutNames_<%=$p%>(NULL)
<%}%>
{
   PyObject * callable;
@include  "../../opt/.splpy/common/py_constructor.cgt"

<% if ($model->getNumberOfInputPorts() != 0) { %>
{
   SplpyGIL lock;
<%

my @portParamStyles = splpy_ParamStyle();
my @portFixedParam = splpy_FixedParam();

for (my $p = 0; $p < $model->getNumberOfInputPorts(); $p++) {

my $iport = $model->getInputPortAt($p);

my $paramStyle = @portParamStyles[$p];
my $fixedParam = @portFixedParam[$p];

if ($fixedParam != -1) {
    if ($fixedParam > $iport->getNumberOfAttributes()) {
       SPL::CodeGen::exitln('%s requires at least %i attributes in input port %i but schema is %s',
           $model->getContext()->getKind(), $fixedParam, $iport->getIndex(), $iport->getSPLTupleType());
 }
}

my $ppn = "";
if ($p >= 1) {
   $ppn = $p;
}

if ($paramStyle eq 'dictionary') { %>
      pyInNames_<%=$ppn%> = streamsx::topology::Splpy::pyAttributeNames(
               getInputPortAt(<%=$p%>));
<%}}%>

   // callFunction takes a reference.
   Py_INCREF(callable);
   pyinputfns_ = SplpyGeneral::callFunction(
             "streamsx.spl.runtime", "_splpy_primitive_input_fns",
             callable, NULL);   
}

<% } %>

<% if ($model->getNumberOfOutputPorts() != 0) { %>
{
    SplpyGIL lock;
<%  for (my $p = 0; $p < $model->getNumberOfOutputPorts(); $p++) { %>
    pyOutNames_<%=$p%> = Splpy::pyAttributeNames(getOutputPortAt(<%=$p%>));
<%}%>
}
<%}%>

}

void MY_OPERATOR::allPortsReady()
{
    createThreads(1);
}

/**
  Call the Python class's all_ports_ready method
  if it exists and use the return to block for
  completion of outstanding work. If this
  operator has no output ports then it will
  complete when this function (thread) returns.
*/
void MY_OPERATOR::process(uint32_t idx)
{
   if (getPE().getShutdownRequested())
       return;

   PyObject *blocker = NULL;

   {
     SplpyGIL lock;

     Py_INCREF(pyop_->callable());
     blocker = SplpyGeneral::callFunction(
             "streamsx.spl.runtime", "_splpy_all_ports_ready",
             pyop_->callable(), NULL);

     bool callable = (bool) PyCallable_Check(blocker);
     if (!callable) {
         int has_background_work = PyObject_IsTrue(blocker);
         Py_DECREF(blocker);
         blocker = NULL;
         
         if (has_background_work == 0)
             return;
     }
   }

   if (blocker == NULL) {
       getPE().blockUntilShutdownRequest();
   } else {
       if (getPE().getShutdownRequested())
           return;

       SplpyGIL lock;

       PyObject *rv = SplpyGeneral::pyCallObject(blocker, NULL);
       Py_DECREF(rv);
       Py_DECREF(blocker);
   }
}

MY_OPERATOR::~MY_OPERATOR() 
{
   SplpyGIL lock;
   {
<%if ($model->getNumberOfInputPorts() != 0) {%>
   Py_CLEAR(pyinputfns_);
   Py_CLEAR(pyInNames_);
<%  for (my $p = 1; $p < $model->getNumberOfInputPorts(); $p++) { %>
       Py_CLEAR(pyInNames_<%=$p%>);
<%}}%>
<%  for (my $p = 0; $p < $model->getNumberOfOutputPorts(); $p++) { %>
       Py_CLEAR(pyOutNames_<%=$p%>);
<%}%>
   }
   delete pyop_;
}

void MY_OPERATOR::prepareToShutdown() 
{
    pyop_->prepareToShutdown();
}

void MY_OPERATOR::process(Tuple const & tuple, uint32_t port)
{
<%
if ($model->getNumberOfInputPorts() != 0) {
my @portParamStyles = splpy_ParamStyle();
my @portFixedParam = splpy_FixedParam();

for (my $p = 0; $p < $model->getNumberOfInputPorts(); $p++) {

  my $iport = $model->getInputPortAt($p);
  my $paramStyle = @portParamStyles[$p];
  my $fixedParam = @portFixedParam[$p];
  my $inputAttrs2Py = $iport->getNumberOfAttributes();
  if ($fixedParam != -1) {
    $inputAttrs2Py = $fixedParam;
  }

  if ($model->getNumberOfInputPorts() > 1) {
%>
      if (port == <%=$p%>)
<%
  }

%>
 try {
 // Code block for a single port
 @include  "../../opt/.splpy/common/py_splTupleCheckForBlobs.cgt"

    SplpyGIL lock;

 @include  "../../opt/.splpy/common/py_splTupleToFunctionArgs.cgt"

    PyObject *fn = PyList_GET_ITEM(pyinputfns_, (Py_ssize_t) <%=$p%>);
    PyObject * pyReturnVar = SplpyGeneral::pyObject_Call(fn, pyTuple, pyDict);

    if (pyReturnVar == NULL) {
        throw SplpyExceptionInfo::pythonError("<%=$functionName%>");
    }
    Py_DECREF(pyReturnVar);

    return;
 } catch (const streamsx::topology::SplpyExceptionInfo& excInfo) {
    SPLPY_OP_HANDLE_EXCEPTION_INFO_GIL(excInfo);
    return;
 }

<%}}%>
}

// Create member functions that convert Python tuple to SPL tuple
// for each port.
<%
for (my $p = 0; $p < $model->getNumberOfOutputPorts(); $p++) {
    my $oport = $model->getOutputPortAt($p);
    my $oport_submission = 1;
    my $iport = undef;
%>
@include  "../../opt/.splpy/common/py_valueToTuples.cgt"
@include  "../../opt/.splpy/common/py_pyTupleTosplTuple.cgt"
<% } %>

void MY_OPERATOR::convertAndSubmit(uint32_t port, PyObject *tuple_) {
     if (SplpyGeneral::isNone(tuple_)) {
          Py_DECREF(tuple_);
          return;
     }
<% for (my $p = 0; $p < $model->getNumberOfOutputPorts(); $p++) { %>
     if (port == <%=$p%>)
         pySubmitTuplesPort<%=$p%>(tuple_);
<%}%>
}

<%SPL::CodeGen::implementationEpilogue($model);%>
