#define USE_FC_LEN_T
#define STRICT_R_HEADERS
#include "codegen.h"
#include <stdlib.h>
#include <string.h>

/**
 * Generates a random string of 4 alphanumeric characters.
 *
 * The returned string is null-terminated and consists of characters
 * from the set [0-9, A-Z, a-z]. The pointer returned by this function
 * points to a global buffer, `_gbuf.s`, whose contents may be overwritten
 * by subsequent calls to this function or other functions that use `_gbuf`.
 *
 * @return A pointer to the global buffer containing the random string.
 */
char * genRandomChar(void) {
  const char digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
  // 62 digits
  for (int j = 0; j < 4; j++) {
    _gbuf.s[j] = digits[(int)floor(Rf_runif(0, 62))];
  }
  _gbuf.s[4] = 0;
  return _gbuf.s;
}

SEXP _rxode2parse_rxFunctionName;
SEXP _rxode2parse_functionName;
SEXP _rxode2parse_functionType;
SEXP _rxode2parse_functionPackageName;
SEXP _rxode2parse_functionPackageFunction;
SEXP _rxode2parse_functionArgMin;
SEXP _rxode2parse_functionArgMax;
SEXP _rxode2parse_functionThreadSafe;

SEXP _rxode2parse_funName;
SEXP _rxode2parse_funNameInt;

SEXP _rxode2parse_packages;

#undef df

SEXP getRxode2ParseDfBuiltin(void);

// Event ("jump") sensitivities: per-model C assignment lines for the dLag/dF
// functions, generated in R (.rxEventSensCLines) and passed as arguments to
// _rxode2_codegen() (set at the top of that function).  NULL/empty -> the
// dLag/dF functions emit only the preamble and leave the buffer untouched.
// Cleared after each codegen so they never leak to the next model.
char *_es_dLagCode = NULL;
char *_es_dFCode = NULL;
char *_es_dRateCode = NULL;
char *_es_dDurCode = NULL;
char *_es_d2FCode = NULL;
char *_es_d2LagCode = NULL;
char *_es_d2RateCode = NULL;
char *_es_d2DurCode = NULL;
char *_es_d3FCode = NULL;
char *_es_dFQCode = NULL;
char *_es_dLagJacCode = NULL;
char *_es_dLagQCode = NULL;
char *_es_dDurQCode = NULL;

static void _es_freeCode(void) {
  if (_es_dLagCode != NULL) { free(_es_dLagCode); _es_dLagCode = NULL; }
  if (_es_dFCode != NULL) { free(_es_dFCode); _es_dFCode = NULL; }
  if (_es_dRateCode != NULL) { free(_es_dRateCode); _es_dRateCode = NULL; }
  if (_es_dDurCode != NULL) { free(_es_dDurCode); _es_dDurCode = NULL; }
  if (_es_d2FCode != NULL) { free(_es_d2FCode); _es_d2FCode = NULL; }
  if (_es_d2LagCode != NULL) { free(_es_d2LagCode); _es_d2LagCode = NULL; }
  if (_es_d2RateCode != NULL) { free(_es_d2RateCode); _es_d2RateCode = NULL; }
  if (_es_d2DurCode != NULL) { free(_es_d2DurCode); _es_d2DurCode = NULL; }
  if (_es_d3FCode != NULL) { free(_es_d3FCode); _es_d3FCode = NULL; }
  if (_es_dFQCode != NULL) { free(_es_dFQCode); _es_dFQCode = NULL; }
  if (_es_dLagJacCode != NULL) { free(_es_dLagJacCode); _es_dLagJacCode = NULL; }
  if (_es_dLagQCode != NULL) { free(_es_dLagQCode); _es_dLagQCode = NULL; }
  if (_es_dDurQCode != NULL) { free(_es_dDurQCode); _es_dDurQCode = NULL; }
}

// Persistent copy of an R string (malloc, not R_alloc: must survive past the
// codegen .Call setup).  Portable replacement for strdup (which is POSIX-only).
static char *_es_dup(SEXP s) {
  if (TYPEOF(s) != STRSXP || Rf_length(s) != 1) return NULL;
  const char *src = CHAR(STRING_ELT(s, 0));
  size_t n = strlen(src) + 1;
  char *dst = (char *) malloc(n);
  if (dst != NULL) memcpy(dst, src, n);
  return dst;
}

int _rxode2parse_protected = 0;
void _rxode2parse_assignTranslationBuiltin(void) {
  SEXP df = getRxode2ParseDfBuiltin();
  _rxode2parse_funName = PROTECT(VECTOR_ELT(df, 0)); _rxode2parse_protected++;
  _rxode2parse_funNameInt = PROTECT(VECTOR_ELT(df, 1)); _rxode2parse_protected++;
}

void _rxode2parse_assignTranslation(SEXP df) {
  _rxode2parse_unprotect();
  _rxode2parse_rxFunctionName = PROTECT(VECTOR_ELT(df, 0)); _rxode2parse_protected++;
  _rxode2parse_functionName = PROTECT(VECTOR_ELT(df, 1)); _rxode2parse_protected++;
  _rxode2parse_functionType = PROTECT(VECTOR_ELT(df, 2)); _rxode2parse_protected++;
  _rxode2parse_functionPackageName = PROTECT(VECTOR_ELT(df, 3)); _rxode2parse_protected++;
  _rxode2parse_functionPackageFunction = PROTECT(VECTOR_ELT(df, 4)); _rxode2parse_protected++;
  _rxode2parse_functionArgMin = PROTECT(VECTOR_ELT(df, 5)); _rxode2parse_protected++;
  _rxode2parse_functionArgMax = PROTECT(VECTOR_ELT(df, 6)); _rxode2parse_protected++;
  _rxode2parse_functionThreadSafe = PROTECT(VECTOR_ELT(df, 7));_rxode2parse_protected++;
  _rxode2parse_assignTranslationBuiltin();
}

void _rxode2parse_unprotect(void) {
  if (_rxode2parse_protected) UNPROTECT(_rxode2parse_protected);
  _rxode2parse_protected = 0;
}

#include "codegen2.h"

SEXP _rxode2_rxQs(SEXP);
SEXP _rxode2_rxQr(SEXP);
SEXP getRxode2ParseDf(void);
SEXP getRxode2ParseGetPointerAssignment(void);

static FILE *fpIO;

/* when prnt_vars() is called, user defines the behavior in "case" */
void prnt_vars(int scenario, int lhs, const char *pre_str, const char *post_str, int show_ode) {
  int i, j;
  char *buf;
  sAppend(&sbOut, "%s", pre_str);
  if (scenario == print_double || scenario == print_void){
    printDdtDefine(show_ode, scenario);
    printPDStateVar(show_ode, scenario);
  }
  for (i=0, j=0; i<NV; i++) {
    if (shouldSkipPrintLhsI(scenario, lhs, i)) continue;
    buf = tb.ss.line[i];
    switch(scenario) {
    case print_paramLags: // Case 5 is for using #define lag_var(x)
      printParamLags(buf, &j, i);
      break;
    case print_lhsLags: // Case 4 is for using #define lag_var(x)
      printLhsLag(buf, &j, i);
      break;
    case print_lastLhsValue: // Case 3 is for using the last lhs value
      printLastLhsValue(buf, &j);
      break;
    case print_double:   // Case 0 is for declaring the variables
      printDoubleDeclaration(buf);
      break;
    case print_void: // Case 2 is for suppressing all the warnings for the variables by using (void)var;
      // See https://stackoverflow.com/questions/1486904/how-do-i-best-silence-a-warning-about-unused-variables
      printVoidDeclaration(buf);
      break;
    case print_populateParameters:
      // Case 1 is for declaring the par_ptr.
      printPopulateParameters(buf, &j, &i);
      break;
    case print_simeps:
      // Case 15 is for declaring eps the sync parameters
      printSimEps(buf, &j);
      break;
    case print_simeta:
      // Case 16 is for declaring eta the sync parameters
      printSimEta(buf, &j);
      break;
    default: break;
    }
  }
  sAppend(&sbOut, "%s", post_str);
}



