/*
 * suboptimal folding - Stefan Wuchty, Walter Fontana & Ivo Hofacker
 *
 *                     Vienna RNA package
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>

#ifdef _WIN32
#ifdef __MINGW32__
#include <unistd.h>
#else
#include "ViennaRNA/unistd_win.h"
#endif
#else
#include <unistd.h>
#endif

#include <ctype.h>
#include <string.h>
#include <math.h>
#include "ViennaRNA/fold.h"
#include "ViennaRNA/constraints/hard.h"
#include "ViennaRNA/constraints/soft.h"
#include "ViennaRNA/utils/basic.h"
#include "ViennaRNA/utils/strings.h"
#include "ViennaRNA/params/default.h"
#include "ViennaRNA/fold_vars.h"
#include "ViennaRNA/datastructures/lists.h"
#include "ViennaRNA/eval.h"
#include "ViennaRNA/params/basic.h"
#include "ViennaRNA/loops/all.h"
#include "ViennaRNA/cofold.h"
#include "ViennaRNA/gquad.h"
#include "ViennaRNA/alphabet.h"
#include "ViennaRNA/subopt.h"

#include "ViennaRNA/loops/external_hc.inc"
#include "ViennaRNA/loops/hairpin_hc.inc"
#include "ViennaRNA/loops/internal_hc.inc"
#include "ViennaRNA/loops/multibranch_hc.inc"

#include "ViennaRNA/loops/external_sc.inc"
#include "ViennaRNA/loops/hairpin_sc.inc"
#include "ViennaRNA/loops/internal_sc.inc"
#include "ViennaRNA/loops/multibranch_sc.inc"

/* hack */
#include "ViennaRNA/color_output.inc"

#ifdef _OPENMP
#include <omp.h>
#endif

#ifdef __GNUC__
# define INLINE inline
#else
# define INLINE
#endif

#define true              1
#define false             0

typedef struct {
  struct hc_ext_def_dat     hc_dat_ext;
  vrna_hc_eval_f hc_eval_ext;

  struct hc_hp_def_dat      hc_dat_hp;
  vrna_hc_eval_f hc_eval_hp;

  struct hc_int_def_dat     hc_dat_int;
  eval_hc                   hc_eval_int;

  struct hc_mb_def_dat      hc_dat_mb;
  vrna_hc_eval_f hc_eval_mb;

  struct sc_f5_dat          sc_dat_ext;
  struct sc_hp_dat          sc_dat_hp;
  struct sc_int_dat         sc_dat_int;
  struct sc_mb_dat          sc_dat_mb;
} constraint_helpers;

/**
 *  @brief  Sequence interval stack element used in subopt.c
 */
typedef struct INTERVAL {
  int i;
  int j;
  int array_flag;
} INTERVAL;

typedef struct {
  char  *structure;
  LIST  *Intervals;
  int   partial_energy;
  int   is_duplex;
  /* int best_energy;   */ /* best attainable energy */
} STATE;

typedef struct {
  LIST  *Intervals;
  LIST  *Stack;
  int   nopush;
} subopt_env;


struct old_subopt_dat {
  unsigned long           max_sol;
  unsigned long           n_sol;
  vrna_subopt_solution_t  *SolutionList;
  FILE                    *fp;
  unsigned int            strands;
  unsigned int            *strand_start;
};

/*
 #################################
 # GLOBAL VARIABLES              #
 #################################
 */
PUBLIC int    subopt_sorted = 0;      /* output sorted by energy */
PUBLIC int    density_of_states[MAXDOS + 1];
PUBLIC double print_energy = 9999;    /* printing threshold for use with logML */

/*
 #################################
 # PRIVATE VARIABLES             #
 #################################
 */

/* some backward compatibility stuff */
PRIVATE int                   backward_compat           = 0;
PRIVATE vrna_fold_compound_t  *backward_compat_compound = NULL;

#ifdef _OPENMP

#pragma omp threadprivate(backward_compat_compound, backward_compat)

#endif

/*
 #################################
 # PRIVATE FUNCTION DECLARATIONS #
 #################################
 */

PRIVATE void
init_constraint_helpers(vrna_fold_compound_t  *fc,
                        constraint_helpers    *d);


PRIVATE void
free_constraint_helpers(constraint_helpers *d);


#ifndef VRNA_DISABLE_BACKWARD_COMPATIBILITY

PRIVATE vrna_subopt_solution_t *
wrap_subopt(char          *seq,
            char          *structure,
            vrna_param_t  *parameters,
            int           delta,
            int           is_constrained,
            int           is_circular,
            FILE          *fp);


#endif

PRIVATE void
make_pair(int   i,
          int   j,
          STATE *state);


/* mark a gquadruplex in the resulting dot-bracket structure */
PRIVATE void
make_gquad(int    i,
           int    L,
           int    l[3],
           STATE  *state);


PRIVATE INTERVAL *
make_interval(int i,
              int j,
              int ml);


PRIVATE STATE *
make_state(LIST *Intervals,
           char *structure,
           int  partial_energy,
           int  is_duplex,
           int  length);


PRIVATE STATE *
copy_state(STATE *state);


PRIVATE void
print_state(STATE *state);


PRIVATE void
print_stack(LIST *list);


PRIVATE LIST *
make_list(void);


PRIVATE void
push(LIST *list,
     void *data);


PRIVATE void *
pop(LIST *list);


PRIVATE int
best_attainable_energy(vrna_fold_compound_t *fc,
                       STATE                *state);


PRIVATE void
scan_interval(vrna_fold_compound_t  *fc,
              int                   i,
              int                   j,
              int                   array_flag,
              int                   threshold,
              STATE                 *state,
              subopt_env            *env,
              constraint_helpers    *constraints_dat);


PRIVATE INLINE void
scan_mb(vrna_fold_compound_t  *fc,
        int                   i,
        int                   j,
        int                   threshold,
        STATE                 *state,
        subopt_env            *env,
        constraint_helpers    *constraints_dat);


PRIVATE INLINE void
scan_m1(vrna_fold_compound_t  *fc,
        int                   i,
        int                   j,
        int                   array_flag,
        int                   threshold,
        STATE                 *state,
        subopt_env            *env,
        constraint_helpers    *constraints_dat);


PRIVATE INLINE void
scan_pair(vrna_fold_compound_t  *fc,
          int                   i,
          int                   j,
          int                   threshold,
          STATE                 *state,
          subopt_env            *env,
          constraint_helpers    *constraints_dat);


PRIVATE INLINE void
scan_ext(vrna_fold_compound_t *fc,
         int                  i,
         int                  j,
         int                  threshold,
         STATE                *state,
         subopt_env           *env,
         constraint_helpers   *constraints_dat);


PRIVATE INLINE void
scan_circular(vrna_fold_compound_t  *fc,
              int                   i,
              int                   j,
              int                   threshold,
              STATE                 *state,
              subopt_env            *env,
              constraint_helpers    *constraints_dat);


PRIVATE INLINE void
scan_fms5(vrna_fold_compound_t  *fc,
          unsigned int          i,
          unsigned int          strand,
          int                   threshold,
          STATE                 *state,
          subopt_env            *env,
          constraint_helpers    *constraints_dat);


PRIVATE INLINE void
scan_fms3(vrna_fold_compound_t  *fc,
          unsigned int          i,
          unsigned int          strand,
          int                   threshold,
          STATE                 *state,
          subopt_env            *env,
          constraint_helpers    *constraints_dat);


PRIVATE INLINE void
scan_gquad(vrna_fold_compound_t *fc,
           int                  i,
           int                  j,
           int                  threshold,
           STATE                *state,
           subopt_env           *env,
           constraint_helpers   *constraints_dat);


PRIVATE void
free_interval_node(INTERVAL *node);


PRIVATE void
free_state_node(STATE *node);


PRIVATE void
push_back(LIST  *Stack,
          STATE *state);


PRIVATE char *
get_structure(STATE *state);


PRIVATE int
compare(const void  *a,
        const void  *b);


PRIVATE int
compare_en(const void *a,
           const void *b);


PRIVATE void
make_output(vrna_subopt_solution_t  *SL,
            unsigned int            strands,
            const unsigned int      *strand_start,
            int                     compressed,
            FILE                    *fp);


PRIVATE void
repeat(vrna_fold_compound_t *fc,
       int                  i,
       int                  j,
       STATE                *state,
       int                  part_energy,
       int                  temp_energy,
       int                  best_energy,
       int                  threshold,
       subopt_env           *env,
       constraint_helpers   *constraints_dat);


PRIVATE void
repeat_gquad(vrna_fold_compound_t *fc,
             int                  i,
             int                  j,
             STATE                *state,
             int                  part_energy,
             int                  temp_energy,
             int                  best_energy,
             int                  threshold,
             subopt_env           *env,
             constraint_helpers   *constraints_dat);


PRIVATE void
old_subopt_print(const char *structure,
                 float      energy,
                 void       *data);


PRIVATE void
old_subopt_store(const char *structure,
                 float      energy,
                 void       *data);


PRIVATE void
old_subopt_store_compressed(const char  *structure,
                            float       energy,
                            void        *data);


/*
 #################################
 # BEGIN OF FUNCTION DEFINITIONS #
 #################################
 */
PUBLIC vrna_subopt_solution_t *
vrna_subopt(vrna_fold_compound_t  *fc,
            int                   delta,
            int                   sorted,
            FILE                  *fp)
{
  struct old_subopt_dat data;
  vrna_subopt_result_f  cb;

  data.SolutionList = NULL;
  data.max_sol      = 128;
  data.n_sol        = 0;
  data.fp           = fp;
  data.strands      = fc->strands;
  data.strand_start = fc->strand_start;

  if (fc) {
    /* SolutionList stores the suboptimal structures found */

    data.SolutionList =
      (vrna_subopt_solution_t *)vrna_alloc(data.max_sol * sizeof(vrna_subopt_solution_t));

    /* end initialize ------------------------------------------------------- */

    if (fp) {
      float min_en;
      char  *SeQ, *energies = NULL;
      min_en = vrna_mfe(fc, NULL);
      char  *tmp_seq = strdup(fc->sequence);

      if (fc->strands == 1) {
        SeQ = tmp_seq;
      } else {
        for (unsigned int i = 1; i < fc->strands; i++) {
          SeQ = vrna_cut_point_insert(tmp_seq, (int)fc->strand_start[i] + (i - 1));
          free(tmp_seq);
          tmp_seq = SeQ;
        }
      }

      energies  = vrna_strdup_printf(" %6.2f %6.2f", min_en, (float)delta / 100.);
      print_structure(fp, SeQ, energies);
      free(SeQ);
      free(energies);

      vrna_mx_mfe_free(fc);
    }

    cb = old_subopt_store;

    if (fp) {
      if (!sorted)
        cb = old_subopt_print;
      else if (!(fc->params->model_details.gquad))
        cb = old_subopt_store_compressed;
    }

    /* call subopt() */
    vrna_subopt_cb(fc, delta, cb, (void *)&data);

    if (sorted) {
      /* sort structures by energy */
      if (data.n_sol > 0) {
        int (*compare_fun)(const void *a,
                           const void *b);

        switch (sorted) {
          case VRNA_SORT_BY_ENERGY_ASC:
            compare_fun = compare_en;
            break;

          default: /* a.k.a. VRNA_SORT_BY_ENERGY_LEXICOGRAPHIC_ASC */
            compare_fun = compare;
            break;
        }

        qsort(data.SolutionList, data.n_sol - 1, sizeof(vrna_subopt_solution_t), compare_fun);
      }

      if (fp)
        make_output(data.SolutionList,
                    fc->strands,
                    fc->strand_start,
                    !(fc->params->model_details.gquad),
                    fp);
    }

    if (fp) {
      /* we've printed everything -- free solutions */
      vrna_subopt_solution_t *sol;
      for (sol = data.SolutionList; sol->structure != NULL; sol++)
        free(sol->structure);
      free(data.SolutionList);
      data.SolutionList = NULL;
    }
  }

  return data.SolutionList;
}


