/*! \file rank.c
  \brief statistical ranking

  Functions for statistical (fractional) ranking
*/

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

#include "stat_rank.h"

/*! \brief Create a DRank object
 * 
 * A item object holds a double value, its original index, and its rank.
 *
 * The rank is initialized with -1, and changed to a positive one (starting from 1) by sortRankDRankList. This is used to check whether that function has been run or not, so please do not change the initial value of rank.
 */

DRank createDRank(const double* ptr, int index) {
  DRank it=(DRank)malloc(sizeof(DRankStruct)); // TAKE CARE: use sizeof(DRank) here will produce bugs that are very difficult to debug
  it->index=index;
  it->vPtr=ptr;
  it->rank=-1.0;
  return(it);
}

/*! \brief destroy an item object */
void destroyDRank(DRank it) {
  it->vPtr=NULL;
  free(it);
}

/*! \brief clear an DRank object */
void clearDRank(DRank it) {
  it->vPtr=NULL;
  it->rank=-1.0;
  it->index=-1;
  free(it);
}

/*! \brief compare item objects by value */
int compareDRank (const void* a, const void* b) // dereference void pointer: *((T*)ptr)
{
  if(*(*(DRank*)a)->vPtr>*(*(DRank*)b)->vPtr) {
    return 1;
  } else if(*(*(DRank*)a)->vPtr==*(*(DRank*)b)->vPtr) {
    return 0;
  } else 
    return -1; // long debug needed: note it must return an integer!
}

/*! \brief compare item objects by input index */
int compareDRankIndex (const void* a, const void* b) // dereference void pointer: *((T*)ptr)
{
  return ((*(DRank*)a)->index-(*(DRank*)b)->index);

}

/*! \brief create an DRankList object 
 \param array: a double array
 \param len: the length of the double array
*/
DRankList createDRankList(const double* array, int len) {
  int i;
  DRankList res=(DRankList)malloc(sizeof(DRankListStruct));
  res->len=len;
  res->ulen=-1;
  res->list=(DRank*)malloc(len*sizeof(DRank));
  for(i=0;i<len;++i) {
    res->list[i]=createDRank(array+i, i);
  }
  return(res);
}

void destroyDRankList(DRankList list) {
  int i;
  for(i=0;i<list->len;i++)
    destroyDRank(list->list[i]);
  free(list->list);
  free(list);
}

void clearDRankList(DRankList list) {
  int i;
  for(i=0;i<list->len;i++)
    clearDRank(list->list[i]);
  list->ulen=-1;
  list->tieCoef=1.0;
}

/*! \brief print an DRankList object
*/
#ifdef DEBUG
void printDRankList(const DRankList list) {
  int i=0;
  printf("--DRankList (Len=%d, UniqLen=%d)--\n",
	 list->len, list->ulen);
  for(i=0;i<list->len;i++)
    printf("ilist[%d]=%.2f, index=%d, rank=%.1f\n",
	   i, 
	   *(list->list[i]->vPtr), 
	   list->list[i]->index,
	   list->list[i]->rank);
}
#endif

/*! \brief test whether the DRankList has been ranked 
 *
 * if sortRankDRankList has been run, the value will be 1, otherwise 0.
 */
int isRanked(const DRankList list) {return(list->list[0]->rank>0);}

/*! \brief: sort and gives rank to a DRankList
 *
 *  sortRankDRankList sorts and gives statistical (fractional) rank to a DRankList.
 * 
 *  sortRankDRankList runs once and only once (controlled by isRanked):
 *  Once the ranks have been set (i.e. ranks>0), the function will exit
 *  without doing anything.
 */ 
void sortRankDRankList(DRankList list) {
  if(isRanked(list)) return; // make sure that this function runs only once
  DRank* ll=list->list;
  int len=list->len;
  int i, j, k;
  int ucount=0;
  double *backup=(double*)malloc(len * sizeof(double));
  for(i=0;i<len; ++i)
    backup[i]=*(ll[i]->vPtr);

  qsort(ll, len, sizeof(DRank), compareDRank);

  for(i=0; i<len;i=j+1) {
    j=i;
    while((j<len-1) && backup[ll[j]->index] == backup[ll[j+1]->index]) {
      j++;
    }
    for(k=i;k<=j;k++)
      ll[k]->rank=(i+j+2)*0.5;
    ucount++;
  }

  free(backup);
  list->ulen=ucount;
}

/*! \brief: rankDRankList
 * \param list A DRankList object
 * It calls sortRankDRankList if the DRankList has not been ranked before
 * The items in the list are sorted by input index
 */
void rankDRankList(DRankList list) {
  sortRankDRankList(list);
  DRank* ll=list->list;
  int len=list->len;
  qsort(ll, len, sizeof(DRank), compareDRankIndex);
}

/*! \brief: sortDRankList
 * \param list A DRankList object
 * It calls sortRankDRankList if the DRankList has not been ranked before
 * The items in the list are sorted by ascending order of the values.
 */
void sortDRankList(DRankList list) {
  sortRankDRankList(list);
  DRank* ll=list->list;
  int len=list->len;
  qsort(ll, len, sizeof(DRank), compareDRank);
}

/*! \brief: prepareDRankList
 * \param list A DRankList object
 * It prepares a DRankList object to be used in Wilcoxon-Mann-Whitney tests
 */

int len(DRankList list) {return(list->len);}
int ulen(DRankList list) {return(list->ulen);}
double tieCoef(DRankList list) {return(list->tieCoef);}

void prepareDRankList(DRankList list) {
  rankDRankList(list);
  
  if(len(list)==ulen(list)) {
    list->tieCoef=1.0;
    return;
  } else {
    int n=len(list);
    int un=ulen(list);
    int *tbl=(int*)malloc(ulen(list) * sizeof(int));
    int ncount=0;
    double mTieCoef=0.0;
    int i, j;
    sortDRankList(list);
    for(i=0;i<n;i=j+1) {
      j=i;
      while(j<n-1 && (*(list->list[j+1]->vPtr)==*(list->list[j]->vPtr))) ++j;
      tbl[ncount++]=j-i+1;
    }
    for(i=0;i<un;++i)
      mTieCoef+=(0.0+tbl[i])/n*(tbl[i]+1)/(n+1)*(tbl[i]-1)/(n-1);
    list->tieCoef=1-mTieCoef;
    free(tbl);
    rankDRankList(list);
    return;
  }
}