/*
 * File : sfun_multirate.c
 * Abstract:
 *      Multiple port, multi-rate S-function example. This S-function accepts
 *	two input signals and produces three outputs:
 *
 *				        .------------.
 *	   ---- enable signal    -----> |            |--- s / k1 ---->
 *	   ---- source signal, s -----> | S-function |--- s / k2 ---->
 *					|            |--- s / k3 ---->
 *				        `------------'
 *
 *	Three positive non-zero integer parameters must be supplied, k1, k2, k3.
 *	The source signal must be a multiple of the enable signal (otherwise
 *	the block won't function correctly in multitasking mode since we
 *	run the fastest tasks first). All signals are scalar. In addition
 *	the source signal must be discrete without an offset.
 *
 *	When enabled (i.e. enable signal is greater than zero), the output
 *	is the source signal decimated by the respective constant (k1, k2, or
 *	k3). If the constant is 1 for a given output port, then the output
 *	signal is equal to the source signal.
 *
 *	This block is designed to be used with fixed-step solvers (solver mode
 *	multitasking), thus there is no finding of the zero crossing location
 *	for the enable signal.
 *
 *  See simulink/src/sfuntmpl.doc
 *
 *  Copyright (c) 1990-1998 by The MathWorks, Inc. All Rights Reserved.
 *  $Revision: 1.7 $
 */


/*
 * You must specify the S_FUNCTION_NAME as the name of your S-function.
 */

#define S_FUNCTION_NAME  sfun_multirate
#define S_FUNCTION_LEVEL 2

/*
 * Need to include simstruc.h for the definition of the SimStruct and
 * its associated macro definitions.
 */
#include <string.h>
#include <math.h>
#include "tmwtypes.h"
#include "simstruc.h"

/*================*
 * Build checking *
 *================*/
#if !defined(MATLAB_MEX_FILE)
/*
 * This file cannot be used directly with the Real-Time Workshop. However,
 * this S-function does work with the Real-Time Workshop via
 * the Target Language Compiler technology. See 
 * matlabroot/toolbox/blocks/tlc_c/sfun_multirate.tlc   for the C version
 * matlabroot/toolbox/blocks/tlc_ada/sfun_multirate.tlc for the Ada version
 */
# error This_file_can_be_used_only_during_simulation_inside_Simulink
#endif

/*=========*
 * Defines *
 *=========*/

#define K1_IDX 0
#define K1_PARAM(S) ssGetSFcnParam(S,K1_IDX)

#define K2_IDX 1
#define K2_PARAM(S) ssGetSFcnParam(S,K2_IDX)

#define K3_IDX 2
#define K3_PARAM(S) ssGetSFcnParam(S,K3_IDX)

#define NKPARAMS 3

#define OPTIONAL_TS_IDX 3
#define OPTIONAL_TS_PARAM(S) ssGetSFcnParam(S,OPTIONAL_TS_IDX)

#define NTOTALPARAMS 4


#define NINPUTS  2
#define NOUTPUTS 3


#define ENABLE_IPORT 0   /* port 1 */
#define SIGNAL_IPORT 1   /* port 2 */


/*====================*
 * S-function methods *
 *====================*/

#define MDL_CHECK_PARAMETERS
#if defined(MDL_CHECK_PARAMETERS) && defined(MATLAB_MEX_FILE)
  /* Function: mdlCheckParameters =============================================
   * Abstract:
   *    Validate our parameters to verify they are okay.
   */
  static void mdlCheckParameters(SimStruct *S)
  {
      int  i;
      bool badParam = false;

      /* All parameters must be positive non-zero integers */
      for (i = 0; i < ssGetSFcnParamsCount(S); i++) {
          const mxArray *m = ssGetSFcnParam(S,i);

          if (mxGetNumberOfElements(m) != 1 ||
              !mxIsNumeric(m) || !mxIsDouble(m) || mxIsLogical(m) ||
              mxIsComplex(m) || mxIsSparse(m) || !mxIsFinite(mxGetPr(m)[0])) {
              badParam = true;
              break;
          }
          if (i < NKPARAMS) {
              int ival;
              if (((ival=(int_T)mxGetPr(m)[0]) <= 0) ||
                  (real_T)ival != mxGetPr(m)[0]) {
                  badParam = true;
                  break;
              }
          } else if (mxGetPr(m)[0] <= 0.0) {
              badParam = true;
              break;
          }
      }

      if (badParam) {
          ssSetErrorStatus(S,"All parameters must be positive non-zero "
                           "values. First three must be integers and the "
                           "optional fourth must be a discrete sample "
                           "time");
          return;
      }
  }