PUBLIC void
vrna_subopt_cb(vrna_fold_compound_t *fc,
               int                  delta,
               vrna_subopt_result_f cb,
               void                 *data)
{
  subopt_env          *env;
  STATE               *state;
  INTERVAL            *interval;
  unsigned int        *so, *ss;
  int                 maxlevel, count, partial_energy, old_dangles, logML, dangle_model, length,
                      circular,
                      threshold;
  double              structure_energy, min_en, eprint;
  char                *struc, *structure;
  float               correction;
  vrna_param_t        *P;
  vrna_md_t           *md;
  int                 minimal_energy;
  int                 Fc;
  int                 *f5;
  constraint_helpers  constraints_dat;

  vrna_fold_compound_prepare(fc, VRNA_OPTION_MFE);

  length  = fc->length;
  so      = fc->strand_order;
  ss      = fc->strand_start;
  P       = fc->params;
  md      = &(P->model_details);

  /*
   * do mfe folding to get fill arrays and get ground state energy
   * in case dangles is neither 0 or 2, set dangles=2 while folding
   */

  circular    = md->circ;
  logML       = md->logML;
  old_dangles = dangle_model = md->dangles;

  if (md->uniq_ML != 1) /* failsafe mechanism to enforce valid fM1 array */
    md->uniq_ML = 1;

  /* temporarily set dangles to 2 if necessary */
  if ((md->dangles != 0) && (md->dangles != 2))
    md->dangles = 2;

  struc = (char *)vrna_alloc(sizeof(char) * (length + 1));

  min_en = vrna_mfe(fc, struc);

  /* restore dangle model */
  md->dangles = old_dangles;

  /* re-evaluate in case we're using logML etc */
  min_en  = vrna_eval_structure(fc, struc);
  f5      = fc->matrices->f5;
  Fc      = fc->matrices->Fc;

  free(struc);
  eprint = print_energy + min_en;

  correction = (min_en < 0) ? -0.1 : 0.1;

  /* Initialize ------------------------------------------------------------ */
  init_constraint_helpers(fc, &constraints_dat);

  maxlevel        = 0;
  count           = 0;
  partial_energy  = 0;

  /* Initialize the stack ------------------------------------------------- */

  minimal_energy  = (circular) ? Fc : f5[length];
  threshold       = minimal_energy + delta;
  if (threshold >= INF) {
    vrna_message_warning("Energy range too high, limiting to reasonable value");
    threshold = INF - EMAX;
  }

  /* init env data structure */
  env             = (subopt_env *)vrna_alloc(sizeof(subopt_env));
  env->Stack      = NULL;
  env->nopush     = true;
  env->Stack      = make_list();                      /* anchor */
  env->Intervals  = make_list();                      /* initial state: */
  interval        = make_interval(1, length, 0);      /* interval [1,length,0] */
  push(env->Intervals, interval);
  env->nopush = false;
  state       = make_state(env->Intervals, NULL, partial_energy, 0, length);
  /* state->best_energy = minimal_energy; */
  push(env->Stack, state);
  env->nopush = false;

  /* end initialize ------------------------------------------------------- */


  while (1) {
    /* forever, til nothing remains on stack */

    maxlevel = (env->Stack->count > maxlevel ? env->Stack->count : maxlevel);

    if (LST_EMPTY(env->Stack)) {
      /*
       * we are done! clean up and quit
       * fprintf(stderr, "maxlevel: %d\n", maxlevel);
       */

      lst_kill(env->Stack, free_state_node);

      cb(NULL, 0, data);   /* NULL (last time to call callback function */

      break;
    }

    /* pop the last element ---------------------------------------------- */

    state = pop(env->Stack);                       /* current state to work with */

    if (LST_EMPTY(state->Intervals)) {
      int e;
      /* state has no intervals left: we got a solution */

      count++;
      structure         = get_structure(state);
      structure_energy  = state->partial_energy / 100.;

#ifdef CHECK_ENERGY
      structure_energy = vrna_eval_structure(fc, structure);

      if (!logML) {
        if ((double)(state->partial_energy / 100.) != structure_energy) {
          vrna_message_error("%s %6.2f %6.2f",
                             structure,
                             state->partial_energy / 100.,
                             structure_energy);
          exit(1);
        }
      }

#endif
      if (logML || (dangle_model == 1) || (dangle_model == 3)) /* recalc energy */
        structure_energy = vrna_eval_structure(fc, structure);

      e = (int)((structure_energy - min_en) * 10. - correction); /* avoid rounding errors */
      if (e > MAXDOS)
        e = MAXDOS;

      density_of_states[e]++;

      if (structure_energy <= eprint) {
        char  *outstruct = NULL;
        char  *tmp_struct = strdup(structure);

        if (fc->strands == 1) {
          outstruct = tmp_struct;
        } else {
          for (unsigned int i = 1; i < fc->strands; i++) {
            outstruct = vrna_cut_point_insert(tmp_struct, (int)fc->strand_start[i] + (i - 1));
            free(tmp_struct);
            tmp_struct = outstruct;
          }
        }
        cb((const char *)tmp_struct, structure_energy, data);
        free(tmp_struct);
      }

      free(structure);
    } else {
      /* get (and remove) next interval of state to analyze */

      interval = pop(state->Intervals);
      scan_interval(fc,
                    interval->i,
                    interval->j,
                    interval->array_flag,
                    threshold,
                    state, env,
                    &constraints_dat);

      free_interval_node(interval);        /* free the current interval */
    }

    free_state_node(state);                     /* free the current state */
  } /* end of while (1) */

  /* cleanup memory */
  free_constraint_helpers(&constraints_dat);

  free(env);
}


/*
 #####################################
 # BEGIN OF STATIC HELPER FUNCTIONS  #
 #####################################
 */
PRIVATE void
init_constraint_helpers(vrna_fold_compound_t  *fc,
                        constraint_helpers    *d)
{
  /* hard constraints first */
  d->hc_eval_ext  = prepare_hc_ext_def(fc, &(d->hc_dat_ext));
  d->hc_eval_hp   = prepare_hc_hp_def(fc, &(d->hc_dat_hp));
  d->hc_eval_int  = prepare_hc_int_def(fc, &(d->hc_dat_int));
  d->hc_eval_mb   = prepare_hc_mb_def(fc, &(d->hc_dat_mb));

  init_sc_f5(fc, &(d->sc_dat_ext));
  init_sc_hp(fc, &(d->sc_dat_hp));
  init_sc_int(fc, &(d->sc_dat_int));
  init_sc_mb(fc, &(d->sc_dat_mb));
}


PRIVATE void
free_constraint_helpers(constraint_helpers *d)
{
  /* currently only required for comparative folding soft constraints, but here for consistency reasons */
  free_sc_f5(&(d->sc_dat_ext));
  free_sc_hp(&(d->sc_dat_hp));
  free_sc_int(&(d->sc_dat_int));
  free_sc_mb(&(d->sc_dat_mb));
}


/*
 * ---------------------------------------------------------------------------
 * List routines--------------------------------------------------------------
 *---------------------------------------------------------------------------
 */
PRIVATE void
make_pair(int   i,
          int   j,
          STATE *state)
{
  state->structure[i - 1] = '(';
  state->structure[j - 1] = ')';
}


PRIVATE void
make_gquad(int    i,
           int    L,
           int    l[3],
           STATE  *state)
{
  int x;

  for (x = 0; x < L; x++) {
    state->structure[i - 1 + x]                               = '+';
    state->structure[i - 1 + x + L + l[0]]                    = '+';
    state->structure[i - 1 + x + 2 * L + l[0] + l[1]]         = '+';
    state->structure[i - 1 + x + 3 * L + l[0] + l[1] + l[2]]  = '+';
  }
}


PRIVATE INTERVAL *
make_interval(int i,
              int j,
              int array_flag)
{
  INTERVAL *interval;

  interval              = lst_newnode(sizeof(INTERVAL));
  interval->i           = i;
  interval->j           = j;
  interval->array_flag  = array_flag;
  return interval;
}


PRIVATE void
free_interval_node(INTERVAL *node)
{
  lst_freenode(node);
}


PRIVATE void
free_state_node(STATE *node)
{
  free(node->structure);
  if (node->Intervals)
    lst_kill(node->Intervals, lst_freenode);

  lst_freenode(node);
}


PRIVATE STATE *
make_state(LIST *Intervals,
           char *structure,
           int  partial_energy,
           int  is_duplex,
           int  length)
{
  STATE *state;

  state = lst_newnode(sizeof(STATE));

  if (Intervals)
    state->Intervals = Intervals;
  else
    state->Intervals = lst_init();

  if (structure) {
    state->structure = structure;
  } else {
    int i;
    state->structure = (char *)vrna_alloc(length + 1);
    for (i = 0; i < length; i++)
      state->structure[i] = '.';
  }

  state->partial_energy = partial_energy;

  return state;
}


PRIVATE STATE *
copy_state(STATE *state)
{
  STATE     *new_state;
  void      *after;
  INTERVAL  *new_interval, *next;

  new_state                 = lst_newnode(sizeof(STATE));
  new_state->Intervals      = lst_init();
  new_state->partial_energy = state->partial_energy;
  /* new_state->best_energy = state->best_energy; */

  if (state->Intervals->count) {
    after = LST_HEAD(new_state->Intervals);
    for (next = lst_first(state->Intervals); next; next = lst_next(next)) {
      new_interval  = lst_newnode(sizeof(INTERVAL));
      *new_interval = *next;
      lst_insertafter(new_state->Intervals, new_interval, after);
      after = new_interval;
    }
  }

  new_state->structure = strdup(state->structure);
  if (!new_state->structure)
    vrna_message_error("out of memory");

  return new_state;
}


/*@unused @*/ PRIVATE void
print_state(STATE *state)
{
  INTERVAL *next;

  if (state->Intervals->count) {
    printf("%d intervals:\n", state->Intervals->count);
    for (next = lst_first(state->Intervals); next; next = lst_next(next))
      printf("[%d,%d],%d ", next->i, next->j, next->array_flag);
    printf("\n");
  }

  printf("partial structure: %s\n", state->structure);
  printf("\n");
  printf(" partial_energy: %d\n", state->partial_energy);
  /* printf(" best_energy: %d\n", state->best_energy); */
  (void)fflush(stdout);
}


/*@unused @*/ PRIVATE void
print_stack(LIST *list)
{
  void *rec;

  printf("================\n");
  printf("%d states\n", list->count);
  for (rec = lst_first(list); rec; rec = lst_next(rec)) {
    printf("state-----------\n");
    print_state(rec);
  }
  printf("================\n");
}


PRIVATE LIST *
make_list(void)
{
  return lst_init();
}


PRIVATE void
push(LIST *list,
     void *data)
{
  lst_insertafter(list, data, LST_HEAD(list));
}


/*
 * PRIVATE void
 * push_stack(STATE *state) { */ /* keep the stack sorted by energy
 *   STATE *after, *next;
 *   nopush = false;
 *   next = after = LST_HEAD(Stack);
 *   while ( next = lst_next(next)) {
 *     if ( next->best_energy >= state->best_energy ) break;
 *     after = next;
 *   }
 *   lst_insertafter(Stack, state, after);
 * }
 */
