/*
 * Copyright 2005-2018 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 *
 * In applying this licence, ECMWF does not waive the privileges and immunities granted to it by
 * virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction.
 */

#include "grib_api_internal.h"
#include "grib_optimize_decimal_factor.h"
#include <math.h>
#include <float.h>
#include <string.h>
#include <stdlib.h>

static double epsilon()
{
    volatile double e = 1.;
    while (1. != (1. + e))
    {
        e /= 2;
    }
    return e;
}

static int range ()
{
    return (int)(log (DBL_MAX) / log (10))-1;
}

#ifdef ECCODES_ON_WINDOWS
#define log2(a) ( log(a)/1.44269504088896340736 )
#endif

static void factec (int * krep, const double pa, const int knbit, const long kdec, const int range, long * ke, int * knutil)
{
    *krep = 0;

    *ke = 0;
    *knutil = 0;

    if (pa < DBL_MIN)
    {
        *knutil = 1;
        goto end;
    }

    if ((fabs (log10 (fabs (pa)) + (double)kdec) >= range))
    {
        *krep = 1;
        goto end;
    }

    /* Binary scale factor associated to kdec */
    *ke     = floor (log2 ((pa * grib_power (kdec, 10)) / (grib_power (knbit, 2) - 0.5))) + 1;
    /* Encoded value for pa = max - min       */
    *knutil = floor (0.5 + pa * grib_power ( kdec, 10) * grib_power (-*ke, 2));

end:
    return;
}

int grib_optimize_decimal_factor (grib_accessor * a, const char * reference_value,
        const double pmax, const double pmin, const int knbit,
        const int compat_gribex, const int compat_32bit,
        long * kdec, long * kbin, double * ref)
{
    grib_handle* gh = grib_handle_of_accessor(a);
    int idecmin = -15;
    int idecmax =   5;
    long inbint;
    double xtinyr4, xhuger4, xnbint;
    int inumax, inutil;
    long jdec, ie;
    int irep;
    int RANGE = range ();
    double EPSILON = epsilon ();
    double pa = pmax-pmin;

    if (pa == 0)
    {
        *kdec = 0;
        *kbin = 0;
        *ref  = 0.;
        return GRIB_SUCCESS;
    }

    inumax  = 0;

    if (fabs (pa) <= EPSILON)
    {
        *kdec = 0;
        idecmin = 1;
        idecmax = 0;
    }
    else if (pmin != 0. && fabs (pmin) < EPSILON)
    {
        *kdec = 0;
        idecmin = 1;
        idecmax = 0;
    }

    xtinyr4 = FLT_MIN;
    xhuger4 = FLT_MAX;

    inbint  = grib_power (knbit, 2) - 1;
    xnbint  = (double)inbint;

    /* Test decimal scale factors; keep the most suitable */
    for (jdec = idecmin; jdec <= idecmax; jdec++)
    {
        /* Fix a problem in GRIBEX */
        if (compat_gribex)
            if (pa * grib_power (jdec, 10) <= 1.E-12)
                continue;

        /* Check it will be possible to decode reference value with 32bit floats */
        if (compat_32bit)
            if (fabs (pmin) > DBL_MIN)
                if (log10 (fabs (pmin)) + (double)jdec <= log10 (xtinyr4))
                    continue;

        /* Check if encoding will not cause an overflow */
        if (fabs (log10 (fabs (pa)) + (double)jdec) >= (double)RANGE)
            continue;

        factec (&irep, pa, knbit, jdec, RANGE, &ie, &inutil);

        if (irep != 0)
            continue;

        /* Check it will be possible to decode the maximum value of the fields using 32bit floats */
        if (compat_32bit)
            if (pmin * grib_power (jdec, 10) + xnbint * grib_power (ie, 2) >= xhuger4)
                continue;

        /* GRIB1 demands that the binary scale factor be encoded in a single byte */
        if (compat_gribex)
            if ((ie < -126) || (ie > 127))
                continue;

        if (inutil > inumax)
        {
            inumax = inutil;
            *kdec  = jdec;
            *kbin  = ie;
        }
    }

    if (inumax > 0) {
        double decimal = grib_power (+*kdec, 10);
        double divisor = grib_power (-*kbin,  2);
        double min = pmin * decimal;
        long vmin, vmax;
        if (grib_get_nearest_smaller_value (gh, reference_value, min, ref) != GRIB_SUCCESS) {
            grib_context_log (gh->context, GRIB_LOG_ERROR,
                    "unable to find nearest_smaller_value of %g for %s", min, reference_value);
            return GRIB_INTERNAL_ERROR;
        }

        vmax = (((pmax*decimal)-*ref)*divisor) + 0.5;
        vmin = (((pmin*decimal)-*ref)*divisor) + 0.5;

        /* This may happen if pmin*decimal-*ref is too large */
        if ((vmin != 0) || (vmax > inbint))
            inumax = 0;

    }

    /* If seeking for an optimal decimal scale factor fails, fall back to a basic method */
    if (inumax == 0)
    {
        int last = compat_gribex ? 99 : 127;
        double min = pmin, max = pmax;
        double range = max - min;
        double f = grib_power (knbit, 2) - 1;
        double minrange = grib_power (-last, 2) * f;
        double maxrange = grib_power (+last, 2) * f;
        double decimal = 1;
        int err;

        *kdec = 0;

        while (range < minrange)
        {
            *kdec += 1;
            decimal *= 10;
            min = pmin * decimal;
            max = pmax * decimal;
            range = max - min;
        }

        while (range > maxrange)
        {
            *kdec -= 1;
            decimal /= 10;
            min = pmin * decimal;
            max = pmax * decimal;
            range = max - min;
        }

        if (grib_get_nearest_smaller_value (gh, reference_value, min, ref) != GRIB_SUCCESS)
        {
            grib_context_log (gh->context, GRIB_LOG_ERROR,
                    "unable to find nearest_smaller_value of %g for %s", min, reference_value);
            return GRIB_INTERNAL_ERROR;
        }

        *kbin = grib_get_binary_scale_fact (max, *ref, knbit, &err);

        if (err == GRIB_UNDERFLOW)
        {
            *kbin = 0;
            *kdec = 0;
            *ref  = 0;
        }
    }

    return GRIB_SUCCESS;
}
