/*
 *  SDSPLEVDURB2 - Levinson-Durbin solver for real correlation functions.
 *  DSP Blockset S-Function to solve a symmetric Toeplitz system of
 *  equations using the Levinson-Durbin recursion.  Input is a vector
 *  of autocorrelation coefficients, starting with lag 0 as the first
 *  element.  Recursion order is length(input)-1.
 *
 *  Copyright 1995-2000 The MathWorks, Inc.
 *  $Revision: 1.12 $ $Date: 2000/08/31 19:25:09 $
 */
#define S_FUNCTION_NAME sdsplevdurb2
#define S_FUNCTION_LEVEL 2

#include "dsp_sim.h"

#define INPORT_R        0
#define ACOEF_DWORK_IDX 0

enum {
    COEFFS_OUT_ARGC=0,
    OUT_P_ARGC,
    ZERO_INP_HNDL_ARGC,
    NUM_ARGS
};

/* Macros for simple access of the "pointers" to the ML API S-fcn args */
#define COEFFS_OUT_ARG(S)  (ssGetSFcnParam(S,COEFFS_OUT_ARGC))
#define OUT_P_ARG(S)       (ssGetSFcnParam(S,OUT_P_ARGC))
#define ZER_INP_HND_ARG(S) (ssGetSFcnParam(S,ZERO_INP_HNDL_ARGC))

/* Macros for simple C access of the values of the S-fcn args */
#define GET_COEFFS_OUT_ARG(S)     (int_T)mxGetPr(COEFFS_OUT_ARG(S))[0]
#define GET_OUT_P_ARG(S)          (int_T)mxGetPr(OUT_P_ARG(S))[0]
#define SPEC_CASE_ZERO_INP_HND(S) (boolean_T)mxGetPr(ZER_INP_HND_ARG(S))[0]

typedef enum {
    fcnAandK = 1,
    fcnA,
    fcnK,
    fcnAandKandP,
    fcnAandP,
    fcnKandP
} FcnType;

#define GET_FCN_TYPE_ENUM(S) (FcnType)((GET_COEFFS_OUT_ARG(S)) + 3*(GET_OUT_P_ARG(S)))

#ifdef MATLAB_MEX_FILE
#define MDL_CHECK_PARAMETERS
static void mdlCheckParameters(SimStruct *S) {
    if(!IS_FLINT_IN_RANGE(COEFFS_OUT_ARG(S),1,3)) {
        THROW_ERROR(S, "Output coefficients parameter must be 1 (A and K), 2 (A), or 3 (K).");
    }
 
    if(!IS_FLINT_IN_RANGE(OUT_P_ARG(S),0,1)) {
        THROW_ERROR(S, "Output P parameter must be 0 or 1.");
    }
 
    if(!IS_FLINT_IN_RANGE(ZER_INP_HND_ARG(S),0,1)) {
        THROW_ERROR(S, "Special-case handling of zero input parameter must be 0 or 1.");
    }
}
#endif


