#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include "grb2.h"
#include "wgrib2.h"
#include "fnlist.h"

/*
 * ens_processing
 *
 *  v 1.0 experimental based on time_processing.c
 *
 *
 * 1/2018: Public Domain: Wesley Ebisuzaki
 */

/* 
 * code table 4.3 used by percentile and prob fcsts, 
 *   WMO: ensemble forecast
 *  NCEP: ensemble forecast based on counting
 */
#define PROCESS 4
#define NCEP_PROCESS 199

/* code table 4.7 */
#define AVE	0
#define SPREAD	4
#define MAX	9
#define MIN	8

/* trace defined to be 0.1 mm  .. trace precip 0.1mm/3hours converted to mm/s */
#define TRACE	(0.1/86400.0/8)

static int testfloat(const void *a, const void *b);
static double percentile_index(float percent, int n);

extern int decode, file_append, nx, ny, save_translation;

extern int flush_mode;
extern unsigned int *translation;
extern int use_scale, dec_scale, bin_scale, wanted_bits, max_bits;
extern enum output_grib_type grib_type;

struct ens_proc_struct {
    unsigned int npnts, nx, ny;
    int has_val, n_ens;
    unsigned char *first_sec[9];
    struct full_date verf_date;
    int use_scale, dec_scale, bin_scale, wanted_bits, max_bits;
    enum output_grib_type grib_type;
    struct seq_file out;
    float *grids;		/* hold grids for median-type calculations */
    int ngrids;
    int option;
};

static int wrt_ens_proc(unsigned char **sec, struct ens_proc_struct *save);
static int free_ens_proc_struct(struct ens_proc_struct *save);
static int init_ens_proc_struct(struct ens_proc_struct *save, unsigned char **sec, float *data, unsigned int ndata);
static int update_ens_proc_struct(struct ens_proc_struct *save, unsigned char **sec, float *data, unsigned int ndata);

/* routines to initialized and free ens_proc_struct */

static int free_ens_proc_struct(struct ens_proc_struct *save) {
    if (save->has_val == 1) {
        free_sec(save->first_sec);
	if (save->ngrids) {
	    free(save->grids);
	}
    }
    free(save);
    return 0;
}

static int init_ens_proc_struct(struct ens_proc_struct *save, 
    unsigned char **sec, float *data, unsigned int ndata) {
    unsigned int i;

    /* if allocated but wrong size, free all */
    if (save->has_val == 1 && save->npnts != ndata) {
	if (save->ngrids) {
	    free(save->grids);
	    save->ngrids=0;
	}
	save->has_val = 0;
    }

    /* if not allocated, allocate */
    if (save->has_val == 0) {
	save->grids = malloc(((size_t) ndata) * ENS_PROCESSING_NGRID0 * sizeof(float));
	if (save->grids == NULL) fatal_error("ens_processing: memory allocation problem","");
	save->ngrids = ENS_PROCESSING_NGRID0;
    }

    save->npnts = ndata;
    save->has_val = 1;
    save->nx = nx;
    save->ny = ny;
    save->use_scale = use_scale;
    save->dec_scale = dec_scale;
    save->bin_scale = bin_scale;
    save->wanted_bits = wanted_bits;
    save->max_bits = max_bits;
    save->grib_type = grib_type;
    free_sec(save->first_sec);
    copy_sec(sec, save->first_sec);
    Verf_time(sec, &(save->verf_date));
//    fprintf(stderr,"verf_date %d-%d %d %d\n", save->verf_date.year, save->verf_date.month,
//       save->verf_date.day, save->verf_date.hour);

    if (translation == NULL) {
#ifdef USE_OPENMP
#pragma omp parallel for private(i)
#endif
        for (i = 0; i < ndata; i++) {
	    save->grids[i] = data[i];
	}
    }
    else {
#ifdef USE_OPENMP
#pragma omp parallel for private(i)
#endif
        for (i = 0; i < ndata; i++) {
	    save->grids[translation[i]] = data[i];
	}
    }
    save->n_ens = 1;
    return 0;
}

/* update_ens_proc_struct: save grid in memory */

