/***************************** 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 "mars.h"

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

Percentile::~Percentile()
{
//empty
}

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

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

	// Check input data
	// Needs to be done

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

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

	return;
}

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

	// Get data information from UI
	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, "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* caux = in("INTERPOLATION");
	interp_ = (strcmp(caux,"NEAREST_NEIGHBOUR") == 0) ? PERC_NN : PERC_LI;

	return true;
}

void Percentile::ComputeRank(vector<double>& vrank, int numInputFields)
{
       double rank;    //auxiliary variable

       for (size_t i = 0; i < vrank.size(); i++)
       {
	     rank = percList_[i]/100. * (numInputFields+1);
	     rank = rank-1.; //c++ index

	     if (rank < 0.)
		     vrank[i] = 0.;
	     else if (rank > double(numInputFields-1))
		     vrank[i] = double(numInputFields-1);
	     else
		     vrank[i] = rank;
       }
}



/*  -----------------------------------------------------------------------------------------
    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;
	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;


	// Number of input fields must be greater than the number
	// of input percentiles values. This is because we are
	// using the input fieldset structure to store the
	// output fieldset
	if (numInputFields < numPercentiles)
	{
		cout << "ERROR: Number of input fields must be greater than the number of input percentiles values" << endl;
		return false;
	}

	// Compute rank of percentiles
	vector<double> vrank(percList_.size());
	ComputeRank(vrank, numInputFields);


	// create the set of output fields (just copy the first 'n' from the input fields)
	fieldset *gs = copy_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)
			{
				cout << "ERROR: FIELDSET CONTAINS DIFFERENT NUMBER OF GRID POINTS" << endl;
				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]);
		free_field(f);  // free_field checks the reference count
	}




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

	vector<double> gvals(numInputFields,0);   // values for one grid point across all fields
	for( size_t gp = 0; gp < numGridPoints; gp++ )
	{
		// 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());

		// update output fieldset for each percentile value
		for (size_t i = 0; i < numPercentiles; i++)
		{
			gs->fields[i]->values[gp] = GetValue(vrank[i], gvals, numInputFields);  
		}
	}


	// 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, vector<double>& gvals, int numInputFields)
{
	if ( interp_ == PERC_NN ) //Nearest neighbour interpolation
		return gvals[int(rank+0.5)];
	else
	{                         //Linear interpolation
		int   ir = int(rank);
		double fr = rank - double(ir);

		// Test boundary 
		if ((numInputFields-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();
}