void print_aux_info(char *model, const char *prefix, const char *libname, const char *pMd5, const char *timeId,
                    const char *libname2){
  sbuf bufw;
  sNull(&bufw);
  sIniTo(&bufw, 1024);
  /* char bufw[1024]; */
  printCModelVars(prefix);

  sAppend(&sbOut,"extern void %sdydt_lsoda(int *neq, double *t, double *A, double *DADT)\n{\n  %sdydt(neq, *t, A, DADT);\n}\n", prefix, prefix);
  sAppend(&sbOut, "extern int %sdydt_liblsoda(double __t, double *y, double *ydot, void *data)\n{\n  int *neq = (int*)(data);\n  %sdydt(neq, __t, y, ydot);\n  return(0);\n}\n",
          prefix,prefix);
  sAppend(&sbOut,"extern void %scalc_jac_lsoda(int *neq, double *t, double *A,int *ml, int *mu, double *JAC, int *nrowpd){\n  // Update all covariate parameters\n  %scalc_jac(neq, *t, A, JAC, *nrowpd);\n}\n",
          prefix, prefix);

  printRInit(libname, libname2, prefix);

  sFree(&bufw);
}

extern SEXP getRxode2ParseDf(void);

void codegen(char *model, int show_ode, const char *prefix, const char *libname, const char *pMd5, const char *timeId, const char *libname2) {
  _rxode2parse_assignTranslation(getRxode2ParseDf());
  _rxode2parse_packages = getRxode2ParseGetPointerAssignment();
  if (show_ode == ode_printaux) {
    print_aux_info(model, prefix, libname, pMd5, timeId, libname2);
  } else {
    int i, j;
    char *buf;
    if (show_ode == ode_dydt){
      const char *extra = "";
      if (strncmp("rx_", libname, 3) != 0) extra = libname;
      writeHeader(md5, extra);
      for (int i = Rf_length(_rxode2parse_functionName); i--;) {
        sAppend(&sbOut, "#define %s _rx%s%s%ld_%s_%s\n",
                R_CHAR(STRING_ELT(_rxode2parse_functionName, i)),
                extra, md5, __timeId++,
                R_CHAR(STRING_ELT(_rxode2parse_functionName, i)),
                genRandomChar());
      }
      sAppendN(&sbOut,"#include <rxode2_model_shared.h>\n",33);
      int mx = maxSumProdN;
      if (SumProdLD > mx) mx = SumProdLD;
      sAppend(&sbOut,"#define __MAX_PROD__ %d\n", mx);
      int baseSize = tb.statei-tb.nExtra+extraCmt - tb.sensi;
      if (tb.sensi > 0){
        // This converts CMT to user CMT in model
        // Hence CMT = 4 could translate in data to 44 with sensi=10
        // Then cmt=44 translates back to cmt-10 or 4.
        // This makes the sensitivity equations insensitive to CMT changes that occur in FOCEi
        sAppend(&sbOut,"#define _CMT ((fabs(CMT)<=%d) ? CMT : ((CMT<0) ? CMT+%d: CMT-%d))\n",
                baseSize, tb.sensi, tb.sensi);
      } else {
        sAppendN(&sbOut,"#define _CMT CMT\n", 17);
      }
      // Now define lhs lags
      prnt_vars(print_lhsLags, 1, "", "", 13);
      // And covariate/parameter lags
      prnt_vars(print_paramLags, 1, "", "", 15);
      // Add sync PP define
      prnt_vars(print_simeps, 1, "#define _SYNC_simeps_ for (int _svari=_solveData->neps; _svari--;){", "}\n", 15);
      prnt_vars(print_simeta, 1, "#define _SYNC_simeta_ for (int _ovari=_solveData->neta; _ovari--;){", "}\n", 16);
      writeBody0();
      for (int i = Rf_length(_rxode2parse_functionName); i--;) {
        const char *pkg = R_CHAR(STRING_ELT(_rxode2parse_functionPackageName, i));
        if (strcmp(pkg, "rxode2") && strcmp(pkg, "rxode2ll")) {
          sAppend(&sbOut, "%s %s;\n",
                  R_CHAR(STRING_ELT(_rxode2parse_functionType, i)),
                  R_CHAR(STRING_ELT(_rxode2parse_functionName, i)));

        }
      }
      sAppendN(&sbOut,"#include \"extraC.h\"\n", 20);
      writeBody1();
      for (int i = Rf_length(_rxode2parse_functionName); i--;) {
        sAppend(&sbOut,"  %s = (%s) R_GetCCallable(\"%s\", \"%s\");\n",
                R_CHAR(STRING_ELT(_rxode2parse_functionName, i)),
                R_CHAR(STRING_ELT(_rxode2parse_functionType, i)),
                R_CHAR(STRING_ELT(_rxode2parse_functionPackageName, i)),
                R_CHAR(STRING_ELT(_rxode2parse_functionPackageFunction, i)));
      }
      writeBody2();
      for (int i = Rf_length(_rxode2parse_packages); i--;) {
        const char* cur = R_CHAR(STRING_ELT(_rxode2parse_packages, i));
        // Do not cache the callable pointer statically: the package DLL can be
        // reloaded (e.g. by pkgload::load_all inside devtools::test), which
        // changes the callable address.  A stale static pointer would write to
        // the old rx_global instead of the current one.  R_GetCCallable is
        // fast enough that the lookup cost here is negligible.
        sAppend(&sbOut,"    rxode2_assignFuns2_t %s_assignFuns2 = (rxode2_assignFuns2_t)(R_GetCCallable(\"%s\", \"_%s_assignFuns2\"));\n",
                cur, cur, cur);
        sAppend(&sbOut,"    %s_assignFuns2(rx, op, f, lag, rate, dur, mtime, me, indf, gettime, timeindex, handleEvid, getdur);\n",
                cur);
      }
      writeBody3();
      sAppend(&sbOut, "extern void  %sode_solver_solvedata (rx_solve *solve){\n  _solveData = solve;\n}\n",prefix);
      sAppend(&sbOut, "extern rx_solve *%sode_solver_get_solvedata(void){\n  return _solveData;\n}\n", prefix);
      sAppend(&sbOut, "SEXP %smodel_vars(void);\n", prefix);
      sAppendN(&sbOut,"\n", 1);

      sAppend(&sbOut, "\n// prj-specific differential eqns\n"
              "#if defined(__GNUC__) || defined(__clang__)\n"
              "__attribute__((hot))\n"
              "#endif\nvoid ");
      sAppend(&sbOut, "%sdydt(int *_neq, double __t, double * __restrict__ __zzStateVar__, double * __restrict__ __DDtStateVar__)\n{\n  int _itwhile = 0;\n  (void)_itwhile;\n  int _cSub = _neq[1];\n  double t = __t + _solveData->subjects[_neq[1]].curShift;\n  (void)t;\n  rx_solving_options_ind *_ind = &(_solveData->subjects[_cSub]);\n  _setThreadInd(_cSub);\n  _ind->_rxFlag=1;\n  int _wasAtEventTime = _ind->_atEventTime; _ind->_atEventTime = 0;\n  (void)_wasAtEventTime;\n", prefix);
    } else if (show_ode == ode_jac){
      sAppend(&sbOut, "// Jacobian derived vars\nvoid %scalc_jac(int *_neq, double __t, double *__zzStateVar__, double *__PDStateVar__, unsigned int __NROWPD__) {\n  int _itwhile = 0;\n  (void)_itwhile;\n    int _cSub=_neq[1];\n  double t = __t + _solveData->subjects[_neq[1]].curShift;\n  (void)t;\n  rx_solving_options_ind *_ind = &(_solveData->subjects[_cSub]);\n  _setThreadInd(_cSub);\n  _ind->_rxFlag=2;\n", prefix);
    } else if (show_ode == ode_ini){
      sAppend(&sbOut,  "// Functional based initial conditions.\nvoid %sinis(int _cSub, double *__zzStateVar__){\n  int _itwhile = 0;\n  (void)_itwhile;\n  \n  rx_solving_options_ind *_ind = &(_solveData->subjects[_cSub]);\n  _setThreadInd(_cSub);\n _ind->_rxFlag=3;\n", prefix);
      if (foundF0){
        sAppendN(&sbOut, "  double t=0;\n", 14);
      }
    } else if (show_ode == ode_fbio){
      if (foundF){
        int nnn = tb.de.n;
        if (tb.linCmt){
          if (tb.hasKa){
            nnn+=2;
          } else {
            nnn+=1;
          }
        }
        sAppend(&sbOut,  "// Functional based bioavailability (returns amount)\ndouble %sF(int _cSub,  int _cmt, double _amt, double __t, double *__zzStateVar__){\n  int _itwhile = 0;\n  (void)_itwhile;\n  double _f[%d];\n  (void)_f;\n  double t = __t + _solveData->subjects[_cSub].curShift;\n  (void)t;\n    rx_solving_options_ind *_ind = &(_solveData->subjects[_cSub]);\n  _setThreadInd(_cSub);\n  _ind->_rxFlag=4;\n",
                prefix, nnn);
        for (int jjj = nnn; jjj--;){
          sAppend(&sbOut, "  _f[%d]=1.0;\n",jjj);
        }
      } else {
        sAppend(&sbOut,  "// Functional based bioavailability\ndouble %sF(int _cSub,  int _cmt, double _amt, double __t, double *__zzStateVar__){\n return _amt;\n  ",
                prefix);
      }
    } else if (show_ode == ode_lag){
      if (foundLag) {
        int nnn = tb.de.n;
        if (tb.linCmt){
          if (tb.hasKa){
            nnn+=2;
          } else {
            nnn+=1;
          }
        }
        sAppend(&sbOut,  "// Functional based absorption lag\ndouble %sLag(int _cSub,  int _cmt, double __t, double *__zzStateVar__){\n  int _itwhile = 0;\n  (void)_itwhile;\n  double _alag[%d];\n  double t = __t + _solveData->subjects[_cSub].curShift;\n  (void)t;\n  rx_solving_options_ind *_ind = &(_solveData->subjects[_cSub]);\n  _setThreadInd(_cSub);\n  _ind->_rxFlag=5;\n",
                prefix, nnn);
        for (int jjj = nnn; jjj--;){
          sAppend(&sbOut, "  _alag[%d]=0.0;\n",jjj);
        }
      } else {
        sAppend(&sbOut,  "// Functional based absorption lag\ndouble %sLag(int _cSub,  int _cmt, double __t, double *__zzStateVar__){\n return __t;\n",
                prefix);
      }
    } else if (show_ode == ode_rate){
      if (foundRate){
        int nnn = tb.de.n;
        if (tb.linCmt){
          if (tb.hasKa){
            nnn+=2;
          } else {
            nnn+=1;
          }
        }
        sAppend(&sbOut,  "// Modeled zero-order rate\ndouble %sRate(int _cSub,  int _cmt, double _amt, double __t, double *__zzStateVar__){\n    int _itwhile = 0;\n  (void)_itwhile;\n  double _rate[%d];\n   double t = __t + _solveData->subjects[_cSub].curShift;\n  (void)t;\n  rx_solving_options_ind *_ind = &(_solveData->subjects[_cSub]);\n  _setThreadInd(_cSub);\n  _ind->_rxFlag=6;\n",
                prefix, nnn);
        for (int jjj = nnn; jjj--;){
          sAppend(&sbOut, "  _rate[%d]=0.0;\n",jjj);
        }
      } else {
        sAppend(&sbOut,  "// Modeled zero-order rate\ndouble %sRate(int _cSub,  int _cmt, double _amt, double __t, double *__zzStateVar__){\n return 0.0;\n",
                prefix);
      }
    } else if (show_ode == ode_dur){
      if (foundDur){
        int nnn = tb.de.n;
        if (tb.linCmt){
          if (tb.hasKa){
            nnn+=2;
          } else {
            nnn+=1;
          }
        }
        sAppend(&sbOut,  "// Modeled zero-order duration\ndouble %sDur(int _cSub,  int _cmt, double _amt, double __t, double *__zzStateVar__){\n  int _itwhile = 0;\n  (void)_itwhile;\n double _dur[%d];\n  double t = __t + _solveData->subjects[_cSub].curShift;\n  (void)t;\n    rx_solving_options_ind *_ind = &(_solveData->subjects[_cSub]);\n  _setThreadInd(_cSub);\n  _ind->_rxFlag=7;\n",
                prefix, nnn);
        for (int jjj = nnn; jjj--;){
          sAppend(&sbOut, "  _dur[%d]=0.0;\n",jjj);
        }
      } else {
        sAppend(&sbOut,  "// Modeled zero-order duration\ndouble %sDur(int _cSub,  int _cmt, double _amt, double __t, double *__zzStateVar__){\n return 0.0;\n",
                prefix);
      }
    } else if (show_ode == ode_past){
      // Non-constant delay() pre-history (past(state, tau) <- expr).  Its own
      // dedicated function/array, called by _rxDelay() for t' = t - tau < t0.
      if (foundPast){
        int nnn = tb.de.n;
        if (tb.linCmt){
          if (tb.hasKa){
            nnn+=2;
          } else {
            nnn+=1;
          }
        }
        sAppend(&sbOut,  "// Non-constant delay() pre-history\ndouble _rxPast(int _cSub, int _cmt, double __t, double *__zzStateVar__){\n  int _itwhile = 0;\n  (void)_itwhile;\n  double _past[%d];\n  (void)_past;\n  double t = __t + _solveData->subjects[_cSub].curShift;\n  (void)t;\n  rx_solving_options_ind *_ind = &(_solveData->subjects[_cSub]);\n  _setThreadInd(_cSub);\n  _ind->_rxFlag=11;\n",
                nnn);
        // default: the constant initial condition (states with no past() keep
        // the previous constant-history behavior)
        for (int jjj = nnn; jjj--;){
          sAppend(&sbOut, "  _past[%d]=_solveData->op->inits[%d];\n", jjj, jjj);
        }
      } else {
        // body left open; the catch-all close ("}\n") at the end finishes it
        sAppend(&sbOut,  "// Non-constant delay() pre-history\ndouble _rxPast(int _cSub, int _cmt, double __t, double *__zzStateVar__){\n  (void)_cSub; (void)__t; (void)__zzStateVar__;\n  return _solveData->op->inits[_cmt];\n");
      }
    } else if (show_ode == ode_mtime){
      if (nmtime){
        sAppend(&sbOut,  "// Model Times\nvoid %smtime(int _cSub, double *_mtime, double *__zzStateVar__){\n  int _itwhile = 0;\n  (void)_itwhile;\n  double t = 0;\n  rx_solving_options_ind *_ind = &(_solveData->subjects[_cSub]);\n  _setThreadInd(_cSub);\n  _ind->_rxFlag=8;\n",
                prefix);
      } else {
        sAppend(&sbOut,  "// Model Times\nvoid %smtime(int _cSub, double *_mtime, double *__zzStateVar__){\n",
                prefix);
      }
    } else if (show_ode == ode_mexp){
      sAppend(&sbOut, "// Matrix Exponential (%d)\nvoid %sME(int _cSub, double _t, double __t, double *_mat, const double *__zzStateVar__){\n  int _itwhile = 0;\n  (void)_itwhile;\n  double t = __t + _solveData->subjects[_cSub].curShift;\n  (void)t;\n  rx_solving_options_ind *_ind = &(_solveData->subjects[_cSub]);\n  _setThreadInd(_cSub);\n  _ind->_rxFlag=9;\n",
              tb.matn, prefix);
    } else if (show_ode == ode_indLinVec) {
      sAppend(&sbOut, "// Inductive linearization Matf\nvoid %sIndF(int _cSub, double _t, double __t, double *_matf){\n int _itwhile = 0;\n  (void)_itwhile;\n  double t = __t + _solveData->subjects[_cSub].curShift;\n  (void)t;\n  rx_solving_options_ind *_ind = &(_solveData->subjects[_cSub]);\n  _setThreadInd(_cSub);\n  _ind->_rxFlag=10;\n", prefix);
    } else if (ode_is_es_dcode(show_ode)) {
      // Event ("jump") sensitivities: total derivative of the modeled
      // alag / F / rate / dur wrt each first-order sensitivity parameter,
      // written into the per-subject buffer.  Shares the Lag/F preamble
      // (state/param population from __zzStateVar__); the per-(cmt,param) body
      // assignment lines come from the R-generated `_es_d*Code` global (empty
      // -> the function just leaves the buffer untouched).  No closing brace:
      // the trailing `else` of the body switch emits it.
      const char *_esDesc =
        (show_ode == ode_dLag) ? "d(alag)/dp" :
        (show_ode == ode_dF)   ? "d(F)/dp" :
        (show_ode == ode_dRate)? "d(rate)/dp" :
        (show_ode == ode_dDur) ? "d(dur)/dp" :
        (show_ode == ode_d2F)  ? "d2(F)/dp/dq" :
        (show_ode == ode_d2Lag)? "d2(alag)/dp/dq" :
        (show_ode == ode_d2Rate)?"d2(rate)/dp/dq" :
        (show_ode == ode_d2Dur)?"d2(dur)/dp/dq" :
        (show_ode == ode_d3F)  ? "d3(F)/dp/dq/dr" :
        (show_ode == ode_dFQ)  ? "d(F)/dq" :
        (show_ode == ode_dLagJac) ? "d(J[k][c])/dq" :
        (show_ode == ode_dLagQ) ? "d(alag)/dq" : "d(dur)/dq";
      const char *_esFun =
        (show_ode == ode_dLag) ? "dLag" :
        (show_ode == ode_dF)   ? "dF" :
        (show_ode == ode_dRate)? "dRate" :
        (show_ode == ode_dDur) ? "dDur" :
        (show_ode == ode_d2F)  ? "d2F" :
        (show_ode == ode_d2Lag)? "d2Lag" :
        (show_ode == ode_d2Rate)?"d2Rate" :
        (show_ode == ode_d2Dur)?"d2Dur" :
        (show_ode == ode_d3F)  ? "d3F" :
        (show_ode == ode_dFQ)  ? "dFQ" :
        (show_ode == ode_dLagJac) ? "dLagJac" :
        (show_ode == ode_dLagQ) ? "dLagQ" : "dDurQ";
      const char *_esBuf =
        (show_ode == ode_dLag) ? "_dLagSave" :
        (show_ode == ode_dF)   ? "_dFSave" :
        (show_ode == ode_dRate)? "_dRateSave" :
        (show_ode == ode_dDur) ? "_dDurSave" :
        (show_ode == ode_d2F)  ? "_d2FSave" :
        (show_ode == ode_d2Lag)? "_d2LagSave" :
        (show_ode == ode_d2Rate)?"_d2RateSave" :
        (show_ode == ode_d2Dur)?"_d2DurSave" :
        (show_ode == ode_d3F)  ? "_d3FSave" :
        (show_ode == ode_dFQ)  ? "_dFQSave" :
        (show_ode == ode_dLagJac) ? "_dLagJacSave" :
        (show_ode == ode_dLagQ) ? "_dLagQSave" : "_dDurQSave";
      sAppend(&sbOut,
              "// Event-sensitivity %s\nvoid %s%s(int _cSub, double __t, double *__zzStateVar__, double *%s){\n"
              "  int _itwhile = 0;\n  (void)_itwhile;\n  double t = __t + _solveData->subjects[_cSub].curShift;\n  (void)t;\n"
              "  rx_solving_options_ind *_ind = &(_solveData->subjects[_cSub]);\n  _setThreadInd(_cSub);\n  _ind->_rxFlag=5;\n  (void)%s;\n",
              _esDesc, prefix, _esFun, _esBuf, _esBuf);
    } else {
      sAppend(&sbOut,  "// prj-specific derived vars\n"
              "#if defined(__GNUC__) || defined(__clang__)\n"
              "__attribute__((hot))\n"
              "#endif\n"
              "void %scalc_lhs(int _cSub, double __t, double * __restrict__ __zzStateVar__, double *_lhs) {\n    int _itwhile = 0;\n  (void)_itwhile;\n  double t = __t + _solveData->subjects[_cSub].curShift;\n  (void)t;\n  rx_solving_options_ind *_ind = &(_solveData->subjects[_cSub]);\n  _setThreadInd(_cSub);\n  _ind->_rxFlag=11;\n  int _wasAtEventTime = _ind->_atEventTime; _ind->_atEventTime = 0; (void)_wasAtEventTime;\n", prefix);
    }
    if ((show_ode == ode_jac && found_jac == 1 && good_jac == 1) ||
        (show_ode != ode_jac && show_ode != ode_ini && show_ode != ode_fbio &&
         show_ode != ode_dur && show_ode != ode_rate && show_ode != ode_lag &&
         show_ode != ode_lhs && show_ode != ode_mtime && show_ode != ode_mexp &&
         show_ode != ode_past &&
         show_ode != ode_indLinVec && !ode_is_es_dcode(show_ode)) ||
        (show_ode == ode_dur && foundDur) ||
        (show_ode == ode_rate && foundRate) ||
        (show_ode == ode_lag && foundLag) ||
        (show_ode == ode_fbio && foundF) ||
        (show_ode == ode_past && foundPast) ||
        ode_is_es_dcode(show_ode) ||
        (show_ode == ode_ini && foundF0) ||
        (show_ode == ode_lhs && tb.li) ||
        (show_ode == ode_mtime && nmtime) ||
        (show_ode == ode_mexp && (tb.matn || tb.isMexp)) ||
        (show_ode == ode_indLinVec && (tb.matnf || tb.isMexp))){
      prnt_vars(print_double, 0, "", "\n",show_ode);     /* declare all used vars */
      if (maxSumProdN > 0 || SumProdLD > 0){
        int mx = maxSumProdN;
        if (SumProdLD > mx) mx = SumProdLD;
        sAppend(&sbOut,  "  double _p[%d], _input[%d];\n", mx, mx);
        sAppend(&sbOut,  "  double _pld[%d];\n", mx);
        sAppend(&sbOut,  "  for (int ddd=%d; ddd--;){_p[ddd]=_input[ddd]=_pld[ddd]=0.0;}", mx);

      }
      if (maxUdf > 0) {
        sAppend(&sbOut,  "  double __udf[%d];\n", maxUdf);
      }
      else prnt_vars(print_void, 0, "  (void)t;\n", "\n",show_ode);     /* declare all used vars */
      if (maxSumProdN){
        sAppendN(&sbOut,  "  (void)_p;\n  (void)_input;\n", 28);
        if (SumProdLD){
          sAppendN(&sbOut,  "  (void)_pld;\n", 14);
        }
      }

      prnt_vars(print_lastLhsValue, 0,"","\n", 12);
      if (show_ode == ode_ini){
        sAppendN(&sbOut, "  _update_par_ptr(0.0, _cSub, _solveData, _idx);\n", 49);
      } else if (show_ode == ode_lag || show_ode == ode_rate || show_ode == ode_dur ||
                 show_ode == ode_mtime || show_ode == ode_past || ode_is_es_dcode(show_ode)){
        // functional lag, rate, duration, mtime, past-history, event-sensitivity dLag/dF
        sAppendN(&sbOut, "  _update_par_ptr(NA_REAL, _cSub, _solveData, _idx);\n", 53);
      } else if (show_ode == ode_indLinVec || show_ode == ode_mexp){
        sAppendN(&sbOut, "  _update_par_ptr(_t, _cSub, _solveData, _idx);\n", 48);
      } else {
        sAppendN(&sbOut, "  _update_par_ptr(__t, _cSub, _solveData, _idx);\n", 49);
      }
      prnt_vars(print_populateParameters, 1, "", "\n",show_ode);                   /* pass system pars */
      if (show_ode != ode_indLinVec && show_ode != ode_past){
        // past() history is a function of t and parameters only (no states); its
        // __zzStateVar__ is NULL, so state-var population is skipped for ode_past.
        for (i=0; i<tb.de.n; i++) {                   /* name state vars */
          buf = tb.ss.line[tb.di[i]];
          if (tb.idu[i] == 0) {
          } else {
            // Rate/Dur/F/Lag functions: populate state vars from __zzStateVar__
            sAppendN(&sbOut, "  ", 2);
            doDot(&sbOut, buf);
            sAppend(&sbOut, " = __zzStateVar__[__DDT%d__]*((double)(_ON[__DDT%d__]));\n", i, i);
          }
        }
        sAppendN(&sbOut, "\n", 1);
      }
    }
    if ((foundDur && show_ode == ode_dur) ||
        (foundRate && show_ode == ode_rate) ||
        (foundLag && show_ode == ode_lag) ||
        (foundF && show_ode == ode_fbio) ||
        (foundPast && show_ode == ode_past) ||
        ode_is_es_dcode(show_ode) ||
        (foundF0 && show_ode == ode_ini) ||
        (show_ode == ode_lhs && tb.li) ||
        (show_ode == ode_mtime && nmtime) ||
        (show_ode == ode_jac && found_jac == 1 && good_jac == 1) ||
        (show_ode != ode_mtime && show_ode != ode_lhs &&
         show_ode != ode_jac && show_ode != ode_ini &&
         show_ode != ode_fbio && show_ode != ode_lag  &&
         show_ode != ode_rate && show_ode != ode_dur &&
         show_ode != ode_past &&
         !ode_is_es_dcode(show_ode))){
      for (i = 0; i < sbPm.n; i++){
        // Event-sensitivity dLag/dF emit no model statements here (preamble
        // only); their body comes from the end switch below.
        if (ode_is_es_dcode(show_ode)) break;
        switch(sbPm.lType[i]){
        case TLIN:
          if (show_ode != ode_mexp && show_ode != ode_indLinVec &&
              show_ode != ode_fbio && show_ode != ode_lag &&
              show_ode != ode_rate && show_ode != ode_dur && show_ode != ode_past){
            sAppend(&sbOut,"  %s",show_ode == ode_dydt ? sbPm.line[i] : sbPmDt.line[i]);
          }
          break;
        case TMTIME:
          if (show_ode != ode_mexp && show_ode != ode_indLinVec){
            // For ode_mtime (calc_mtime): use the primal equation (sbPm) to evaluate
            // the mtime expression using the provided __zzStateVar__ state vector.
            // sbPmDt contains the sensitivity form which references dual state vars
            // (__DDtStateVar_k__) that are excluded (TDDT is skipped for ode_mtime),
            // so using sbPmDt here would leave the mtime expression unevaluated.
            sAppend(&sbOut,"  %s",(show_ode == ode_dydt || show_ode == ode_mtime) ? sbPm.line[i] : sbPmDt.line[i]);
          }
          break;
        case TEVID:
          /* evid_() calls emit code in dydt and lhs, guarded by _wasAtEventTime */
          if (show_ode == ode_dydt || show_ode == ode_lhs) {
            sAppend(&sbOut, "  if (_wasAtEventTime) { %s }\n", sbPm.line[i]);
          }
          break;
        case TASSIGN:
          if (tb.isMexp || (show_ode != ode_mexp && show_ode != ode_indLinVec)){
            const char *_taLine = (show_ode == ode_dydt || show_ode == ode_mexp || show_ode == ode_indLinVec) ? sbPm.line[i] : sbPmDt.line[i];
            // The pre-history function _rxPast() must never evaluate a delay():
            // past() is a function of t and parameters only, and rxOptExpr can
            // hoist delay() into a common-subexpression assignment -- emitting it
            // into _rxPast would recurse (_rxDelay -> _rxPast -> _rxDelay ...).
            if (!(show_ode == ode_past && strstr(_taLine, "_rxDelay") != NULL)) {
              sAppend(&sbOut, "  %s", _taLine);
            }
          }
          break;
        case TINI:
          // See if this is an ini or a reclaimed expression.
          if (show_ode != ode_mexp && show_ode != ode_indLinVec){
            if (sbPm.lProp[i] >= 0 ){
              tb.ix = sbPm.lProp[i];
              if (tb.lh[tb.ix] == isLHS || tb.lh[tb.ix] == isLHSstr ||
                  tb.lh[tb.ix] == isLHSparam){
                sAppend(&sbOut,"  %s",show_ode == ode_dydt ? sbPm.line[i] : sbPmDt.line[i]);
              }
            }
          }
          break;
        case TF0:
          // functional ini
          if (show_ode == ode_ini) sAppend(&sbOut,"  %s",sbPmDt.line[i]);
          break;
        case FBIO:
          if (show_ode == ode_fbio) sAppend(&sbOut,"  %s", sbPmDt.line[i]);
          break;
        case ALAG:
          if (show_ode == ode_lag) sAppend(&sbOut, "  %s", sbPmDt.line[i]);
          break;
        case RATE:
          if (show_ode == ode_rate) sAppend(&sbOut, "  %s", sbPmDt.line[i]);
          break;
        case DUR:
          if (show_ode == ode_dur) sAppend(&sbOut,"  %s", sbPmDt.line[i]);
          break;
        case PAST:
          if (show_ode == ode_past) sAppend(&sbOut,"  %s", sbPmDt.line[i]);
          break;
        case TJAC:
          if (show_ode == ode_lhs) sAppend(&sbOut, "  %s", sbPmDt.line[i]);
          else if (show_ode == ode_jac)  sAppend(&sbOut, "  %s", sbPm.line[i]);
          break;
        case TDDT:
          // d/dt()
          if (show_ode != ode_ini && show_ode != ode_fbio && show_ode != ode_lag &&
              show_ode != ode_rate && show_ode != ode_dur && show_ode != ode_mtime &&
              show_ode != ode_past &&
              show_ode !=10 && show_ode != ode_indLinVec){
            sAppend(&sbOut, "  %s", show_ode == ode_dydt ? sbPm.line[i] : sbPmDt.line[i]);
          }
          break;
        case PPRN:
          // Rprintf
          if ((fullPrint && show_ode != ode_mexp && show_ode != ode_indLinVec) || (!fullPrint && show_ode == 1)) {
            sAppend(&sbOut, "  %s", show_ode == ode_dydt ? sbPm.line[i] : sbPmDt.line[i]);
          }
          break;
        case TLOGIC:
          if (tb.isMexp || (show_ode != ode_mexp && show_ode != ode_indLinVec)){
            sAppend(&sbOut,"  %s",(show_ode == ode_dydt || show_ode == ode_mexp || show_ode == ode_indLinVec) ? sbPm.line[i] : sbPmDt.line[i]);
          }
          break;
        case TMAT0:
          if (show_ode == ode_mexp){
            sAppend(&sbOut,"  %s", sbPm.line[i]);
          }
          break;
        case TMATF:
          if (show_ode == ode_indLinVec){
            sAppend(&sbOut,"  %s", sbPm.line[i]);
          }
          break;
        case TNONE: // no code is output (just props)
          break;
        default:
          RSprintf("line Number: %d\n", i);
          RSprintf("type: %d\n", sbPm.lType[i]);
          RSprintf("line: %s\n", sbPm.line[i]);
          RSprintf("PmDt Line: %s\n", sbPmDt.line[i]);
          RSprintf("Prop: %d\n", sbPm.lProp[i]);
        }
      }
      // End statements
      switch (show_ode){
      case ode_dur:
        // RATE
        sAppendN(&sbOut, "\n  return _dur[_cmt];\n", 22);
        break;
      case ode_rate:
        // DUR
        sAppendN(&sbOut, "\n  return _rate[_cmt];\n", 23);
        break;
      case ode_lag:
        // Alag
        sAppendN(&sbOut, "\n  return t + _alag[_cmt] - _solveData->subjects[_cSub].curShift;\n", 66);
        break;
      case ode_fbio:
        sAppendN(&sbOut, "\n  return _f[_cmt]*_amt;\n", 25);
        break;
      case ode_past:
        // when foundPast the header opened the body + declared _past[]; close
        // with the selected compartment's history value.  When !foundPast the
        // header already emitted a complete self-contained function.
        if (foundPast) sAppendN(&sbOut, "\n  return _past[_cmt];\n", 22);
        break;
      case ode_dLag:
        // Event-sensitivity d(alag)/dp assignment lines (may be empty)
        if (_es_dLagCode != NULL && _es_dLagCode[0] != '\0') {
          sAppend(&sbOut, "\n%s\n", _es_dLagCode);
        }
        break;
      case ode_dF:
        // Event-sensitivity d(F)/dp assignment lines (may be empty)
        if (_es_dFCode != NULL && _es_dFCode[0] != '\0') {
          sAppend(&sbOut, "\n%s\n", _es_dFCode);
        }
        break;
      case ode_dRate:
        // Event-sensitivity d(rate)/dp assignment lines (may be empty)
        if (_es_dRateCode != NULL && _es_dRateCode[0] != '\0') {
          sAppend(&sbOut, "\n%s\n", _es_dRateCode);
        }
        break;
      case ode_dDur:
        // Event-sensitivity d(dur)/dp assignment lines (may be empty)
        if (_es_dDurCode != NULL && _es_dDurCode[0] != '\0') {
          sAppend(&sbOut, "\n%s\n", _es_dDurCode);
        }
        break;
      case ode_d2F:
        // Second-order event-sensitivity d2(F)/dp/dq assignment lines (may be empty)
        if (_es_d2FCode != NULL && _es_d2FCode[0] != '\0') {
          sAppend(&sbOut, "\n%s\n", _es_d2FCode);
        }
        break;
      case ode_d2Lag:
        // Second-order event-sensitivity d2(alag)/dp/dq assignment lines (may be empty)
        if (_es_d2LagCode != NULL && _es_d2LagCode[0] != '\0') {
          sAppend(&sbOut, "\n%s\n", _es_d2LagCode);
        }
        break;
      case ode_d2Rate:
        // Second-order event-sensitivity d2(rate)/dp/dq assignment lines (may be empty)
        if (_es_d2RateCode != NULL && _es_d2RateCode[0] != '\0') {
          sAppend(&sbOut, "\n%s\n", _es_d2RateCode);
        }
        break;
      case ode_d2Dur:
        // Second-order event-sensitivity d2(dur)/dp/dq assignment lines (may be empty)
        if (_es_d2DurCode != NULL && _es_d2DurCode[0] != '\0') {
          sAppend(&sbOut, "\n%s\n", _es_d2DurCode);
        }
        break;
      case ode_d3F:
        // Third-order event-sensitivity d3(F)/dp/dq/dr assignment lines (may be empty)
        if (_es_d3FCode != NULL && _es_d3FCode[0] != '\0') {
          sAppend(&sbOut, "\n%s\n", _es_d3FCode);
        }
        break;
      case ode_dFQ:
        // Event-sensitivity d(F)/dq assignment lines (may be empty)
        if (_es_dFQCode != NULL && _es_dFQCode[0] != '\0') {
          sAppend(&sbOut, "\n%s\n", _es_dFQCode);
        }
        break;
      case ode_dLagJac:
        // Event-sensitivity d(J[k][c])/dq assignment lines (may be empty)
        if (_es_dLagJacCode != NULL && _es_dLagJacCode[0] != '\0') {
          sAppend(&sbOut, "\n%s\n", _es_dLagJacCode);
        }
        break;
      case ode_dLagQ:
        // Event-sensitivity d(alag)/dq assignment lines (may be empty)
        if (_es_dLagQCode != NULL && _es_dLagQCode[0] != '\0') {
          sAppend(&sbOut, "\n%s\n", _es_dLagQCode);
        }
        break;
      case ode_dDurQ:
        // Event-sensitivity d(dur)/dq assignment lines (may be empty)
        if (_es_dDurQCode != NULL && _es_dDurQCode[0] != '\0') {
          sAppend(&sbOut, "\n%s\n", _es_dDurQCode);
        }
        break;
      }
    }
    if (show_ode == ode_dydt){
      sAppendN(&sbOut,  "  (&_solveData->subjects[_cSub])->dadt_counter[0]++;\n}\n\n", 56);
    } else if (show_ode == ode_jac){
      //sAppendN(&sbOut, "  free(__ld_DDtStateVar__);\n");
      sAppendN(&sbOut,  "  (&_solveData->subjects[_cSub])->jac_counter[0]++;\n", 52);
      sAppendN(&sbOut,  "}\n", 2);
    } else if (show_ode == ode_ini){
      if (foundF0){
        for (i = 0; i < tb.de.n; i++) {
          buf=tb.ss.line[tb.di[i]];
          sAppend(&sbOut, "  __zzStateVar__[__DDT%d__]=((double)(_ON[__DDT%d__]))*(",i,i);
          doDot(&sbOut, buf);
          sAppendN(&sbOut,  ");\n", 3);
        }
      }
      sAppendN(&sbOut,  "}\n", 2);
    } else if (show_ode == ode_fbio || show_ode == ode_lag || show_ode == ode_rate ||
               show_ode == ode_dur){
      sAppendN(&sbOut,  "}\n", 2);
    } else if (show_ode == ode_past && foundPast){
      // !foundPast already closed the function body in its header
      sAppendN(&sbOut,  "}\n", 2);
    } else if (show_ode == ode_lhs && tb.li){
      sAppendN(&sbOut,  "\n", 1);
      for (i=0, j=0; i<NV; i++) {
        if (tb.lh[i] != isLHS && tb.lh[i] != isLhsStateExtra &&
            tb.lh[i] != isLHSparam && tb.lh[i] != isLHSstr) continue;
        buf = tb.ss.line[i];
        sAppend(&sbOut,  "  _lhs[_LHS_%d_]=", j);
        doDot(&sbOut, buf);
        sAppendN(&sbOut,  ";\n", 2);
        j++;
      }
      sAppendN(&sbOut,  "}\n", 2);
    } else if (show_ode == ode_mtime && nmtime){
      sAppendN(&sbOut,  "\n", 1);
      for (i=0, j=0; i<NV; i++) {
        if (tb.mtime[i] != 1) continue;
        buf = tb.ss.line[i];
        sAppend(&sbOut,  "  _mtime[%d]=", j);
        doDot(&sbOut, buf);
        sAppendN(&sbOut,  ";\n", 2);
        j++;
      }
      sAppendN(&sbOut,  "}\n", 2);
    } else if (show_ode == ode_mexp && tb.isMexp) {
      sAppend(&sbOut, "\n  for (int _i = 0; _i < %d; _i++) _mat[_i] = 0.0;\n", tb.de.n * tb.de.n);
      for (j = 0; j < NV; j++) {
        char cmt1[100];
        char cmt2[100];
        int res = parse_micro_constant(tb.ss.line[j], cmt1, cmt2);
        if (res) {
          int idx1 = -1, idx2 = -1;
          for (int k = 0; k < tb.de.n; k++) {
            if (strcmp(tb.de.line[k], cmt1) == 0) idx1 = k;
            if (strcmp(tb.de.line[k], cmt2) == 0) idx2 = k;
          }
          if (idx1 != -1 && idx2 != -1) {
            sAppend(&sbOut, "  _mat[__DDT%d__ * %d + __DDT%d__] += ", idx1, tb.de.n, idx2);
            doDot(&sbOut, tb.ss.line[j]);
            sAppendN(&sbOut, ";\n", 2);

            if (res != 2) {
              sAppend(&sbOut, "  _mat[__DDT%d__ * %d + __DDT%d__] -= ", idx1, tb.de.n, idx1);
              doDot(&sbOut, tb.ss.line[j]);
              sAppendN(&sbOut, ";\n", 2);
            }
          }
        }
      }
      sAppendN(&sbOut,  "}\n", 2);
    } else if (show_ode == ode_indLinVec && tb.isMexp) {
      sAppend(&sbOut, "\n  for (int _i = 0; _i < %d; _i++) _matf[_i] = _IR[_i];\n", tb.de.n);
      for (j = 0; j < NV; j++) {
        if (strncmp(tb.ss.line[j], "rx_indLin_", 10) == 0) {
          const char *cmt = tb.ss.line[j] + 10;
          int idx = -1;
          for (int k = 0; k < tb.de.n; k++) {
            if (strcmp(tb.de.line[k], cmt) == 0) {
              idx = k;
              break;
            }
          }
          if (idx != -1) {
            sAppend(&sbOut, "  _matf[__DDT%d__] += ", idx);
            doDot(&sbOut, tb.ss.line[j]);
            sAppendN(&sbOut, ";\n", 2);
          }
        }
      }
      sAppendN(&sbOut,  "}\n", 2);
    } else {
      sAppendN(&sbOut,  "}\n", 2);
    }
  }
  _rxode2parse_unprotect();
}