static int update_ens_proc_struct(struct ens_proc_struct *save, 
    unsigned char **sec, float *data, unsigned int ndata) {

    unsigned int i;

    if (save->npnts != ndata) fatal_error("ens_processing: size mismatch","");

    if (save->n_ens == save->ngrids) {		/* need to make save->grids bigger */
	save->ngrids *= ENS_PROCESSING_NGRID_FACTOR;
	save->grids = realloc(save->grids, ((size_t) save->ngrids) * save->npnts * sizeof (float));
	if (save->grids == NULL) {
	    /* if realloc fails, original memory is retained .. some memory is lost here, don't care */
	    save->ngrids = 0;
	    save->has_val = 0;
	    fatal_error("ens_processing: memory allocation in update","");
	}
    }

    /* the data needs to be translated from we:sn to raw, need to 
       do it now because translation[] may be different in finalized phase */

    if (translation == NULL) {
#ifdef USE_OPENMP
#pragma omp parallel for private(i)
#endif
        for (i = 0; i < ndata; i++) {
	    save->grids[i+save->n_ens*ndata] = data[i];
	}
    }
    else {
#ifdef USE_OPENMP
#pragma omp parallel for private(i)
#endif
        for (i = 0; i < ndata; i++) {
	    save->grids[translation[i]+save->n_ens*ndata] = data[i];
	}
    }
    save->n_ens++;
    return 0;
}

/* routine is called when you want to write the ensemble statistics */

