//- ****************************************************************************
//-
//- Copyright 2009 Sandia Corporation. Under the terms of Contract
//- DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
//- certain rights in this software.
//-
//- BSD Open Source License.
//- All rights reserved.
//-
//- Redistribution and use in source and binary forms, with or without
//- modification, are permitted provided that the following conditions are met:
//-
//-    * Redistributions of source code must retain the above copyright notice,
//-      this list of conditions and the following disclaimer.
//-    * Redistributions in binary form must reproduce the above copyright
//-      notice, this list of conditions and the following disclaimer in the
//-      documentation and/or other materials provided with the distribution.
//-    * Neither the name of Sandia National Laboratories nor the names of its
//-      contributors may be used to endorse or promote products derived from
//-      this software without specific prior written permission.
//-
//- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
//- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
//- ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
//- LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//- CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
//- SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//- CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
//- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
//- POSSIBILITY OF SUCH DAMAGE.
//-

#include <stdio.h>
#include "slbm_C_shell.h"
#include <stdlib.h>
#include "time.h"
#include "string.h"
//#include "curses.h"
#include <math.h>

void check_err(int err);
void print_error(int err);
char * getline_slbm(FILE * f);

double DEG_TO_RAD = 1.7453292519943300E-02;
double RAD_TO_DEG = 5.7295779513082300E+01;

int main(int argc, char* argv[])
{
	int regression_test(char* modelName, char* regressionFile);

	int simple_test(char* modelName, char* phase,
			double srclatDegrees, double srclonDegrees, double srcDepthKm,
			double rcvlatDegrees, double rcvlonDegrees, double rcvElevKm);

	char SlbmVersion[20];

	if (argc == 3)
	{
		// run the regresion test
		return regression_test(argv[1], argv[2]);
	}
	else if (argc == 4)
	{
		slbm_shell_create();
		printf("SLBM C reformat model\n");
		printf("Loading velocity model %s\n", argv[1]);
		check_err(slbm_shell_loadVelocityModel(argv[1]));
		printf("Saving  velocity model %s\n", argv[2]);
		check_err(slbm_shell_saveVelocityModelFormat(argv[2], atoi(argv[3])));
		printf("Done.");
	}
	else if (argc == 9)
	{
		// run the single path test.
		int i = 1;
		char* modelName = argv[i++];
		char* phase     = argv[i++];
		double srclatDegrees = atof(argv[i++]);
		double srclonDegrees = atof(argv[i++]);
		double srcDepthKm    = atof(argv[i++]);
		double rcvlatDegrees = atof(argv[i++]);
		double rcvlonDegrees = atof(argv[i++]);
		double rcvElevKm     = atof(argv[i++]);

		simple_test(modelName, phase, 
				srclatDegrees, srclonDegrees, srcDepthKm, 
				rcvlatDegrees, rcvlonDegrees, rcvElevKm);
	}
	else
	{
		slbm_shell_getVersion(SlbmVersion);
		printf("SLBM SLBM version %s\n\n", SlbmVersion);

		printf("Expecting either 2 or 8 command line parameters:\n\n");

		printf("2 parameters - run a regression test:\n");
		printf("    1: directory where slbm velocity model can be found\n");
		printf("    2: file containing the regression test data\n");
		printf("    If running this program from SLBM_Root, try:\n");
		printf("    bin/slbmtestc models/rstt201404um.geotess models/rstt201404um.regression.dat\n\n");

		printf("8 parameters - run a single path test:\n");
		printf("    1: directory where slbm velocity model can be found\n");
		printf("    2: phase (one of Pn, Sn, Pg, Lg)\n");
		printf("    3: source latitude in degees\n");
		printf("    4: source longitude in degrees\n");
		printf("    5: source depth in km below sea level\n");
		printf("    6: receiver latitude in degees\n");
		printf("    7: receiver longitude in degrees\n");
		printf("    8: receiver DEPTH (!!!) in km BELOW sea level\n");
		printf("    If running this program from SLBM_Root, try:\n");
		printf("    bin/slbmtestc models/na1010pn Pn 10 20 5 15 25 -0.1\n\n");
		exit(1);
	}
	return 0;
}