#endif /* MDL_CHECK_PARAMETERS */



/* Function: mdlInitializeSizes ===============================================
 * Abstract:
 *    Call mdlCheckParameters to verify that the parameters are okay,
 *    then setup sizes of the various vectors.
 *
 *    We specify 2 input and 3 output ports with inherited port based sample 
 *    times.
 */
static void mdlInitializeSizes(SimStruct *S)
{
    int_T  i;
    real_T ts;

    /* See sfuntmpl.doc for more details on the macros below */

    /* Set number of expected parameters */
    if (ssGetSFcnParamsCount(S) == NKPARAMS ||
        ssGetSFcnParamsCount(S) == NTOTALPARAMS) {
        ssSetNumSFcnParams(S, ssGetSFcnParamsCount(S));
    } else {
        ssSetNumSFcnParams(S, NKPARAMS);
    }
#if defined(MATLAB_MEX_FILE)
    if (ssGetNumSFcnParams(S) == ssGetSFcnParamsCount(S)) {
        mdlCheckParameters(S);
        if (ssGetErrorStatus(S) != NULL) {
            return;
        }
    } else {
        return; /* Parameter mismatch will be reported by Simulink */
    }
#endif

    /* Parameters can't be tuned */
    ssSetSFcnParamNotTunable(S,K1_IDX);
    ssSetSFcnParamNotTunable(S,K2_IDX);
    ssSetSFcnParamNotTunable(S,K3_IDX);

    /* Load ts for input and output ports */
    if (ssGetNumSFcnParams(S) == NTOTALPARAMS) {
        ssSetSFcnParamNotTunable(S,OPTIONAL_TS_IDX);
        ts = mxGetPr(OPTIONAL_TS_PARAM(S))[0];
    } else {
        ts = INHERITED_SAMPLE_TIME;
    }

    ssSetNumSampleTimes(S, PORT_BASED_SAMPLE_TIMES);

    /* Two inputs */
    if (!ssSetNumInputPorts(S, NINPUTS)) return;

    for (i = 0; i < NINPUTS; i++) {
        ssSetInputPortWidth(S, i, 1);
        ssSetInputPortDirectFeedThrough(S, i, 1);
        ssSetInputPortSampleTime(S, i, ts);
        ssSetInputPortOffsetTime(S, i, 0.0);
        ssSetInputPortOverWritable(S, i, 0); /* Output is decimated! */
    }

    /*
     * We are always looking at the enable input in the correct task so we can
     * optimize away this entry from the block I/O.
     */
    ssSetInputPortReusable(S, ENABLE_IPORT, 1);

    /*
     * Input port must be test pointed since we are looking at 
     * the input in different tasks.
     */
    ssSetInputPortReusable(S, SIGNAL_IPORT, 0);   

    /* Three outputs */
    if (!ssSetNumOutputPorts(S, NOUTPUTS)) return;
    for (i = 0; i < NOUTPUTS; i++) {
        ssSetOutputPortWidth(S, i, 1);
        ssSetOutputPortReusable(S, i, 1); /* Need to be persistent since the
                                              since we don't update the
                                              outputs at every sample hit
                                              for this block */
        if (ts == INHERITED_SAMPLE_TIME) {
            ssSetOutputPortSampleTime(S, i, ts);
        } else {
            ssSetOutputPortSampleTime(S, i, ts*mxGetPr(ssGetSFcnParam(S,i))[0]);
        }
        ssSetOutputPortOffsetTime(S, i, 0.0);
    }

    ssSetNumIWork(S, 1);

    /* Take care when specifying exception free code - see sfuntmpl.doc */
    ssSetOptions(S, (SS_OPTION_EXCEPTION_FREE_CODE));

} /* end mdlInitializeSizes */



#if defined(MATLAB_MEX_FILE)
/* Function CheckInputSampleTimes ==============================================
 * Abstract:
 *	We know both input sample times, now verify that the make sense
 *	(see top of file).
 */