static int wrt_ens_proc(unsigned char **sec, struct ens_proc_struct *save) {
    int pdt, pdt_ens, pdt_probability, pdt_percentile;
    unsigned int i, ndata, k, k2;
    int j;
    float *data, *data10, *data25, *data50, *data75, *data90;
    float *datamean, *datavar, *datamin, *datamax, *dataextra, *dataextra2;
    unsigned char sec4[SET_PDT_SIZE], sec4_probability[SET_PDT_SIZE], sec4_percentile[SET_PDT_SIZE];
    unsigned char *new_sec[9], *new_sec_probability[9], *new_sec_percentile[9], *p;
    unsigned char *table_4_7;
    unsigned char *table_4_9_probability, *value_percentile;
    double sum, sq;
    double x25, x75, x10, x50, x90, x95;
    int i25, i75, i10, i50, i90, i95;
    double d25, d75, d10, d50, d90, d95;
    char name[INV_STRING_SIZE], level[INV_STRING_SIZE];
    int mode, extra, isNCEP;
    int scale_factor, scale_value;

    if (save->has_val == 0 || save->n_ens == 0) return 0; 

    pdt = GB2_ProdDefTemplateNo(save->first_sec);
    switch(pdt) {
	case 0:
	case 1:
		pdt_ens = 2; pdt_probability = 5; pdt_percentile = 6; break;
	case 8:
	case 11:
		pdt_ens = 12; pdt_probability = 9; pdt_percentile = 10; break;
	default:
		pdt_ens = -1; break;
    }
    if (pdt_ens == -1) return 0;

    extra = 0;
    if (save->option == 1) {
        getName(save->first_sec, 0, NULL, name, NULL, NULL);
        *level = 0;
        mode = 0;
	data = NULL;
	ndata = save->npnts;		/* to remove compiler complaints */
        f_lev(call_ARG0(level,NULL));
        if (strcmp(level,"2 m above ground") == 0 &&
            (strcmp(name, "TMP") == 0 || strcmp(name,"TMIN") == 0 || strcmp(name,"TMAX") == 0)) extra = 1;
        else if (strcmp(level,"surface") == 0 && (strcmp(name, "APCP") == 0)) {
		extra = 2;
	}
        else if (strcmp(level,"surface") == 0 && (strcmp(name, "PRATE") == 0)) {
		extra = 2;
	}
        else if (strcmp(level,"10 m above ground") == 0 && strcmp(name,"WIND") == 0) extra = 3;
    }

    isNCEP = GB2_Center(save->first_sec) == NCEP;

    /* create new_pdt (sec4) */

    if (new_pdt(save->first_sec, sec4, pdt_ens, -1, 1, NULL)) 
        fatal_error("ens_processing: new_pdt failed","");
    /* make a new sec[][] */
    for (i = 0; i < 9; i++) new_sec[i] = save->first_sec[i];
    new_sec[4] = sec4;

    p = number_of_forecasts_in_the_ensemble_location(new_sec);
    if (p != NULL) *p = save->n_ens < 254 ? save->n_ens : 255;

    /* do not change code table 4_3 */
    table_4_7 = code_table_4_7_location(new_sec);
    if (table_4_7 == NULL) fatal_error("ens_processing: program error 4.7","");

    /* create new_pdt_probability */

    table_4_9_probability = NULL;
    if (extra) {		/* extra .. use probability template for extras */
        /* create new_pdt for probability sec4_probability */
        if (new_pdt(save->first_sec, sec4_probability, pdt_probability, -1, 1, NULL)) 
            fatal_error("ens_processing: new_pdt probability failed","");
        for (i = 0; i < 9; i++) new_sec_probability[i] = save->first_sec[i];
        new_sec_probability[4] = sec4_probability;
	p = code_table_4_3_location(new_sec_probability);
	if (p == NULL) fatal_error("ens_processing: program error 4.3","");
        *p = isNCEP ? NCEP_PROCESS : PROCESS;
	table_4_9_probability = code_table_4_9_location(new_sec_probability);
	if (table_4_9_probability == NULL) fatal_error("ens_processing: program error 4.9","");
    }

    /* creaate new_pdt_percentile */
    if (new_pdt(save->first_sec, sec4_percentile, pdt_percentile, -1, 1, NULL)) 
            fatal_error("ens_processing: new_pdt percentile failed","");
    for (i = 0; i < 9; i++) new_sec_percentile[i] = save->first_sec[i];
    new_sec_percentile[4] = sec4_percentile;
    p = code_table_4_3_location(new_sec_percentile);
    if (p == NULL) fatal_error("ens_processing: program error 4.3","");
    *p = isNCEP ? NCEP_PROCESS : PROCESS;
    value_percentile = percentile_value_location(new_sec_percentile);
    if (value_percentile == NULL) fatal_error("ens_processing: program error percentile","");

    ndata = save->npnts;

    data = (float *) malloc(sizeof(float) * ((size_t) ndata));
    data10 = (float *) malloc(sizeof(float) * ((size_t) ndata));
    data25 = (float *) malloc(sizeof(float) * ((size_t) ndata));
    data50 = (float *) malloc(sizeof(float) * ((size_t) ndata));
    data75 = (float *) malloc(sizeof(float) * ((size_t) ndata));
    data90 = (float *) malloc(sizeof(float) * ((size_t) ndata));
    datamean = (float *) malloc(sizeof(float) * ((size_t) ndata));
    datavar = (float *) malloc(sizeof(float) * ((size_t) ndata));
    datamin = (float *) malloc(sizeof(float) * ((size_t) ndata));
    datamax = (float *) malloc(sizeof(float) * ((size_t) ndata));
    if (data == NULL || data10 == NULL || data25 == NULL || data50 == NULL || data75 == NULL || data90 == NULL
       || datamean == NULL || datavar == NULL || datamin == NULL || datamax == NULL)
            fatal_error("ens_processing: wrt_ens_proc memory allocation","");

    dataextra = dataextra2 = NULL;
    if (extra) {
	if ((dataextra = (float *) malloc(sizeof(float) * ((size_t) ndata))) == NULL) 
            fatal_error("ens_processing: wrt_ens_proc memory allocation","");
	if (extra == 2) {
	    if ((dataextra2 = (float *) malloc(sizeof(float) * ((size_t) ndata))) == NULL) 
                fatal_error("ens_processing: wrt_ens_proc memory allocation","");
	}
    }

    /* sort grids .. for statistics  */

    x10 = percentile_index(10.0, save->n_ens);
    i10 = floor(x10);
    d10 = x10-i10;

    x25 = percentile_index(25.0, save->n_ens);
    i25 = floor(x25);
    d25 = x25-i25;

    x50 = percentile_index(50.0, save->n_ens);
    i50 = floor(x50);
    d50 = x50-i50;

    x75 = percentile_index(75.0, save->n_ens);
    i75 = floor(x75);
    d75 = x75-i75;

    x90 = percentile_index(90.0, save->n_ens);
    i90 = floor(x90);
    d90 = x90-i90;

    x95 = percentile_index(95.0, save->n_ens);
    i95 = floor(x95);
    d95 = x95-i95;

#ifdef USE_OPENMP
#pragma omp parallel for private(i,j,k,k2,sum,sq)
#endif
    for (i = 0; i < ndata; i++) {
	float ens[save->n_ens];
        
	/* make vector of ensemble member grid points */
	k = 0;
#ifdef IS_OPENMP_4_0
#pragma omp simd reduction(+:k)
#endif
	for (j = 0; j < save->n_ens; j++) {
	    ens[j] = save->grids[i+j*ndata];
    	    if (DEFINED_VAL(save->grids[i+j*ndata])) k++;
	}

	if (k == save->n_ens) {
	    /* sort */
	    qsort(&(ens[0]), save->n_ens, sizeof(float), &testfloat);

	    /* find the various percentiles */	    
	    data10[i] = i10 != save->n_ens - 1 ? ens[i10]*(1.0-d10) + ens[i10+1]*d10 : ens[i10];
            data25[i] = i25 != save->n_ens - 1 ? ens[i25]*(1.0-d25) + ens[i25+1]*d25 : ens[i25];
            data50[i] = i50 != save->n_ens - 1 ? ens[i50]*(1.0-d50) + ens[i50+1]*d50 : ens[i50];
            data75[i] = i75 != save->n_ens - 1 ? ens[i75]*(1.0-d75) + ens[i75+1]*d75 : ens[i75];
            data90[i] = i90 != save->n_ens - 1 ? ens[i90]*(1.0-d90) + ens[i90+1]*d90 : ens[i90];
	    datamin[i] = ens[0];
	    datamax[i] = ens[save->n_ens - 1];

	    sum = sq = 0.0;
#ifdef IS_OPENMP_4_0
#pragma omp simd reduction(+:sum)
#endif
	    for (j = 0; j < save->n_ens; j++) {
		sum += ens[j];
	    }
	    sum = sum / save->n_ens;
	    datamean[i] = sum;

#ifdef IS_OPENMP_4_0
#pragma omp simd reduction(+:sq)
#endif
	    for (j = 0; j < save->n_ens; j++) {
		sq += (ens[j]-sum)*(ens[j]-sum);
	    }
            datavar[i] = sqrt(sq/save->n_ens);

	    /* extra 1   TMP2m < 0C */
	    if (extra == 1) {
		k = 0;
#ifdef IS_OPENMP_4_0
#pragma omp simd reduction(+:k)
#endif
		for (j = 0; j < save->n_ens; j++) {
		    if (ens[j] < 273.15) k++;
		}
		dataextra[i] = k / (double) save->n_ens;
	    }
	    /* extra 2   precip > 0, precip > trace */
	    else if (extra == 2) {
		k = k2 = 0;
#ifdef IS_OPENMP_4_0
#pragma omp simd reduction(+:k,k2)
#endif
		for (j = 0; j < save->n_ens; j++) {
		    if (ens[j] > 0.0) k++;
		    if (ens[j] > TRACE) k2++;
		}
		dataextra[i] = k / (double) save->n_ens;
		dataextra2[i] = k2 / (double) save->n_ens;
	    }
	    /* extra 3   wind speed at 10m  95%  */
	    else if (extra == 3) {
                dataextra[i] = i95 != save->n_ens - 1 ? ens[i95]*(1.0-d95) + ens[i95+1]*d95 : ens[i95];
	    }
	}
	else {
	    data10[i] = data25[i] = data50[i] = data75[i] = data90[i] = UNDEFINED;
	    datamean[i] = datavar[i] = datamin[i] = datamax[i] = UNDEFINED;
	    if (extra) {
		dataextra[i] = UNDEFINED;
		if (extra == 2) dataextra2[i] = UNDEFINED;
	    }
	}
    }

    /* min */
    *table_4_7 = MIN;
    grib_wrt(new_sec, datamin, ndata, save->nx, save->ny, 
	save->use_scale, save->dec_scale, save->bin_scale, 
	save->wanted_bits, save->max_bits, save->grib_type, &(save->out));

    /* max */
    *table_4_7 = MAX;
    grib_wrt(new_sec, datamax, ndata, save->nx, save->ny, 
	save->use_scale, save->dec_scale, save->bin_scale, 
	save->wanted_bits, save->max_bits, save->grib_type, &(save->out));

    /* ave */
    *table_4_7 = AVE;
    grib_wrt(new_sec, datamean, ndata, save->nx, save->ny, 
	save->use_scale, save->dec_scale, save->bin_scale, 
	save->wanted_bits, save->max_bits, save->grib_type, &(save->out));

    /* spread */
    *table_4_7 = SPREAD;
    grib_wrt(new_sec, datavar, ndata, save->nx, save->ny, 
	save->use_scale, save->dec_scale, save->bin_scale, 
	save->wanted_bits, save->max_bits, save->grib_type, &(save->out));

    *value_percentile = 10;
    grib_wrt(new_sec_percentile, data10, ndata, save->nx, save->ny, 
	save->use_scale, save->dec_scale, save->bin_scale, 
	save->wanted_bits, save->max_bits, save->grib_type, &(save->out));

    *value_percentile = 25;
    grib_wrt(new_sec_percentile, data25, ndata, save->nx, save->ny, 
	save->use_scale, save->dec_scale, save->bin_scale, 
	save->wanted_bits, save->max_bits, save->grib_type, &(save->out));

    *value_percentile = 50;
    grib_wrt(new_sec_percentile, data50, ndata, save->nx, save->ny, 
	save->use_scale, save->dec_scale, save->bin_scale, 
	save->wanted_bits, save->max_bits, save->grib_type, &(save->out));

    *value_percentile = 75;
    grib_wrt(new_sec_percentile, data75, ndata, save->nx, save->ny, 
	save->use_scale, save->dec_scale, save->bin_scale, 
	save->wanted_bits, save->max_bits, save->grib_type, &(save->out));

    *value_percentile = 90;
    grib_wrt(new_sec_percentile, data90, ndata, save->nx, save->ny, 
	save->use_scale, save->dec_scale, save->bin_scale, 
	save->wanted_bits, save->max_bits, save->grib_type, &(save->out));

    /* extra */
    if (extra == 1) {
	table_4_9_probability[-2] = 1;
	table_4_9_probability[-1] = 1;
	table_4_9_probability[0] = 0;
	best_scaled_value(273.15, &scale_factor, &scale_value);
	table_4_9_probability[1] = scale_factor;
        int_char(scale_value, table_4_9_probability + 2);

        grib_wrt(new_sec_probability, dataextra, ndata, save->nx, save->ny, 
	    save->use_scale, save->dec_scale, save->bin_scale, 
	    save->wanted_bits, save->max_bits, save->grib_type, &(save->out));
    }
    else if (extra == 2) {
	table_4_9_probability[-2] = 1;
	table_4_9_probability[-1] = 2;
	table_4_9_probability[0] = 3;
	best_scaled_value(0.0, &scale_factor, &scale_value);
	table_4_9_probability[1] = scale_factor;
        int_char(scale_value, table_4_9_probability + 2);

        grib_wrt(new_sec_probability, dataextra, ndata, save->nx, save->ny, 
	    save->use_scale, save->dec_scale, save->bin_scale, 
	    save->wanted_bits, save->max_bits, save->grib_type, &(save->out));

	table_4_9_probability[-2] = 2;
	table_4_9_probability[-1] = 2;
	table_4_9_probability[0] = 3;
	best_scaled_value(TRACE, &scale_factor, &scale_value);
	table_4_9_probability[1] = scale_factor;
        int_char(scale_value, table_4_9_probability + 2);
        grib_wrt(new_sec_probability, dataextra, ndata, save->nx, save->ny, 
	    save->use_scale, save->dec_scale, save->bin_scale, 
	    save->wanted_bits, save->max_bits, save->grib_type, &(save->out));
    }
    else if (extra == 3) {
        *value_percentile = 95;
        grib_wrt(new_sec_percentile, dataextra, ndata, save->nx, save->ny, 
	    save->use_scale, save->dec_scale, save->bin_scale, 
	    save->wanted_bits, save->max_bits, save->grib_type, &(save->out));
    }
    if (flush_mode) fflush_file(&(save->out));

    free(data);
    free(data10);
    free(data25);
    free(data50);
    free(data75);
    free(data90);
    free(datamean);
    free(datavar);
    free(datamin);
    free(datamax);
    if (extra) free(dataextra);
    if (extra == 2) free(dataextra2);
    return 0;
}


