/***************************** 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 "inc_iostream.h"
#include "Metview.h"

#ifndef MISSING_DATA
const double MISSING_DATA =  1E21;
#endif

double makeDateNumber( long date, long time );

class geopt {
public:
    float  latitude;
    float  longitude;
    float  height;
    long   date;
    long   time;
    double value;

	geopt()
		{ latitude = 0; longitude = 0; 
		  height   = 0; date      = 0;
		  time     = 0; value     = 0; }
    geopt& operator = (const geopt& in) 
		{
			latitude  = in.latitude;
			longitude = in.longitude;
			height    = in.height;
			date      = in.date;
			time      = in.time;
			value     = in.value;
			return *this;
		}
	void load(const char* line) ;
		//{ 
           // sscanf(line,"%f %f %f %ld %ld %lf",
           //     &latitude, &longitude, &height, &date, &time, &value);
		//}
	void   print()
		{ printf("Latitude = %f Longitude = %f Value = %f\n", latitude, longitude, value); }

};

class ToMatrix 
{
    geopt     *Points;
    int        NbPoints;
	double      North;
	double      South;
	double      West;
	double      East;
	double      GridLat;
	double      GridLon;
	double      Tolerance;
	int        NbLat;
	int        NbLon;
	double     Date;
	double    *Matrix;

public:
	ToMatrix(MvRequest&);
	int load(const char*);
	int save(const char*);
	void estimate();
	double value(float lat, float lon);
	void sortPoints();
	void print()
		{
			printf("Print Matrix....\n");
			for (int i = 0; i < NbPoints; i++) Points[i].print();
		}
protected:
	float long180( float x ){ return x>180 ? x-360 : x; }
	float long360( float x ){ return x<0   ? x+360 : x; }
	float longDistance( float x1, float x2 );
};

class GeoToMatrix : public MvService {
public:
	GeoToMatrix() : MvService("GEO_TO_MATRIX") {};
	void serve(MvRequest&,MvRequest&);
};

double makeDateNumber( long date, long time )
{
	double myDate = date;
	double myTime = (double)time;

	if( myDate < 470620 )  //-- must be relative => no time
	    return myDate;

	if( myTime < 0 )       //-- illegal or rubbish
	    return myDate;

	if( myTime > 2400 )    //-- illegal or rubbish
	    return myDate;

	if( myTime >= 100 )    //-- 01:00...24:00
	    return myDate + myTime / 2400.;

	if( myTime > 59 )      //-- illegal or rubbish
	    return myDate;

	if( myTime > 24 )      //-- 00:25...00:59
	    return myDate + myTime/60. / 24.;

	return myDate + myTime / 24.;
}

void geopt::load(const char* line)
{ 
    double   d_date; //-- if user has floating point valued data here...
    double   d_time;

	istringstream myInput( line );
	myInput >> latitude >> longitude >> height >> d_date >> d_time >> value;

	date = (long)d_date;
	time = (long)d_time;
}

ToMatrix::ToMatrix(MvRequest& def)
{
	North     = def("AREA", 0);
	West      = def("AREA", 1);
	South     = def("AREA", 2);
	East      = def("AREA", 3);
	GridLat   = def("GRID");
	GridLon   = def("GRID", 1);
	Tolerance = def("TOLERANCE");

	if(North < South)
	{
		double tmp = North;
		North      = South;
		South      = tmp;
	}

	if (!GridLat) GridLat = 1.5;
	if (!GridLon) GridLon = GridLat;


	MvRequest data;
	def.getValue(data,"GEOPOINTS");

	load(data("PATH"));
}

int ToMatrix::load(const char* path)
{
    FILE *f = fopen(path,"r");
    if(f == NULL) return 1;

    // first count the lines
    char line[1024];
    int n = 0;

    while(fgets(line,sizeof(line),f))
        n++;


    Points = new geopt[n];

    n = 0;
    rewind(f);

    while(fgets(line,sizeof(line),f))
        if(strncmp(line,"#DATA",5)==0)
            break;

    while(fgets(line,sizeof(line),f))
    {
        if(*line != '#')
        {
			Points[n].load(line);
            n++;
        }
    }
    NbPoints = n;
    fclose(f);

	Date = makeDateNumber( Points[0].date, Points[0].time );

	return 0;
}

double ToMatrix::value(float lat, float lon)
{
	double val = MISSING_DATA;
	double dist, coef;
	double sigma = 0;

	for (int i = 0; i < NbPoints; i++) {
		if ((Points[i].latitude - lat) > Tolerance ) break;
		if ((lat - Points[i].latitude) > Tolerance ) continue;

		//if ((Points[i].longitude - lon) > Tolerance ) continue;
		//if ((lon - Points[i].longitude) > Tolerance ) continue;
		if( longDistance( Points[i].longitude, lon ) > Tolerance ) continue;

		float x = lon - Points[i].longitude;
		float y = lat - Points[i].latitude;
		// Here we have found a point inside the Interval;
		dist = (x*x) + (y*y);
		if (dist == 0) return Points[i].value; // Here the point is on the Grid
		dist = 1/dist;
		sigma +=dist;
		coef = dist * Points[i].value;
		if (val == MISSING_DATA) val = coef;
		else val += coef;
	}

	if (sigma) val = val / sigma;
	return val;
}

float
ToMatrix::longDistance( float x1, float x2 )
{
	// calculate two ways, in case given values are
	// on the different sides of discontinuity line!

	float min1 = long180( x1 ) - long180( x2 );
	if( min1 < 0.0 )
	    min1 = -min1;

	float min2 = long360( x1 ) - long360( x2 );
	if( min2 < 0.0 )
	    min2 = -min2;

	return min1 < min2 ? min1 : min2;
}

void ToMatrix::sortPoints()
{
	
	float lonmin = 99999;
	float latmin = 99999;
	int   min    = 99999;
	geopt tmp;

	
	for (int s = 0; s < NbPoints; s++) {
		lonmin = 99999;
		latmin = 99999;
	 	min    = 99999;
		for (int i = s; i < NbPoints; i++) {
			if (Points[i].latitude < latmin) {
				lonmin = Points[i].longitude;
				latmin = Points[i].latitude;
				min    = i;
			}
			else if (Points[i].latitude == latmin) {
				if (Points[i].longitude < lonmin) {
					lonmin = Points[i].longitude;
					latmin = Points[i].latitude;
					min    = i;
				}
			}	
		}
		if (min != 99999) {
			tmp = Points[s];
			Points[s] = Points[min];
			Points[min] = tmp;
		}
	}
}

void ToMatrix::estimate()
{

	int   i, j;
	float lat,lon;
	
	if ((East - West) == 360) East -= GridLon;
    NbLon = ((East - West) / GridLon) + 1;
    NbLat = ((North - South) / GridLat) + 1;

	Matrix = new double[NbLon*NbLat];

	lat = North;
	for (j = 0; j < NbLat; j++) {
		lon = West;
		for (i = 0; i < NbLon; i++) {
			Matrix[i + ( j*NbLon) ] = value(lat, lon);
			lon += GridLon;
		}
		lat -= GridLat;
	}
}

int ToMatrix::save(const char * path)
{
    FILE *f = fopen(path,"w");
    if(f == NULL) return 1;

	fprintf(f, "#LLMATRIX\n");
	fprintf(f, "GRID = %g/%g\n", GridLat, GridLon);
	fprintf(f, "NORTH = %g\n", North);
	fprintf(f, "SOUTH = %g\n", South);
	fprintf(f, "EAST = %g\n", East);
	fprintf(f, "WEST = %g\n", West);
	fprintf(f, "NLAT = %d\n", NbLat);
	fprintf(f, "NLON = %d\n", NbLon);
	fprintf(f, "DATE = %fl\n", Date);
	fprintf(f, "MISSING = '%g'\n", MISSING_DATA);
	fprintf(f, "#DATA\n");

	int last;

	for (int j = 0; j < NbLat; j++) {
		for (int i = 0; i < NbLon; i++) {
			last = i + ( j*NbLon);
			fprintf(f, "%g\n", Matrix[i + ( j*NbLon)]);
		}
	}
	fclose(f);
	printf("indice  = %d \n", last);
	return 0;
}



void GeoToMatrix::serve(MvRequest& in,MvRequest& out)
{


	ToMatrix matrix(in);
	matrix.sortPoints();
	matrix.estimate();

    char *path  = marstmp();
	if (matrix.save(path)) { 
		setError(1,"Can not write to file %s", path);
		return;
	}


	MvRequest llmatrix("LLMATRIX");
    llmatrix("PATH")      = path;
    llmatrix("TEMPORARY") = 0;

    out = llmatrix;
	out.print();
}

int main(int argc,char **argv)
{
    MvApplication theApp(argc,argv);
    GeoToMatrix tool;
    theApp.run();
}


