/*
 * Copyright (c) The acados authors.
 *
 * This file is part of acados.
 *
 * The 2-Clause BSD License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.;
 */

#define S_FUNCTION_NAME acados_solver_sfunction_{{ model.name }}
#define S_FUNCTION_LEVEL 2

#define MDL_START

// acados
// #include "acados/utils/print.h"
#include "acados_c/sim_interface.h"
#include "acados_c/external_function_interface.h"

// example specific
#include "{{ model.name }}_model/{{ model.name }}_model.h"
#include "acados_solver_{{ model.name }}.h"

#include "simstruc.h"

{% if simulink_opts.samplingtime == "t0" -%}
#define SAMPLINGTIME {{ solver_options.time_steps[0] }}
{%- elif simulink_opts.samplingtime == "-1" -%}
#define SAMPLINGTIME -1
{%- else -%}
  {{ throw(message = "simulink_opts.samplingtime must be '-1' or 't0', got val") }}
{%- endif %}

static void mdlInitializeSizes (SimStruct *S)
{
    // specify the number of continuous and discrete states
    ssSetNumContStates(S, 0);
    ssSetNumDiscStates(S, 0);

    int N = {{ model.name | upper }}_N;

  {%- for key, val in simulink_opts.inputs -%}
    {%- if val != 0 and val != 1 -%}
      {{ throw(message = "simulink_opts.inputs must be 0 or 1, got val") }}
    {%- endif -%}
  {%- endfor -%}

  {#- compute number of input ports #}
  {%- set n_inputs = 0 -%}
  {%- if dims.nbx_0 > 0 and simulink_opts.inputs.lbx_0 -%}  {#- lbx_0 #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.nbx_0 > 0 and simulink_opts.inputs.ubx_0 -%}  {#- ubx_0 #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.np > 0 and simulink_opts.inputs.parameter_traj -%}  {#- parameter_traj #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.ny_0 > 0 and simulink_opts.inputs.y_ref_0 -%}  {#- y_ref_0 -#}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.ny > 0 and dims.N > 1 and simulink_opts.inputs.y_ref -%}  {#- y_ref -#}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.ny_e > 0 and dims.N > 0 and simulink_opts.inputs.y_ref_e -%}  {#- y_ref_e #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.nbx > 0 and dims.N > 1 and simulink_opts.inputs.lbx -%}  {#- lbx #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.nbx > 0 and dims.N > 1 and simulink_opts.inputs.ubx -%}  {#- ubx #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.nbx_e > 0 and dims.N > 0 and simulink_opts.inputs.lbx_e -%}  {#- lbx_e #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.nbx_e > 0 and dims.N > 0 and simulink_opts.inputs.ubx_e -%}  {#- ubx_e #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.nbu > 0 and dims.N > 0 and simulink_opts.inputs.lbu -%}  {#- lbu #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.nbu > 0 and dims.N > 0 and simulink_opts.inputs.ubu -%}  {#- ubu #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.ng > 0 and simulink_opts.inputs.lg -%}  {#- lg #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.ng > 0 and simulink_opts.inputs.ug -%}  {#- ug #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.nh > 0 and simulink_opts.inputs.lh -%}  {#- lh #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.nh > 0 and simulink_opts.inputs.uh -%}  {#- uh #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.nh_e > 0 and simulink_opts.inputs.lh_e -%}  {#- lh_e #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}
  {%- if dims.nh_e > 0 and simulink_opts.inputs.uh_e -%}  {#- uh_e #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}

  {%- for key, val in simulink_opts.inputs -%}
    {%- if val != 0 and val != 1 -%}
      {{ throw(message = "simulink_opts.inputs must be 0 or 1, got val") }}
    {%- endif -%}
  {%- endfor -%}
  {%- if dims.ny_0 > 0 and simulink_opts.inputs.cost_W_0 %}  {#- cost_W_0 #}
    {%- set n_inputs = n_inputs + 1 %}
  {%- endif -%}
  {%- if dims.ny > 0 and simulink_opts.inputs.cost_W %}  {#- cost_W #}
    {%- set n_inputs = n_inputs + 1 %}
  {%- endif -%}
  {%- if dims.ny_e > 0 and simulink_opts.inputs.cost_W_e %}  {#- cost_W_e #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}

  {%- if simulink_opts.inputs.reset_solver -%}  {#- reset_solver #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}

  {%- if simulink_opts.inputs.x_init -%}  {#- x_init #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}

  {%- if simulink_opts.inputs.u_init -%}  {#- u_init #}
    {%- set n_inputs = n_inputs + 1 -%}
  {%- endif -%}

    // specify the number of input ports
    if ( !ssSetNumInputPorts(S, {{ n_inputs }}) )
        return;

    // specify the number of output ports
    {%- set_global n_outputs = 0 %}
    {%- for key, val in simulink_opts.outputs %}
      {%- if val == 1 %}
        {%- set_global n_outputs = n_outputs + val %}
      {%- elif val != 0 %}
        {{ throw(message = "simulink_opts.outputs must be 0 or 1, got val") }}
      {%- endif %}
    {%- endfor %}
    if ( !ssSetNumOutputPorts(S, {{ n_outputs }}) )
        return;

    // specify dimension information for the input ports
    {%- set i_input = -1 %}{# note here i_input is 0-based #}
  {%- if dims.nbx_0 > 0 and simulink_opts.inputs.lbx_0 -%}  {#- lbx_0 #}
    {%- set i_input = i_input + 1 %}
    // lbx_0
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.nbx_0 }});
  {%- endif %}
  {%- if dims.nbx_0 > 0 and simulink_opts.inputs.ubx_0 -%}  {#- ubx_0 #}
    {%- set i_input = i_input + 1 %}
    // ubx_0
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.nbx_0 }});
  {%- endif %}

  {%- if dims.np > 0 and simulink_opts.inputs.parameter_traj -%}  {#- parameter_traj #}
    {%- set i_input = i_input + 1 %}
    // parameters
    ssSetInputPortVectorDimension(S, {{ i_input }}, (N+1) * {{ dims.np }});
  {%- endif %}

  {%- if dims.ny > 0 and simulink_opts.inputs.y_ref_0 %}
    {%- set i_input = i_input + 1 %}
    // y_ref_0
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.ny_0 }});
  {%- endif %}

  {%- if dims.ny > 0 and dims.N > 1 and simulink_opts.inputs.y_ref %}
    {%- set i_input = i_input + 1 %}
    // y_ref
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ (dims.N-1) * dims.ny }});
  {%- endif %}

  {%- if dims.ny_e > 0 and dims.N > 0 and simulink_opts.inputs.y_ref_e %}
    {%- set i_input = i_input + 1 %}
    // y_ref_e
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.ny_e }});
  {%- endif %}

  {%- if dims.nbx > 0 and dims.N > 1 and simulink_opts.inputs.lbx -%}  {#- lbx #}
    {%- set i_input = i_input + 1 %}
    // lbx
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ (dims.N-1) * dims.nbx }});
  {%- endif %}
  {%- if dims.nbx > 0 and dims.N > 1 and simulink_opts.inputs.ubx -%}  {#- ubx #}
    {%- set i_input = i_input + 1 %}
    // ubx
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ (dims.N-1) * dims.nbx }});
  {%- endif %}

  {%- if dims.nbx_e > 0 and dims.N > 0 and simulink_opts.inputs.lbx_e -%}  {#- lbx_e #}
    {%- set i_input = i_input + 1 %}
    // lbx_e
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.nbx_e }});
  {%- endif %}
  {%- if dims.nbx_e > 0 and dims.N > 0 and simulink_opts.inputs.ubx_e -%}  {#- ubx_e #}
    {%- set i_input = i_input + 1 %}
    // ubx_e
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.nbx_e }});
  {%- endif %}

  {%- if dims.nbu > 0 and dims.N > 0 and simulink_opts.inputs.lbu -%}  {#- lbu #}
    {%- set i_input = i_input + 1 %}
    // lbu
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.N*dims.nbu }});
  {%- endif -%}
  {%- if dims.nbu > 0 and dims.N > 0 and simulink_opts.inputs.ubu -%}  {#- ubu #}
    {%- set i_input = i_input + 1 %}
    // ubu
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.N*dims.nbu }});
  {%- endif -%}


  {%- if dims.ng > 0 and simulink_opts.inputs.lg -%}  {#- lg #}
    {%- set i_input = i_input + 1 %}
    // lg
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.N*dims.ng }});
  {%- endif -%}
  {%- if dims.ng > 0 and simulink_opts.inputs.ug -%}  {#- ug #}
    {%- set i_input = i_input + 1 %}
    // ug
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.N*dims.ng }});
  {%- endif -%}

  {%- if dims.nh > 0 and simulink_opts.inputs.lh -%}  {#- lh #}
    {%- set i_input = i_input + 1 %}
    // lh
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.N*dims.nh }});
  {%- endif -%}
  {%- if dims.nh > 0 and simulink_opts.inputs.uh -%}  {#- uh #}
    {%- set i_input = i_input + 1 %}
    // uh
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.N*dims.nh }});
  {%- endif -%}

  {%- if dims.nh_e > 0 and simulink_opts.inputs.lh_e -%}  {#- lh_e #}
    {%- set i_input = i_input + 1 %}
    // lh_e
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.nh_e }});
  {%- endif -%}
  {%- if dims.nh_e > 0 and simulink_opts.inputs.uh_e -%}  {#- uh_e #}
    {%- set i_input = i_input + 1 %}
    // uh_e
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.nh_e }});
  {%- endif -%}

  {%- if dims.ny_0 > 0 and simulink_opts.inputs.cost_W_0 %}  {#- cost_W_0 #}
    {%- set i_input = i_input + 1 %}
    // cost_W_0
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.ny_0 * dims.ny_0 }});
  {%- endif %}

  {%- if dims.ny > 0 and simulink_opts.inputs.cost_W %}  {#- cost_W #}
    {%- set i_input = i_input + 1 %}
    // cost_W
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.ny * dims.ny }});
  {%- endif %}

  {%- if dims.ny_e > 0 and simulink_opts.inputs.cost_W_e %}  {#- cost_W_e #}
    {%- set i_input = i_input + 1 %}
    // cost_W_e
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.ny_e * dims.ny_e }});
  {%- endif %}

  {%- if simulink_opts.inputs.reset_solver -%}  {#- reset_solver #}
    {%- set i_input = i_input + 1 %}
    // reset_solver
    ssSetInputPortVectorDimension(S, {{ i_input }}, 1);
  {%- endif -%}

  {%- if simulink_opts.inputs.x_init -%}  {#- x_init #}
    {%- set i_input = i_input + 1 %}
    // x_init
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.nx * (dims.N+1) }});
  {%- endif -%}

  {%- if simulink_opts.inputs.u_init -%}  {#- u_init #}
    {%- set i_input = i_input + 1 %}
    // u_init
    ssSetInputPortVectorDimension(S, {{ i_input }}, {{ dims.nu * (dims.N) }});
  {%- endif -%}

    /* specify dimension information for the OUTPUT ports */
    {%- set i_output = -1 %}{# note here i_output is 0-based #}
  {%- if dims.nu > 0 and simulink_opts.outputs.u0 == 1 %}
    {%- set i_output = i_output + 1 %}
    ssSetOutputPortVectorDimension(S, {{ i_output }}, {{ dims.nu }} );
  {%- endif %}

  {%- if simulink_opts.outputs.utraj == 1 %}
    {%- set i_output = i_output + 1 %}
    ssSetOutputPortVectorDimension(S, {{ i_output }}, {{ dims.nu * dims.N }} );
  {%- endif %}

  {%- if simulink_opts.outputs.xtraj == 1 %}
    {%- set i_output = i_output + 1 %}
    ssSetOutputPortVectorDimension(S, {{ i_output }}, {{ dims.nx * (dims.N+1) }} );
  {%- endif %}

  {%- if simulink_opts.outputs.solver_status == 1 %}
    {%- set i_output = i_output + 1 %}
    ssSetOutputPortVectorDimension(S, {{ i_output }}, 1 );
  {%- endif %}

  {%- if simulink_opts.outputs.cost_value == 1 %}
    {%- set i_output = i_output + 1 %}
    ssSetOutputPortVectorDimension(S, {{ i_output }}, 1 );
  {%- endif %}

  {%- if simulink_opts.outputs.KKT_residual == 1 %}
    {%- set i_output = i_output + 1 %}
    ssSetOutputPortVectorDimension(S, {{ i_output }}, 1 );
  {%- endif %}

  {%- if simulink_opts.outputs.KKT_residuals == 1 %}
    {%- set i_output = i_output + 1 %}
    ssSetOutputPortVectorDimension(S, {{ i_output }}, 4 );
  {%- endif %}

  {%- if dims.N > 0 and simulink_opts.outputs.x1 == 1 %}
    {%- set i_output = i_output + 1 %}
    ssSetOutputPortVectorDimension(S, {{ i_output }}, {{ dims.nx }} ); // state at shooting node 1
  {%- endif %}

  {%- if simulink_opts.outputs.CPU_time == 1 %}
    {%- set i_output = i_output + 1 %}
    ssSetOutputPortVectorDimension(S, {{ i_output }}, 1);
  {%- endif %}

  {%- if simulink_opts.outputs.CPU_time_sim == 1 %}
    {%- set i_output = i_output + 1 %}
    ssSetOutputPortVectorDimension(S, {{ i_output }}, 1);
  {%- endif %}

  {%- if simulink_opts.outputs.CPU_time_qp == 1 %}
    {%- set i_output = i_output + 1 %}
    ssSetOutputPortVectorDimension(S, {{ i_output }}, 1);
  {%- endif %}

  {%- if simulink_opts.outputs.CPU_time_lin == 1 %}
    {%- set i_output = i_output + 1 %}
    ssSetOutputPortVectorDimension(S, {{ i_output }}, 1);
  {%- endif %}

  {%- if simulink_opts.outputs.sqp_iter == 1 %}
    {%- set i_output = i_output + 1 %}
    ssSetOutputPortVectorDimension(S, {{ i_output }}, 1 );
  {%- endif %}

    // specify the direct feedthrough status
    // should be set to 1 for all inputs used in mdlOutputs
    {%- for i in range(end=n_inputs) %}
    ssSetInputPortDirectFeedThrough(S, {{ i }}, 1);
    {%- endfor %}

    // one sample time
    ssSetNumSampleTimes(S, 1);
}


#if defined(MATLAB_MEX_FILE)

#define MDL_SET_INPUT_PORT_DIMENSION_INFO
#define MDL_SET_OUTPUT_PORT_DIMENSION_INFO

static void mdlSetInputPortDimensionInfo(SimStruct *S, int_T port, const DimsInfo_T *dimsInfo)
{
    if ( !ssSetInputPortDimensionInfo(S, port, dimsInfo) )
         return;
}

static void mdlSetOutputPortDimensionInfo(SimStruct *S, int_T port, const DimsInfo_T *dimsInfo)
{
    if ( !ssSetOutputPortDimensionInfo(S, port, dimsInfo) )
         return;
}

#endif /* MATLAB_MEX_FILE */


static void mdlInitializeSampleTimes(SimStruct *S)
{
    ssSetSampleTime(S, 0, SAMPLINGTIME);
    ssSetOffsetTime(S, 0, 0.0);
}


static void mdlStart(SimStruct *S)
{
    {{ model.name }}_solver_capsule *capsule = {{ model.name }}_acados_create_capsule();
    {{ model.name }}_acados_create(capsule);

    ssSetUserData(S, (void*)capsule);
}


static void mdlOutputs(SimStruct *S, int_T tid)
{
    {{ model.name }}_solver_capsule *capsule = ssGetUserData(S);
    ocp_nlp_config *nlp_config = {{ model.name }}_acados_get_nlp_config(capsule);
    ocp_nlp_dims *nlp_dims = {{ model.name }}_acados_get_nlp_dims(capsule);
    ocp_nlp_in *nlp_in = {{ model.name }}_acados_get_nlp_in(capsule);
    ocp_nlp_out *nlp_out = {{ model.name }}_acados_get_nlp_out(capsule);

    InputRealPtrsType in_sign;

    int N = {{ model.name | upper }}_N;

    {%- set buffer_sizes = [dims.nbx_0, dims.np, dims.nbx, dims.nbx_e, dims.nbu, dims.ng, dims.nh, dims.ng_e, dims.nh_e] -%}

  {%- if dims.ny_0 > 0 and simulink_opts.inputs.y_ref_0 %}  {# y_ref_0 #}
    {%- set buffer_sizes = buffer_sizes | concat(with=(dims.ny_0)) %}
  {%- endif %}
  {%- if dims.ny > 0 and dims.N > 1 and simulink_opts.inputs.y_ref %}  {# y_ref #}
    {%- set buffer_sizes = buffer_sizes | concat(with=(dims.ny)) %}
  {%- endif %}
  {%- if dims.ny_e > 0 and dims.N > 0 and simulink_opts.inputs.y_ref_e %}  {# y_ref_e #}
    {%- set buffer_sizes = buffer_sizes | concat(with=(dims.ny_e)) %}
  {%- endif %}

  {%- if dims.ny_0 > 0 and simulink_opts.inputs.cost_W_0 %}  {#- cost_W_0 #}
    {%- set buffer_sizes = buffer_sizes | concat(with=(dims.ny_0 * dims.ny_0)) %}
  {%- endif %}
  {%- if dims.ny > 0 and simulink_opts.inputs.cost_W %}  {#- cost_W #}
    {%- set buffer_sizes = buffer_sizes | concat(with=(dims.ny * dims.ny)) %}
  {%- endif %}
  {%- if dims.ny_e > 0 and simulink_opts.inputs.cost_W_e %}  {#- cost_W_e #}
    {%- set buffer_sizes = buffer_sizes | concat(with=(dims.ny_e * dims.ny_e)) %}
  {%- endif %}

    // local buffer
    {%- set buffer_size = buffer_sizes | sort | last %}
    real_t buffer[{{ buffer_size }}];

    /* go through inputs */
    {%- set i_input = -1 %}
  {%- if dims.nbx_0 > 0 and simulink_opts.inputs.lbx_0 -%}  {#- lbx_0 #}
    // lbx_0
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    for (int i = 0; i < {{ dims.nbx_0 }}; i++)
        buffer[i] = (double)(*in_sign[i]);

    ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, 0, "lbx", buffer);
  {%- endif %}

  {%- if dims.nbx_0 > 0 and simulink_opts.inputs.ubx_0 -%}  {#- ubx_0 #}
    // ubx_0
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    for (int i = 0; i < {{ dims.nbx_0 }}; i++)
        buffer[i] = (double)(*in_sign[i]);
    ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, 0, "ubx", buffer);
  {%- endif %}

  {%- if dims.np > 0 and simulink_opts.inputs.parameter_traj -%}  {#- parameter_traj #}
    // parameters - stage-variant !!!
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});

    // update value of parameters
    for (int ii = 0; ii <= N; ii++)
    {
        for (int jj = 0; jj < {{ dims.np }}; jj++)
            buffer[jj] = (double)(*in_sign[ii*{{dims.np}}+jj]);
        {{ model.name }}_acados_update_params(capsule, ii, buffer, {{ dims.np }});
    }
  {%- endif %}

  {% if dims.ny_0 > 0 and simulink_opts.inputs.y_ref_0 %}
    // y_ref_0
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});

    for (int i = 0; i < {{ dims.ny_0 }}; i++)
        buffer[i] = (double)(*in_sign[i]);

    ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, 0, "yref", (void *) buffer);
  {%- endif %}

  {% if dims.ny > 0 and dims.N > 1 and simulink_opts.inputs.y_ref %}
    // y_ref - for stages 1 to N-1
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});

    for (int ii = 1; ii < N; ii++)
    {
        for (int jj = 0; jj < {{ dims.ny }}; jj++)
            buffer[jj] = (double)(*in_sign[(ii-1)*{{ dims.ny }}+jj]);
        ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, ii, "yref", (void *) buffer);
    }
  {%- endif %}

  {% if dims.ny_e > 0 and dims.N > 0 and simulink_opts.inputs.y_ref_e %}
    // y_ref_e
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});

    for (int i = 0; i < {{ dims.ny_e }}; i++)
        buffer[i] = (double)(*in_sign[i]);

    ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, N, "yref", (void *) buffer);
  {%- endif %}

  {%- if dims.nbx > 0 and dims.N > 1 and simulink_opts.inputs.lbx -%}  {#- lbx #}
    // lbx
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    for (int ii = 1; ii < N; ii++)
    {
        for (int jj = 0; jj < {{ dims.nbx }}; jj++)
            buffer[jj] = (double)(*in_sign[(ii-1)*{{ dims.nbx }}+jj]);
        ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, ii, "lbx", (void *) buffer);
    }
  {%- endif %}
  {%- if dims.nbx > 0 and dims.N > 1 and simulink_opts.inputs.ubx -%}  {#- ubx #}
    // ubx
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    for (int ii = 1; ii < N; ii++)
    {
        for (int jj = 0; jj < {{ dims.nbx }}; jj++)
            buffer[jj] = (double)(*in_sign[(ii-1)*{{ dims.nbx }}+jj]);
        ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, ii, "ubx", (void *) buffer);
    }
  {%- endif %}


  {%- if dims.nbx_e > 0 and dims.N > 0 and simulink_opts.inputs.lbx_e -%}  {#- lbx_e #}
    // lbx_e
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});

    for (int i = 0; i < {{ dims.nbx_e }}; i++)
        buffer[i] = (double)(*in_sign[i]);
    ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, N, "lbx", buffer);
  {%- endif %}
  {%- if dims.nbx_e > 0 and dims.N > 0 and simulink_opts.inputs.ubx_e -%}  {#- ubx_e #}
    // ubx_e
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});

    for (int i = 0; i < {{ dims.nbx_e }}; i++)
        buffer[i] = (double)(*in_sign[i]);
    ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, N, "ubx", buffer);
  {%- endif %}


  {%- if dims.nbu > 0 and dims.N > 0 and simulink_opts.inputs.lbu -%}  {#- lbu #}
    // lbu
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    for (int ii = 0; ii < N; ii++)
    {
        for (int jj = 0; jj < {{ dims.nbu }}; jj++)
            buffer[jj] = (double)(*in_sign[ii*{{ dims.nbu }}+jj]);
        ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, ii, "lbu", (void *) buffer);
    }
  {%- endif -%}
  {%- if dims.nbu > 0 and dims.N > 0 and simulink_opts.inputs.ubu -%}  {#- ubu #}
    // ubu
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    for (int ii = 0; ii < N; ii++)
    {
        for (int jj = 0; jj < {{ dims.nbu }}; jj++)
            buffer[jj] = (double)(*in_sign[ii*{{ dims.nbu }}+jj]);
        ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, ii, "ubu", (void *) buffer);
    }
  {%- endif -%}

  {%- if dims.ng > 0 and simulink_opts.inputs.lg -%}  {#- lg #}
    // lg
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});

    for (int ii = 0; ii < N; ii++)
    {
        for (int jj = 0; jj < {{ dims.ng }}; jj++)
            buffer[jj] = (double)(*in_sign[ii*{{ dims.ng }}+jj]);
        ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, ii, "lg", (void *) buffer);
    }
  {%- endif -%}
  {%- if dims.ng > 0 and simulink_opts.inputs.ug -%}  {#- ug #}
    // ug
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});

    for (int ii = 0; ii < N; ii++)
    {
        for (int jj = 0; jj < {{ dims.ng }}; jj++)
            buffer[jj] = (double)(*in_sign[ii*{{ dims.ng }}+jj]);
        ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, ii, "ug", (void *) buffer);
    }
  {%- endif -%}

  {%- if dims.nh > 0 and simulink_opts.inputs.lh -%}  {#- lh #}
    // lh
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});

    for (int ii = 0; ii < N; ii++)
    {
        for (int jj = 0; jj < {{ dims.nh }}; jj++)
            buffer[jj] = (double)(*in_sign[ii*{{ dims.nh }}+jj]);
        ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, ii, "lh", (void *) buffer);
    }
  {%- endif -%}
  {%- if dims.nh > 0 and simulink_opts.inputs.uh -%}  {#- uh #}
    // uh
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});

    for (int ii = 0; ii < N; ii++)
    {
        for (int jj = 0; jj < {{ dims.nh }}; jj++)
            buffer[jj] = (double)(*in_sign[ii*{{ dims.nh }}+jj]);
        ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, ii, "uh", (void *) buffer);
    }
  {%- endif -%}


  {%- if dims.nh_e > 0 and simulink_opts.inputs.lh_e -%}  {#- lh_e #}
    // lh_e
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    for (int i = 0; i < {{ dims.nh_e }}; i++)
        buffer[i] = (double)(*in_sign[i]);
    ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, N, "lh", buffer);
  {%- endif -%}
  {%- if dims.nh_e > 0 and simulink_opts.inputs.uh_e -%}  {#- uh_e #}
    // uh_e
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    for (int i = 0; i < {{ dims.nh_e }}; i++)
        buffer[i] = (double)(*in_sign[i]);
    ocp_nlp_constraints_model_set(nlp_config, nlp_dims, nlp_in, N, "uh", buffer);
  {%- endif -%}

  {%- if dims.ny_0 > 0 and simulink_opts.inputs.cost_W_0 %}  {#- cost_W_0 #}
    // cost_W_0
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    for (int i = 0; i < {{ dims.ny_0 * dims.ny_0 }}; i++)
        buffer[i] = (double)(*in_sign[i]);

    ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, 0, "W", buffer);
  {%- endif %}

  {%- if dims.ny > 0 and simulink_opts.inputs.cost_W %}  {#- cost_W #}
    // cost_W
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    for (int i = 0; i < {{ dims.ny * dims.ny }}; i++)
        buffer[i] = (double)(*in_sign[i]);

    for (int ii = 1; ii < N; ii++)
        ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, ii, "W", buffer);
  {%- endif %}

  {%- if dims.ny_e > 0 and simulink_opts.inputs.cost_W_e %}  {#- cost_W_e #}
    // cost_W_e
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    for (int i = 0; i < {{ dims.ny_e * dims.ny_e }}; i++)
        buffer[i] = (double)(*in_sign[i]);

    ocp_nlp_cost_model_set(nlp_config, nlp_dims, nlp_in, N, "W", buffer);
  {%- endif %}

  {%- if simulink_opts.inputs.reset_solver %}  {#- reset_solver #}
    // reset_solver
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    double reset = (double)(*in_sign[0]);
    if (reset)
    {
        {{ model.name }}_acados_reset(capsule, 1);
    }
  {%- endif %}

  {%- if simulink_opts.inputs.x_init %}  {#- x_init #}
    // x_init
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    for (int ii = 0; ii < {{ dims.N + 1 }}; ii++)
    {
        for (int jj = 0; jj < {{ dims.nx }}; jj++)
            buffer[jj] = (double)(*in_sign[(ii)*{{ dims.nx }}+jj]);
        ocp_nlp_out_set(nlp_config, nlp_dims, nlp_out, ii, "x", (void *) buffer);
    }
  {%- endif %}

  {%- if simulink_opts.inputs.u_init %}  {#- u_init #}
    // u_init
    {%- set i_input = i_input + 1 %}
    in_sign = ssGetInputPortRealSignalPtrs(S, {{ i_input }});
    for (int ii = 0; ii < N; ii++)
    {
        for (int jj = 0; jj < {{ dims.nu }}; jj++)
            buffer[jj] = (double)(*in_sign[(ii)*{{ dims.nu }}+jj]);
        ocp_nlp_out_set(nlp_config, nlp_dims, nlp_out, ii, "u", (void *) buffer);
    }
  {%- endif %}

    /* call solver */
    int rti_phase = 0;
    ocp_nlp_solver_opts_set(nlp_config, capsule->nlp_opts, "rti_phase", &rti_phase);
    int acados_status = {{ model.name }}_acados_solve(capsule);


    /* set outputs */
    // assign pointers to output signals
    real_t *out_u0, *out_utraj, *out_xtraj, *out_status, *out_sqp_iter, *out_KKT_res, *out_KKT_residuals, *out_x1, *out_cpu_time, *out_cpu_time_sim, *out_cpu_time_qp, *out_cpu_time_lin, *out_cost_value;
    int tmp_int;

    {%- set i_output = -1 -%}{# note here i_output is 0-based #}
  {%- if dims.nu > 0 and simulink_opts.outputs.u0 == 1 %}
    {%- set i_output = i_output + 1 %}
    out_u0 = ssGetOutputPortRealSignal(S, {{ i_output }});
    ocp_nlp_out_get(nlp_config, nlp_dims, nlp_out, 0, "u", (void *) out_u0);
  {%- endif %}

  {%- if simulink_opts.outputs.utraj == 1 %}
    {%- set i_output = i_output + 1 %}
    out_utraj = ssGetOutputPortRealSignal(S, {{ i_output }});
    for (int ii = 0; ii < N; ii++)
        ocp_nlp_out_get(nlp_config, nlp_dims, nlp_out, ii,
                        "u", (void *) (out_utraj + ii * {{ dims.nu }}));
  {%- endif %}

  {% if simulink_opts.outputs.xtraj == 1 %}
    {%- set i_output = i_output + 1 %}

    out_xtraj = ssGetOutputPortRealSignal(S, {{ i_output }});
    for (int ii = 0; ii < {{ dims.N + 1 }}; ii++)
        ocp_nlp_out_get(nlp_config, nlp_dims, nlp_out, ii,
                        "x", (void *) (out_xtraj + ii * {{ dims.nx }}));
  {%- endif %}

  {%- if simulink_opts.outputs.solver_status == 1 %}
    {%- set i_output = i_output + 1 %}
    out_status = ssGetOutputPortRealSignal(S, {{ i_output }});
    *out_status = (real_t) acados_status;
  {%- endif %}

  {%- if simulink_opts.outputs.cost_value == 1 %}
    {%- set i_output = i_output + 1 %}
    out_cost_value = ssGetOutputPortRealSignal(S, {{ i_output }});
    ocp_nlp_eval_cost(capsule->nlp_solver, nlp_in, nlp_out);
    ocp_nlp_get(nlp_config, capsule->nlp_solver, "cost_value", (void *) out_cost_value);
  {%- endif %}

  {%- if simulink_opts.outputs.KKT_residual == 1 %}
    {%- set i_output = i_output + 1 %}
    out_KKT_res = ssGetOutputPortRealSignal(S, {{ i_output }});
    *out_KKT_res = (real_t) nlp_out->inf_norm_res;
  {%- endif %}

  {%- if simulink_opts.outputs.KKT_residuals == 1 %}
    {%- set i_output = i_output + 1 %}
    out_KKT_residuals = ssGetOutputPortRealSignal(S, {{ i_output }});

    {%- if solver_options.nlp_solver_type == "SQP_RTI" %}
    ocp_nlp_eval_residuals(capsule->nlp_solver, nlp_in, nlp_out);
    {%- endif %}
    ocp_nlp_get(nlp_config, capsule->nlp_solver, "res_stat", (void *) &out_KKT_residuals[0]);
    ocp_nlp_get(nlp_config, capsule->nlp_solver, "res_eq", (void *) &out_KKT_residuals[1]);
    ocp_nlp_get(nlp_config, capsule->nlp_solver, "res_ineq", (void *) &out_KKT_residuals[2]);
    ocp_nlp_get(nlp_config, capsule->nlp_solver, "res_comp", (void *) &out_KKT_residuals[3]);
  {%- endif %}

  {%- if dims.N > 0 and simulink_opts.outputs.x1 == 1 %}
    {%- set i_output = i_output + 1 %}
    out_x1 = ssGetOutputPortRealSignal(S, {{ i_output }});
    ocp_nlp_out_get(nlp_config, nlp_dims, nlp_out, 1, "x", (void *) out_x1);
  {%- endif %}

  {%- if simulink_opts.outputs.CPU_time == 1 %}
    {%- set i_output = i_output + 1 %}
    out_cpu_time = ssGetOutputPortRealSignal(S, {{ i_output }});
    // get solution time
    ocp_nlp_get(nlp_config, capsule->nlp_solver, "time_tot", (void *) out_cpu_time);
  {%- endif -%}

  {%- if simulink_opts.outputs.CPU_time_sim == 1 %}
    {%- set i_output = i_output + 1 %}
    out_cpu_time_sim = ssGetOutputPortRealSignal(S, {{ i_output }});
    ocp_nlp_get(nlp_config, capsule->nlp_solver, "time_sim", (void *) out_cpu_time_sim);
  {%- endif -%}

  {%- if simulink_opts.outputs.CPU_time_qp == 1 %}
    {%- set i_output = i_output + 1 %}
    out_cpu_time_qp = ssGetOutputPortRealSignal(S, {{ i_output }});
    ocp_nlp_get(nlp_config, capsule->nlp_solver, "time_qp", (void *) out_cpu_time_qp);
  {%- endif -%}

  {%- if simulink_opts.outputs.CPU_time_lin == 1 %}
    {%- set i_output = i_output + 1 %}
    out_cpu_time_lin = ssGetOutputPortRealSignal(S, {{ i_output }});
    ocp_nlp_get(nlp_config, capsule->nlp_solver, "time_lin", (void *) out_cpu_time_lin);
  {%- endif -%}

  {%- if simulink_opts.outputs.sqp_iter == 1 %}
    {%- set i_output = i_output + 1 %}
    out_sqp_iter = ssGetOutputPortRealSignal(S, {{ i_output }});
    // get sqp iter
    ocp_nlp_get(nlp_config, capsule->nlp_solver, "sqp_iter", (void *) &tmp_int);
    *out_sqp_iter = (real_t) tmp_int;
  {%- endif %}

}

static void mdlTerminate(SimStruct *S)
{
    {{ model.name }}_solver_capsule *capsule = ssGetUserData(S);

    {{ model.name }}_acados_free(capsule);
    {{ model.name }}_acados_free_capsule(capsule);
}


#ifdef  MATLAB_MEX_FILE
#include "simulink.c"
#else
#include "cg_sfun.h"
#endif