static void mdlInitializeSizes(SimStruct *S)
{
    /* REGISTER_SFCN_PARAMS(S, NUM_ARGS); NOT SUPPORTED FOR CODE GENERATION WITHOUT TLC...  */
    /* After TLC is created for this S-fcn, replace the following with REGISTER_SFCN_PARAMS */
    {
        boolean_T argCountOK = (boolean_T)(NUM_ARGS == ssGetSFcnParamsCount(S));
        ssSetNumSFcnParams(S, NUM_ARGS);
        if (!argCountOK) return;
#ifdef MATLAB_MEX_FILE
        mdlCheckParameters(S);
#endif
        if (ANY_ERRORS(S)) return;
    }

    ssSetSFcnParamNotTunable(S, COEFFS_OUT_ARGC);
    ssSetSFcnParamNotTunable(S, OUT_P_ARGC);
    ssSetSFcnParamNotTunable(S, ZERO_INP_HNDL_ARGC);

    /* Inputs: */
    if (!ssSetNumInputPorts(S, 1)) return;
    if (!ssSetInputPortDimensionInfo(S, INPORT_R, DYNAMIC_DIMENSION)) return;
    ssSetInputPortFrameData(         S, INPORT_R, FRAME_INHERITED);
    ssSetInputPortComplexSignal(     S, INPORT_R, COMPLEX_INHERITED);
    ssSetInputPortReusable(          S, INPORT_R, 1);
    ssSetInputPortDirectFeedThrough( S, INPORT_R, 1);
    ssSetInputPortOverWritable(      S, INPORT_R, 0);  /* revisits inputs multiple times */

    /* Outputs: */
    {
        switch (GET_FCN_TYPE_ENUM(S)) {
        case fcnAandKandP:
            /* 3 outputs: */
            if (!ssSetNumOutputPorts(S,3)) return;
            
            /* Set the first output port properties */
            if (!ssSetOutputPortDimensionInfo(S, 0, DYNAMIC_DIMENSION)) return;
            ssSetOutputPortFrameData(         S, 0, FRAME_NO);
            ssSetOutputPortComplexSignal(     S, 0, COMPLEX_INHERITED); 
            ssSetOutputPortReusable(          S, 0, 1);
            
            /* Set the second output port properties */
            if (!ssSetOutputPortDimensionInfo(S, 1, DYNAMIC_DIMENSION)) return;
            ssSetOutputPortFrameData(         S, 1, FRAME_NO);
            ssSetOutputPortComplexSignal(     S, 1, COMPLEX_INHERITED); 
            ssSetOutputPortReusable(          S, 1, 1);
            
            /* Set the third output port (P) properties */
            if (!ssSetOutputPortDimensionInfo(S, 2, DYNAMIC_DIMENSION)) return;
            ssSetOutputPortFrameData(         S, 2, FRAME_NO);
            ssSetOutputPortComplexSignal(     S, 2, COMPLEX_NO); /* power is always real */
            ssSetOutputPortReusable(          S, 2, 1);
            
            break;
            
        case fcnAandK:
            /* 2 outputs (both are coeffs): */
            if (!ssSetNumOutputPorts(S,2)) return;
            
            /* Set the first output port (A) properties */
            if (!ssSetOutputPortDimensionInfo(S, 0, DYNAMIC_DIMENSION)) return;
            ssSetOutputPortFrameData(         S, 0, FRAME_NO);
            ssSetOutputPortComplexSignal(     S, 0, COMPLEX_INHERITED); 
            ssSetOutputPortReusable(          S, 0, 1);
            
            /* Set the second output port (K) properties */
            if (!ssSetOutputPortDimensionInfo(S, 1, DYNAMIC_DIMENSION)) return;
            ssSetOutputPortFrameData(         S, 1, FRAME_NO);
            ssSetOutputPortComplexSignal(     S, 1, COMPLEX_INHERITED); 
            ssSetOutputPortReusable(          S, 1, 1);
            
            break;
            
        case fcnAandP:
        case fcnKandP:
            /* 2 outputs (2nd is P): */
            if (!ssSetNumOutputPorts(S,2)) return;
            
            /* Set the first output port properties */
            if (!ssSetOutputPortDimensionInfo(S, 0, DYNAMIC_DIMENSION)) return;
            ssSetOutputPortFrameData(         S, 0, FRAME_NO);
            ssSetOutputPortComplexSignal(     S, 0, COMPLEX_INHERITED); 
            ssSetOutputPortReusable(          S, 0, 1);
            
            /* Set the second output port (P) properties */
            if (!ssSetOutputPortDimensionInfo(S, 1, DYNAMIC_DIMENSION)) return;
            ssSetOutputPortFrameData(         S, 1, FRAME_NO);
            ssSetOutputPortComplexSignal(     S, 1, COMPLEX_NO); /* power is always real */
            ssSetOutputPortReusable(          S, 1, 1);
            
            break;
            
        default:
            /* 1 Output (either polynomial or reflection coefficients) */
            if (!ssSetNumOutputPorts(S,1)) return;
            
            /* Set the first output port properties */
            if (!ssSetOutputPortDimensionInfo(S, 0, DYNAMIC_DIMENSION)) return;
            ssSetOutputPortFrameData(         S, 0, FRAME_NO);
            ssSetOutputPortComplexSignal(     S, 0, COMPLEX_INHERITED); 
            ssSetOutputPortReusable(          S, 0, 1);
            
            break;
        }
    }

    if(!ssSetNumDWork(S, DYNAMICALLY_SIZED)) return;

    ssSetNumSampleTimes(S, 1);
    ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE);
}


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


