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

package slbmtest;

import static java.lang.Math.*;
import gov.sandia.gnem.slbmjni.SLBMException;
import gov.sandia.gnem.slbmjni.SlbmInterface;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.Scanner;

/**
 * <p>SLBM Java Regression Test
 *
 * <p>Run a regression test by reading from file a bunch of
 * source and receiver positions and previously computed travel times.
 * Travel times are computed again and compared to previously stored 
 * values.  
 *
 * @author Sandy Ballard
 * @version 1.0
 */
public class SLBMTest
{
	
	static{
		System.loadLibrary("slbmjni");
	}
	
	
	public static void main(String[] args)
	{

		if (args.length == 2)
		{
			// run regression test.
			run_regression_test(args);			
		}
		else if (args.length == 3 || args.length == 4)
		{
			// generate new regression data
			generate_regression_test_data(args);			
		}
		else if (args.length == 8)
		{
			simple_test(args);
		}
		else
		{
			System.out.println("Expecting either 2 or 8 command line parameters:");
			System.out.println();
			System.out.println("2 parameters - run a regression test:");
			System.out.println("    1: directory where slbm velocity model can be found");
			System.out.println("    2: file containing the regression test data");
			System.out.println("    If running this program from SLBM_Root, try:");
			System.out.println("    java -jar bin/slbmtest.jar models/na1010pn models/na1010pn/regression_na1010pn.dat");
			System.out.println();
			System.out.println("8 parameters - run a single path test:");
			System.out.println("    1: directory where slbm velocity model can be found");
			System.out.println("    2: phase (one of Pn, Sn, Pg, Lg)");
			System.out.println("    3: source latitude in degees");
			System.out.println("    4: source longitude in degrees");
			System.out.println("    5: source depth in km below sea level");
			System.out.println("    6: receiver latitude in degees");
			System.out.println("    7: receiver longitude in degrees");
			System.out.println("    8: receiver DEPTH (!!!) in km BELOW sea level");
			System.out.println("    If running this program from SLBM_Root, try:");
			System.out.println("    java -jar bin/slbmtest.jar models/na1010pn Pn 10 20 5 15 25 -0.1");
			System.out.println();
			System.exit(1);
		}

	}

