/***************************** 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 nfsin)
{
       double rank;    //auxiliary variable

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

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

bool Percentile::ComputePercentile(MvRequest& out)
{
	int       i;                        //auxialiary variable
	int       gp;                       //grid point index
	int       fi;                       //fieldset index
	int       nsize;                    //number of grid points

	MvField   field;
	vector<MvField> vfield;             //store fields
	vector<MvFieldExpander*> vfieldExp; //store expanded fields

	// Initialize fieldset iterator
        MvFieldSet fs(dataRequest_);
	MvFieldSetIterator iter(fs);
	int nfsin = fs.countFields();       //number of input fields

	// 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 ( nfsin < percList_.size() )
	{
		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,nfsin);

	// Create fieldset objects
	bool first = true;
	vfield.reserve(nfsin);
	vfieldExp.reserve(nfsin);
	while( field = iter() )
	{
	    vfield.push_back(field);
	    MvFieldExpander* vex = new MvFieldExpander(field);
	    vfieldExp.push_back(vex);

	    // Check fieldset: all fields should have the
	    // same number of grid points
	    if (first)
	    {
		  first = false;
		  nsize = field.countValues();
	    }
	    else
	    {
		    if ( nsize != field.countValues() )
		    {
			    cout << "ERROR: FIELDSET CONTAINS DIFFERENT NUMBER OF GRID POINTS" << endl;
			    return false;
		    }
	    }
	}



	// after some modifications to Metview's memory management, it turns out that
	// the stored fields are no longer expanded in memory at this point (because
	// of the copy operator with the iterator). Therefore, we delete the field expander
	// list and create a new one.

	for (fi = 0; fi < nfsin; fi++)
	{
		MvFieldExpander* vex = vfieldExp[fi];
		delete vex;
	}

	vfieldExp.clear();

	for (size_t nf = 0; nf < nfsin; nf++)
	{
	    MvFieldExpander* vex = new MvFieldExpander(vfield[nf]);
	    vfieldExp.push_back(vex);
	}


	// Main loop
	// Get fieldset values for each grid point
	vector<double> gvals(nfsin,0);   //grid point values
	for( gp = 0; gp < nsize; gp++ )
	{
	      // get grid point values
	      for (fi = 0; fi < nfsin; fi++)
	      {
		   gvals[fi] = (vfield[fi])[gp];
	      }

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

              // update output fieldset for each percentile value
              for (i = 0; i < percList_.size(); i++)
	      {
		      (vfield[i])[gp] = GetValue(vrank[i],gvals,nfsin);  
	      }
      	}

	// Create output fieldset
	MvFieldSet fsOut;
        for (i = 0; i < percList_.size(); i++)
		 fsOut += vfield[i];

	// Write output fieldset on disk
	// This function calls Mars to write the fieldset
	MvRequest req = fsOut.getRequest();

	// Update output request
	out = out + req;

	// Release memory
        for (fi = 0; fi < nfsin; fi++)
	{
		MvFieldExpander* vex = vfieldExp[fi];
		delete vex;
	}

	return true;
}

double Percentile::GetValue(double rank, vector<double>& gvals, int nfsin)
{
	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 ((nfsin-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();
}