/* Private function getPOutputIndex:
 *
 * Returns the index of P output port if it exists.
 * If the P output port does not exist, -1 is returned.
 */
static int_T getPOutputIndex(SimStruct *S)
{
    int_T OUTPORT_P = -1; /* Assume no P output port */
    if (GET_OUT_P_ARG(S) == 1) {
        /* P output port exists */
        if (GET_FCN_TYPE_ENUM(S) == fcnAandKandP) {
            OUTPORT_P = 2; /* 3rd port (index 2) */
        }
        else {
            OUTPORT_P = 1; /* 2nd port (index 1) */
        }
    }
    return(OUTPORT_P);
}


static void mdlOutputs(SimStruct *S, int_T tid)
{
    const FcnType   ftype          = GET_FCN_TYPE_ENUM(S);
    const boolean_T inputConnected = (boolean_T)ssGetInputPortConnected(S,INPORT_R);
    if (inputConnected)  {
        const boolean_T c0 = (boolean_T)(ssGetInputPortComplexSignal(S,INPORT_R) == COMPLEX_YES);
        const boolean_T AoutputExists = (ftype != fcnK) && (ftype != fcnKandP);
        const int_T     OUTPORT_P     = getPOutputIndex(S);
        
        if (!c0) {
            /* 
             * Real input:
             */
            InputRealPtrsType  uptr = ssGetInputPortRealSignalPtrs(S,INPORT_R);
            const int_T        N    = ssGetInputPortWidth(S,INPORT_R);
            real_T            *y0   = ssGetOutputPortRealSignal(S,0);
            int_T              i;
            
            /* Special case when input starts with a 0: 
             * If option is selected then 
             * the block will output zeros instead of computing NaN's
             * First polynomial coefficient is always 1.
             */
            if ((SPEC_CASE_ZERO_INP_HND(S)) && (*uptr[0] == 0.0)) {
                if (OUTPORT_P < 0) {
                    /* -------------------------------------- */
                    /* In "A", "K", or "A and K" output modes */
                    /* -------------------------------------- */
                    
                    /* 1st output port could be A or K */
                    i = (AoutputExists)
                      ? N
                      : N-1;

                    while (i-- > 0) {
                        *y0++ = 0.0;
                    }
                    if (AoutputExists) {
                        y0 = ssGetOutputPortRealSignal(S,0);
                        y0[0] = 1.0;
                    }
                    
                    /* 2nd output port exists only in "A and K" output mode */
                    if (ftype == fcnAandK) {
                        real_T *y1 = ssGetOutputPortRealSignal(S,1); /* K */
                        
                        i = N-1;
                        while (i-- > 0) {
                            *y1++ = 0.0;
                        }
                    }
                } else {
                    /* ------------------------------------------- */
                    /* In "K and P", "A and P", or "A and K and P" */
                    /* ------------------------------------------- */
                    
                    /* 1st output port could be A or K */
                    i = (AoutputExists)
                      ? N
                      : N-1;
                    while (i-- > 0) {
                        *y0++ = 0.0;
                    }
                    if (AoutputExists) {
                        y0    = ssGetOutputPortRealSignal(S,0);
                        y0[0] = 1.0;
                    }
                    
                    /* 2nd and possibly 3rd output ports */
                    if (OUTPORT_P == 1) {
                        real_T *y1 = ssGetOutputPortRealSignal(S,1);
                        *y1 = 0.0; /* P */
                    } else {
                        real_T *y1 = ssGetOutputPortRealSignal(S,1);
                        real_T *y2 = ssGetOutputPortRealSignal(S,2);
                        
                        /* Set K outputs to zero */
                        i = N-1;
                        while (i-- > 0) {
                            *y1++ = 0.0;
                        }
                        
                        *y2 = 0.0; /* P */
                    }
                }
            } else {
                /* Real input, "normal" cases */
                real_T *a = (AoutputExists)
                          ? y0
                          : (real_T *)ssGetDWork(S,ACOEF_DWORK_IDX);

                real_T E = *uptr[0];
                
                for(i=1; i<N; i++) {
                    int_T  j;
                    real_T ki = *uptr[i];
                    
                    /* Update reflection coefficient: */
                    for (j=1; j<i; j++) {
                        ki += a[j] * *uptr[i-j];
                    }
                    ki /= -E;
                    E *= (1 - ki*ki);
                    
                    /* Update polynomial: */
                    for (j=1; j<=(i-1)/2; j++) {
                        real_T t = a[j];
                        a[j]   += ki * a[i-j];
                        a[i-j] += ki * t;
                    }
                    
                    if (i%2 == 0) {
                        a[i/2] *= 1+ki;
                    }
                    
                    /* Record reflection coefficient */
                    a[i] = ki;
                    
                    if ((ftype == fcnAandK) || (ftype == fcnAandKandP)) {
                        real_T *y1 = ssGetOutputPortRealSignal(S,1);
                        y1[i-1] = ki;  
                    } else if ((ftype == fcnK) || (ftype == fcnKandP)) {
                        y0[i-1] = ki;
                    }
                    
                }
                
                a[0] = 1.0;
                
                if (OUTPORT_P >= 0) {
                    real_T *outP = ssGetOutputPortRealSignal(S,OUTPORT_P);
                    *outP = E;
                }
            }
   } else {
       /* 
        * Complex
        */
       InputPtrsType  uptr = ssGetInputPortSignalPtrs(S,INPORT_R);
       const int_T    N    = ssGetInputPortWidth(S,INPORT_R);
       creal_T       *u    = (creal_T *)uptr[0];
       int_T          i;
       
       /* Special case when input starts with a 0: 
        * If option is selected then 
        * the block will output zeros instead of computing NaN's
        * First polynomial coefficient is always 1.
        */
       if ((SPEC_CASE_ZERO_INP_HND(S)) && (u->re == 0.0) && (u->im == 0.0)) {
           /* NOTE: Initial lag must be REAL for a valid autocorr sequence input
            *       Checking both the real and imag parts here (to be consistent
            *       with Signal Processing Toolbox "levinson" function).
            */
           if (OUTPORT_P < 0) {
               /* -------------------------------------- */
               /* In "A", "K", or "A and K" output modes */
               /* -------------------------------------- */
               creal_T *y0 = (creal_T *)ssGetOutputPortSignal(S,0); /* A or K */
               
               /* 1st output port could be A or K */
               i = (AoutputExists)
                 ? N
                 : N-1;

               while (i-- > 0) {
                   y0->re   = 0.0;
                   y0++->im = 0.0;
               }
               if (AoutputExists) {
                   y0     = (creal_T *)ssGetOutputPortSignal(S,0);
                   y0->re = 1.0;
               }
               
               /* 2nd output port */
               if (ftype == fcnAandK) {
                   creal_T *y1 = ssGetOutputPortSignal(S,1); /* K */
                   
                   i = N-1;
                   while (i-- > 0) {
                       y1->re   = 0.0;
                       y1++->im = 0.0;
                   }
               }
           } else {
               /* ------------------------------------------- */
               /* In "K and P", "A and P", or "A and K and P" */
               /* ------------------------------------------- */
               creal_T *y0 = (creal_T *)ssGetOutputPortSignal(S,0); /* A or K */
               
               /* 1st output port could be A or K */
               i = (AoutputExists)
                 ? N
                 : N-1;

               while (i-- > 0) {
                   y0->re   = 0.0;
                   y0++->im = 0.0;
               }
               if (AoutputExists) {
                   y0     = (creal_T *)ssGetOutputPortSignal(S,0);
                   y0->re = 1.0;
               }
               
               /* 2nd and possibly 3rd output ports */
               if (OUTPORT_P == 1) {
                   real_T *y1 = ssGetOutputPortRealSignal(S,1);
                   *y1 = 0.0; /* P */
               } else {
                   creal_T *y1 = (creal_T *)ssGetOutputPortSignal(S,1);
                   real_T  *y2 = ssGetOutputPortRealSignal(S,2);
                   
                   /* Set K outputs to zero */
                   i = N-1;
                   while (i-- > 0) {
                       y1->re   = 0.0;
                       y1++->im = 0.0;
                   }
                   
                   *y2 = 0.0; /* P */
               }
           }
       } else {
           /* Complex input, "normal" cases */
           creal_T *a = (AoutputExists)
                      ? (creal_T *)ssGetOutputPortSignal(S,0)
                      : (creal_T *)ssGetDWork(S,ACOEF_DWORK_IDX);

           real_T E = u->re;  /* Only use the real part of the 1st element*/
           
           for(i=1; i<N; i++) {
               const creal_T *ui = (creal_T *)uptr[i];
               creal_T        k  = *ui;
               int_T          j;
               
               for (j=1; j<i; j++) {
                   /* k = y * r[reverse order] */
                   const creal_T *ui = (creal_T *)uptr[i-j];
                   creal_T        u = *ui;
                   
                   k.re += CMULT_RE(u,a[j]);
                   k.im += CMULT_IM(u,a[j]);
               }
               
               k.re /= -E;
               k.im /= -E;
               E *= (1 - CMAGSQ(k));
               
               /* Update polynomial: */
               for (j=1; j<=(i-1)/2; j++) {
                   creal_T t = a[j];
                   
                   /*
                    * ynew = yold + conj(yold[reverse order]) * k 
                    */
                   a[j].re += CMULT_XCONJ_RE(a[i-j], k);
                   a[j].im += CMULT_XCONJ_IM(a[i-j], k);
                   
                   a[i-j].re += CMULT_XCONJ_RE(t,k);
                   a[i-j].im += CMULT_XCONJ_IM(t,k);
               }
               
               if (i%2 == 0) {
                   creal_T t   = a[i/2];
                   
                   /* To compare to the real input case:
                    * y *= 1 + ki    <==>   y += k * conj(y)
                    *  (Real)                (Complex)
                    */
                   a[i/2].re += CMULT_XCONJ_RE(t,k);
                   a[i/2].im += CMULT_XCONJ_IM(t,k);
               } 
               
               /* Record reflection coefficient */
               a[i] = k;
               
               if ((ftype == fcnAandK) || (ftype == fcnAandKandP)) {
                   creal_T *y1 = (creal_T *)ssGetOutputPortSignal(S,1); 
                   y1[i-1] = k;  
               } else if ((ftype == fcnK) || (ftype == fcnKandP)) {
                   creal_T *y0 = (creal_T *)ssGetOutputPortSignal(S,0);
                   y0[i-1] = k;
               }
           }
           
           a[0].re = 1.0;
           a[0].im = 0.0;
           
           if (OUTPORT_P >= 0) {
               real_T *outP = ssGetOutputPortRealSignal(S,OUTPORT_P);
               *outP = E;
           }
       }
    }
    }
}


