/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "Percentile.h"

#include <algorithm>
#include <iostream>

#include "mars.h"


/*
    Convenience function to make a copy of N fields from a fieldset, where N can
    be larger than the number of fields in the original. In this case, the last
    field will be replicated to fit.
*/

static fieldset* copy_and_extend_fieldset(fieldset* x, int count, boolean copydata)
{
    fieldset* v = new_fieldset(count);

    if (count != 0) {
        int i = 0;
        for (i = 0; i < count; i++) {
            int source_index = 0;
            if (i < x->count)
                source_index = i;
            else
                source_index = x->count - 1;

            field* gx = get_field(x, source_index, expand_mem);
            v->fields[i] = copy_field(gx, copydata);
            v->fields[i]->refcnt++;
            release_field(gx);
        }
    }
    return v;
}


Percentile::Percentile(const char* kw) :
    MvService(kw)
{
    // empty
}

Percentile::~Percentile() = default;

void Percentile::serve(MvRequest& in, MvRequest& out)
{
    std::cout << "Percentile::serve in" << std::endl;
    in.print();

    // Get information from the user interface
    if (!GetInputInfo(in))
        return;

    // Compute percentiles
    if (!ComputePercentile(out))
        return;

    std::cout << "Percentile::serve out" << std::endl;
    out.print();

    return;
}

bool Percentile::GetInputInfo(MvRequest& in)
{
    const char* cw = nullptr;  // auxiliary variables
    int i = 0;                 // auxiliary variables

    // Get data information from UI
    std::string str;
    if ((const char*)in("SOURCE") && (strcmp((const char*)in("SOURCE"), "OFF") && strcmp((const char*)in("SOURCE"), "off"))) {
        str = (const char*)in("SOURCE");
        dataRequest_.setVerb("GRIB");
        dataRequest_("PATH") = str.c_str();
    }
    else {
        // Get information from the icon
        in.getValue(dataRequest_, "DATA");
        if (!in.countValues("DATA") || !dataRequest_.countValues("PATH")) {
            setError(1, "Percentile-> No Data files specified");
            return false;
        }
    }

    // Get list of percentiles
    // First initialize a vector
    int nc = in.countValues("PERCENTILES");
    if (percList_.size())
        percList_.erase(percList_.begin(), percList_.end());
    else
        percList_.reserve(nc);

    for (i = 0; i < nc; i++) {
        cw = in("PERCENTILES", i);
        percList_.push_back(atof(cw));
    }

    // Get interpolation method
    const char* caux1 = in("INTERPOLATION");
    interp_ = (strcmp(caux1, "NEAREST_NEIGHBOUR") == 0) ? PERC_NN : PERC_LI;

    // Get missing value strategy
    const char* caux2 = in("COMPUTE_IF_MISSING_PRESENT");
    computeWithMissing_ = (strcmp(caux2, "OFF") == 0) ? false : true;

    return true;
}

double Percentile::ComputeRank(double percentile, int numInputFields)
{
    double rank;

    rank = percentile / 100. * (numInputFields + 1);
    rank = rank - 1.;  // c++ index

    if (rank < 0.)
        return 0.;
    else if (rank > double(numInputFields - 1))
        return double(numInputFields - 1);
    else
        return rank;
}


/*
    This computes the ranks for the given percentiles, assuming that all the values are non-missing.
    If there are grid points with missing values, then a new rank will be computed for that
    grid point.
*/
void Percentile::ComputeRankForAllPercentiles(std::vector<double>& percRanks, int numInputFields)
{
    for (size_t i = 0; i < percRanks.size(); i++) {
        percRanks[i] = ComputeRank(percList_[i], numInputFields);
    }
}


/*  -----------------------------------------------------------------------------------------
    NOTE: we use the basic MARS fieldset here instead of the MvFieldSet. This is because this
    is a highly memory-intensive module, and we want to be able to get rid of the GRIB
    handles as quickly as possible - this is easier with the basic fieldset structure.
    ----------------------------------------------------------------------------------------- */

