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


//- ****************************************************************************
//-
//- Program:       SLBM
//- Module:        $RCSfile: main.cc,v $
//- Revision:      $Revision: 1.102 $
//- Last Modified: $Date: 2013/05/08 23:39:04 $
//- Last Check-in: $Author: sballar $
//-
//- ****************************************************************************
#include "SlbmInterface.h"
#include "SLBMException.h"
#include "CpuTimer.h"
#include <stdlib.h>

using namespace slbm;

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

    int simple_test(string modelname, string phase, 
				double srclatDegrees, double srclonDegrees, double srcDepthKm, 
				double rcvlatDegrees, double rcvlonDegrees, double rcvElevKm);

    int reformat(string inputModelName, string outputModelName, int format);

    if (argc == 3)
	{
		// run the regresion test
		string modelName = (string)argv[1];
		string regressionFile = (string)argv[2];
		return regression_test(modelName, regressionFile);
	}
    else if (argc == 4)
	{
		string inputModelName = (string)argv[1];
		string outputModelName = (string)argv[2];
		int format = atoi(argv[3]);
		return reformat(inputModelName, outputModelName, format);
	}
	else if (argc == 9)
	{
		// run the single path test.
		int i = 1;
		string modelName = (string)argv[i++];
		string phase     = (string)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
	{
		cout << "Expecting either 2 or 8 command line parameters:" << endl;
	    cout << endl;
		cout << "2 parameters - run a regression test:" << endl;
		cout << "    1: directory where slbm velocity model can be found" << endl;
		cout << "    2: file containing the regression test data" << endl;
		cout << "    If running this program from SLBM_Root, try:" << endl;
		cout << "    bin/slbmtestcc models/na1010pn models/na1010pn/regression_na1010pn.dat" << endl;
		cout << endl;
		cout << "8 parameters - run a single path test:" << endl;
		cout << "    1: directory where slbm velocity model can be found" << endl;
		cout << "    2: phase (one of Pn, Sn, Pg, Lg)" << endl;
		cout << "    3: source latitude in degees" << endl;
		cout << "    4: source longitude in degrees" << endl;
		cout << "    5: source depth in km below sea level" << endl;
		cout << "    6: receiver latitude in degees" << endl;
		cout << "    7: receiver longitude in degrees" << endl;
		cout << "    8: receiver DEPTH (!!!) in km BELOW sea level" << endl;
		cout << "    If running this program from SLBM_Root, try:" << endl;
		cout << "    bin/slbmtestcc models/na1010pn Pn 10 20 5 15 25 -0.1" << endl;
		cout << endl;
    	exit(1);
	}
}