static void mdlTerminate(SimStruct *S)
{
}


#if defined(MATLAB_MEX_FILE)
#define MDL_SET_INPUT_PORT_FRAME_DATA
static void mdlSetInputPortFrameData(SimStruct *S, 
                                     int_T      port,
                                     Frame_T    frameData)
{
    /* Input frame status is inherited - can be anything (maximally flexible). */
    /* Output frame status already set to FRAME_NO in mdlInitializeSizes fcn.  */
    ssSetInputPortFrameData(S, port, frameData);
}


/* --------------------------------------------------
 * PORT DIMENSIONS PROPAGATION RULES FOR THIS BLOCK:
 *
 * Input  R, width N   (vector)
 * Output A, width N   (vector)
 * Output K, width N-1 (vector)
 * Output P, width 1   (scalar)
 * --------------------------------------------------
 */


/* Private function checkAndSetPOutport:
 *
 * Check and/or set dimensions of P output port to be scalar (if it exists)
 */
static void checkAndSetPOutport(SimStruct *S)
{
    /* If P output exists, check if it is a scalar (and set it if not set) */
    const int_T OUTPORT_P = getPOutputIndex(S);
    if (OUTPORT_P >= 0) {
        const int_T widthOutP = ssGetOutputPortWidth(S, OUTPORT_P);
        if (widthOutP == DYNAMICALLY_SIZED) {
            if(!ssSetOutputPortVectorDimension(S, OUTPORT_P, 1)) return;
        }
        else if (widthOutP != 1) {
            THROW_ERROR(S, "P output must be a scalar.");
        }
    }
}


