/*
 *                centroid structure prediction
 *
 *                Ivo L Hofacker + Ronny Lorenz
 *                Vienna RNA package
 */

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

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

#include "ViennaRNA/utils/basic.h"
#include "ViennaRNA/fold_vars.h"
#include "ViennaRNA/gquad.h"
#include "ViennaRNA/centroid.h"

/*
 #################################
 # GLOBAL VARIABLES              #
 #################################
 */

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

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

/*
 #################################
 # BEGIN OF FUNCTION DEFINITIONS #
 #################################
 */

/* compute the centroid structure of the ensemble, i.e. the strutcure
 * with the minimal average distance to all other structures
 * <d(S)> = \sum_{(i,j) \in S} (1-p_{ij}) + \sum_{(i,j) \notin S} p_{ij}
 * Thus, the centroid is simply the structure containing all pairs with
 * p_ij>0.5
 */
PUBLIC char *
vrna_centroid_from_plist(int        length,
                         double     *dist,
                         vrna_ep_t  *pl)
{
  int   i;
  char  *centroid;

  if (pl == NULL) {
    vrna_message_warning("vrna_centroid_from_plist: "
                         "pl == NULL!");
    return NULL;
  }

  *dist     = 0.;
  centroid  = (char *)vrna_alloc((length + 1) * sizeof(char));
  for (i = 0; i < length; i++)
    centroid[i] = '.';
  for (i = 0; pl[i].i > 0; i++) {
    if ((pl[i].p) > 0.5) {
      centroid[pl[i].i - 1] = '(';
      centroid[pl[i].j - 1] = ')';
      *dist                 += (1 - pl[i].p);
    } else {
      *dist += pl[i].p;
    }
  }
  centroid[length] = '\0';
  return centroid;
}


/* compute the centroid structure of the ensemble, i.e. the strutcure
 * with the minimal average distance to all other structures
 * <d(S)> = \sum_{(i,j) \in S} (1-p_{ij}) + \sum_{(i,j) \notin S} p_{ij}
 * Thus, the centroid is simply the structure containing all pairs with
 * p_ij>0.5
 */
PUBLIC char *
vrna_centroid_from_probs(int        length,
                         double     *dist,
                         FLT_OR_DBL *probs)
{
  int         i, j;
  FLT_OR_DBL  p;
  char        *centroid;
  int         *index = vrna_idx_row_wise(length);

  if (probs == NULL) {
    vrna_message_warning("vrna_centroid_from_probs: "
                         "probs == NULL!");
    return NULL;
  }

  *dist     = 0.;
  centroid  = (char *)vrna_alloc((length + 1) * sizeof(char));
  for (i = 0; i < length; i++)
    centroid[i] = '.';
  for (i = 1; i <= length; i++)
    for (j = i + 1; j <= length; j++) {
      if ((p = probs[index[i] - j]) > 0.5) {
        centroid[i - 1] = '(';
        centroid[j - 1] = ')';
        *dist           += (1 - p);
      } else {
        *dist += p;
      }
    }
  free(index);
  centroid[length] = '\0';
  return centroid;
}


/* compute the centroid structure of the ensemble, i.e. the strutcure
 * with the minimal average distance to all other structures
 * <d(S)> = \sum_{(i,j) \in S} (1-p_{ij}) + \sum_{(i,j) \notin S} p_{ij}
 * Thus, the centroid is simply the structure containing all pairs with
 * p_ij>0.5
 */
PUBLIC char *
vrna_centroid(vrna_fold_compound_t  *vc,
              double                *dist)
{
  int               i, j, k, length;
  FLT_OR_DBL        p;
  char              *centroid;
  short             *S;
  vrna_mx_pf_t      *matrices;
  FLT_OR_DBL        *probs;
  int               *my_iindx;
  vrna_exp_param_t  *pf_params;


  if (!vc) {
    vrna_message_warning("vrna_centroid: "
                         "run vrna_pf_fold first!");
    return NULL;
  } else if (!vc->exp_matrices->probs) {
    vrna_message_warning("vrna_centroid: "
                         "probs == NULL!");
    return NULL;
  }

  length    = vc->length;
  pf_params = vc->exp_params;
  S         = (vc->type == VRNA_FC_TYPE_SINGLE) ? vc->sequence_encoding2 : vc->S_cons;
  my_iindx  = vc->iindx;

  matrices  = vc->exp_matrices;
  probs     = matrices->probs;

  *dist     = 0.;
  centroid  = (char *)vrna_alloc((length + 1) * sizeof(char));
  for (i = 0; i < length; i++)
    centroid[i] = '.';
  for (i = 1; i <= length; i++)
    for (j = i + 1; j <= length; j++) {
      if ((p = probs[my_iindx[i] - j]) > 0.5) {
        if (pf_params->model_details.gquad) {
          /* check for presence of gquadruplex */
          if ((S[i] == 3) && (S[j] == 3)) {
            int L, l[3];
            get_gquad_pattern_pf(S, i, j, pf_params, &L, l);
            for (k = 0; k < L; k++) {
              centroid[i + k - 1] \
                      = centroid[i + k + L + l[0] - 1] \
                      = centroid[i + k + 2 * L + l[0] + l[1] - 1] \
                      = centroid[i + k + 3 * L + l[0] + l[1] + l[2] - 1] \
                      = '+';
            }
            /* skip everything within the gquad */
            i     = j;
            j     = j + 1;
            *dist += (1 - p); /* right? */
            break;
          }
        }

        /* regular base pair */
        centroid[i - 1] = '(';
        centroid[j - 1] = ')';
        *dist           += (1 - p);
      } else {
        *dist += p;
      }
    }

  centroid[length] = '\0';
  return centroid;
}


/*
 * ###########################################
 * # deprecated functions below              #
 *###########################################
 */


/* this function is a threadsafe replacement for centroid() */
PUBLIC char *
get_centroid_struct_pl(int        length,
                       double     *dist,
                       vrna_ep_t  *pl)
{
  return vrna_centroid_from_plist(length, dist, pl);
}


/* this function is a threadsafe replacement for centroid() */
PUBLIC char *
get_centroid_struct_pr(int        length,
                       double     *dist,
                       FLT_OR_DBL *probs)
{
  return vrna_centroid_from_probs(length, dist, probs);
}