PRIVATE void *
pop(LIST *list)
{
  void *data;

  data = lst_deletenext(list, LST_HEAD(list));
  return data;
}


/*
 * ---------------------------------------------------------------------------
 * auxiliary routines---------------------------------------------------------
 *---------------------------------------------------------------------------
 */
PRIVATE int
best_attainable_energy(vrna_fold_compound_t *fc,
                       STATE                *state)
{
  /* evaluation of best possible energy attainable within remaining intervals */

  register int  sum;
  INTERVAL      *next;
  vrna_md_t     *md;
  vrna_mx_mfe_t *matrices;
  int           *indx;

  md        = &(fc->params->model_details);
  matrices  = fc->matrices;
  indx      = fc->jindx;

  sum = state->partial_energy;  /* energy of already found elements */

  for (next = lst_first(state->Intervals); next; next = lst_next(next)) {
    if (next->array_flag == 0)
      sum += (md->circ) ? matrices->Fc : matrices->f5[next->j];
    else if (next->array_flag == 1)
      sum += matrices->fML[indx[next->j] + next->i];
    else if (next->array_flag == 2)
      sum += matrices->c[indx[next->j] + next->i];
    else if (next->array_flag == 3)
      sum += matrices->fM1[indx[next->j] + next->i];
    else if (next->array_flag == 4)
      sum += matrices->fms5[next->j][next->i];
    else if (next->array_flag == 5)
      sum += matrices->fms3[next->j][next->i];
    else if (next->array_flag == 6)
      sum += matrices->ggg[indx[next->j] + next->i];
  }

  return sum;
}


PRIVATE void
push_back(LIST  *Stack,
          STATE *state)
{
  push(Stack, copy_state(state));
  return;
}


PRIVATE char *
get_structure(STATE *state)
{
  char *structure;

  structure = strdup(state->structure);
  return structure;
}


PRIVATE int
compare(const void  *a,
        const void  *b)
{
  if (((vrna_subopt_solution_t *)a)->energy > ((vrna_subopt_solution_t *)b)->energy)
    return 1;

  if (((vrna_subopt_solution_t *)a)->energy < ((vrna_subopt_solution_t *)b)->energy)
    return -1;

  return strcmp(((vrna_subopt_solution_t *)a)->structure,
                ((vrna_subopt_solution_t *)b)->structure);
}


PRIVATE int
compare_en(const void *a,
           const void *b)
{
  if (((vrna_subopt_solution_t *)a)->energy > ((vrna_subopt_solution_t *)b)->energy)
    return 1;

  if (((vrna_subopt_solution_t *)a)->energy < ((vrna_subopt_solution_t *)b)->energy)
    return -1;

  return 0;
}


PRIVATE void
make_output(vrna_subopt_solution_t  *SL,
            unsigned int            strands,
            const unsigned int      *strand_start,
            int                     compressed,
            FILE                    *fp)                  /* prints stuff */
{
  vrna_subopt_solution_t *sol;

  for (sol = SL; sol->structure != NULL; sol++) {
    char  *e_string = vrna_strdup_printf(" %6.2f", sol->energy);
    char  *ss       = (compressed) ? vrna_db_unpack(sol->structure) : strdup(sol->structure);
    char  *s        = ss;

    if (strands > 1) {
      for (unsigned int i = 1; i < strands; i++) {
        s = vrna_cut_point_insert(ss, (int)strand_start[i] + (i - 1));
        free(ss);
        ss = s;
      }
    }

    print_structure(fp, s, e_string);
    free(s);
    free(e_string);
  }
}


PRIVATE STATE *
derive_new_state(int    i,
                 int    j,
                 STATE  *s,
                 int    e,
                 int    flag)
{
  STATE     *s_new  = copy_state(s);
  INTERVAL  *ival   = make_interval(i, j, flag);

  push(s_new->Intervals, ival);

  s_new->partial_energy += e;

  return s_new;
}


PRIVATE void
fork_state(int        i,
           int        j,
           STATE      *s,
           int        e,
           int        flag,
           subopt_env *env)
{
  STATE *s_new = derive_new_state(i, j, s, e, flag);

  push(env->Stack, s_new);
  env->nopush = false;
}


PRIVATE void
fork_int_state(int        i,
               int        j,
               int        p,
               int        q,
               STATE      *s,
               int        e,
               subopt_env *env)
{
  STATE *s_new = derive_new_state(p, q, s, e, 2);

  make_pair(i, j, s_new);
  make_pair(p, q, s_new);
  push(env->Stack, s_new);
  env->nopush = false;
}


PRIVATE void
fork_state_pair(int         i,
                int         j,
                STATE       *s,
                int         e,
                subopt_env  *env)
{
  STATE *new_state;

  new_state = copy_state(s);
  make_pair(i, j, new_state);
  new_state->partial_energy += e;
  push(env->Stack, new_state);
  env->nopush = false;
}


PRIVATE void
fork_two_states_pair(int        i,
                     int        j,
                     int        k,
                     STATE      *s,
                     int        e,
                     int        flag1,
                     int        flag2,
                     subopt_env *env)
{
  INTERVAL  *interval1, *interval2;
  STATE     *new_state;

  new_state = copy_state(s);
  interval1 = make_interval(i + 1, k - 1, flag1);
  interval2 = make_interval(k, j - 1, flag2);
  if (k - i < j - k) {
    /* push larger interval first */
    push(new_state->Intervals, interval1);
    push(new_state->Intervals, interval2);
  } else {
    push(new_state->Intervals, interval2);
    push(new_state->Intervals, interval1);
  }

  make_pair(i, j, new_state);
  new_state->partial_energy += e;

  push(env->Stack, new_state);
  env->nopush = false;
}


PRIVATE void
fork_state_pair_interval(int        i,
                         int        j,
                         int        k,
                         int        l,
                         STATE      *s,
                         int        e,
                         int        flag,
                         subopt_env *env)
{
  INTERVAL  *interval;
  STATE     *new_state;

  new_state = copy_state(s);
  interval  = make_interval(k, l, flag);
  push(new_state->Intervals, interval);

  make_pair(i, j, new_state);
  new_state->partial_energy += e;

  push(env->Stack, new_state);
  env->nopush = false;
}


PRIVATE void
fork_two_states_pair_ms(int         i,
                        int         j,
                        int         sn1,
                        int         sn2,
                        STATE       *s,
                        int         e,
                        subopt_env  *env)
{
  INTERVAL  *interval1, *interval2;
  STATE     *new_state;

  new_state = copy_state(s);
  interval1 = make_interval(i + 1, sn1, 4);
  interval2 = make_interval(j - 1, sn2, 5);
  push(new_state->Intervals, interval1);
  push(new_state->Intervals, interval2);

  make_pair(i, j, new_state);
  new_state->partial_energy += e;

  push(env->Stack, new_state);
  env->nopush = false;
}


PRIVATE void
fork_two_states(int         i,
                int         j,
                int         p,
                int         q,
                STATE       *s,
                int         e,
                int         flag1,
                int         flag2,
                subopt_env  *env)
{
  INTERVAL  *interval1, *interval2;
  STATE     *new_state;

  new_state = copy_state(s);
  interval1 = make_interval(i, j, flag1);
  interval2 = make_interval(p, q, flag2);

  if ((j - i) < (q - p)) {
    push(new_state->Intervals, interval1);
    push(new_state->Intervals, interval2);
  } else {
    push(new_state->Intervals, interval2);
    push(new_state->Intervals, interval1);
  }

  new_state->partial_energy += e;

  push(env->Stack, new_state);
  env->nopush = false;
}


PRIVATE void
scan_interval(vrna_fold_compound_t  *fc,
              int                   i,
              int                   j,
              int                   array_flag,
              int                   threshold,
              STATE                 *state,
              subopt_env            *env,
              constraint_helpers    *constraints_dat)
{
  /* real backtrack routine */

  /*
   * array_flag = 0:  trace back in f5-array
   * array_flag = 1:  trace back in fML-array
   * array_flag = 2:  trace back in repeat()
   * array_flag = 3:  trace back in fM1-array
   */

  env->nopush = true;

  switch (array_flag) {
    case 0:
      scan_ext(fc, i, j, threshold, state, env, constraints_dat);
      break;

    case 1:
      scan_mb(fc, i, j, threshold, state, env, constraints_dat);
    /* fall through */

    case 3:
      scan_m1(fc, i, j, array_flag, threshold, state, env, constraints_dat);
      break;

    case 2:
      scan_pair(fc, i, j, threshold, state, env, constraints_dat);
      return;

    case 4:
      scan_fms5(fc, i, j, threshold, state, env, constraints_dat);
      break;

    case 5:
      scan_fms3(fc, i, j, threshold, state, env, constraints_dat);
      break;

    case 6:
      scan_gquad(fc, i, j, threshold, state, env, constraints_dat);
      return;
  }

  if (env->nopush) {
    push_back(env->Stack, state);
    env->nopush = false;
  }
}