/* Private function checkAndSetAandKOutports:
 *
 * Check and/or set dimensions of A AND K output ports
 * (assumes both exist, A is index 0, K is index 1)
 */
static void checkAndSetAandKOutports(SimStruct *S, const DimsInfo_T *inpDimsInfo)
{
    const int_T OUTPORT_A = 0;
    const int_T OUTPORT_K = 1;
    const int_T widthOutA = ssGetOutputPortWidth(S, OUTPORT_A);
    const int_T widthOutK = ssGetOutputPortWidth(S, OUTPORT_K);
    
    /* port A */
    if (widthOutA == DYNAMICALLY_SIZED) {
        if(!ssSetOutputPortVectorDimension(S, OUTPORT_A, inpDimsInfo->width)) return;
    }
    else if (widthOutA != inpDimsInfo->width) {
        THROW_ERROR(S, "Width of input (R) and A output are not equal.");
    }
    
    /* port K */
    if (widthOutK == DYNAMICALLY_SIZED) {
        const int_T width = MAX(inpDimsInfo->width - 1,1);
        if(!ssSetOutputPortVectorDimension(S, OUTPORT_K, width)) return;
    }
    else if (widthOutK != (inpDimsInfo->width - 1)) {
        THROW_ERROR(S, "K output width is not one less than the (R) input width.");
    }
}


