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

// This is an extended version of llmatrix_to_grib function
// available in Mars library.  Within Metview both 'togrib'
// and macro now use this extended version.
//                                             vk/1999-07-19
// Re-written to use GRIB_API                  vk/2009-07-07

#include "inc_iostream.h"
#include "Metview.h"
#include "LLMatrixToGRIB.h"
#include "UtilitiesC.h"
#include "grib_api.h"
#include <math.h>

#define CLEAR(a) memset(&a,0,sizeof(a))


void do_swap( double& s1, double& s2 )
{
    double t=s1;
    s1=s2;
    s2=t;
}

err LLMatrixToGRIB( const char* matrixFile, const char* gribFileName )
{
	//-- open LatLonMatrix file

	ifstream matrixStream( matrixFile, ios::in );
	if(!matrixStream)
	{
		marslog(LOG_EROR|LOG_PERR,"Cannot open %s",matrixFile);
		return -1;
	}

	//-- temporary file to store LatLonMatrix metadata as a Request

	char *requestFile = marstmp();
	FILE *reqFILE = fopen( requestFile, "w" );
	if(!reqFILE)
	{
		marslog(LOG_EROR|LOG_PERR,"Cannot open %s",requestFile);
		return -1;
	}

	//-- extract LatLonMatrix metadata and write metadata into a Request file

	int  n = 0;
	char line[1024];
	matrixStream.getline( line, sizeof(line) );
	while( matrixStream.gcount() > 0 )
	{
		if(*line != '#')
		{
			int l = strlen(line)-1;

			while(l >= 0 && (
					line[l] == ' '  ||
					line[l] == '\n' ||
					line[l] == ','  ||
					line[l] == '\t')
					)
				line[l--] = 0;


			if(n == 0) {
				// Look for a '='
				char *p  = line;
				int flg  = 0;
				while(*p) if(*p++ == '=') flg++;
				// If a '=', add a verb
				if(flg) {
					fprintf(reqFILE,"LLMATRIX");
					fprintf(reqFILE,",%s",line);
				}
				else fprintf(reqFILE,"%s",line);
			}
			else fprintf(reqFILE,",%s",line);
			n++;
		}
		if(strncmp(line,"#DATA",5)==0)
			break;

		matrixStream.getline( line, sizeof(line) );
	}

	fclose( reqFILE );

	//-- read into an internal Request structure and remove the temporary file

	request* r = read_request_file( requestFile );
	unlink( requestFile );
	print_all_requests(r);

	//-- extract grid related metadata

	const char* p;
	double north   = atof((p=get_value(r,"NORTH",0))?p:"0");
	double west    = atof((p=get_value(r,"WEST", 0))?p:"0");
	int    nlat    = atol((p=get_value(r,"NLAT", 0))?p:"0");
	int    nlon    = atol((p=get_value(r,"NLON", 0))?p:"0");
	double ew      = atof((p=get_value(r,"GRID", 0))?p:"0");
	double ns      = atof((p=get_value(r,"GRID", 1))?p:"0");

	if( ns == 0 )
		ns = ew;

	if(ew == 0 || ns == 0 || nlat == 0 || nlon == 0)
	{
		marslog(LOG_EROR,"Invalid ll-matrix");
		return -1;
	}

	p=get_value(r,"HSCAN", 0);    //-- 'WE' (default) or 'EW'
	int  hscan = ( p && ( *p == 'E' || *p == 'e' ) ) ? 128 : 0;

	p=get_value(r,"VSCAN", 0);    //-- 'NS' (default) or 'SN'
	int  vscan = ( p && ( *p == 'S' || *p == 's' ) ) ? 64 : 0;

	p=get_value(r,"SCANDIR", 0);  //-- 'HORIZONTAL' (default) or 'VERTICAL'
	int  scandir = ( p && ( *p == 'V' || *p == 'v' ) ) ? 32 : 0;

	double missing = (p=get_value(r,"MISSING", 0))?atof(p):mars.grib_missing_value;

	//-- extract PARAM, TABLE2, LEVEL, CENTRE, DATE, FCAST

	int   param   = (p=get_value(r,"PARAM", 0))?atol(p):255;
	int   table   = (p=get_value(r,"TABLE2", 0))?atol(p):128;
	int   level   = (p=get_value(r,"LEVEL", 0))?atol(p):0;
	int   centre  = (p=get_value(r,"CENTRE", 0))?atol(p):localWmoSiteNumber();
	if( param == 0 )
	    param = 255;

	long msave = expand_flags(0);
	p = get_value(r,"DATE", 0);
	double dseed = p ? atof(p) : 0.0;
	MvDate    date( dseed );
	expand_flags(msave);

	int   fc_len  = ( p = get_value(r,"FCAST", 0) ) ? atol(p) : 0;


	//-- set correct boarder values

	double first_lat = north;
	double last_lat  = north - (nlat-1) * ns;

	if( vscan > 0 )
		do_swap( first_lat, last_lat );

	double first_lon = west;
	double last_lon  = west + (nlon-1) * ew;

	if( hscan > 0 )
		do_swap( first_lon, last_lon );

	/* ============================================================ */

	//-- read data values from LatLonMatrix file

	int npoints = nlat * nlon;
	double* dataValues = new double[ npoints ];

	n = 0;
	int miss = 0;
	for( int i = 0; i < npoints; ++i )
	{
		if( matrixStream.eof() )
		{
		   dataValues[i] = mars.grib_missing_value;
		   miss++;
		}
		else
		{
		   double myValue = DBL_MAX;
		   matrixStream >> myValue;

		   if( myValue == DBL_MAX )  //-- check read success
		   {
		      dataValues[i] = mars.grib_missing_value;
		      miss++;
		   }
		   else
		   {
		      dataValues[i] = myValue;
		      ++n;

		      if( dataValues[i] == missing )
		      {
		         miss++;             //-- adapt to mars
		         dataValues[i] = mars.grib_missing_value;
		      }
		   }

		   if( matrixStream.peek() == ',' )
		       matrixStream.ignore(1,',');    //-- skip possible comma separators
		}
	}
	matrixStream.close();

	if( n != npoints )
	{
		marslog(LOG_EROR,"LLMatrixToGRIB: file %s, read %d values out of %d expected",
			matrixFile, n, npoints );
		return -1;
	}

	/* ============================================================ */

	//-- create a new grib handle from a GRIB_API sample file

	const char* sample = "regular_ll_sfc_grib1";

	grib_handle* h = grib_handle_new_from_samples( 0, sample );
	if( !h ) {
	  marslog( LOG_EROR
	         , "Failed to create handle from sample %s[.tmpl]", sample );
	  return -1;
	}

	//-- start modifying the GRIB metadata, according to LatLonMatrix metadata

	int stat = 0;
	stat += grib_set_long( h, "table2Version", table );
	stat += grib_set_long( h, "centre", centre );
	stat += grib_set_long( h, "gridDefinition", 255 );
	stat += grib_set_long( h, "section1Flags", miss ? 192 : 128 );
	stat += grib_set_long( h, "indicatorOfParameter", param );

	if( stat > 0 ) {
		marslog( LOG_EROR, "Errors calling 'grib_api' (1)!" );
		return -1;
	}


	//-- UKMO uses 'generating process' octet as part of their STASH code;
	//-- use env.variable MV_GENERATING_PROCESS to set a custom value
	const char* generProcAscii = getenv( "MV_GENERATING_PROCESS" );
	if( generProcAscii )
	  stat += grib_set_long( h, "generatingProcessIdentifier", atoi( generProcAscii ) );
	else
	  stat += grib_set_long( h, "generatingProcessIdentifier", mars.computeflg );

	//-- value zero for 'generating process' causes problems!
	long generProc = 0;
	stat += grib_get_long( h, "generatingProcessIdentifier", &generProc );
	if( generProc == 0 )
	  stat += grib_set_long( h, "generatingProcessIdentifier", 255 );


	stat += grib_set_long( h, "indicatorOfUnitOfTimeRange", 1 );
	if( fc_len > 0 )
	{
	  stat += grib_set_long( h, "periodOfTime", fc_len );
	  stat += grib_set_long( h, "marsType", 9 ); //-- forecast
	}

	if( level == 0 ) {
		stat += grib_set_long( h, "levtype", GRIB_SURFACE );
	} else {
		stat += grib_set_long( h, "levtype", GRIB_PRESSURE_LEVEL );
		stat += grib_set_long( h, "level", level );
	}

#if 0
//??????
	if( centre == 98 ) //-- ??????????????????? vk/20090707
	{
		locsec1.ecmwf_local_id  = GRIB_ENSEMBLE_FORECAST;
		locsec1.mars_class      = GRIB_CLASS_OD;
		locsec1.mars_type       = 2;          /* AN */
		locsec1.mars_stream     = 1025;       /* DA */
		locsec1.mars_expver     = 0x30303031; /* 01 */

		ksec1.local_use         = 1;
		ksec1.local             = locsec1;
	}

	ksec2.data_rep        = GRIB_LAT_LONG;
#endif
	stat += grib_set_long( h, "date", date.YyyyMmDd() );
	stat += grib_set_long( h, "time", date.hhmmss()/100 );


	stat += grib_set_long( h, "Ni", nlon );
	stat += grib_set_long( h, "Nj", nlat );

	stat += grib_set_double( h, "latitudeOfFirstGridPointInDegrees", first_lat );
	stat += grib_set_double( h, "longitudeOfFirstGridPointInDegrees", first_lon );
	stat += grib_set_double( h, "latitudeOfLastGridPointInDegrees", last_lat );
	stat += grib_set_double( h, "longitudeOfLastGridPointInDegrees", last_lon );
	stat += grib_set_long( h, "scanningMode", hscan + vscan + scandir );

	if( stat > 0 ) {
		marslog( LOG_EROR, "Errors calling 'grib_api' (2)!" );
		return -1;
	}

	int ewi = ew * GRIB_FACTOR;
	int nsi = ns * GRIB_FACTOR;

	//----------------------------------------------------------//
	//-- GRIB1 precision is millidegrees: 0.001 degrees only! --//
	//-- If grid interval requires more precision then do not --//
	//-- store grid intervals (let them to be computed).      --//
	//----------------------------------------------------------//
	bool not_enough_precision =
	  (fabs( ew - (double)ewi/GRIB_FACTOR ) > 0.00005) ||
	  (fabs( ns - (double)nsi/GRIB_FACTOR ) > 0.00005);

	stat += grib_set_long( h, "ijDirectionIncrementGiven", not_enough_precision ? 0 : 1 );
	stat += grib_set_double( h, "iDirectionIncrementInDegrees", not_enough_precision ? 0 : ew );
	stat += grib_set_double( h, "jDirectionIncrementInDegrees", not_enough_precision ? 0 : ns );

	if( stat > 0 ) {
		marslog( LOG_EROR, "Errors calling 'grib_api' (3)!" );
		return -1;
	}


	//  Missing value

	stat += grib_set_long( h, "bitmapPresent", 1 );
	stat += grib_set_double( h, "missingValue", mars.grib_missing_value );

	if( stat > 0 ) {
		marslog( LOG_EROR, "Errors calling 'grib_api' (4)!" );
		return -1;
	}

	//--  Data

	stat += grib_set_long( h, "numberOfPoints", npoints );

	int nbits = 24;  //-- currently hard coded
	stat += grib_set_long( h, "bitsPerValue", nbits );

	printf("nbits   = %d\n",nbits);
	printf("missing = %g\n",missing);

	//-- set new grid point values
	stat = grib_set_double_array( h, "values", dataValues, npoints );

	//-- get the GRIB1 message into 'buffer'
	size_t      buflen   = 0;
	const void* buffer = NULL;
	stat = grib_get_message( h, &buffer, &buflen );

	if( stat > 0 ) {
		marslog( LOG_EROR, "Errors calling 'grib_api' (5)!" );
		return -1;
	}

	//-- write the GRIB1 message from 'buffer' to the file

	FILE *gribFile = fopen(gribFileName,"w");
	if(!gribFile)
	{
		marslog(LOG_EROR|LOG_PERR,"Cannot open %s",gribFileName);
		return -1;
	}

	err e = 0;
	if(fwrite(buffer,1,buflen,gribFile) != buflen)
	{
		marslog(LOG_EROR|LOG_PERR,"fwrite(%s) failed",gribFileName);
		e = -1;
	}

	if(fclose(gribFile))
	{
		marslog(LOG_EROR|LOG_PERR,"fclose(%s) failed",gribFileName);
		e = -1;
	}

	delete [] dataValues;
//	delete [] buffer;
	return e;
}