PRIVATE INLINE void
scan_mb(vrna_fold_compound_t  *fc,
        int                   i,
        int                   j,
        int                   threshold,
        STATE                 *state,
        subopt_env            *env,
        constraint_helpers    *constraints_dat)
{
  char                      *ptype;
  short                     *S1, s5, s3;
  unsigned int              *sn, *so;
  int                       k, type, dangle_model, element_energy, best_energy, *c, *fML,
                            *ggg, *indx, with_gquad, stopp, k1j;
  vrna_param_t              *P;
  vrna_md_t                 *md;
  struct hc_mb_def_dat      *hc_dat;
  vrna_hc_eval_f evaluate;
  struct sc_mb_dat          *sc_dat;
  sc_mb_red_cb              sc_red_stem;
  sc_mb_red_cb              sc_decomp_ml;

  STATE                     *temp_state;

  sn    = fc->strand_number;
  so    = fc->strand_order;
  indx  = fc->jindx;
  ptype = fc->ptype;
  S1    = fc->sequence_encoding;
  P     = fc->params;
  md    = &(P->model_details);

  dangle_model  = md->dangles;
  with_gquad    = md->gquad;

  c   = fc->matrices->c;
  fML = fc->matrices->fML;
  ggg = fc->matrices->ggg;

  hc_dat    = &(constraints_dat->hc_dat_mb);
  evaluate  = constraints_dat->hc_eval_mb;

  sc_dat        = &(constraints_dat->sc_dat_mb);
  sc_red_stem   = constraints_dat->sc_dat_mb.red_stem;
  sc_decomp_ml  = constraints_dat->sc_dat_mb.decomp_ml;

  best_energy = best_attainable_energy(fc, state);  /* .. on remaining intervals */

  if ((j < i + 1) &&
      (sn[i] == so[j])) {
    if (env->nopush) {
      push_back(env->Stack, state);
      env->nopush = false;
    }

    return;
  }

  if ((sn[i - 1] == sn[i]) && (sn[j] == sn[j + 1])) {
    /*backtrack in FML only if multiloop is possible*/
    for (k = i + 1; k <= j - 1; k++) {
      /* Multiloop decomposition if i,j contains more than 1 stack */

      if ((with_gquad) &&
          (sn[k] == sn[k + 1]) &&
          (fML[indx[k] + i] != INF) &&
          (ggg[indx[j] + k + 1] != INF)) {
        element_energy = E_MLstem(0, -1, -1, P);

        if (fML[indx[k] + i] + ggg[indx[j] + k + 1] + element_energy + best_energy <= threshold) {
          temp_state  = derive_new_state(i, k, state, 0, 1);
          env->nopush = false;
          repeat_gquad(fc,
                       k + 1,
                       j,
                       temp_state,
                       element_energy,
                       fML[indx[k] + i],
                       best_energy,
                       threshold,
                       env,
                       constraints_dat);
          free_state_node(temp_state);
        }
      }

      k1j = indx[j] + k + 1;

      if ((evaluate(i, j, k, k + 1, VRNA_DECOMP_ML_ML_STEM, hc_dat)) &&
          (fML[indx[k] + i] != INF) &&
          (c[k1j] != INF)) {
        type = vrna_get_ptype(k1j, ptype);

        switch (dangle_model) {
          case 0:
            s5 = s3 = -1;
            break;

          default:
            s5  = (sn[i - 1] == sn[i]) ? S1[k] : -1;
            s3  = (sn[j] == sn[j + 1]) ? S1[j + 1] : -1;
            break;
        }

        element_energy = E_MLstem(type, s5, s3, P);

        if (sc_decomp_ml)
          element_energy += sc_decomp_ml(i, j, k, k + 1, sc_dat);

        if (sc_red_stem)
          element_energy += sc_red_stem(k + 1, j, k + 1, j, sc_dat);

        if (fML[indx[k] + i] + c[k1j] + element_energy + best_energy <= threshold) {
          temp_state  = derive_new_state(i, k, state, 0, 1);
          env->nopush = false;
          repeat(fc,
                 k + 1,
                 j,
                 temp_state,
                 element_energy,
                 fML[indx[k] + i],
                 best_energy,
                 threshold,
                 env,
                 constraints_dat);
          free_state_node(temp_state);
        }
      }
    }
  }

  stopp = j - 1;

  int up = 1;

  for (k = i; k <= stopp; k++, up++) {
    k1j = indx[j] + k + 1;

    /* Multiloop decomposition if i,j contains only 1 stack */
    if ((with_gquad) &&
        (ggg[k1j] != INF) &&
        (sn[i] == sn[j])) {
      element_energy = E_MLstem(0, -1, -1, P) + P->MLbase * up;

      if (sc_red_stem)
        element_energy += sc_red_stem(i, j, k + 1, j, sc_dat);

      if (ggg[k1j] + element_energy + best_energy <= threshold) {
        repeat_gquad(fc,
                     k + 1,
                     j,
                     state,
                     element_energy,
                     0,
                     best_energy,
                     threshold,
                     env,
                     constraints_dat);
      }
    }

    if (evaluate(i, j, k + 1, j, VRNA_DECOMP_ML_STEM, hc_dat)) {
      if (c[k1j] != INF) {
        type = vrna_get_ptype(k1j, ptype);

        switch (dangle_model) {
          case 0:
            s5 = s3 = -1;
            break;
          default:
            s5  = (sn[k - 1] == sn[k]) ? S1[k] : -1;
            s3  = (sn[j] == sn[j + 1]) ? S1[j + 1] : -1;
            break;
        }

        element_energy = E_MLstem(type, s5, s3, P);

        element_energy += P->MLbase * up;

        if (sc_red_stem)
          element_energy += sc_red_stem(i, j, k + 1, j, sc_dat);

        if (c[k1j] + element_energy + best_energy <= threshold) {
          repeat(fc,
                 k + 1,
                 j,
                 state,
                 element_energy,
                 0,
                 best_energy,
                 threshold,
                 env,
                 constraints_dat);
        }
      }
    }
  }
}


PRIVATE INLINE void
scan_m1(vrna_fold_compound_t  *fc,
        int                   i,
        int                   j,
        int                   array_flag,
        int                   threshold,
        STATE                 *state,
        subopt_env            *env,
        constraint_helpers    *constraints_dat)
{
  char                      *ptype;
  short                     *S1;
  unsigned int              *sn, *so;
  int                       fi, cij, ij, type, dangle_model, element_energy, best_energy,
                            *c, *fML, *fM1, *ggg, length, *indx, circular, with_gquad;
  vrna_param_t              *P;
  vrna_md_t                 *md;
  struct hc_mb_def_dat      *hc_dat;
  vrna_hc_eval_f evaluate;
  struct sc_mb_dat          *sc_dat;
  sc_mb_red_cb              sc_red_stem;
  sc_mb_red_cb              sc_red_ml;

  length  = fc->length;
  sn      = fc->strand_number;
  so      = fc->strand_order;
  indx    = fc->jindx;
  ptype   = fc->ptype;
  S1      = fc->sequence_encoding;
  P       = fc->params;
  md      = &(P->model_details);

  dangle_model  = md->dangles;
  circular      = md->circ;
  with_gquad    = md->gquad;

  c   = fc->matrices->c;
  fML = fc->matrices->fML;
  fM1 = fc->matrices->fM1;
  ggg = fc->matrices->ggg;

  hc_dat    = &(constraints_dat->hc_dat_mb);
  evaluate  = constraints_dat->hc_eval_mb;

  sc_dat      = &(constraints_dat->sc_dat_mb);
  sc_red_stem = constraints_dat->sc_dat_mb.red_stem;
  sc_red_ml   = constraints_dat->sc_dat_mb.red_ml;

  best_energy = best_attainable_energy(fc, state);  /* .. on remaining intervals */

  if ((j < i + 1) &&
      (sn[i] == so[j])) {
    if (env->nopush) {
      push_back(env->Stack, state);
      env->nopush = false;
    }

    return;
  }

  ij = indx[j] + i;

  if ((evaluate(i, j, i, j - 1, VRNA_DECOMP_ML_ML, hc_dat)) &&
      (((array_flag == 3) && (fM1[indx[j - 1] + i] != INF)) ||
       (fML[indx[j - 1] + i] != INF))) {
    element_energy = P->MLbase;

    if (sc_red_ml)
      element_energy += sc_red_ml(i, j, i, j - 1, sc_dat);

    if (array_flag == 3)
      fi = element_energy +
           fM1[indx[j - 1] + i];
    else
      fi = element_energy +
           fML[indx[j - 1] + i];

    if (fi + best_energy <= threshold)
      fork_state(i, j - 1, state, element_energy, array_flag, env);
  }

  if (evaluate(i, j, i, j, VRNA_DECOMP_ML_STEM, hc_dat)) {
    /* i,j may pair */
    cij = c[ij];
    if (cij != INF) {
      type = vrna_get_ptype(ij, ptype);

      switch (dangle_model) {
        case 0:
          element_energy = E_MLstem(type, -1, -1, P);
          break;
        default:
          element_energy = E_MLstem(type,
                                    (((i > 1) && (sn[i - 1] == sn[i])) || circular) ? S1[i - 1] : -1,
                                    (((j < length) && (sn[j] == sn[j + 1])) || circular)  ? S1[j + 1] : -1,
                                    P);
          break;
      }

      if (sc_red_stem)
        element_energy += sc_red_stem(i, j, i, j, sc_dat);

      cij += element_energy;

      if (cij + best_energy <= threshold) {
        repeat(fc,
               i,
               j,
               state,
               element_energy,
               0,
               best_energy,
               threshold,
               env,
               constraints_dat);
      }
    }
  } else if ((with_gquad) &&
             (ggg[ij] != INF)) {
    element_energy = E_MLstem(0, -1, -1, P);

    if (sc_red_stem)
      element_energy += sc_red_stem(i, j, i, j, sc_dat);

    if (ggg[ij] + element_energy + best_energy <= threshold) {
      repeat_gquad(fc,
                   i,
                   j,
                   state,
                   element_energy,
                   0,
                   best_energy,
                   threshold,
                   env,
                   constraints_dat);
    }
  }

  return;
}


PRIVATE INLINE void
scan_pair(vrna_fold_compound_t  *fc,
          int                   i,
          int                   j,
          int                   threshold,
          STATE                 *state,
          subopt_env            *env,
          constraint_helpers    *constraints_dat)
{
  unsigned int  *sn, noLP;
  int           best_energy;

  sn          = fc->strand_number;
  noLP        = fc->params->model_details.noLP;
  best_energy = best_attainable_energy(fc, state);  /* .. on remaining intervals */

  if ((j < i + 1) &&
      (sn[i] == sn[j])) {
    if (env->nopush) {
      push_back(env->Stack, state);
      env->nopush = false;
    }

    return;
  }

  repeat(fc,
         i,
         j,
         state,
         0,
         0,
         best_energy,
         threshold,
         env,
         constraints_dat);

  if (env->nopush)
    if (!noLP)
      vrna_message_warning("%d,%d\nOops, no solution in repeat!", i, j);
}