	public static void simple_test(String[] args)
	{
		System.out.println("SLBM Java Regression Test");
		try
		{
			File modelDirectory = new File(args[0]);

			// instantiate an SLBMInterface object
			SlbmInterface slbm = new SlbmInterface();

			System.out.println("Running SLBM Version "+slbm.getVersion());

			// load the velocity model
			slbm.loadVelocityModel(modelDirectory.getCanonicalPath());
			
			String phase = args[1];
			double srclatDegrees = Double.parseDouble(args[2]);
			double srclonDegrees = Double.parseDouble(args[3]);
			double srcdepthKm = Double.parseDouble(args[4]);
			double rcvlatDegrees = Double.parseDouble(args[5]);
			double rcvlonDegrees = Double.parseDouble(args[6]);
			double rcvdepthKm = Double.parseDouble(args[7]);
			
			System.out.printf("Phase  = %s%n", phase);
			System.out.printf("Source   Lat   = %9.4f deg%n", srclatDegrees);
			System.out.printf("Source   Lon   = %9.4f deg%n", srclonDegrees);
			System.out.printf("Source   Depth = %9.4f km%n", srcdepthKm);
			System.out.printf("Receiver Lat   = %9.4f deg%n", rcvlatDegrees);
			System.out.printf("Receiver Lon   = %9.4f deg%n", rcvlonDegrees);
			System.out.printf("Receiver Depth = %9.4f km%n", rcvdepthKm);
			System.out.println();
			
			double DEG_TO_RAD = Math.toRadians(1.);
			slbm.createGreatCircle(phase,
					srclatDegrees*DEG_TO_RAD, srclonDegrees*DEG_TO_RAD, srcdepthKm,
					rcvlatDegrees*DEG_TO_RAD, rcvlonDegrees*DEG_TO_RAD, rcvdepthKm);
			
			System.out.printf("Distance       = %9.4f deg%n", slbm.getDistance()/DEG_TO_RAD);
			System.out.printf("Travel time    = %9.4f sec%n", slbm.getTravelTime());
			System.out.printf("TT uncertainty = %9.4f sec%n", slbm.getTravelTimeUncertainty());
			System.out.printf("Slowness       = %9.4f sec/deg%n", slbm.getSlowness()*DEG_TO_RAD);
			System.out.printf("dtt/dlat       = %9.4f sec/deg%n", slbm.get_dtt_dlat()*DEG_TO_RAD);
			System.out.printf("dtt/dlat       = %9.4f sec/deg%n", slbm.get_dtt_dlon()*DEG_TO_RAD);
			System.out.printf("dtt/ddepth     = %9.4f sec/deg%n", slbm.get_dtt_ddepth());
			System.out.println();
			System.out.println("Done.");

		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}	

	/**
	 * Run a regression test.
	 * <ul>
	 * <li>Instantiate an SlbmInterface object</li>
	 * <li>Load a velocity model from the file system</li>
	 * <li>Read an ascii file containing phase, source, receiver, and previously computed travel time values.</li>
	 * <li>Compute travel time for same positions</li>
	 * <li>Compare computed and stored travel time values</li>
	 * <li>Report discrepancies.</li>
	 * </ul>
	 * @param args slbm velocity model, and name of file containing regression data.
	 */
	public static void run_regression_test(String[] args)
	{
		try
		{
			System.out.println("SLBM Java Regression Test");

			// instantiate an SLBMInterface object
			SlbmInterface slbm = new SlbmInterface();
				
			System.out.print("Running SLBM Version "+slbm.getVersion());

			File modelDirectory = new File(args[0]);

			File dataFile = new File(args[1]);
			
			String interpolatorType = "NATURAL_NEIGHBOR";

			//open the input file.  
			Scanner input = new Scanner(dataFile);

			// read comments from the input file until an empty line is found
			String line = input.nextLine();
			while (line.trim().length() > 0)
			{
				if (line.contains("LINEAR"))
					interpolatorType = "LINEAR";
					
				System.out.println(line);
				line = input.nextLine();
			}

			// load the velocity model
			slbm.loadVelocityModel(modelDirectory.getCanonicalPath());
			
			slbm.setInterpolatorType(interpolatorType);

			// instantiate variables
			String phase;
			double srcLat, srcLon, srcDepth, rcvLat, rcvLon, rcvDepth, 
			distance0, distance, tt0, tt, tt_uncertainty0, tt_uncertainty, slow0, slow;

			// number of source-receiver-phase combinations read from input file
			int nrecords = 0;    

			// number of source-receiver-phase combinations for which slbm computed
			// a travel time value that does not agree with previously computed value
			int ndifferent = 0;

			// tolerance in seconds.  If difference between  computed and stored travel times
			// exceeds this value, it counts as different.
			double tolerance = 0.02; 

			// keep processing data until there is no more
			while (input.hasNext())
			{
				// read record from the input 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
				// distance0 : source-receiver distance in degrees
				// tt0    : previously computed travel time in seconds
				// tt_uncertainty0    : previously computed travel time uncertainty in seconds
				// slow0 : previously computed horizontal slowness in seconds/degree


				phase  = input.next();
				srcLat   = input.nextDouble();
				srcLon   = input.nextDouble();
				srcDepth = input.nextDouble();
				rcvLat   = input.nextDouble();
				rcvLon   = input.nextDouble();
				rcvDepth = input.nextDouble();
				distance0    = input.nextDouble();
				tt0          = input.nextDouble();
				tt_uncertainty0 = input.nextDouble();
				slow0    = input.nextDouble();

				try
				{
					// create a GreatCircle object at the requested location.
					// Note that source and receiver latitudes and longitudes are converted
					// from degrees to radians.
					slbm.createGreatCircle(phase,
							toRadians(srcLat),
							toRadians(srcLon),
							srcDepth,
							toRadians(rcvLat),
							toRadians(rcvLon),
							rcvDepth);

					distance = Math.toDegrees(slbm.getDistance());

					// retrieve the travel time, in seconds.
					tt = slbm.getTravelTime();

					tt_uncertainty = slbm.getTravelTimeUncertainty();

					slow = Math.toRadians(slbm.getSlowness());

				}
				catch (SLBMException ex)
				{
					// slbm threw an exception.

					// Print the reason to screen
					// cout << ex.emessage << endl;

					// set computed travel time to invalid value and keep going.
					// Presumably the previously computed value was invalid also.
					distance = tt = tt_uncertainty = slow = -999.;
				}

				// increment number of records read from file.
				++nrecords;

				// if computed travel time does not agree with previously
				// computed value, print information to the screen
				if (abs(distance-distance0) > tolerance 
						|| abs(tt-tt0) > tolerance 
						|| abs(tt_uncertainty-tt_uncertainty0) > tolerance 
						|| abs(slow-slow0) > 10*tolerance)
				{
					++ndifferent;

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

					System.out.printf("%s %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, srcLat, srcLon, srcDepth, rcvLat, rcvLon, rcvDepth, 
							distance0, distance, tt0, tt, tt_uncertainty0, tt_uncertainty, slow0, slow);
				}
			}

			System.out.println();
			System.out.printf("%d of %d travel times differences exceeded tolerance of %1.3f seconds%n%n",
					ndifferent, nrecords, tolerance);

			if (ndifferent == 0)
				System.out.printf("PASSED%n%n");
			else
				System.out.printf("FAILED%n%n");

		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * Generate data for the regression test.
	 * @param args Expects three string values: the name of the directory
	 * containing the slbm model that should be used, the name of the 
	 * file to which regression data should be written, the name of the 
	 * data file that contains old regression data.  Only the phase and
	 * source and receiver positions are used from the old regression file.
	 * New travel times are computed and written to the new file.
	 */
	protected static void generate_regression_test_data(String[] args)
	{
		System.out.println("Start generate_regression_test_data()");
		try
		{
			File modelDirectory = new File(args[0]);

			File outputFile = new File(args[1]);

			File inputFile = new File(args[2]);
			
			String interpolatorType = "natural_neighbor";
			if (args.length == 4 && args[3].equalsIgnoreCase("linear"))
			  interpolatorType = "linear";

			// instantiate an SLBMInterface object
			SlbmInterface slbm = new SlbmInterface();

			slbm.loadVelocityModelBinary(modelDirectory.getCanonicalPath());
			
			slbm.setInterpolatorType(interpolatorType);

			Scanner input = null;

			//open the input file.  Return an error if cannot open the file.
			input = new Scanner(inputFile);

			BufferedWriter output = new BufferedWriter(new FileWriter(outputFile));

			// read comments from the input file until an empty line is found
			String line = input.nextLine();
			while (line.trim().length() > 0)
			{
				//System.out.println(line);
				line = input.nextLine();
			}

			output.write(String.format("# Regression data computed with model = %s, SLBM version %s interpolatorType %s%n", 
					modelDirectory.getName(), slbm.getVersion(), slbm.getInterpolatorType()));
			output.write(String.format("#ph   srcLat      srcLon srcDepth    rcvLat     rcvLon  rcvDepth    dist     tt     uncert    slow%n"));
			output.newLine();

			// instantiate variables
			String phase;
			double slat, slon, sdepth, rlat, rlon, rdepth, distance, tt, sh, tt_uncertainty;

			// number of source-receiver-phase combinations read from input file
			int nrecords = 0;    

			// number of source-receiver-phase combinations for which slbm computed
			while (input.hasNext())
			{
				// read record from the input 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

				phase = input.next();
				slat = input.nextDouble();
				slon = input.nextDouble();
				sdepth = input.nextDouble();
				rlat = input.nextDouble();
				rlon = input.nextDouble();
				rdepth = input.nextDouble();

				// skip the rest of the line
				input.nextLine();

				try
				{
					// create a GreatCircle object at the requested location.
					// Note that source and receiver latitudes and longitudes are converted
					// from degrees to radians.
					slbm.createGreatCircle(phase,
							toRadians(slat),
							toRadians(slon),
							sdepth,
							toRadians(rlat),
							toRadians(rlon),
							rdepth);

					// retrieve the travel time, in seconds.
					tt = slbm.getTravelTime();

					distance = Math.toDegrees(slbm.getDistance());

					sh = Math.toRadians(slbm.getSlowness());

					tt_uncertainty = slbm.getTravelTimeUncertainty();

				}
				catch (SLBMException ex)
				{
					// slbm threw an exception.

					// Print the reason to screen
					// cout << ex.emessage << endl;

					// set computed travel time to invalid value and keep going.
					// Presumably the previously computed value was invalid also.
					tt = distance = sh = tt_uncertainty = -999.;
				}

				output.write(String.format("%s %10.6f %11.6f %7.3f %10.6f %11.6f %7.3f %8.3f %8.3f %8.3f %8.3f%n",
						phase, slat, slon, sdepth, rlat, rlon, rdepth, distance, tt, tt_uncertainty, sh));

				++nrecords;
			}

			System.out.println();
			System.out.printf("%d ray paths generated and written to file %s%n%n", nrecords, outputFile.getCanonicalPath());

			output.close();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}		

		System.out.println("Done with generate_regression_test_data()");
		System.out.println();
	}
}