int simple_test(string modelName, string phase, 
				double srclatDegrees, double srclonDegrees, double srcDepthKm, 
				double rcvlatDegrees, double rcvlonDegrees, double rcvDepthKm)
{
	cout << "SLBM C++ Single Path Test -- SLBM version " << SlbmVersion << endl << endl;

	// instantiate an SLBMInterface object
	SlbmInterface slbm;

	cout << "Loading slbm velocity model from directory " << modelName << endl << endl;

	// load the earth model from a binary file
	slbm.loadVelocityModel(modelName.c_str());

	double distance, tt, tt_uncertainty, slow, sh_uncertainty, dtt_dlat, dtt_dlon, dtt_ddepth;

	try
	{
		cout << setiosflags(ios::fixed) << setiosflags(ios::showpoint) << setprecision(4);
		cout << "Phase  = " << phase << endl;
		cout << "Source   Lat   = " << setw(9) << srclatDegrees << " deg" << endl;
		cout << "Source   Lon   = " << setw(9) << srclonDegrees << " deg" << endl;
		cout << "Source   Depth = " << setw(9) << srcDepthKm << " km" << endl;
		cout << "Receiver Lat   = " << setw(9) << rcvlatDegrees << " deg" << endl;
		cout << "Receiver Lon   = " << setw(9) << rcvlonDegrees << " deg" << endl;
		cout << "Receiver Depth = " << setw(9) << rcvDepthKm << " km" << endl;
		cout << endl;

		// 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,
			srclatDegrees*DEG_TO_RAD, srclonDegrees*DEG_TO_RAD, srcDepthKm,
			rcvlatDegrees*DEG_TO_RAD, rcvlonDegrees*DEG_TO_RAD, rcvDepthKm);

		cout << slbm.toString(99);

		slbm.getDistance(distance);
		distance *= RAD_TO_DEG;

		cout << "Distance       = " << setw(9) << distance << " deg" << endl;
		// retrieve the travel time, in seconds
		slbm.getTravelTime(tt);

		cout << "Travel time    = " << setw(9) << tt << " sec" << endl;

		// retrieve the travel time uncertainty, in seconds
		slbm.getTravelTimeUncertainty(tt_uncertainty);

		cout << "TT uncertainty = " << setw(9) << tt_uncertainty << " sec" << endl;

		// retrieve slowness
		slbm.getSlowness(slow);
		slow *= DEG_TO_RAD; // convert from sec/radian to sec/degrees

		cout << "Slowness       = " << setw(9) << slow << " sec/deg" << endl;

		// retrieve the travel time uncertainty, in seconds
		slbm.getSlownessUncertainty(sh_uncertainty);
		sh_uncertainty *= DEG_TO_RAD; // convert from sec/radian to sec/degrees

		cout << "SH uncertainty = " << setw(9) << sh_uncertainty << " sec/deg" << endl;

		// retrieve dtt_dlat
		slbm.get_dtt_dlat(dtt_dlat);
		dtt_dlat *= DEG_TO_RAD; // convert from sec/radian to sec/degrees

		cout << "dtt/dlat       = " << setw(9) << dtt_dlat << " sec/deg" << endl;

		// retrieve dtt_dlon
		slbm.get_dtt_dlon(dtt_dlon);
		dtt_dlon *= DEG_TO_RAD; // convert from sec/radian to sec/degrees

		cout << "dtt/dlon       = " << setw(9) << dtt_dlon << " sec/deg" << endl;

		// retrieve dtt_dlon
		slbm.get_dtt_ddepth(dtt_ddepth);

		cout << "dtt/ddepth     = " << setw(9) << dtt_ddepth << " sec/km" << endl;

		cout << endl;
		cout << endl;

		//// retrieve the travel time uncertainty, in seconds
		//slbm.getTravelTimeUncertainty(0, 9.5*DEG_TO_RAD, tt_uncertainty);

		//cout << "TT uncertainty(Pn, 9.5 deg) = " << setw(9) << tt_uncertainty << " sec" << endl;

		//// retrieve the travel time uncertainty, in seconds
		//slbm.getTravelTimeUncertainty(1, 9.5*DEG_TO_RAD, tt_uncertainty);
		//cout << "TT uncertainty(Sn, 9.5 deg) = " << setw(9) << tt_uncertainty << " sec" << endl;

		//// retrieve the travel time uncertainty, in seconds
		//slbm.getTravelTimeUncertainty(2, 9.5*DEG_TO_RAD, tt_uncertainty);
		//cout << "TT uncertainty(Pg, 9.5 deg) = " << setw(9) << tt_uncertainty << " sec" << endl;

		//// retrieve the travel time uncertainty, in seconds
		//slbm.getTravelTimeUncertainty(3, 9.5*DEG_TO_RAD, tt_uncertainty);
		//cout << "TT uncertainty(Lg, 9.5 deg) = " << setw(9) << tt_uncertainty << " sec" << endl;

		//// retrieve the slowness uncertainty, in seconds
		//slbm.getSlownessUncertainty(0, 9.5*DEG_TO_RAD, sh_uncertainty);
		//sh_uncertainty *= DEG_TO_RAD; // convert from sec/radian to sec/degrees
		//cout << "SH uncertainty(Pn, 9.5 deg) = " << setw(9) << sh_uncertainty << " sec/deg" << endl;

		//// retrieve the slowness uncertainty, in seconds
		//slbm.getSlownessUncertainty(1, 9.5*DEG_TO_RAD, sh_uncertainty);
		//sh_uncertainty *= DEG_TO_RAD; // convert from sec/radian to sec/degrees
		//cout << "SH uncertainty(Sn, 9.5 deg) = " << setw(9) << sh_uncertainty << " sec/deg" << endl;

		//// retrieve the slowness uncertainty, in seconds
		//slbm.getSlownessUncertainty(2, 9.5*DEG_TO_RAD, sh_uncertainty);
		//sh_uncertainty *= DEG_TO_RAD; // convert from sec/radian to sec/degrees
		//cout << "SH uncertainty(Pg, 9.5 deg) = " << setw(9) << sh_uncertainty << " sec/deg" << endl;

		//// retrieve the slowness uncertainty, in seconds
		//slbm.getSlownessUncertainty(3, 9.5*DEG_TO_RAD, sh_uncertainty);
		//sh_uncertainty *= DEG_TO_RAD; // convert from sec/radian to sec/degrees
		//cout << "SH uncertainty(Lg, 9.5 deg) = " << setw(9) << sh_uncertainty << " sec/deg" << endl;

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

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

int reformat(string inputModelName, string outputModelName, int format)
{
	cout << "SLBM C++ Reformat Model -- SLBM version " << SlbmVersion << endl << endl;

	try
	{

		// instantiate an SLBMInterface object
		SlbmInterface slbm;

		cout << "Loading slbm velocity model from directory " << inputModelName << endl << endl;

		// load the earth model from a binary file
		slbm.loadVelocityModel(inputModelName.c_str());

		cout << "Saving slbm velocity model to destination " << outputModelName << endl << endl;

		slbm.saveVelocityModel(outputModelName, format);

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

		// Print the reason to screen
		cout << ex.emessage << endl;
		return 1;
	}
	return 0;
}

int regression_test(string modelName, string regressionFile)
{
	cout << "SLBM C++ Regression Test -- SLBM version " << SlbmVersion<< endl;

	// instantiate an SLBMInterface object
	SlbmInterface slbm;
	
	cout << "Loading slbm velocity model from directory " << modelName << endl;

	// load the earth model from a binary file
	slbm.loadVelocityModel(modelName.c_str());

	cout << "Loading regression data from " << regressionFile << endl << endl;

	//open the file.  Return an error if cannot open the file.
	ifstream fin;
	fin.open(regressionFile.c_str());
	if (fin.fail() || !fin.is_open())
	{
		ostringstream os;
		os << endl << "ERROR in main::regression_test2()  " 
			<< "Could not open file "<< endl << regressionFile << endl;

		cout << os.str();

		throw SLBMException(os.str(),108);
	}

	// read comments from the input file until an empty line is found
	string line;
	vector<string> lines;
	getline(fin, line);
	while (!fin.eof() && line.length() > 0)
	{
		if (line.find("LINEAR") != string::npos)
			slbm.setInterpolatorType("LINEAR");
		else if (line.find("NATURAL_NEIGHBOR") != string::npos)
			slbm.setInterpolatorType("NATURAL_NEIGHBOR");
		else
			lines.push_back(line);

		getline(fin, line);
	}

	cout << "InterpolatorType " << slbm.getInterpolatorType() << endl;

	for (int i=0; i<(int)lines.size(); ++i)
		cout << lines[i] << endl;

	// instantiate variables
	vector<string> phase;
	vector<double> slat, slon, sdepth, rlat, rlon, rdepth,
		distance0, distance, tt0, tt, tt_uncertainty0, tt_uncertainty, slow0, slow;

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

	phase.resize(nrecords);
	slat.resize(nrecords);
	slon.resize(nrecords);
	sdepth.resize(nrecords);
	rlat.resize(nrecords);
	rlon.resize(nrecords);
	rdepth.resize(nrecords);
	distance0.resize(nrecords);
	distance.resize(nrecords);
	tt0.resize(nrecords);
	tt.resize(nrecords);
	tt_uncertainty0.resize(nrecords);
	tt_uncertainty.resize(nrecords);
	slow0.resize(nrecords);
	slow.resize(nrecords);

	nrecords = 0;
	while (!fin.eof())
	{
		// 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
		fin >> phase[nrecords] >> slat[nrecords] >> slon[nrecords] >> sdepth[nrecords] >> rlat[nrecords]
		    >> rlon[nrecords] >> rdepth[nrecords] >> distance0[nrecords] >> tt0[nrecords]
		    >> tt_uncertainty0[nrecords] >> slow0[nrecords] >> ws;
		++nrecords;
	}
	fin.close();

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

	double tolerance = 0.02;

	cout << setiosflags(ios::fixed) << setiosflags(ios::showpoint);

	geotess::CpuTimer timer;

	for (int i=0; i<nrecords; ++i)
	{
		distance[i] = tt[i] = tt_uncertainty[i] = slow[i] = -999.;

		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[i],
				slat[i]*DEG_TO_RAD, slon[i]*DEG_TO_RAD, sdepth[i],
				rlat[i]*DEG_TO_RAD, rlon[i]*DEG_TO_RAD, rdepth[i]);

			slbm.getDistance(distance[i]);
			distance[i] *= RAD_TO_DEG;

			// retrieve the travel time, in seconds
			slbm.getTravelTime(tt[i]);

			// retrieve the travel time uncertainty, in seconds
			slbm.getTravelTimeUncertainty(tt_uncertainty[i]);

			// retrieve slowness
			slbm.getSlowness(slow[i]);
			slow[i] *= DEG_TO_RAD; // convert from sec/radian to sec/degrees

		}
		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[i] = tt[i] = tt_uncertainty[i] = slow[i] = -999.;
		}
		
		// if computed travel time does not agree with previously
		// computed value, print information to the screen
		if (abs(distance[i]-distance0[i]) > tolerance
				|| abs(tt[i]-tt0[i]) > tolerance
				|| abs(tt_uncertainty[i]-tt_uncertainty0[i]) > tolerance
				|| abs(slow[i]-slow0[i]) > 10*tolerance)
		{
			++ndifferent;
			cout << setw(2) << phase[i];
			cout << " " << setw(10) << setprecision(6) << slat[i];
			cout << " " << setw(11) << setprecision(6) << slon[i];
			cout << " " << setw(7) << setprecision(3) << sdepth[i];
			cout << " " << setw(10) << setprecision(6) << rlat[i];
			cout << " " << setw(11) << setprecision(6) << rlon[i];
			cout << " " << setw(7) << setprecision(3) << rdepth[i];
			cout << " " << setw(8) << setprecision(3) << distance0[i];
			cout << " " << setw(8) << setprecision(3) << distance[i];
			cout << " " << setw(8) << setprecision(3) << tt0[i];
			cout << " " << setw(8) << setprecision(3) << tt[i];
			cout << " " << setw(8) << setprecision(3) << tt_uncertainty0[i];
			cout << " " << setw(8) << setprecision(3) << tt_uncertainty[i];
			cout << " " << setw(8) << setprecision(3) << slow0[i];
			cout << " " << setw(8) << setprecision(3) << slow[i];
			cout << endl;
		}
	}

	double tim = timer.cpuTime()*0.001;

	cout << endl << "Computed " << nrecords << " rays in " << tim << " seconds." << endl;
	cout << ndifferent << " rays failed to meet tolerance of " << tolerance << endl;

	if (nrecords > 0 && ndifferent == 0)
		cout << "PASSED." << endl << endl;
	else
		cout << "FAILED" << endl << endl;

	return 0;
}
