/*
 ***********************************************************************
 * © 2016 and later: Unicode, Inc. and others.
 * License & terms of use: http://www.unicode.org/copyright.html
 ***********************************************************************
 ***********************************************************************
 * Copyright (c) 2011-2012,International Business Machines
 * Corporation and others.  All Rights Reserved.
 ***********************************************************************
 */

#include "unicode/utimer.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>

#include "sieve.h"

/* prime number sieve */

U_CAPI double uprv_calcSieveTime() {
#if 1
#define SIEVE_SIZE U_LOTS_OF_TIMES /* standardized size */
#else
#define SIEVE_SIZE  <something_smaller>
#endif

#define SIEVE_PRINT 0

  char sieve[SIEVE_SIZE];
  UTimer a,b;
  int i,k;
  
  utimer_getTime(&a);
  for(int j=0;j<SIEVE_SIZE;j++) {
    sieve[j]=1;
  }
  sieve[0]=0;
  utimer_getTime(&b);
  

#if SIEVE_PRINT
  printf("init %d: %.9f\n", SIEVE_SIZE,utimer_getDeltaSeconds(&a,&b));
#endif

  utimer_getTime(&a);
  for(i=2;i<SIEVE_SIZE/2;i++) {
    for(k=i*2;k<SIEVE_SIZE;k+=i) {
      sieve[k]=0;
    }
  }
  utimer_getTime(&b);
#if SIEVE_PRINT
  printf("sieve %d: %.9f\n", SIEVE_SIZE,utimer_getDeltaSeconds(&a,&b));

  if(SIEVE_PRINT>0) {
    k=0;
    for(i=2;i<SIEVE_SIZE && k<SIEVE_PRINT;i++) {
      if(sieve[i]) {
        printf("%d ", i);
        k++;
      }
    }
    puts("");
  }  
  {
    k=0;
    for(i=0;i<SIEVE_SIZE;i++) {
      if(sieve[i]) k++;
    }
    printf("Primes: %d\n", k);
  }
#endif

  return utimer_getDeltaSeconds(&a,&b);
}
static int comdoub(const void *aa, const void *bb) 
{
  const double* a = static_cast<const double*>(aa);
  const double* b = static_cast<const double*>(bb);
  
  return (*a==*b)?0:((*a<*b)?-1:1);
}

double midpoint(double *times, double i, int n) {
  double fl = floor(i);
  double ce = ceil(i);
  if(ce>=n) ce=n;
  if(fl==ce) {
    return times[static_cast<int>(fl)];
  } else {
    return (times[static_cast<int>(fl)] + times[static_cast<int>(ce)]) / 2;
  }
}

double medianof(double *times, int n, int type) {
  switch(type) {
  case 1:
    return midpoint(times,n/4,n);
  case 2:
    return midpoint(times,n/2,n);
  case 3:
    return midpoint(times,(n/2)+(n/4),n);
  }
  return -1;
}

double qs(double *times, int n, double *q1, double *q2, double *q3) {
  *q1 = medianof(times,n,1);
  *q2 = medianof(times,n,2);
  *q3 = medianof(times,n,3);
  return *q3-*q1;
}

U_CAPI double uprv_getMeanTime(double *times, uint32_t *timeCount, double *marginOfError) {
  double q1,q2,q3;
  int n = *timeCount;

  /* calculate medians */
  qsort(times,n,sizeof(times[0]),comdoub);
  double iqr = qs(times,n,&q1,&q2,&q3);
  double rangeMin=  (q1-(1.5*iqr));
  double rangeMax =  (q3+(1.5*iqr));

  /* Throw out outliers */
  int newN = n;
#if U_DEBUG
  printf("iqr: %.9f, q1=%.9f, q2=%.9f, q3=%.9f, max=%.9f, n=%d\n", iqr,q1,q2,q3,(double)-1, n);
#endif
  for(int i=0;i<newN;i++) {
    if(times[i]<rangeMin || times[i]>rangeMax) {
#if U_DEBUG
      printf("Removing outlier: %.9f outside [%.9f:%.9f]\n", times[i], rangeMin, rangeMax);
#endif
      times[i--] = times[--newN]; // bring down a new value
    }
  }

#if U_DEBUG
  UBool didRemove = false;
#endif
  /* if we removed any outliers, recalculate iqr */
  if(newN<n) {
#if U_DEBUG
    didRemove = true;
    printf("removed %d outlier(s), recalculating IQR..\n", n-newN);
#endif
    n = newN;
    *timeCount = n;

    qsort(times,n,sizeof(times[0]),comdoub);
    double iqr = qs(times,n,&q1,&q2,&q3);
    rangeMin=  (q1-(1.5*iqr));
    rangeMax =  (q3+(1.5*iqr));
  }

  /* calculate min/max and mean */
  double minTime = times[0];
  double maxTime = times[0];
  double meanTime = times[0];
  for(int i=1;i<n;i++) {
    if(minTime>times[i]) minTime=times[i];
    if(maxTime<times[i]) maxTime=times[i];
    meanTime+=times[i];
  }
  meanTime /= n;

  /* calculate standard deviation */
  double sd = 0;
  for(int i=0;i<n;i++) {
#if U_DEBUG
    if(didRemove) {
      printf("recalc %d/%d: %.9f\n", i, n, times[i]);
    }
#endif
    sd += (times[i]-meanTime)*(times[i]-meanTime);
  }
  sd = sqrt(sd/((double)n-1.0));

#if U_DEBUG
  printf("sd: %.9f, mean: %.9f\n", sd, meanTime);
  printf("min: %.9f, q1=%.9f, q2=%.9f, q3=%.9f, max=%.9f, n=%d\n", minTime,q1,q2,q3,maxTime, n);
  printf("iqr/sd = %.9f\n", iqr/sd);
#endif

  /* 1.960 = z sub 0.025 */
  *marginOfError = 1.960 * (sd/sqrt((double)n));
  /*printf("Margin of Error = %.4f (95%% confidence)\n", me);*/

  return meanTime;
}

UBool calcSieveTime = false;
double meanSieveTime = 0.0;
double meanSieveME = 0.0;

U_CAPI double uprv_getSieveTime(double *marginOfError) {
  if(!calcSieveTime) {
#define SAMPLES 50
    uint32_t samples = SAMPLES;
    double times[SAMPLES];
    
    for(int i=0;i<SAMPLES;i++) {
      times[i] = uprv_calcSieveTime();
#if U_DEBUG
      printf("sieve: %d/%d: %.9f\n", i,SAMPLES, times[i]);
#endif
    }
    
    meanSieveTime = uprv_getMeanTime(times, &samples,&meanSieveME);
    calcSieveTime=true;
  }
  if(marginOfError!=nullptr) {
    *marginOfError = meanSieveME;
  }
  return meanSieveTime;
}