PRIVATE INLINE void
scan_ext(vrna_fold_compound_t *fc,
         int                  i,
         int                  j,
         int                  threshold,
         STATE                *state,
         subopt_env           *env,
         constraint_helpers   *constraints_dat)
{
  char                      *ptype;
  short                     *S1, s5, s3;
  unsigned int              *sn, *so;
  int                       k, type, dangle_model, element_energy, best_energy, *f5, *c, *ggg,
                            length, *indx, circular, with_gquad, kj, tmp_en;
  vrna_param_t              *P;
  vrna_md_t                 *md;
  vrna_sc_t                 *sc;
  struct hc_ext_def_dat     *hc_dat;
  vrna_hc_eval_f evaluate;
  struct sc_f5_dat          *sc_dat;
  sc_f5_cb                  sc_red_ext;
  sc_f5_cb                  sc_red_stem;
  sc_f5_cb                  sc_decomp_stem;

  STATE                     *temp_state;

  length  = fc->length;
  sn      = fc->strand_number;
  so      = fc->strand_order;
  indx    = fc->jindx;
  ptype   = fc->ptype;
  S1      = fc->sequence_encoding;
  P       = fc->params;
  md      = &(P->model_details);
  sc      = fc->sc;

  dangle_model  = md->dangles;
  circular      = md->circ;
  with_gquad    = md->gquad;

  f5  = fc->matrices->f5;
  c   = fc->matrices->c;
  ggg = fc->matrices->ggg;

  if (circular) {
    scan_circular(fc, i, j, threshold, state, env, constraints_dat);
    return;
  }

  hc_dat    = &(constraints_dat->hc_dat_ext);
  evaluate  = constraints_dat->hc_eval_ext;

  sc_dat          = &(constraints_dat->sc_dat_ext);
  sc_red_ext      = sc_dat->red_ext5;
  sc_red_stem     = sc_dat->red_stem5;
  sc_decomp_stem  = sc_dat->decomp_stem5;

  best_energy = best_attainable_energy(fc, state);  /* .. on remaining intervals */

  if (i > 1)
    vrna_message_error("Error while backtracking!");

  if ((j < i + 1) &&
      (sn[i] == so[j])) {
    /*
     * minimal structure element
     * do not forget to add f5[j], since it may contain pseudo energies from soft constraining
     */
    state->partial_energy += f5[j];

    if (env->nopush) {
      push_back(env->Stack, state);
      env->nopush = false;
    }

    return;
  }

  if ((evaluate(1, j, 1, j - 1, VRNA_DECOMP_EXT_EXT, hc_dat)) &&
      (f5[j - 1] != INF)) {
    tmp_en = 0;

    if (sc_red_ext)
      tmp_en += sc_red_ext(j, 1, j - 1, sc_dat);

    if (f5[j - 1] + tmp_en + best_energy <= threshold)
      /* no basepair, nibbling of 3'-end */
      fork_state(i, j - 1, state, tmp_en, 0, env);
  }

  for (k = j - 1; k > 1; k--) {
    kj = indx[j] + k;

    if ((with_gquad) &&
        (sn[k - 1] == sn[j]) &&
        (f5[k - 1] != INF) &&
        (ggg[kj] != INF)) {
      element_energy = 0;

      if (sc_decomp_stem)
        element_energy += sc_decomp_stem(j, k - 1, k, sc_dat);

      if (f5[k - 1] + ggg[kj] + element_energy + best_energy <= threshold) {
        temp_state  = derive_new_state(1, k - 1, state, 0, 0);
        env->nopush = false;
        /* backtrace the quadruplex */
        repeat_gquad(fc,
                     k,
                     j,
                     temp_state,
                     element_energy,
                     f5[k - 1],
                     best_energy,
                     threshold,
                     env,
                     constraints_dat);
        free_state_node(temp_state);
      }
    }

    if ((evaluate(1, j, k - 1, k, VRNA_DECOMP_EXT_EXT_STEM, hc_dat)) &&
        (f5[k - 1] != INF) &&
        (c[kj] != INF)) {
      type = vrna_get_ptype(kj, ptype);

      /* k and j pair */
      switch (dangle_model) {
        case 0:
          s5 = s3 = -1;
          break;
        default:
          s5  = (sn[k - 1] == sn[k]) ? S1[k - 1] : -1;
          s3  = ((j < length) && (sn[j] == sn[j + 1])) ? S1[j + 1] : -1;
          break;
      }

      element_energy = vrna_E_ext_stem(type, s5, s3, P);

      if (sc_decomp_stem)
        element_energy += sc_decomp_stem(j, k - 1, k, sc_dat);

      if (f5[k - 1] + c[kj] + element_energy + best_energy <= threshold) {
        temp_state  = derive_new_state(1, k - 1, state, 0, 0);
        env->nopush = false;
        repeat(fc,
               k,
               j,
               temp_state,
               element_energy,
               f5[k - 1],
               best_energy,
               threshold,
               env,
               constraints_dat);
        free_state_node(temp_state);
      }
    }
  }

  kj = indx[j] + 1;

  if ((with_gquad) &&
      (sn[1] == sn[j]) &&
      (ggg[kj] != INF)) {
    element_energy = 0;

    if (sc_red_stem)
      element_energy += sc_red_stem(j, 1, j, sc_dat);

    if (ggg[kj] + element_energy + best_energy <= threshold) {
      /* backtrace the quadruplex */
      repeat_gquad(fc,
                   1,
                   j,
                   state,
                   element_energy,
                   0,
                   best_energy,
                   threshold,
                   env,
                   constraints_dat);
    }
  }

  if ((evaluate(1, j, 1, j, VRNA_DECOMP_EXT_STEM, hc_dat)) &&
      (c[kj] != INF)) {
    type  = vrna_get_ptype(kj, ptype);
    s5    = -1;

    switch (dangle_model) {
      case 0:
        s3 = -1;
        break;
      default:
        s3 = (j < length) && (sn[j] == sn[j + 1]) ? S1[j + 1] : -1;
        break;
    }

    element_energy = vrna_E_ext_stem(type, s5, s3, P);

    if (sc)
      if (sc->f)
        element_energy += sc->f(1, j, 1, j, VRNA_DECOMP_EXT_STEM, sc->data);

    if (c[kj] + element_energy + best_energy <= threshold) {
      repeat(fc,
             1,
             j,
             state,
             element_energy,
             0,
             best_energy,
             threshold,
             env,
             constraints_dat);
    }
  }
}


PRIVATE INLINE void
scan_circular(vrna_fold_compound_t  *fc,
              int                   i,
              int                   j,
              int                   threshold,
              STATE                 *state,
              subopt_env            *env,
              constraint_helpers    *constraints_dat)
{
  unsigned char             *hard_constraints;
  char                      *ptype;
  short                     *S1;
  int                       k, l, p, q, tmp_en, best_energy, *c, *fML, *fM1, Fc, FcH,
                            FcI, FcM, *fM2, length, *indx, *rtype, turn, kl, type, tmpE,
                            u1, qmin, u2, type_2, tmpE2;
  vrna_param_t              *P;
  vrna_md_t                 *md;
  vrna_hc_t                 *hc;
  vrna_sc_t                 *sc;
  struct hc_ext_def_dat     *hc_dat_ext;
  struct hc_int_def_dat     *hc_dat_int;
  struct hc_mb_def_dat      *hc_dat_mb;
  vrna_hc_eval_f evaluate_ext;
  eval_hc                   evaluate_int;
  vrna_hc_eval_f evaluate_mb;

  struct sc_int_dat         *sc_dat_int;
  struct sc_mb_dat          *sc_dat_mb;
  sc_int_cb                 sc_int_pair_ext;
  sc_mb_red_cb              sc_mb_decomp_ml;

  STATE                     *new_state;
  INTERVAL                  *new_interval;

  length  = fc->length;
  indx    = fc->jindx;
  ptype   = fc->ptype;
  S1      = fc->sequence_encoding;
  P       = fc->params;
  md      = &(P->model_details);
  rtype   = &(md->rtype[0]);

  turn = md->min_loop_size;

  c   = fc->matrices->c;
  fML = fc->matrices->fML;
  fM1 = fc->matrices->fM1;
  Fc  = fc->matrices->Fc;
  FcH = fc->matrices->FcH;
  FcI = fc->matrices->FcI;
  FcM = fc->matrices->FcM;
  fM2 = fc->matrices->fM2;

  hc                = fc->hc;
  hard_constraints  = hc->mx;

  sc = fc->sc;

  hc_dat_ext    = &(constraints_dat->hc_dat_ext);
  hc_dat_int    = &(constraints_dat->hc_dat_int);
  hc_dat_mb     = &(constraints_dat->hc_dat_mb);
  evaluate_ext  = constraints_dat->hc_eval_ext;
  evaluate_int  = constraints_dat->hc_eval_int;
  evaluate_mb   = constraints_dat->hc_eval_mb;

  sc_dat_int      = &(constraints_dat->sc_dat_int);
  sc_dat_mb       = &(constraints_dat->sc_dat_mb);
  sc_int_pair_ext = constraints_dat->sc_dat_int.pair_ext;
  sc_mb_decomp_ml = constraints_dat->sc_dat_mb.decomp_ml;

  best_energy = best_attainable_energy(fc, state);  /* .. on remaining intervals */

  if (i > 1)
    vrna_message_error("Error while backtracking!");

  if (j < i + turn + 1) {
    /* minimal structure element */
    state->partial_energy += Fc;

    if (env->nopush) {
      push_back(env->Stack, state);
      env->nopush = false;
    }

    return;
  }

  /*
   * if we've done everything right, we will never reach this point more than once
   * right after the initilization of the stack with ([1,n], empty, 0)
   * lets check, if we can have an open chain without breaking the threshold
   * this is an ugly work-arround cause in case of an open chain we do not have to
   * backtrack anything further...
   */
  if (evaluate_ext(1, length, 1, length, VRNA_DECOMP_EXT_UP, hc_dat_ext)) {
    tmp_en = 0;

    if (sc) {
      if (sc->energy_up)
        tmp_en += sc->energy_up[1][length];

      if (sc->f)
        tmp_en += sc->f(1, j, 1, j, VRNA_DECOMP_EXT_UP, sc->data);
    }

    if (tmp_en <= threshold) {
      new_state                 = derive_new_state(1, 2, state, 0, 0);
      new_state->partial_energy = 0;
      push(env->Stack, new_state);
      env->nopush = false;
    }
  }

  /*
   * ok, lets check if we can do an exterior hairpin without breaking the threshold
   * best energy should be 0 if we are here
   */
  if (FcH + best_energy <= threshold) {
    /*
     * lets search for all exterior hairpin cases, that fit into our threshold barrier
     * we use index k,l to avoid confusion with i,j index of our state...
     * if we reach here, i should be 1 and j should be n respectively
     */
    for (k = i; k < j; k++) {
      if (hc->up_hp[1] < k)
        break;

      for (l = j; l >= k + turn + 1; l--) {
        kl = indx[l] + k;

        if (c[kl] != INF) {
          tmpE = vrna_E_hp_loop(fc, l, k);

          if (c[kl] + tmpE + best_energy <= threshold)
            /*
             * what we really have to do is something like this, isn't it?
             * we have to create a new state, with interval [k,l], then we
             * add our loop energy as initial energy of this state and put
             * the state onto the stack R... for further refinement...
             * we also denote this new interval to be scanned in C
             */
            fork_state(k, l, state, tmpE, 2, env);
        }
      }
    }
  }

  /* now lets see, if we can do an exterior interior loop without breaking the threshold */
  if (FcI + best_energy <= threshold) {
    /* now we search for our exterior interior loop possibilities */
    for (k = i; k < j; k++) {
      for (l = j; l >= k + turn + 1; l--) {
        kl = indx[l] + k;         /* just confusing these indices ;-) */

        if ((hard_constraints[length * k + l] & VRNA_CONSTRAINT_CONTEXT_INT_LOOP) &&
            (c[kl] != INF)) {
          type = rtype[vrna_get_ptype(kl, ptype)];

          for (p = l + 1; p < j; p++) {
            u1 = p - l - 1;
            if (u1 + k - 1 > MAXLOOP)
              break;

            if (hc->up_int[l + 1] < u1)
              break;

            qmin = u1 + k - 1 + j - MAXLOOP;
            if (qmin < p + turn + 1)
              qmin = p + turn + 1;

            for (q = j; q >= qmin; q--) {
              if (hc->up_int[q + 1] < (j - q + k - 1))
                break;

              if ((evaluate_int(k, l, p, q, hc_dat_int)) &&
                  (c[indx[q] + p] != INF)) {
                type_2 = rtype[vrna_get_ptype(indx[q] + p, ptype)];

                u2 = k - 1 + j - q;
                if (u1 + u2 > MAXLOOP)
                  continue;

                tmpE = E_IntLoop(u1,
                                 u2,
                                 type,
                                 type_2,
                                 S1[l + 1],
                                 S1[k - 1],
                                 S1[p - 1],
                                 S1[q + 1],
                                 P);

                if (sc_int_pair_ext)
                  tmpE += sc_int_pair_ext(k, l, p, q, sc_dat_int);

                if (c[kl] + c[indx[q] + p] + tmpE + best_energy <= threshold)
                  /*
                   * ok, similar to the hairpin stuff, we add new states onto the stack R
                   * but in contrast to the hairpin decomposition, we have to add two new
                   * intervals, enclosed by k,l and p,q respectively and we also have to
                   * add the partial energy, that comes from the exterior interior loop
                   */
                  fork_two_states(k, l, p, q, state, tmpE, 2, 2, env);
              }
            }
          }
        }
      }
    }
  }

  /* and last but not least, we have a look, if we can do an exterior multiloop within the energy threshold */
  if (FcM <= threshold) {
    /*
     * this decomposition will be somehow more complicated...so lets see what we do here...
     * first we want to find out which split inidices we can use without exceeding the threshold
     */
    for (k = turn + 1; k < j - 2 * turn; k++) {
      if ((evaluate_mb(1, j, k, k + 1, VRNA_DECOMP_ML_ML_ML, hc_dat_mb)) &&
          (fML[indx[k] + 1] != INF) &&
          (fM2[k + 1] != INF)) {
        tmpE2 = fML[indx[k] + 1] + fM2[k + 1] + P->MLclosing;

        if (sc_mb_decomp_ml) {
          tmpE2 += sc_mb_decomp_ml(1,
                                   j,
                                   k,
                                   k + 1,
                                   sc_dat_mb);
        }

        if (tmpE2 + best_energy <= threshold) {
          /*
           * grmpfh, we have found a possible split index k so we have to split fM2 and fML now
           * lets do it first in fM2 anyway
           */
          for (l = k + turn + 2; l < j - turn - 1; l++) {
            if ((evaluate_mb(k + 1, j, l, l + 1, VRNA_DECOMP_ML_ML_ML, hc_dat_mb)) &&
                (fM1[indx[l] + k + 1] != INF) &&
                (fM1[indx[j] + l + 1] != INF)) {
              tmpE2 = fM1[indx[l] + k + 1] + fM1[indx[j] + l + 1];

              if (sc_mb_decomp_ml)
                tmpE2 += sc_mb_decomp_ml(k + 1, j, l, l + 1, sc_dat_mb);

              if (tmpE2 + fML[indx[k] + 1] + P->MLclosing <= threshold) {
                /*
                 * we've (hopefully) found a valid decomposition of fM2 and therefor we have all
                 * three intervals for our new state to be pushed on stack R
                 */
                new_state = copy_state(state);

                /* first interval leads for search in fML array */
                new_interval = make_interval(1, k, 1);
                push(new_state->Intervals, new_interval);
                env->nopush = false;

                /* next, we have the first interval that has to be traced in fM1 */
                new_interval = make_interval(k + 1, l, 3);
                push(new_state->Intervals, new_interval);
                env->nopush = false;

                /* and the last of our three intervals is also one to be traced within fM1 array... */
                new_interval = make_interval(l + 1, j, 3);
                push(new_state->Intervals, new_interval);
                env->nopush = false;

                /* mmh, we add the energy for closing the multiloop now... */
                new_state->partial_energy += P->MLclosing;
                /* next we push our state onto the R stack */
                push(env->Stack, new_state);
                env->nopush = false;
              }
            }

            /* else we search further... */
          }

          /* ok, we have to decompose fML now... */
        }
      }
    }
  }
}


