//- ****************************************************************************
//-
//- Copyright 2009 National Technology & Engineering Solutions of Sandia, LLC
//- (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, 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:
//-
//-   1. Redistributions of source code must retain the above copyright notice,
//-      this list of conditions and the following disclaimer.
//-
//-   2. 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.
//-
//-   3. Neither the name of the copyright holder 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 "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, 3 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/rstt201404um.geotess models/rstt201404um.regression.dat" << endl;
        cout << endl;
        cout << "3 parameters - convert a model to a different file format:" << endl;
        cout << "    1: file or directory containing the slbm velocity model" << endl;
        cout << "    2: name of file/directory to receive the reformatted model" << endl;
        cout << "    3: model output format (integer in range 1 - 4)" << 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 degrees" << endl;
        cout << "    4: source longitude in degrrees" << endl;
        cout << "    5: source depth in km below sea level" << endl;
        cout << "    6: receiver latitude in degrees" << 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/rstt201404um.geotess 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);

        cout << "Done." << endl;

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