static void CheckInputSampleTimes(SimStruct *S)
{
    real_T enableTs = ssGetInputPortSampleTime(S, ENABLE_IPORT);

    if (enableTs != CONTINUOUS_SAMPLE_TIME) {
        /* signal must be a multiple of the enable */
        real_T signalTs = ssGetInputPortSampleTime(S, SIGNAL_IPORT);
        real_T k        = floor(signalTs/enableTs+0.5);
        
        if (k < 1.0 ||
            fabs(k - signalTs/enableTs) > mxGetEps()*128.0) {
            ssSetErrorStatus(S, "Sample time of source signal (port 2) "
                             "must be a positive multiple of enable "
                             "(port 1) signal");
            return;
        }
    }
} /* end CheckInputSampleTimes */
#endif



#define MDL_SET_INPUT_PORT_SAMPLE_TIME
#if defined(MDL_SET_INPUT_PORT_SAMPLE_TIME) && defined(MATLAB_MEX_FILE)
/* Function: mdlSetInputPortSampleTime =========================================
 * Abstract:
 *	When asked by Simulink, set the sample time of the enable or
 *	signal port. If we know both sample times, also set the output
 *	port sample times.
 */
static void mdlSetInputPortSampleTime(SimStruct *S,
                                      int_T     portIdx,
                                      real_T    sampleTime,
                                      real_T    offsetTime)
{

    /*************************************
     * Check for valid input sample time *
     *************************************/

    if (portIdx == ENABLE_IPORT) {
        /*
         * Enable signal port
         */
        if (offsetTime != 0.0) {
            ssSetErrorStatus(S,"Enable (port 1) signal must be fed by a signal "
                             "with a sample time that doesn't have an offset");
            return;
        }

    } else {
        /*
         * source signal port (i.e. signal to be decimated). The sample time
         * of this signal must be discrete (constant equates to inf,
         * continuous equates to zero).
         */

        if (sampleTime <= 0.0) {
            ssSetErrorStatus(S,"Source signal (port 2) must be fed by a "
                             "discrete signal");
            return;
        }

        if (offsetTime != 0.0) {
            ssSetErrorStatus(S,"Source signal (port 2) must be fed by a siganl "
                             "with a sample time that doesn't have an offset");
            return;
        }

        /*
         * Set output port sample times. Note, we are assured that all output
         * port sample times are unknown since when the output port sample
         * time routine is called, all sample times will be set.
         *
         * (note offset is already zero for inherited sample time)
         */

        {
            int_T  i;
            real_T k[NOUTPUTS];

            k[0] = mxGetPr(K1_PARAM(S))[0];
            k[1] = mxGetPr(K2_PARAM(S))[0];
            k[2] = mxGetPr(K3_PARAM(S))[0];

            for (i = 0; i < NOUTPUTS; i++) {
                ssSetOutputPortSampleTime(S, i, sampleTime*k[i]);
                ssSetOutputPortOffsetTime(S, i, 0.0);
            }
        }
    }

    ssSetInputPortSampleTime(S, portIdx, sampleTime);
    ssSetInputPortOffsetTime(S, portIdx, offsetTime);

    if (ssGetInputPortSampleTime(S, (portIdx+1) % 2) != INHERITED_SAMPLE_TIME) {
        CheckInputSampleTimes(S);
    }

} /* end mdlSetInputPortSampleTime */
#endif



#define MDL_SET_OUTPUT_PORT_SAMPLE_TIME
#if defined(MDL_SET_OUTPUT_PORT_SAMPLE_TIME) && defined(MATLAB_MEX_FILE)
/* Function: mdlSetOutputPortSampleTime ========================================
 * Abstract:
 *	When asked by Simulink, set the sample time of the specified output
 *	port. This occurs when back propagating sample times (see sfuntmpl.doc).
 *
 *	When called to set one sample time, we set them all. 
 *	We also verify that the 
 */
static void mdlSetOutputPortSampleTime(SimStruct *S,
                                      int_T     portIdx,
                                      real_T    sampleTime,
                                      real_T    offsetTime)
{

    /***********************************************************************
     * Output sample time must be discrete and we don't support discrete " *
     * offsets.								   *
     ***********************************************************************/

    if (sampleTime <= 0.0 || offsetTime != 0.0) {
        ssSetErrorStatus(S,"This block must back-inherit to the output ports "
                           "a discrete (zero-offset) signal");
        return;
    }

    /*******************************************************************
     * Set all output and input sample times. Note enable port is only *
     * port which may have a known sample time.                        *
     *******************************************************************/
    {
        real_T k[NOUTPUTS];
        real_T signalTs;
        int_T  i;

        k[0] = mxGetPr(K1_PARAM(S))[0];
        k[1] = mxGetPr(K2_PARAM(S))[0];
        k[2] = mxGetPr(K3_PARAM(S))[0];

        signalTs = sampleTime/k[portIdx];

        for (i = 0; i < NOUTPUTS; i++) {
            ssSetOutputPortSampleTime(S, i, signalTs*k[i]);
            ssSetOutputPortOffsetTime(S, i, 0.0);
        }

        ssSetInputPortSampleTime(S, SIGNAL_IPORT, signalTs);
        ssSetInputPortOffsetTime(S, SIGNAL_IPORT, offsetTime);

        if (ssGetInputPortSampleTime(S, ENABLE_IPORT) == INHERITED_SAMPLE_TIME){
            ssSetInputPortSampleTime(S, ENABLE_IPORT, signalTs);
        } else {
            CheckInputSampleTimes(S);
        }
    }
    

} /* end mdlSetOutputPortSampleTime */
#endif