PRIVATE INLINE void
scan_fms5(vrna_fold_compound_t  *fc,
          unsigned int          i,
          unsigned int          strand,
          int                   threshold,
          STATE                 *state,
          subopt_env            *env,
          constraint_helpers    *constraints_dat)
{
  char                      *ptype;
  short                     *S1, s5, s3;
  unsigned int              k, type, *sn, *se, end;
  int                       dangle_model, element_energy, best_energy, *c, *ggg, **fms5,
                            *indx, with_gquad;
  vrna_param_t              *P;
  vrna_md_t                 *md;
  struct hc_ext_def_dat     *hc_dat;
  vrna_hc_eval_f evaluate;
  struct sc_f5_dat          *sc_dat;
  sc_ext_red_cb             sc_red_ext;
  sc_ext_red_cb             sc_red_stem;
  sc_ext_red_cb             sc_decomp;

  STATE                     *temp_state;

  sn    = fc->strand_number;
  se    = fc->strand_end;
  indx  = fc->jindx;
  ptype = fc->ptype;
  S1    = fc->sequence_encoding;
  P     = fc->params;
  md    = &(P->model_details);

  dangle_model  = md->dangles;
  with_gquad    = md->gquad;

  c     = fc->matrices->c;
  ggg   = fc->matrices->ggg;
  fms5  = fc->matrices->fms5;

  hc_dat      = &(constraints_dat->hc_dat_ext);
  evaluate    = constraints_dat->hc_eval_ext;
  sc_dat      = &(constraints_dat->sc_dat_ext);
  sc_red_ext  = sc_dat->red_ext;
  sc_red_stem = sc_dat->red_stem;
  sc_decomp   = sc_dat->decomp;

  best_energy = best_attainable_energy(fc, state);  /* .. on remaining intervals */

  end = se[strand];

  /* no more pairs if too close to strand boundary ? */
  if (i + 1 > se[strand]) {
    state->partial_energy += fms5[strand][i];

    if (env->nopush) {
      push_back(env->Stack, state);
      env->nopush = false;
    }

    return;
  }

  /* find split in fms5 */

  if ((evaluate(i, end, i + 1, end, VRNA_DECOMP_EXT_EXT, hc_dat)) &&
      (fms5[strand][i] != INF)) {
    element_energy = 0;

    if (sc_red_ext)
      element_energy += sc_red_ext(i, end, i + 1, end, sc_dat);

    if (fms5[strand][i + 1] + element_energy + best_energy <= threshold)
      /* no basepair, nibbling of 5'-end */
      fork_state(i + 1, strand, state, element_energy, 4, env);
  }

  if (evaluate(i, end, i, end, VRNA_DECOMP_EXT_STEM, hc_dat)) {
    type = vrna_get_ptype(indx[end] + i, ptype);

    switch (dangle_model) {
      case 2:
        s5  = ((i > 1) && (sn[i - 1] == sn[i])) ? S1[i - 1] : -1;
        s3  = -1;
        break;

      default:
        s5  = -1;
        s3  = -1;
        break;
    }

    element_energy = vrna_E_ext_stem(type, s5, s3, P);

    if (sc_red_stem)
      element_energy += sc_red_stem(i, end, i, end, sc_dat);

    if (c[indx[end] + i] + element_energy + best_energy <= threshold) {
      repeat(fc,
             i,
             end,
             state,
             element_energy,
             0,
             best_energy,
             threshold,
             env,
             constraints_dat);
    }
  }

  if ((with_gquad) &&
      (ggg[indx[end] + i] != INF)) {
    element_energy = 0;

    if (sc_red_stem)
      element_energy += sc_red_stem(i, end, i, end, sc_dat);

    if (ggg[indx[end] + i] + element_energy + best_energy <= threshold) {
      repeat_gquad(fc,
                   i,
                   end,
                   state,
                   element_energy,
                   0,
                   best_energy,
                   threshold,
                   env,
                   constraints_dat);
    }
  }

  for (k = i + 1; k < end; k++) {
    if ((with_gquad) &&
        (fms5[strand][k + 1] != INF) &&
        (ggg[indx[k] + i] != INF)) {
      element_energy = 0;

      if (sc_decomp)
        element_energy += sc_decomp(i, end, k, k + 1, sc_dat);

      if (sc_red_stem)
        element_energy += sc_red_stem(i, k, i, k, sc_dat);

      if (fms5[strand][k + 1] + ggg[indx[k] + i] + element_energy + best_energy <= threshold) {
        temp_state  = derive_new_state(k + 1, strand, state, 0, 4);
        env->nopush = false;
        repeat_gquad(fc,
                     i,
                     k,
                     temp_state,
                     element_energy,
                     fms5[strand][k + 1],
                     best_energy,
                     threshold,
                     env,
                     constraints_dat);
        free_state_node(temp_state);
      }
    }

    if (evaluate(i, end, k, k + 1, VRNA_DECOMP_EXT_STEM_EXT, hc_dat)) {
      type = vrna_get_ptype(indx[k] + i, ptype);

      switch (dangle_model) {
        case 2:
          s5  = ((i > 1) && (sn[i - 1] == sn[i])) ? S1[i - 1] : -1;
          s3  = (sn[k] == sn[k + 1]) ? S1[k + 1] : -1;
          break;

        default:
          s5  = -1;
          s3  = -1;
          break;
      }

      element_energy = vrna_E_ext_stem(type, s5, s3, P);

      if (sc_decomp)
        element_energy += sc_decomp(i, end, k, k + 1, sc_dat);

      if (sc_red_stem)
        element_energy += sc_red_stem(i, k, i, k, sc_dat);

      if (fms5[strand][k + 1] + c[indx[k] + i] + element_energy + best_energy <= threshold) {
        temp_state  = derive_new_state(k + 1, strand, state, 0, 4);
        env->nopush = false;
        repeat(fc,
               i,
               k,
               temp_state,
               element_energy,
               fms5[strand][k + 1],
               best_energy,
               threshold,
               env,
               constraints_dat);
        free_state_node(temp_state);
      }
    }
  }
}


PRIVATE INLINE void
scan_fms3(vrna_fold_compound_t  *fc,
          unsigned int          i,
          unsigned int          strand,
          int                   threshold,
          STATE                 *state,
          subopt_env            *env,
          constraint_helpers    *constraints_dat)
{
  char                      *ptype;
  short                     *S1, s5, s3;
  unsigned int              *sn, *ss, start, k, type;
  int                       dangle_model, element_energy, best_energy, *c, *ggg, **fms3, length,
                            *indx, with_gquad;
  vrna_param_t              *P;
  vrna_md_t                 *md;
  struct hc_ext_def_dat     *hc_dat;
  vrna_hc_eval_f evaluate;
  struct sc_f5_dat          *sc_dat;
  sc_ext_red_cb             sc_red_ext;
  sc_ext_red_cb             sc_red_stem;
  sc_ext_red_cb             sc_decomp;
  STATE                     *temp_state;

  length  = fc->length;
  sn      = fc->strand_number;
  ss      = fc->strand_start;
  indx    = fc->jindx;
  ptype   = fc->ptype;
  S1      = fc->sequence_encoding;
  P       = fc->params;
  md      = &(P->model_details);

  dangle_model  = md->dangles;
  with_gquad    = md->gquad;

  c     = fc->matrices->c;
  ggg   = fc->matrices->ggg;
  fms3  = fc->matrices->fms3;

  start = ss[strand];

  hc_dat      = &(constraints_dat->hc_dat_ext);
  evaluate    = constraints_dat->hc_eval_ext;
  sc_dat      = &(constraints_dat->sc_dat_ext);
  sc_red_ext  = sc_dat->red_ext;
  sc_red_stem = sc_dat->red_stem;
  sc_decomp   = sc_dat->decomp;

  best_energy = best_attainable_energy(fc, state);  /* .. on remaining intervals */

  /* no more pairs if too close to strand boundary ? */
  if (i < ss[strand] + 1) {
    state->partial_energy += fms3[strand][i];

    if (env->nopush) {
      push_back(env->Stack, state);
      env->nopush = false;
    }

    return;
  }

  if ((evaluate(start, i, start, i - 1, VRNA_DECOMP_EXT_EXT, hc_dat)) &&
      (fms3[strand][i - 1] != INF)) {
    element_energy = 0;

    if (sc_red_ext)
      element_energy += sc_red_ext(start, i, start, i - 1, sc_dat);

    if (fms3[strand][i - 1] + element_energy + best_energy <= threshold)
      /* no basepair, nibbling of 5'-end */
      fork_state(i - 1, strand, state, element_energy, 5, env);
  }

  if (evaluate(start, i, start, i, VRNA_DECOMP_EXT_STEM, hc_dat)) {
    type = vrna_get_ptype(indx[i] + start, ptype);

    switch (dangle_model) {
      case 2:
        s5  = -1;
        s3  = ((i < length) && (sn[i] == sn[i + 1])) ? S1[i + 1] : -1;
        break;

      default:
        s5 = s3 = -1;
        break;
    }

    element_energy = vrna_E_ext_stem(type, s5, s3, P);

    if (sc_red_stem)
      element_energy += sc_red_stem(start, i, start, i, sc_dat);

    if (c[indx[i] + start] + element_energy + best_energy <= threshold) {
      repeat(fc,
             start,
             i,
             state,
             element_energy,
             0,
             best_energy,
             threshold,
             env,
             constraints_dat);
    }
  }

  if ((with_gquad) && (ggg[indx[i] + start] != INF)) {
    element_energy = 0;

    if (sc_red_stem)
      element_energy += sc_red_stem(start, i, start, i, sc_dat);

    if (ggg[indx[i] + start] + element_energy + best_energy <= threshold) {
      repeat_gquad(fc,
                   start,
                   i,
                   state,
                   element_energy,
                   0,
                   best_energy,
                   threshold,
                   env,
                   constraints_dat);
    }
  }

  for (k = start; k < i; k++) {
    if ((with_gquad) &&
        (fms3[strand][k] != INF) &&
        (ggg[indx[i] + k + 1] != INF)) {
      element_energy = 0;

      if (sc_decomp)
        element_energy += sc_decomp(start, i, k, k + 1, sc_dat);

      if (sc_red_stem)
        element_energy += sc_red_stem(k + 1, i, k + 1, i, sc_dat);

      if (fms3[strand][k] + ggg[indx[i] + k + 1] + element_energy + best_energy <= threshold) {
        temp_state  = derive_new_state(k, strand, state, 0, 5);
        env->nopush = false;
        repeat_gquad(fc,
                     k + 1,
                     i,
                     temp_state,
                     element_energy,
                     fms3[strand][k],
                     best_energy,
                     threshold,
                     env,
                     constraints_dat);
        free_state_node(temp_state);
      }
    }

    if (evaluate(start, i, k, k + 1, VRNA_DECOMP_EXT_EXT_STEM, hc_dat)) {
      type = vrna_get_ptype(indx[i] + k + 1, ptype);

      switch (dangle_model) {
        case 2:
          s5  = (sn[k] == sn[k + 1]) ? S1[k] : -1;
          s3  = ((i < length) && (sn[i] == sn[i + 1])) ? S1[i + 1] : -1;
          break;

        default:
          s5 = s3 = -1;
          break;
      }

      element_energy = vrna_E_ext_stem(type, s5, s3, P);

      if (sc_decomp)
        element_energy += sc_decomp(start, i, k, k + 1, sc_dat);

      if (sc_red_stem)
        element_energy += sc_red_stem(k + 1, i, k + 1, i, sc_dat);

      if (fms3[strand][k] + c[indx[i] + k + 1] + element_energy + best_energy <= threshold) {
        temp_state  = derive_new_state(k, strand, state, 0, 5);
        env->nopush = false;
        repeat(fc,
               k + 1,
               i,
               temp_state,
               element_energy,
               fms3[strand][k],
               best_energy,
               threshold,
               env,
               constraints_dat);
        free_state_node(temp_state);
      }
    }
  }
}