/*
 * HEADER:000:ens_processing:output:2:ave/min/max/spread X=output Y=0/1 default/CORe
 */

int f_ens_processing(ARG2) {
    struct ens_proc_struct *save;
    struct full_date verf_date;
    int pdt, new_type;

    if (mode == -1) {
        save_translation = decode = 1;

        // allocate static structure

        *local = save = (struct ens_proc_struct *) malloc( sizeof(struct ens_proc_struct));

        if (fopen_file(&(save->out), arg1, file_append ? "ab" : "wb") != 0) {
	    free(save);
            fatal_error("ens_processing: Could not open %s", arg1);
        }
        save->has_val = 0;
	save->n_ens = 0;
        save->grids = NULL;
        save->ngrids = 0;
	init_sec(save->first_sec);
        save->option = atoi(arg2);
    
	return 0;
    }
    save = (struct ens_proc_struct *) *local;
    if (mode == -2) {
        wrt_ens_proc(save->first_sec, save);
        fclose_file(&(save->out));
        free_ens_proc_struct(save);
        return 0;
    }

    /* processing a record */
    if (mode < 0) return 0;

    pdt = GB2_ProdDefTemplateNo(sec);
    if (pdt != 0 && pdt != 1 && pdt != 8 && pdt != 11) return 0;

    // check to see continuation of previous averaging

    new_type = (save->has_val == 0) ? 1 : 0;

    /* get verification date */
    if (new_type == 0) {
        Verf_time(sec, &(verf_date));
        if (Cmp_time(&(verf_date), &(save->verf_date)) != 0) {
	    new_type = 1;
// fprintf(stderr,"failed verf date %d-%d %d %d\n", verf_date.year, verf_date.month, verf_date.day, verf_date.hour);
	}
    }

    if (new_type == 0) {
	if (same_sec4_but_ensemble(mode, sec, save->first_sec) == 0) new_type = 1;
    }
if (mode == 98) fprintf(stderr,"ens_processing: pdt=%d, new_type=%d\n",pdt, new_type);
    if (new_type == 1) {
if (mode == 98) fprintf(stderr,"ens_processing: >> wrt a\n");
        wrt_ens_proc(save->first_sec, save);
        init_ens_proc_struct(save, sec, data, ndata);
    }
    else {
if (mode == 98) fprintf(stderr,"ens_processing: >> update\n");
        update_ens_proc_struct(save, sec, data, ndata);
    }

    return 0;
}


/*
 * To find a percentile, you sort the values, get an index (floating) 
 * and find the value with interpolation. the Wiki for percentile lists 
 * 3 ways to get the index.  This routine find the index.
 * returns: -1 (not valid)
 *           x 1..N (float)
 */

static double percentile_index(float percent, int n) {

   double p;

   if (n < 1) return -1;
   p = percent * 0.01;
   if (p < 0.0 || p > 1.0) return -1;

   p = p*(n+1);
   if (p > n) return (double) n-1;
   if (p < 1) return 0.0;
   return (p-1.0);
}

/* function to sort floats as used by qsort */

static int testfloat(const void *a, const void *b) {
        float  aa, bb;
        aa = *(float *)a;
        bb = *(float *)b;
        if (aa < bb) return -1;
        if (aa == bb) return 0;
        return 1;
}