int simple_test(char* modelName, char* phase, 
		double srclatDegrees, double srclonDegrees, double srcDepthKm,
		double rcvlatDegrees, double rcvlonDegrees, double rcvDepthKm)
{
	double srclat, srclon, rcvlat, rcvlon;
	double distance, tt, tt_uncertainty, slow, sh_uncertainty, dtt_dlat, dtt_dlon, dtt_ddepth;
	char SlbmVersion[20];

	slbm_shell_getVersion(SlbmVersion);

	slbm_shell_create();

	printf("SLBM C Single Path Test -- SLBM version %s\n\n", SlbmVersion);

	printf("Loading velocity model %s\n\n", modelName);

	// load the velocity model.  if any errors happen, print message and exit
	check_err(slbm_shell_loadVelocityModel(modelName));

	printf("Phase  = %s\n", phase);
	printf("Source   Lat   = %9.4f deg\n",srclatDegrees);
	printf("Source   Lon   = %9.4f deg\n",srclonDegrees);
	printf("Source   Depth = %9.4f km\n",srcDepthKm);
	printf("Receiver Lat   = %9.4f deg\n",rcvlatDegrees);
	printf("Receiver Lon   = %9.4f deg\n",rcvlonDegrees);
	printf("Receiver Depth = %9.4f km\n\n",rcvDepthKm);

	srclat = srclatDegrees*DEG_TO_RAD;
	srclon = srclonDegrees*DEG_TO_RAD;
	rcvlat = rcvlatDegrees*DEG_TO_RAD;
	rcvlon = rcvlonDegrees*DEG_TO_RAD;

	// create a GreatCircle object at the requested location.
	// Note that source and receiver latitudes and longitudes are converted
	// from degrees to radians.
	check_err(slbm_shell_createGreatCircle(phase, 
			&srclat, &srclon, &srcDepthKm,
			&rcvlat, &rcvlon, &rcvDepthKm));

	check_err(slbm_shell_getDistance(&distance));
	distance *= RAD_TO_DEG;

	printf("Distance       = %9.4f deg\n",distance);
	// retrieve the travel time, in seconds
	check_err(slbm_shell_getTravelTime(&tt));

	printf("Travel time    = %9.4f sec\n",tt );

	// retrieve the travel time uncertainty, in seconds
	check_err(slbm_shell_getTTUncertainty(&tt_uncertainty));

	printf("TT uncertainty = %9.4f sec\n",tt_uncertainty);

	// retrieve slowness
	check_err(slbm_shell_getSlowness(&slow));
	slow *= DEG_TO_RAD; // convert from sec/radian to sec/degrees

	printf("Slowness       = %9.4f sec/deg\n",slow);

	// retrieve the travel time uncertainty, in seconds
	check_err(slbm_shell_getSHUncertainty(&sh_uncertainty));
	sh_uncertainty *= DEG_TO_RAD; // convert from sec/radian to sec/degrees

	printf("SH uncertainty = %9.4f sec/deg\n",sh_uncertainty);

	// retrieve dtt_dlat
	check_err(slbm_shell_get_dtt_dlat(&dtt_dlat));
	dtt_dlat *= DEG_TO_RAD; // convert from sec/radian to sec/degrees

	printf("dtt/dlat       = %9.4f sec/deg\n",dtt_dlat);

	// retrieve dtt_dlon
	check_err(slbm_shell_get_dtt_dlon(&dtt_dlon));
	dtt_dlon *= DEG_TO_RAD; // convert from sec/radian to sec/degrees

	printf("dtt/dlon       = %9.4f sec/deg\n",dtt_dlon);

	// retrieve dtt_dlon
	check_err(slbm_shell_get_dtt_ddepth(&dtt_ddepth));

	printf("dtt/ddepth     = %9.4f sec/km\n\n",dtt_ddepth);

	printf("Done.\n");

	return 0;
}