bool Percentile::ComputePercentile(MvRequest& out)
{
    size_t numGridPoints = 0;
    size_t numPercentiles = percList_.size();

    std::vector<std::vector<double> > fsValues;  // vector of vector of values - a vector for each field's values


    // initialise the fieldset
    fieldset* fs = request_to_fieldset(dataRequest_);
    size_t numInputFields = fs->count;


    // Compute rank of percentiles
    std::vector<double> percRanks(percList_.size());
    ComputeRankForAllPercentiles(percRanks, numInputFields);


    // create the set of output fields (just copy the first 'n' from the input fields)
    fieldset* gs = copy_and_extend_fieldset(fs, numPercentiles, false);


    // extract the value arrays and make local copies of them
    bool first = true;
    fsValues.resize(numInputFields);
    for (size_t i = 0; i < numInputFields; i++) {
        // expand this field into memory
        field* f = get_field(fs, i, expand_mem);


        // Check fieldset: all fields should have the same number of grid points
        if (first) {
            first = false;
            numGridPoints = f->value_count;
        }
        else {
            if (numGridPoints != f->value_count) {
                setError(1, "Percentile-> Fieldset contains different number of grid points");
                return false;
            }
        }

        // copy the values to a local vector and free the original field including its GRIB handle
        fsValues[i].assign(&f->values[0], &f->values[f->value_count]);
        mars_free_field(f);  // mars_free_field checks the reference count
    }

    // Main loop - get fieldset values for each grid point

    std::vector<double> gvals(numInputFields, 0);  // values for one grid point across all fields
    for (size_t gp = 0; gp < numGridPoints; gp++) {
        bool returnMissing = false;
        bool missingPresent = false;
        size_t numValsForCompute = numInputFields;

        // get values for the current grid point across all fields
        for (size_t fi = 0; fi < numInputFields; fi++) {
            gvals[fi] = fsValues[fi][gp];
        }

        // Sort values
        std::sort(gvals.begin(), gvals.end());

        // this code assumes that the missing value indicator is larger than
        // any actual data value and therefore, if a missing value is present,
        // it will be at the end of the sorted list; if this assumption is challenged,
        // then the code will need to be revised.

        if (MISSING_VALUE(gvals.back())) {
            missingPresent = true;
            if (computeWithMissing_) {
                auto firstMissing = std::find_if(gvals.rbegin(), gvals.rend(),
                                                 [](double d) { return !(MISSING_VALUE(d)); });
                numValsForCompute = gvals.rend() - firstMissing;
            }
            else {
                returnMissing = true;
            }
        }


        // update output fieldset for each percentile value
        for (size_t i = 0; i < numPercentiles; i++) {
            double rank = (missingPresent)
                              ? ComputeRank(percList_[i], numValsForCompute)
                              : percRanks[i];

            gs->fields[i]->values[gp] = (returnMissing) ? mars.grib_missing_value : GetValue(rank, gvals, numValsForCompute);
        }
    }

    // ensure that the bitmap is present in all output fields, as we might have
    // put missing values into the results
    for (size_t i = 0; i < numPercentiles; i++) {
        gs->fields[i]->bitmap = 1;
    }


    // Write output fieldset on disk
    // This function calls Mars to write the fieldset
    MvRequest req = fieldset_to_request(gs);

    // Update output request
    out = out + req;

    return true;
}

double Percentile::GetValue(double rank, std::vector<double>& gvals, size_t numVals)
{
    if (interp_ == PERC_NN)  // Nearest neighbour interpolation
        return gvals[int(rank + 0.5)];
    else {  // Linear interpolation
        auto ir = size_t(rank);
        double fr = rank - double(ir);

        // Test boundary
        if ((numVals - 1) == ir)
            return gvals[ir];
        else
            return (fr * (gvals[ir + 1] - gvals[ir]) + gvals[ir]);
    }
}


//--------------------------------------------------------

int main(int argc, char** argv)
{
    MvApplication theApp(argc, argv);
    Percentile perc("PERCENTILE");

    // The applications don't try to read or write from pool, this
    // should not be done with the new PlotMod.
    // a.addModeService("GRIB", "DATA");
    // c.saveToPool(false);
    // perc.saveToPool(false);

    theApp.run();
}