/* Private function checkAndSetAOutport:
 *
 * Check and/or set dimensions of A output port (assumes A is port index 0)
 */
static void checkAndSetAOutport(SimStruct *S, const DimsInfo_T *inpDimsInfo)
{
    const int_T OUTPORT_A = 0;
    const int_T widthOutA = ssGetOutputPortWidth(S, OUTPORT_A);
    
    if (widthOutA == DYNAMICALLY_SIZED) {
        if(!ssSetOutputPortVectorDimension(S, OUTPORT_A, inpDimsInfo->width)) return;
    }
    else if (widthOutA != inpDimsInfo->width) {
        THROW_ERROR(S, "Width of input (R) and output (A) are not equal.");
    }
}


/* Private function checkAndSetKOutport:
 *
 * Check and/or set dimensions of K output port (assumes K is port index 0)
 */
static void checkAndSetKOutport(SimStruct *S, const DimsInfo_T *inpDimsInfo)
{
    const int_T OUTPORT_K = 0;
    const int_T widthOutK = ssGetOutputPortWidth(S, OUTPORT_K);
    
    if (widthOutK == DYNAMICALLY_SIZED) {
        const int_T width = MAX(inpDimsInfo->width - 1,1);
        if(!ssSetOutputPortVectorDimension(S, OUTPORT_K, width)) return;
    }
    else if (widthOutK != (inpDimsInfo->width - 1)) {
        THROW_ERROR(S, "K output width is not one less than the (R) input width.");
    }
}