int regression_test(char* modelDirectory, char* dataFile)
{
	char* line;

	FILE* fin;

	char phase[3];

	double slat, slon, sdepth, rlat, rlon, rdepth,
	distance0, distance, tt0, tt, tt_uncertainty0, tt_uncertainty, slow0, slow;

	double tolerance = 0.02;

	int err, nrecords=0, ndifferent=0;

	char SlbmVersion[20];

	slbm_shell_getVersion(SlbmVersion);

	printf("SLBM C Regression Test -- SLBM version %s\n", SlbmVersion);

	slbm_shell_create();

	printf("Loading velocity model %s\n", modelDirectory);

	// load the velocity model.  if any errors happen, print message and exit
	check_err(slbm_shell_loadVelocityModel(modelDirectory));

	// open the file with regression data
	printf("Loading regression data from %s\n\n", dataFile);

	if ((fin = fopen(dataFile, "r")) == NULL)
	{
		printf("ERROR: Unable to open file %s", dataFile);
		return -1;
	}

	// read comment lines until an empty line is encountered
	line = getline_slbm(fin);
	while (strlen(line) > 0)
	{
		if (strstr(line, "LINEAR"))
			slbm_shell_setInterpolatorType("LINEAR");

		printf("%s\n", line);
		line = getline_slbm(fin);
	}

	// read records from the input file until end of file.
	// phase  : one of Pn, Sn, Pg, Lg
	// slat   : source latitude in degrees
	// slon   : source longitude in degrees
	// sdepth : source depth below sea level in km
	// rlat   : receiver latitude in degrees
	// rlon   : receiver longitude in degrees
	// rdepth : receiver depth below sea level in km
	// tt0    : previously computed travel time in seconds
	while (fscanf(fin,"%s %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",
			phase, &slat, &slon, &sdepth, &rlat, &rlon, &rdepth,
			&distance0, &tt0, &tt_uncertainty0, &slow0) != EOF)
	{
		// count records
		++nrecords;

		// convert source and receiver lat, lon from degrees to radians
		slat *= DEG_TO_RAD;
		slon *= DEG_TO_RAD;
		rlat *= DEG_TO_RAD;
		rlon *= DEG_TO_RAD;

		// call create_great_circle to compute travel times.
		// err will be zero if successful, positive error code if an error happens.
		err = slbm_shell_createGreatCircle(phase,
				&slat, &slon, &sdepth, &rlat, &rlon, &rdepth);

		if (err)
		{
			// an error occurred creating the great circle.
			// Respond by setting all regression values for this record
			// to -999 and do not retrieve values for tt, slowness, etc.
			// Presumably the same error happened when the regression data were generated.
			distance = tt = tt_uncertainty = slow = -999.;

			// if you want to see the error messages, uncomment following line:
			//print_error(slbm_shell_getTravelTime(&tt));
		}
		else
		{
			// great circle successfully created.  Retrieve values for distance,
			// travel time, tt uncertainty and slowness.  If any of the method
			// calls returns non-zero error code, set all the values to -999.
			if (slbm_shell_getDistance(&distance))
				distance = tt = tt_uncertainty = slow = -999.;
			else
			{
				distance *= RAD_TO_DEG;

				if (slbm_shell_getTravelTime(&tt))
					distance = tt = tt_uncertainty = slow = -999.;
				else
				{

					if (slbm_shell_getTTUncertainty(&tt_uncertainty))
						distance = tt = tt_uncertainty = slow = -999.;
					else
					{
						if (slbm_shell_getSlowness(&slow))
							distance = tt = tt_uncertainty = slow = -999.;
						else
							slow *= DEG_TO_RAD;
					}
				}
			}
		}

		// if previously stored travel time and new computed travel time don't agree,
		// print a message.
		if (abs(distance-distance0) > tolerance
				|| abs(tt-tt0) > tolerance 
				|| abs(tt_uncertainty-tt_uncertainty0) > tolerance 
				|| abs(slow-slow0) > tolerance)
		{
			++ndifferent;

			if (ndifferent == 1)
				printf("\n\n#ph   srcLat      srcLon srcDepth    rcvLat     rcvLon  rcvDepth    dist0     dist     tt0       tt   uncert0   uncert    slow0     slow\n");


			printf("%2s %10.6f %11.6f %7.3f %10.6f %11.6f %7.3f %8.3f %8.3f %8.3f %8.3f %8.3f %8.3f %8.3f %8.3f\n",
					phase, slat*RAD_TO_DEG, slon*RAD_TO_DEG, sdepth, 
					rlat*RAD_TO_DEG, rlon*RAD_TO_DEG, rdepth, 
					distance0, distance, tt0, tt, tt_uncertainty0, tt_uncertainty, slow0, slow);
		}
	}

	// print results
	printf("\n%d of %d travel times differences exceeded tolerance of %f seconds\n\n",
			ndifferent, nrecords, tolerance);

	if (ndifferent == 0 && nrecords > 0)
		printf("PASSED\n\n");
	else
		printf("FAILED\n\n");

	return 0;
}

void check_err(int err)
{
	if( err )
	{
		print_error(err);
		exit(1);
	}
}

void print_error(int err)
{
	char errmsg[1000];
	if( err )
	{
		slbm_shell_getErrorMessage( errmsg );
		printf( "ERROR %d%s\n\n", err, errmsg);
	}
}

/**
 * This function reads characters from a FILE until \n is found
 * and returns a pointer to the resulting char*.
 * Copied from http://sucs.org/Knowledge/Help
 * look for link "Reading an arbitrarily long line in C"
 */
char * getline_slbm(FILE * f)
{
	size_t size = 0;
	size_t len  = 0;
	size_t last = 0;
	char * buf  = NULL;
	do
	{
		size += BUFSIZ; /* BUFSIZ is defined as "the optimal read size for this platform" */
		buf = (char*)realloc(buf,size); /* realloc(NULL,n) is the same as malloc(n) */
		/* Actually do the read. Note that fgets puts a terminal '\0' on the
		end of the string, so we make sure we overwrite this */
		fgets(buf+last,size,f);
		len = strlen(buf);
		last = len - 1;
	} while (!feof(f) && buf[last]!='\n');
	/* delete \n at end of string */
	buf[last] = '\0';
	return buf;
}