/* Function: mdlInitializeSampleTimes =========================================
 * Abstract:
 *      Port based sample times have already been configured, therefore this
 *	method doesn't need to perform any action (you can check the
 *	current port sample times).
 */
static void mdlInitializeSampleTimes(SimStruct *S)
{
#if 0   /* set to 1 to see port sample times */
    const char_T *bpath = ssGetPath(S);
    int_T        i;

    for (i = 0; i < NINPUTS; i++) {
        mexPrintf("%s input port %d sample time = [%g, %g]\n", bpath, i,
                  ssGetInputPortSampleTime(S,i),
                  ssGetInputPortOffsetTime(S,i));
    }

    for (i = 0; i < NOUTPUTS; i++) {
        mexPrintf("%s output port %d sample time = [%g, %g]\n", bpath, i,
                  ssGetOutputPortSampleTime(S,i),
                  ssGetOutputPortOffsetTime(S,i));
    }
#endif

} /* end mdlInitializeSampleTimes */



/* Function: mdlOutputs =======================================================
 * Abstract:
 *
 *      y1 = "sum" * "gain" where sum operation is defined by a list of
 *           '+' and '-' characters.
 *      y2 = "modified" average of the input signals (i.e. sum/nInputPorts).
 */
static void mdlOutputs(SimStruct *S, int_T tid)
{
    InputRealPtrsType enablePtrs;
    int               *enabled = ssGetIWork(S);

    if (ssGetInputPortSampleTime(S,ENABLE_IPORT)==CONTINUOUS_SAMPLE_TIME &&
        ssGetInputPortOffsetTime(S,ENABLE_IPORT)==0.0) {
        if (ssIsMajorTimeStep(S) && ssIsContinuousTask(S,tid)) {
            enablePtrs = ssGetInputPortRealSignalPtrs(S,ENABLE_IPORT);
            *enabled = (*enablePtrs[0] > 0.0);
        }
    } else {
        int enableTid = ssGetInputPortSampleTimeIndex(S,ENABLE_IPORT);
        if (ssIsSampleHit(S, enableTid, tid)) {
            enablePtrs = ssGetInputPortRealSignalPtrs(S,ENABLE_IPORT);
            *enabled = (*enablePtrs[0] > 0.0);
        }
    }

    if (*enabled) {
        InputRealPtrsType uPtrs  = ssGetInputPortRealSignalPtrs(S,SIGNAL_IPORT);
        real_T            signal = *uPtrs[0];
        int               i;

        for (i = 0; i < NOUTPUTS; i++) {
            if (ssIsSampleHit(S, ssGetOutputPortSampleTimeIndex(S,i), tid)) {
                real_T *y = ssGetOutputPortRealSignal(S,i);
                *y = signal;
            }
        }
    }

} /* end mdlOutputs */



/* Function: mdlTerminate =====================================================
 * Abstract:
 *      Free the user data.
 */
static void mdlTerminate(SimStruct *S)
{
}



#if defined(MATLAB_MEX_FILE)
#define MDL_RTW
/* Function: mdlRTW ===========================================================
 * Abstract:
 *	Since we've declared all are parameters as non-tunable, we need
 *	only provide this routine so that they aren't written to the model.rtw
 *	file. The values of the parameters are implicitly encoded in the
 *	sample times.
 */
static void mdlRTW(SimStruct *S)
{
}
#endif /* MDL_RTW */


/*=============================*
 * Required S-function trailer *
 *=============================*/

#ifdef  MATLAB_MEX_FILE    /* Is this file being compiled as a MEX-file? */
#include "simulink.c"      /* MEX-file interface mechanism */
#else
#include "cg_sfun.h"       /* Code generation registration function */
#endif
