//- ****************************************************************************
//-
//- 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 statements
#include "SlbmInterface.h"  // RSTT library
#include "SLBMException.h"  // RSTT error catching
#include <iostream>         // print to terminal
#include <string>           // strings
#include <cmath>            // math functions and constants

// declare some variables relevant to testing
bool PASSED = 1;   // bool for whether all the tests passed (default true)
double EPS = 1e-6; // small value to allow for machine error differences

// define some constants for converting between degrees<-->radians
double DEG_PER_RAD = 180.0 / M_PI;
double RAD_PER_DEG = M_PI / 180.0;

// declare namespace (for convenience)
using namespace slbm;

// function to check if two floats are equal to some high precision
void assertIsEqual(double x, double y, string varname)
{
    // print status message
    cout << fixed << setprecision(8)
         << setw(21) << left << varname << right << "  "
         << setw(12) << x << " == " << setw(12) << y << flush;

    // if the two values are equal
    if (abs(x-y) < EPS)
    {
        cout << " OK" << endl;
    }
    else
    {
        cout << " FAIL" << endl;
        PASSED = false;
    }
}

// main program
int main()
{

    // wrap everything in a `try-catch` block
    try
    {
        // print a quick message
        cout << "======================================================" << endl
             << "Performing C++ tests..."                                << endl
             << "------------------------------------------------------" << endl
             << "Variable                 Calculated       Reference"    << endl
             << "------------------------------------------------------" << endl;

        // arbitrary source (Meteor Crater, AZ)
        double srcLatDeg =   35.0274;  // latitude (degrees)
        double srcLonDeg = -111.0228;  // longitude (degrees)
        double srcDepKm  =    1.2345;  // depth (km)

        // arbitrary receiver (Albuquerque, NM)
        double rcvLatDeg =   35.1053;  // latitude (degrees)
        double rcvLonDeg = -106.6294;  // longitude (degrees)
        double rcvDepKm  =   -1.6000;  // depth (km)

        // convert lat/lon from degrees to radians
        double srcLatRad = srcLatDeg * RAD_PER_DEG;
        double srcLonRad = srcLonDeg * RAD_PER_DEG;
        double rcvLatRad = rcvLatDeg * RAD_PER_DEG;
        double rcvLonRad = rcvLonDeg * RAD_PER_DEG;

        // instantiate an RSTT object
        SlbmInterface slbm;

        // load the velocity model
        slbm.loadVelocityModel("models/unittest.geotess");

        // initialize variables for distance, travel time, and uncertainties
        double distRad, travelTimeSec, travelTimeUncertSec, travelTime1DUncertSec;

        // loop over each type of phase
        string phases[4] = {"Pn", "Sn", "Pg", "Lg"};
        for (int i=0; i<4; ++i)
        {
            // print status message
            if (i > 0)
                cout << endl;
            cout << "Computing values for " << phases[i] << " phase..." << endl;

            // create a great circle from source to the receiver
            slbm.createGreatCircle(phases[i],
                srcLatRad, srcLonRad, srcDepKm,
                rcvLatRad, rcvLonRad, rcvDepKm);

            // get the distance, travel time, and uncertainties from source --> receiver
            slbm.getDistance(distRad);
            slbm.getTravelTime(travelTimeSec);
            slbm.getTravelTimeUncertainty(travelTimeUncertSec);
            slbm.getTravelTimeUncertainty1D(travelTime1DUncertSec);

            // make sure all the values are correct
            switch (i)
            {
                case 0:
                    assertIsEqual(distRad,                0.06290927, "distRad");
                    assertIsEqual(travelTimeSec,         56.88549071, "travelTimeSec");
                    assertIsEqual(travelTimeUncertSec,    3.12378957, "travelTimeUncertSec");
                    assertIsEqual(travelTime1DUncertSec,  1.15423530, "travelTime1DUncertSec");
                    break;
                case 1:
                    assertIsEqual(distRad,                0.06290927, "distRad");
                    assertIsEqual(travelTimeSec,         98.95213093, "travelTimeSec");
                    assertIsEqual(travelTimeUncertSec,    6.29314026, "travelTimeUncertSec");
                    assertIsEqual(travelTime1DUncertSec,  2.47666863, "travelTime1DUncertSec");
                    break;
                case 2:
                    assertIsEqual(distRad,                0.06290927, "distRad");
                    assertIsEqual(travelTimeSec,         70.40335768, "travelTimeSec");
                    assertIsEqual(travelTimeUncertSec,   15.58790453, "travelTimeUncertSec");
                    assertIsEqual(travelTime1DUncertSec,  1.61320256, "travelTime1DUncertSec");
                    break;
                case 3:
                    assertIsEqual(distRad,                0.06290927, "distRad");
                    assertIsEqual(travelTimeSec,        119.65236722, "travelTimeSec");
                    assertIsEqual(travelTimeUncertSec,   20.08714726, "travelTimeUncertSec");
                    assertIsEqual(travelTime1DUncertSec,  1.98573827, "travelTime1DUncertSec");
                    break;
            }
        }

    }

    // catch possible exceptions
    catch (const SLBMException& e)
    {
        // print the exception
        cout << __FILE__ << ":" << __LINE__ << endl
             << e.emessage << endl;

        // return with error
        cout << "Exited with error code " << 1 << endl;
        return e.ecode;

    }
    catch (const GeoTessException& e)
    {
        // print the exception
        cout << __FILE__ << ":" << __LINE__ << endl
             << e.emessage << endl;

        // return with error
        cout << "Exited with error code " << e.ecode << endl;
        return e.ecode;

    }
    catch (...)
    {
        // print the exception
        cout << __FILE__ << ":" << __LINE__ << endl
             << "ERROR: An unidentified error has occurred." << endl;

        // return with error
        cout << "Exited with error code " << 1 << endl;
        return 1;
    }

    // return
    if (PASSED)
    {
        cout << "------------------------------------------------------" << endl
             << "C++ tests PASSED."                                      << endl
             << "======================================================" << endl;
        return 0;
    }
    else
    {
        cout << "------------------------------------------------------" << endl
             << "C++ tests FAILED."                                      << endl
             << "======================================================" << endl;
        return 1;
    }

}