PRIVATE INLINE void
scan_gquad(vrna_fold_compound_t *fc,
           int                  i,
           int                  j,
           int                  threshold,
           STATE                *state,
           subopt_env           *env,
           constraint_helpers   *constraints_dat)
{
  int best_energy;

  best_energy = best_attainable_energy(fc, state);  /* .. on remaining intervals */

  /* we have a gquad */
  repeat_gquad(fc,
               i,
               j,
               state,
               0,
               0,
               best_energy,
               threshold,
               env,
               constraints_dat);

  if (env->nopush)
    vrna_message_warning("%d,%d\nOops, no solution in gquad-repeat!", i, j);
}


PRIVATE void
repeat_gquad(vrna_fold_compound_t *fc,
             int                  i,
             int                  j,
             STATE                *state,
             int                  part_energy,
             int                  temp_energy,
             int                  best_energy,
             int                  threshold,
             subopt_env           *env,
             constraint_helpers   *constraints_dat)
{
  short         *S1;
  unsigned int  *sn;
  int           *ggg, *indx, element_energy, cnt, *L, *l, num_gquads;
  vrna_param_t  *P;

  indx  = fc->jindx;
  sn    = fc->strand_number;
  ggg   = fc->matrices->ggg;
  S1    = fc->sequence_encoding;
  P     = fc->params;


  /* find all gquads that fit into the energy range and the interval [i,j] */
  STATE *new_state;

  best_energy += part_energy; /* energy of current structural element */
  best_energy += temp_energy; /* energy from unpushed interval */

  if (sn[i] == sn[j]) {
    element_energy = ggg[indx[j] + i];
    if ((element_energy != INF) &&
        (element_energy + best_energy <= threshold)) {
      /* find out how many gquads we might expect in the interval [i,j] */
      num_gquads = get_gquad_count(S1, i, j);
      num_gquads++;
      L     = (int *)vrna_alloc(sizeof(int) * num_gquads);
      l     = (int *)vrna_alloc(sizeof(int) * num_gquads * 3);
      L[0]  = -1;

      get_gquad_pattern_exhaustive(S1, i, j, P, L, l, threshold - best_energy);

      for (cnt = 0; L[cnt] != -1; cnt++) {
        new_state = copy_state(state);
        make_gquad(i, L[cnt], &(l[3 * cnt]), new_state);
        new_state->partial_energy += part_energy;
        /* re-compute energies */
        new_state->partial_energy += E_gquad(L[cnt], &(l[3*cnt]), P);
        /* new_state->best_energy =
         * hairpin[unpaired] + element_energy + best_energy; */
        push(env->Stack, new_state);
        env->nopush = false;
      }
      free(L);
      free(l);
    }
  }

  best_energy -= part_energy;
  best_energy -= temp_energy;
  return;
}


PRIVATE void
repeat(vrna_fold_compound_t *fc,
       int                  i,
       int                  j,
       STATE                *state,
       int                  part_energy,
       int                  temp_energy,
       int                  best_energy,
       int                  threshold,
       subopt_env           *env,
       constraint_helpers   *constraints_dat)
{
  /*
   * routine to find stacks, bulges, internal loops and  multiloops
   * within interval closed by basepair i,j
   */

  char                      *ptype;
  short                     *S1;
  unsigned int              n, *sn, *se, nick;
  int                       ij, k, p, q, energy, new, mm, no_close, type, type_2, element_energy,
                            *c, *fML, *fM1, *ggg, **fms5, **fms3, rt, *indx, *rtype, noGUclosure,
                            noLP, with_gquad, dangle_model, minq, eee, aux_eee, cnt, *ps, *qs,
                            *en, tmp_en;
  vrna_param_t              *P;
  vrna_md_t                 *md;
  vrna_hc_t                 *hc;
  vrna_sc_t                 *sc;
  struct hc_int_def_dat     *hc_dat_int;
  struct hc_ext_def_dat     *hc_dat_ext;
  struct hc_mb_def_dat      *hc_dat_mb;
  eval_hc                   evaluate_int;
  vrna_hc_eval_f evaluate_ext;
  vrna_hc_eval_f evaluate_mb;

  struct sc_int_dat         *sc_dat_int;
  struct sc_mb_dat          *sc_dat_mb;
  sc_int_cb                 sc_int_pair;
  sc_mb_pair_cb             sc_mb_pair;
  sc_mb_red_cb              sc_mb_decomp_ml;
  STATE                     *new_state;

  n     = fc->length;
  S1    = fc->sequence_encoding;
  ptype = fc->ptype;
  indx  = fc->jindx;
  sn    = fc->strand_number;
  se    = fc->strand_end;
  P     = fc->params;
  md    = &(P->model_details);
  rtype = &(md->rtype[0]);

  noGUclosure   = md->noGUclosure;
  noLP          = md->noLP;
  with_gquad    = md->gquad;
  dangle_model  = md->dangles;

  c     = fc->matrices->c;
  fML   = fc->matrices->fML;
  fM1   = fc->matrices->fM1;
  ggg   = fc->matrices->ggg;
  fms5  = fc->matrices->fms5;
  fms3  = fc->matrices->fms3;

  hc = fc->hc;

  hc_dat_ext    = &(constraints_dat->hc_dat_ext);
  hc_dat_int    = &(constraints_dat->hc_dat_int);
  hc_dat_mb     = &(constraints_dat->hc_dat_mb);
  evaluate_ext  = constraints_dat->hc_eval_ext;
  evaluate_int  = constraints_dat->hc_eval_int;
  evaluate_mb   = constraints_dat->hc_eval_mb;

  sc              = fc->sc;
  sc_dat_int      = &(constraints_dat->sc_dat_int);
  sc_dat_mb       = &(constraints_dat->sc_dat_mb);
  sc_int_pair     = constraints_dat->sc_dat_int.pair;
  sc_mb_pair      = constraints_dat->sc_dat_mb.pair;
  sc_mb_decomp_ml = constraints_dat->sc_dat_mb.decomp_ml;

  ij = indx[j] + i;

  type = vrna_get_ptype(ij, ptype);
  /*
   * if (type==0) fprintf(stderr, "repeat: Warning: %d %d can't pair\n", i,j);
   */

  no_close = (((type == 3) || (type == 4)) && noGUclosure);

  if ((noLP) &&
      (i + 2 < j)) {
    /* always consider the structure with additional stack */
    if (evaluate_int(i, j, i + 1, j - 1, hc_dat_int)) {
      type_2  = rtype[vrna_get_ptype(indx[j - 1] + i + 1, ptype)];
      energy  = 0;

      energy = E_IntLoop(0, 0, type, type_2, S1[i + 1], S1[j - 1], S1[i + 1], S1[j - 1], P);

      if (sc_int_pair) {
        energy += sc_int_pair(i,
                              j,
                              i + 1,
                              j - 1,
                              sc_dat_int);
      }

      new_state = derive_new_state(i + 1, j - 1, state, part_energy + energy, 2);
      make_pair(i, j, new_state);
      make_pair(i + 1, j - 1, new_state);

      /* new_state->best_energy = new + best_energy; */
      push(env->Stack, new_state);
      env->nopush = false;
      if (i == 1 || state->structure[i - 2] != '(' || state->structure[j] != ')')
        /* adding a stack is the only possible structure */
        return;
    }
  }

  best_energy += part_energy; /* energy of current structural element */
  best_energy += temp_energy; /* energy from unpushed interval */

  if (hc->mx[n * i + j] & VRNA_CONSTRAINT_CONTEXT_INT_LOOP) {
    for (p = i + 1; p <= MIN2(j - 2, i + MAXLOOP + 1); p++) {
      minq = j - i + p - MAXLOOP - 2;
      if (minq < p + 1)
        minq = p + 1;

      if (hc->up_int[i + 1] < (p - i - 1))
        break;

      for (q = j - 1; q >= minq; q--) {
        if (hc->up_int[q + 1] < (j - q - 1))
          break;

        /* skip stack if noLP, since we've already processed it above */
        if ((noLP) && (p == i + 1) && (q == j - 1))
          continue;

        if (!(hc->mx[n * p + q] & VRNA_CONSTRAINT_CONTEXT_INT_LOOP_ENC))
          continue;

        if (c[indx[q] + p] == INF)
          continue;

        type_2 = vrna_get_ptype(indx[q] + p, ptype);

        if (noGUclosure)
          if (no_close || (type_2 == 3) || (type_2 == 4))
            if ((p > i + 1) || (q < j - 1))
              continue;

        if (evaluate_int(i, j, p, q, hc_dat_int)) {
          energy = E_IntLoop(p - i - 1, j - q - 1, type, rtype[type_2],
                             S1[i + 1], S1[j - 1], S1[p - 1], S1[q + 1], P);

          new = energy + c[indx[q] + p];

          if (sc_int_pair)
            energy += sc_int_pair(i, j, p, q, sc_dat_int);

          new = energy + c[indx[q] + p];

          if (new + best_energy <= threshold)
            /* stack, bulge, or interior loop */
            fork_int_state(i, j, p, q, state, part_energy + energy, env);
        } /*end of if block */
      }   /* end of q-loop */
    }     /* end of p-loop */
  }

  /* base pair (i,j) encloses a loop with strand nick? */
  if ((sn[i] != sn[j]) &&
      (evaluate_ext(i, j, i, j, VRNA_DECOMP_EXT_STEM, hc_dat_ext))) {
    rt = rtype[type];

    element_energy = P->DuplexInit;

    switch (dangle_model) {
      case 0:
        element_energy += vrna_E_ext_stem(rt, -1, -1, P);
        break;
      default:
        element_energy +=
          vrna_E_ext_stem(rt,
                          (sn[j - 1] == sn[j]) ?
                          S1[j - 1] :
                          -1,
                          (sn[i] == sn[i + 1]) ?
                          S1[i + 1] :
                          -1,
                          P);
        break;
    }

    if ((sc) && (sc->f))
      element_energy += sc->f(i, j, i, j, VRNA_DECOMP_EXT_STEM, sc->data);

    if (sn[i] != sn[i + 1]) {
      if ((sn[j - 1] != sn[j]) &&
          (i + 1 == j)) {
        if (element_energy + best_energy <= threshold) {
          fork_state_pair(i,
                          j,
                          state,
                          part_energy + element_energy,
                          env);
        }
      } else if (sn[j - 1] == sn[j]) {
        if (fms3[sn[i + 1]][j - 1] + element_energy + best_energy <= threshold) {
          /* continue backtracking in fms3[sn[i + 1]][j - 1] */
          fork_state_pair_interval(i,
                                   j,
                                   j - 1,
                                   sn[i + 1],
                                   state,
                                   part_energy + element_energy,
                                   5,
                                   env);
        }
      }
    } else if (sn[j - 1] != sn[j]) {
      if (fms5[sn[j - 1]][i + 1] + element_energy + best_energy <= threshold) {
        /* continue backtracking in fms5[sn[j - 1]][i + 1] */
        fork_state_pair_interval(i,
                                 j,
                                 i + 1,
                                 sn[j - 1],
                                 state,
                                 part_energy + element_energy,
                                 4,
                                 env);
      }
    } else {
      energy = 0;

      if (se[sn[i]] > i)
        energy += fms5[sn[i]][i + 1];

      if (j - 1 > se[sn[i]])
        energy += fms3[sn[se[sn[i]] + 1]][j - 1];

      if (energy + element_energy + best_energy <= threshold) {
        fork_two_states_pair_ms(i,
                                j,
                                sn[i],
                                sn[se[sn[i]] + 1],
                                state,
                                part_energy + element_energy,
                                env);
      }

      nick = se[sn[i]] + 1;

      while (sn[nick] != sn[j]) {
        energy = 0;
        if (i + 1 <= se[sn[nick]])
          energy += fms5[sn[nick]][i + 1];

        if (se[sn[nick]] + 1 <= j - 1)
          energy += fms3[sn[se[sn[nick]] + 1]][j - 1];

        if (energy + element_energy + best_energy <= threshold) {
          fork_two_states_pair_ms(i,
                                  j,
                                  sn[nick],
                                  sn[se[sn[nick]] + 1],
                                  state,
                                  part_energy + element_energy,
                                  env);
        }

        nick = se[sn[nick]] + 1;
      }
    }
  }

  mm  = P->MLclosing;
  rt  = rtype[type];

  if (evaluate_mb(i, j, i + 1, j - 1, VRNA_DECOMP_PAIR_ML, hc_dat_mb)) {
    element_energy = mm;
    switch (dangle_model) {
      case 0:
        element_energy = E_MLstem(rt, -1, -1, P) + mm;
        break;
      default:
        element_energy = E_MLstem(rt, S1[j - 1], S1[i + 1], P) + mm;
        break;
    }

    if (sc_mb_pair)
      element_energy += sc_mb_pair(i, j, sc_dat_mb);

    /* multiloop decomposition */
    for (k = i + 2; k <= j - 2; k++) {
      if (evaluate_mb(i + 1, j - 1, k - 1, k, VRNA_DECOMP_ML_ML_ML, hc_dat_mb)) {
        eee = fML[indx[k - 1] + i + 1];

        if ((eee != INF) &&
            (fM1[indx[j - 1] + k] != INF)) {
          eee += fM1[indx[j - 1] + k] +
                 best_energy;

          aux_eee = element_energy;

          if (sc_mb_decomp_ml)
            aux_eee += sc_mb_decomp_ml(i + 1, j - 1, k - 1, k, sc_dat_mb);

          if ((eee + aux_eee) <= threshold)
            fork_two_states_pair(i, j, k, state, part_energy + aux_eee, 1, 3, env);
        }
      }
    }
  }

  if (sn[i] == sn[j]) {
    if (!no_close) {
      element_energy = vrna_E_hp_loop(fc, i, j);

      if (element_energy != INF) {
        if (element_energy + best_energy <= threshold)
          /* hairpin structure */
          fork_state_pair(i, j, state, part_energy + element_energy, env);
      }
    }

    if (with_gquad) {
      /* now we have to find all loops where (i,j) encloses a gquad in an interior loops style */
      ps  = qs = en = NULL;
      en  = E_GQuad_IntLoop_exhaustive(i,
                                       j,
                                       &ps,
                                       &qs,
                                       type,
                                       S1,
                                       ggg,
                                       threshold - best_energy,
                                       indx,
                                       P);
      for (cnt = 0; ps[cnt] != -1; cnt++) {
        if ((hc->up_int[i + 1] >= ps[cnt] - i - 1) &&
            (hc->up_int[qs[cnt] + 1] >= j - qs[cnt] - 1)) {
          tmp_en = en[cnt];

          if (sc_int_pair)
            tmp_en += sc_int_pair(i, j, ps[cnt], qs[cnt], sc_dat_int);

          new_state = derive_new_state(ps[cnt], qs[cnt], state, tmp_en + part_energy, 6);

          make_pair(i, j, new_state);

          /* new_state->best_energy = new + best_energy; */
          push(env->Stack, new_state);
          env->nopush = false;
        }
      }
      free(en);
      free(ps);
      free(qs);
    }
  }

  best_energy -= part_energy;
  best_energy -= temp_energy;
  return;
}