#define MDL_SET_INPUT_PORT_DIMENSION_INFO
static void mdlSetInputPortDimensionInfo(SimStruct *S, 
                                      int_T port,
                                      const DimsInfo_T *dimsInfo)
{
    const boolean_T inputConnected = (boolean_T)ssGetInputPortConnected(S,INPORT_R);

    if(!ssSetInputPortDimensionInfo(S, port, dimsInfo)) return;
    ErrorIfInputIsNot1or2D(S, port);
    ErrorIfInputIsFullMatrix(S, port);

    /* Error out if input is a frame matrix */
    if (ssGetInputPortFrameData(S, INPORT_R) == FRAME_YES) {
        /* Note: we know that input is 2D since frame status is on: */
        const int_T numRows = dimsInfo->dims[0];
        const int_T numCols = dimsInfo->dims[1];
        if ((numRows > 1) && (numCols > 1)) {
            THROW_ERROR(S, "Multi-channel frame-based inputs not currently supported.");
        }
    }

    /* Check and set output ports */
    if (inputConnected) {
        const FcnType   ftype         = GET_FCN_TYPE_ENUM(S);
        const boolean_T kOutputExists = ((ftype != fcnA) && (ftype != fcnAandP));

        /* Error handling of K output port dimensions for degenerate (scalar) input case */
        if ((kOutputExists) && (dimsInfo->width <= 1)) {
            THROW_ERROR(S, "Cannot output reflection coefficients K when the input is a scalar. "
                           "Please change the output mode of the block.");
        }

        /* Check and set A and/or K output ports */
        switch (ftype) {
        case fcnAandK:
            checkAndSetAandKOutports(S, dimsInfo);
            break;

        case fcnA:
            checkAndSetAOutport(S, dimsInfo);
            break;

        case fcnK:
            checkAndSetKOutport(S, dimsInfo);
            break;

        case fcnAandKandP:
            checkAndSetAandKOutports(S, dimsInfo);
            checkAndSetPOutport(S);
            break;
            
        case fcnAandP:
            checkAndSetAOutport(S, dimsInfo);
            checkAndSetPOutport(S);
            break;
            
        case fcnKandP:
        default:
            checkAndSetKOutport(S, dimsInfo);
            checkAndSetPOutport(S);
            break;
        }
    }
}