void writeSb(sbuf *sbb, FILE *fp){
  // Adapted from ideas by Christian H
  // http://forums.codeguru.com/showthread.php?77477-What-is-the-fastest-way-to-write-data-to-a-file
  unsigned totalWritten=0;
  const unsigned OS_PAGESIZE = 4*1024;
  while( totalWritten < sbb->o) {
    register unsigned toWrite = min( OS_PAGESIZE, sbb->o - totalWritten);
    register unsigned written = fwrite(sbb->s + totalWritten, 1, toWrite, fp);
    if( toWrite != written){
      fclose(fp);
      _rxode2parse_unprotect();
      err_trans("IO error writing parsed C file");
    } else{
      totalWritten += written; // add the written bytes
    }
  }
  if (totalWritten != sbb->o) {
    fclose(fp);
    _rxode2parse_unprotect();
    err_trans("IO error writing parsed C file");
  }
}

extern SEXP _goodFuns;

SEXP _rxode2_codegen(SEXP c_file, SEXP prefix, SEXP libname,
                          SEXP pMd5, SEXP timeId, SEXP mvLast,
                          SEXP goodFuns, SEXP esDLagCode, SEXP esDFCode,
                          SEXP esDRateCode, SEXP esDDurCode, SEXP esD2FCode,
                          SEXP esD2LagCode, SEXP esD2RateCode, SEXP esD2DurCode,
                          SEXP esD3FCode, SEXP esDFQCode, SEXP esDLagJacCode,
                          SEXP esDLagQCode, SEXP esDDurQCode) {
  // Event ("jump") sensitivities: the dLag/dF/dRate/dDur body lines are passed as
  // arguments (not a module global) so the setter and the codegen always run in
  // the same package instance -- the global channel was unreliable under
  // pkgload's load_all (setter and codegen could bind different rxode2 C
  // instances).
  _es_freeCode();
  _es_dLagCode = _es_dup(esDLagCode);
  _es_dFCode = _es_dup(esDFCode);
  _es_dRateCode = _es_dup(esDRateCode);
  _es_dDurCode = _es_dup(esDDurCode);
  _es_d2FCode = _es_dup(esD2FCode);
  _es_d2LagCode = _es_dup(esD2LagCode);
  _es_d2RateCode = _es_dup(esD2RateCode);
  _es_d2DurCode = _es_dup(esD2DurCode);
  _es_d3FCode = _es_dup(esD3FCode);
  _es_dFQCode = _es_dup(esDFQCode);
  _es_dLagJacCode = _es_dup(esDLagJacCode);
  _es_dLagQCode = _es_dup(esDLagQCode);
  _es_dDurQCode = _es_dup(esDDurQCode);
  _goodFuns = PROTECT(goodFuns); _rxode2parse_protected++;
  if (!sbPm.o || !sbNrm.o){
    _rxode2parse_unprotect();
    err_trans("nothing in output queue to write");
  }
  if (!Rf_isString(c_file) || Rf_length(c_file) != 1){
    _rxode2parse_unprotect();
    err_trans("c_file should only be 1 file");
  }
  if (Rf_length(libname) != 2){
    _rxode2parse_unprotect();
    err_trans("libname needs 2 elements");
  }
  fpIO = fopen(CHAR(STRING_ELT(c_file,0)), "wb");
  err_msg((intptr_t) fpIO, "error opening output c file\n", -2);

  if (badMd5){
    SET_STRING_ELT(VECTOR_ELT(mvLast, RxMv_md5), 0, Rf_mkChar(""));
  } else {
    SET_STRING_ELT(VECTOR_ELT(mvLast, RxMv_md5), 0, Rf_mkChar(md5));
  }
  SET_STRING_ELT(VECTOR_ELT(mvLast, RxMv_model), 1, Rf_mkChar(me_code));
  int pro = 0;
  SEXP trans = PROTECT(VECTOR_ELT(mvLast, RxMv_trans)); pro++;
  sbuf buf; sNull(&buf);
  sIni(&buf);
  if (strcmp(CHAR(STRING_ELT(trans, 0)), CHAR(STRING_ELT(libname, 0)))) {
    SET_STRING_ELT(trans, 0, STRING_ELT(libname, 0)); // libname
    SET_STRING_ELT(trans, 2, STRING_ELT(prefix, 0)); // prefix
    const char *curPrefix = CHAR(STRING_ELT(prefix,0));
    sPrint(&buf, "%sdydt", curPrefix);
    SET_STRING_ELT(trans, 3, Rf_mkChar(buf.s)); // dydt
    sPrint(&buf, "%scalc_jac", curPrefix);
    SET_STRING_ELT(trans, 4, Rf_mkChar(buf.s)); // calc_jac
    sPrint(&buf, "%scalc_lhs", curPrefix);
    SET_STRING_ELT(trans, 5, Rf_mkChar(buf.s)); // calc_lhs
    sPrint(&buf, "%smodel_vars", curPrefix);
    SET_STRING_ELT(trans, 6, Rf_mkChar(buf.s)); // model_vars
    sPrint(&buf, "%stheta", curPrefix);
    SET_STRING_ELT(trans, 7, Rf_mkChar(buf.s)); // theta
    sPrint(&buf, "%sinis", curPrefix);
    SET_STRING_ELT(trans, 8, Rf_mkChar(buf.s)); // inis
    sPrint(&buf, "%sdydt_lsoda", curPrefix);
    SET_STRING_ELT(trans, 9, Rf_mkChar(buf.s)); // dydt_lsoda
    sPrint(&buf, "%scalc_jac_lsoda", curPrefix);
    SET_STRING_ELT(trans, 10, Rf_mkChar(buf.s)); // calc_jac_lsoda
    sPrint(&buf, "%sode_solver_solvedata", curPrefix);
    SET_STRING_ELT(trans, 11, Rf_mkChar(buf.s)); // ode_solver_solvedata
    sPrint(&buf, "%sode_solver_get_solvedata", curPrefix);
    SET_STRING_ELT(trans, 12, Rf_mkChar(buf.s)); // ode_solver_get_solvedata
    sPrint(&buf, "%sdydt_liblsoda", curPrefix);
    SET_STRING_ELT(trans, 13, Rf_mkChar(buf.s)); // dydt_liblsoda
    sPrint(&buf, "%sF", curPrefix);
    SET_STRING_ELT(trans, 14, Rf_mkChar(buf.s)); // F
    sPrint(&buf, "%sLag", curPrefix);
    SET_STRING_ELT(trans, 15, Rf_mkChar(buf.s)); // Lag
    sPrint(&buf, "%sRate", curPrefix);
    SET_STRING_ELT(trans, 16, Rf_mkChar(buf.s)); // Rate
    sPrint(&buf, "%sDur", curPrefix);
    SET_STRING_ELT(trans, 17, Rf_mkChar(buf.s)); // Dur
    sPrint(&buf, "%smtime", curPrefix);
    SET_STRING_ELT(trans, 18, Rf_mkChar(buf.s)); // mtime
    sPrint(&buf, "%sassignFuns", curPrefix);
    SET_STRING_ELT(trans, 19, Rf_mkChar(buf.s)); // assignFuns
    sPrint(&buf, "%sME", curPrefix);
    SET_STRING_ELT(trans, 20, Rf_mkChar(buf.s)); // ME
    sPrint(&buf, "%sIndF", curPrefix);
    SET_STRING_ELT(trans, 21, Rf_mkChar(buf.s)); // IndF
  }
  //sPrint(&_mv, "%s", CHAR(STRING_ELT(PROTECT(_rxode2_rxQs(mvLast)), 0))); pro++;
  SEXP rawMV = PROTECT(_rxode2_rxQs(mvLast)); pro++;
  sPrint(&_mv,"   SEXP rw    = PROTECT(Rf_allocVector(RAWSXP, %d));pro++;\n", Rf_length(rawMV));
  unsigned char *raw = RAW(rawMV);
  sAppendN(&_mv, "    unsigned char r[]={", 23);
  for (int i = 0; i < Rf_length(rawMV); i++) {
    if (i > 0){
      sAppendN(&_mv, ", ", 2);
    } if (i % 10 == 0) {
      sAppendN(&_mv, "\n                ", 17);
    }
    sAppend(&_mv, "0x%02x", raw[i]);
  }
  sAppendN(&_mv, "\n                };\n", 20);
  sAppendN(&_mv, "    memcpy(RAW(rw), r, sizeof(r));\n", 35);
  sFree(&buf);
  //SET_STRING_ELT(tran, 0, Rf_mkChar());
  sFree(&sbOut);
  sIniTo(&sbOut, (int)((sbPm.sN)*5.3));

  SEXP stateOrd = PROTECT(VECTOR_ELT(mvLast, RxMv_stateOrd)); pro++;
  int nOrd = Rf_length(stateOrd);
  if (nOrd > 0) {
    SEXP stateOrdNames = PROTECT(Rf_getAttrib(stateOrd, R_NamesSymbol)); pro++;
    int *stateOrdInt = INTEGER(stateOrd);
    sAppend(&sbOut, "// Define translation state order for %d states\n", Rf_length(stateOrd));
    // Re-key the __DDT defines by each state's actual tb.de index, so that
    // __DDT<tb.de index>__ expands to that state's __zzStateVar__ slot (== the
    // stateOrd loop index i).  Every __DDT *use* in this file indexes by the
    // tb.de loop position (codegen reads/writes __zzStateVar__[__DDT<i>__] with
    // i the tb.de index, and the Jacobian/matrix code finds idx via
    // tb.de.line[k]); keying the *define* by stateOrdInt[i]-1 only agrees with
    // that when tb.de order matches stateOrd order.  An endpoint that injects an
    // observation cmt() (registered ahead of the linCmt compartments, which
    // calcLinCmt appends last) breaks that alignment and made in-equation reads
    // of e.g. peripheral1 resolve to an unwritten slot (== 0).  Mapping by NAME
    // fixes all uses and is a no-op when the orders already agree.
    //
    // Match each stateOrd name to its tb.de index; the injected observation
    // compartment (stateOrd name may be "NA") is never read in equations, so it
    // gets any leftover (unclaimed) tb.de index -- guaranteeing no duplicate
    // __DDT keys.
    {
      int nde = tb.de.n;
      // tb.de index claimed by a matched state (-1 == free)
      int *deClaimed = (int*)R_alloc(nde > 0 ? nde : 1, sizeof(int));
      for (int k = 0; k < nde; k++) deClaimed[k] = 0;
      // tb.de index assigned to each stateOrd slot (-1 == not yet assigned)
      int *slotDe = (int*)R_alloc(nOrd > 0 ? nOrd : 1, sizeof(int));
      for (int i = 0; i < nOrd; i++) slotDe[i] = -1;
      // First pass: name-match stateOrd entries to tb.de indices
      for (int i = 0; i < nOrd; i++) {
        const char *nm = CHAR(STRING_ELT(stateOrdNames, i));
        for (int k = 0; k < nde; k++) {
          if (!deClaimed[k] && !strcmp(tb.de.line[k], nm)) {
            slotDe[i] = k;
            deClaimed[k] = 1;
            break;
          }
        }
      }
      // Second pass: give unmatched stateOrd entries the leftover tb.de indices
      for (int i = 0; i < nOrd; i++) {
        if (slotDe[i] == -1) {
          for (int k = 0; k < nde; k++) {
            if (!deClaimed[k]) {
              slotDe[i] = k;
              deClaimed[k] = 1;
              break;
            }
          }
        }
      }
      for (int i = 0; i < nOrd; i++) {
        // __DDT<tb.de index>__ -> __zzStateVar__ slot i.  In every model
        // tb.de.n == nOrd (each solved state is a declared compartment), so a
        // tb.de index is always found; fall back to the legacy key only if the
        // table is somehow shorter, to avoid emitting __DDT-1__.
        int ddtKey = (slotDe[i] >= 0) ? slotDe[i] : (stateOrdInt[i]-1);
        sAppend(&sbOut, "#define __DDT%d__ %d // %s\n", ddtKey, i,
                CHAR(STRING_ELT(stateOrdNames, i)));
        if (!strcmp("depot", CHAR(STRING_ELT(stateOrdNames, i)))) {
          sAppend(&sbOut, "#define _DEPOT_ %d // %s\n", i,
                  CHAR(STRING_ELT(stateOrdNames, i)));
        } else if (!strcmp("central", CHAR(STRING_ELT(stateOrdNames, i)))) {
          sAppend(&sbOut, "#define _CENTRAL_ %d // %s\n", i,
                  CHAR(STRING_ELT(stateOrdNames, i)));
        }
      }
    }
    writeSb(&sbOut, fpIO);
    sbOut.o = 0;
  }
  SEXP lhsOrd = PROTECT(VECTOR_ELT(mvLast, RxMv_lhsOrd)); pro++;
  int  nLhs  =  Rf_length(lhsOrd);
  if (nLhs > 0) {
    SEXP lhsOrdNames = PROTECT(Rf_getAttrib(lhsOrd, R_NamesSymbol)); pro++;
    int *lhsOrdInt = INTEGER(lhsOrd);
    sAppend(&sbOut, "// Define %d LHS values\n", nLhs);
    for (int i = 0; i < nLhs; i++){
      sAppend(&sbOut, "#define _LHS_%d_ %d // %s\n", lhsOrdInt[i], i,
              CHAR(STRING_ELT(lhsOrdNames, i)));
    }
    writeSb(&sbOut, fpIO);
    sbOut.o = 0;
  }
  UNPROTECT(pro);
  // show_ode = 1 dydt
  // show_ode = 2 Jacobian
  // show_ode = 3 Ini statement
  // show_ode = 0 LHS
  // show_ode = 5 functional bioavailibility
  // show_ode = 6 functional rate
  gCode(1); // d/dt()
  gCode(2); // jac
  gCode(3); // ini()
  gCode(0); //
  gCode(5);
  gCode(6);
  gCode(7);
  gCode(8);
  gCode(9); // mtime
  gCode(10); //mat
  gCode(11); //matF
  gCode(ode_past); // non-constant delay() pre-history _rxPast()
  gCode(ode_dLag); // event-sensitivity d(alag)/dp
  gCode(ode_dF);   // event-sensitivity d(F)/dp
  gCode(ode_dRate);// event-sensitivity d(rate)/dp
  gCode(ode_dDur); // event-sensitivity d(dur)/dp
  gCode(ode_d2F);  // second-order event-sensitivity d2(F)/dp/dq
  gCode(ode_d2Lag);  // second-order event-sensitivity d2(alag)/dp/dq
  gCode(ode_d2Rate); // second-order event-sensitivity d2(rate)/dp/dq
  gCode(ode_d2Dur);  // second-order event-sensitivity d2(dur)/dp/dq
  gCode(ode_d3F);    // third-order event-sensitivity d3(F)/dp/dq/dr
  gCode(ode_dFQ);    // event-sensitivity d(F)/dq (Phase H1 dtau/lag row)
  gCode(ode_dLagJac);// event-sensitivity d(J[k][c])/dq (Phase H1 dtau/lag row)
  gCode(ode_dLagQ);  // event-sensitivity d(alag)/dq (Phase H1 dtau/lag row guard)
  gCode(ode_dDurQ);  // event-sensitivity d(dur)/dq (modeled-DUR continuous-forcing 2nd order)
  gCode(4); // Registration
  writeFooter(); // undef
  fclose(fpIO);
  parseFree(0);
  reset();
  _es_freeCode(); // event-sensitivity code is single-use per model
  return R_NilValue;
}