PRIVATE void
old_subopt_print(const char *structure,
                 float      energy,
                 void       *data)
{
  struct old_subopt_dat *d = (struct old_subopt_dat *)data;

  if (structure && d->fp) {
    char *e_string = vrna_strdup_printf(" %6.2f", energy);
    print_structure(d->fp, structure, e_string);
    free(e_string);
  }
}


PRIVATE void
old_subopt_store(const char *structure,
                 float      energy,
                 void       *data)
{
  struct old_subopt_dat *d = (struct old_subopt_dat *)data;

  /* store solution */
  if (d->n_sol + 1 == d->max_sol) {
    d->max_sol      *= 2;
    d->SolutionList =
      (vrna_subopt_solution_t *)vrna_realloc(d->SolutionList,
                                             d->max_sol * sizeof(vrna_subopt_solution_t));
  }

  if (structure) {
    d->SolutionList[d->n_sol].energy      = energy;
    d->SolutionList[d->n_sol++].structure = strdup(structure);
  } else {
    d->SolutionList[d->n_sol].energy      = 0;
    d->SolutionList[d->n_sol++].structure = NULL;
  }
}


PRIVATE void
old_subopt_store_compressed(const char  *structure,
                            float       energy,
                            void        *data)
{
  struct old_subopt_dat *d = (struct old_subopt_dat *)data;

  /* store solution */
  if (d->n_sol + 1 == d->max_sol) {
    d->max_sol      *= 2;
    d->SolutionList =
      (vrna_subopt_solution_t *)vrna_realloc(d->SolutionList,
                                             d->max_sol * sizeof(vrna_subopt_solution_t));
  }

  if (structure) {
    d->SolutionList[d->n_sol].energy = energy;
    if (d->strands > 1) {
      char **tok  = vrna_strsplit(structure, NULL);
      char *s     = vrna_strjoin((const char **)tok, NULL);

      for (char **ptr = tok; *ptr!= NULL; ptr++)
        free(*ptr);
      free(tok);

      d->SolutionList[d->n_sol++].structure = vrna_db_pack(s);
      free(s);
    } else {
      d->SolutionList[d->n_sol++].structure = vrna_db_pack(structure);
    }
  } else {
    d->SolutionList[d->n_sol].energy      = 0;
    d->SolutionList[d->n_sol++].structure = NULL;
  }
}


/*
 * ###########################################
 * # deprecated functions below              #
 *###########################################
 */
#ifndef VRNA_DISABLE_BACKWARD_COMPATIBILITY

PUBLIC SOLUTION *
subopt(char *seq,
       char *structure,
       int  delta,
       FILE *fp)
{
  return wrap_subopt(seq, structure, NULL, delta, fold_constrained, 0, fp);
}


PUBLIC SOLUTION *
subopt_circ(char  *seq,
            char  *structure,
            int   delta,
            FILE  *fp)
{
  return wrap_subopt(seq, structure, NULL, delta, fold_constrained, 1, fp);
}


PUBLIC SOLUTION *
subopt_par(char         *seq,
           char         *structure,
           vrna_param_t *parameters,
           int          delta,
           int          is_constrained,
           int          is_circular,
           FILE         *fp)
{
  return wrap_subopt(seq, structure, parameters, delta, is_constrained, is_circular, fp);
}


PRIVATE SOLUTION *
wrap_subopt(char          *string,
            char          *structure,
            vrna_param_t  *parameters,
            int           delta,
            int           is_constrained,
            int           is_circular,
            FILE          *fp)
{
  vrna_fold_compound_t  *fc;
  vrna_param_t          *P;
  char                  *seq;

#ifdef _OPENMP
  /* Explicitly turn off dynamic threads */
  omp_set_dynamic(0);
#endif

  /* we need the parameter structure for hard constraints */
  if (parameters) {
    P = vrna_params_copy(parameters);
  } else {
    vrna_md_t md;
    set_model_details(&md);
    md.temperature  = temperature;
    P               = vrna_params(&md);
  }

  P->model_details.circ     = is_circular;
  P->model_details.uniq_ML  = uniq_ML = 1;

  /*
   * what about cofold sequences here? Is it safe to call the below cut_point_insert() ?
   * dirty hack to reinsert the '&' according to the global variable 'cut_point'
   */
  seq = vrna_cut_point_insert(string, cut_point);

  fc =
    vrna_fold_compound(seq,
                       &(P->model_details),
                       ((is_circular == 0) ? VRNA_OPTION_HYBRID : VRNA_OPTION_DEFAULT));

  if (parameters) {
    /* replace params if necessary */
    free(fc->params);
    fc->params = P;
  } else {
    free(P);
  }

  /* handle hard constraints in pseudo dot-bracket format if passed via simple interface */
  if (is_constrained && structure) {
    unsigned int constraint_options = 0;
    constraint_options |= VRNA_CONSTRAINT_DB
                          | VRNA_CONSTRAINT_DB_PIPE
                          | VRNA_CONSTRAINT_DB_DOT
                          | VRNA_CONSTRAINT_DB_X
                          | VRNA_CONSTRAINT_DB_ANG_BRACK
                          | VRNA_CONSTRAINT_DB_RND_BRACK
                          | VRNA_CONSTRAINT_DB_INTRAMOL
                          | VRNA_CONSTRAINT_DB_INTERMOL;

    vrna_constraints_add(fc, (const char *)structure, constraint_options);
  }

  if (backward_compat_compound && backward_compat)
    vrna_fold_compound_free(backward_compat_compound);

  backward_compat_compound  = fc;
  backward_compat           = 1;

  /* cleanup */
  free(seq);

  return vrna_subopt(fc, delta, subopt_sorted, fp);
}


#endif

/*
 * ---------------------------------------------------------------------------
 * Well, that is the end!----------------------------------------------------
 *---------------------------------------------------------------------------
 */