#define MDL_SET_OUTPUT_PORT_DIMENSION_INFO
static void mdlSetOutputPortDimensionInfo(SimStruct        *S, 
                                          int_T            port,
                                          const DimsInfo_T *dimsInfo)
{
    const boolean_T inputConnected = (boolean_T)ssGetInputPortConnected(S,INPORT_R);

    if(!ssSetOutputPortDimensionInfo(S, port, dimsInfo)) return;

    /* Only sample-based vectors (1-D or 2-D) are allowed on outputs */
    ErrorIfOutputIsNot1or2D(  S, port);
    ErrorIfOutputIsFullMatrix(S, port);

    /* Impossible to set input port dimensions, since input could be */
    /* 1-D or 2-D.  Instead check other output ports if they exist.  */
    /* Also, allow for 2-D outputs during backpropagation (can't set */
    /* the output dimensions since could be 1-D or 2-D here...)      */
    if (inputConnected) {
        const FcnType ftype = GET_FCN_TYPE_ENUM(S);

        /* Check validity of A and K output port widths if both specified */
        if ((ftype == fcnAandK) || (ftype == fcnAandKandP)) {
            const int_T OUTPORT_A = 0;
            const int_T OUTPORT_K = 1;

            if (port == OUTPORT_A) {
                /* A was just set above -> check outport K */
                const int_T widthOutK = ssGetOutputPortWidth(S, OUTPORT_K);
                if (widthOutK != DYNAMICALLY_SIZED) {
                    const int_T width = (dimsInfo->width) - 1;
                    if (width != widthOutK) {
                        THROW_ERROR(S, "Width of K output must be one less than width of A output.");
                    }
                }
            } else {
                /* K was just set above -> check outport A */
                const int_T widthOutA = ssGetOutputPortWidth(S, OUTPORT_A);
                if (widthOutA != DYNAMICALLY_SIZED) {
                    const int_T width = (dimsInfo->width) + 1;
                    if (width != widthOutA) {
                        THROW_ERROR(S, "Width of K output must be one less than width of A output.");
                    }
                }
            }
        }

        /* Check validity of P output port width if specified and if set */
        if ((ftype == fcnAandP) || (ftype == fcnKandP) || (ftype == fcnAandKandP)) {
            const int_T OUTPORT_P = getPOutputIndex(S);
            const int_T widthOutP = ssGetOutputPortWidth(S, OUTPORT_P);
            if (widthOutP != DYNAMICALLY_SIZED) {
                if (widthOutP != 1) THROW_ERROR(S, "P output must be a scalar.");
            }
        }
    }
}


/* Private function setAllInOutComplexities:
 *
 * Set all I/O port complexities the same (except for P output port)
 */
static void setAllInOutComplexities(SimStruct *S, CSignal_T portComplex)
{
    int_T outPortIdx;
    int_T numOutPortsToSet;

    if (getPOutputIndex(S) < 0) {
        /* No P output port -> need to set all output port complexities */
        numOutPortsToSet = ssGetNumOutputPorts(S);
    } else {
        /* P output port complexity already set to REAL in mdlInitializeSizes */
        numOutPortsToSet = ssGetNumOutputPorts(S) - 1;
    }

    ssSetInputPortComplexSignal(S, INPORT_R, portComplex);
    for (outPortIdx=0; outPortIdx<numOutPortsToSet; outPortIdx++) {
        ssSetOutputPortComplexSignal(S, outPortIdx, portComplex);
    }

}


#define MDL_SET_INPUT_PORT_COMPLEX_SIGNAL
static void mdlSetInputPortComplexSignal(SimStruct *S,
                                         int_T      portIdx,
                                         CSignal_T  iPortComplexSignal)
{
    setAllInOutComplexities(S, iPortComplexSignal);
}


#define MDL_SET_OUTPUT_PORT_COMPLEX_SIGNAL
static void mdlSetOutputPortComplexSignal(SimStruct *S,
                                          int_T      portIdx, 
                                          CSignal_T  oPortComplexSignal)
{
    setAllInOutComplexities(S, oPortComplexSignal);
}


#define MDL_SET_WORK_WIDTHS
static void mdlSetWorkWidths(SimStruct *S)
{
    const FcnType   ftype         = GET_FCN_TYPE_ENUM(S);
    const boolean_T AoutputExists = (ftype != fcnK) && (ftype != fcnKandP);

    if (AoutputExists) {
        /* A output port is available -> do not need DWork buffer space */
        if(!ssSetNumDWork(S, 0)) return;
    }
    else {
        /* A output port is not available -> need DWork buffer space */
        if(!ssSetNumDWork(      S, 1)) return;
        ssSetDWorkWidth(        S, ACOEF_DWORK_IDX, ssGetInputPortWidth(S,INPORT_R));
        ssSetDWorkComplexSignal(S, ACOEF_DWORK_IDX, ssGetInputPortComplexSignal(S,INPORT_R));
    }
}

#endif /* MATLAB_MEX_FILE */

#include "dsp_trailer.c"

/* [EOF] sdsplevdurb2.c */
