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

// package name
package slbmtest;

// import statements
import java.io.File;  // file, path, and environment variable stuff
import static java.lang.Math.*;  // mathematics
import gov.sandia.gnem.slbmjni.SlbmInterface;  // RSTT library
import gov.sandia.gnem.slbmjni.SLBMException;  // RSTT error catching

// main class
public class SLBMTest
{
    // annoyingly long method which will run at startup and try to load slbmjni,
    // first from the system paths, then by searching environment variables
    // ($RSTT_ROOT, $RSTT_HOME, $SLBM_ROOT, $SLBM_HOME). this also has to take
    // into account multiple possible file extensions on MacOS (.dylib, .jnilib)
    static
    {
        // first we'll just try and load the library
        try
        {
            System.loadLibrary("slbmjni");
        }

        // if that didn't work, we'll start checking environmental variables
        catch (java.lang.UnsatisfiedLinkError e)
        {
            // get the filename of the library we're looking for
            String libName = System.mapLibraryName("slbmjni");  // e.g., "libslbmjni.so"
            String libBase = libName.split("\\.")[0];  // file basename
            String libExt  = libName.split("\\.")[1];  // file extension

            // make our list of env vars to search, in preferred order
            String envVars[] = {"RSTT_ROOT", "RSTT_HOME", "SLBM_ROOT", "SLBM_HOME"};

            // initialize a boolean for when the library has loaded. if we have
            // successfully loaded it, we'll end the whole method
            boolean jniLoaded = false;

            // loop through each environment variable and look for slbmjni
            for (String env : envVars)
            {
                // try and get the environment variable
                String rootDir = System.getenv(env);

                // move on if it wasn't set
                if (rootDir == null)
                    continue;

                // first check if libName exists
                if (new File(rootDir + "/lib/" + libBase + "." + libExt).exists())
                    System.load(rootDir + "/lib/" + libBase + "." + libExt);  // load it

                // if that file doesn't exist, look for libslbmjni.jnilib
                else if (new File(rootDir + "/lib/" + libBase + ".jnilib").exists())
                    System.load(rootDir + "/lib/" + libBase + ".jnilib");  // load it

                // if that doesn't exist, I we'll move onto the next variable
                else
                    continue;

                // we made it this far, so we must have loaded the library!
                jniLoaded = true;  // set our boolean to true
                break;             // break out of the loop

            }

            // if, we still haven't loaded slbmjni, throw a helpful error message
            if (!jniLoaded)
            {
                // append some helpful info to the error message
                String errMsg = e.getMessage() + " or [$RSTT_ROOT/lib, $RSTT_HOME/lib, $SLBM_ROOT/lib, $SLBM_HOME/lib]";

                // make a new UnsatisfiedLinkError with our updated message
                UnsatisfiedLinkError ex = new UnsatisfiedLinkError(errMsg);

                // print out the stacktrace, some helpful info, and exit
                ex.printStackTrace();
                System.out.println("Did you try adding '-Djava.library.path=\"/path/to/rstt/lib\"' to your 'java' command?");
                System.out.println("Alternatively, set $RSTT_ROOT, $RSTT_HOME, $SLBM_ROOT, or $SLBM_HOME environment variables.");
                System.exit(1);
            }
        }
    }

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

    // function to check if two floats are equal to some high precision
    public static void assertIsEqual(double x, double y, String varname)
    {
        // print status message
        System.out.format("%-21s  %12.8f == %12.8f", varname, x, y);

        // if the two values are equal
        if (abs(x-y) < EPS)
        {
            System.out.format(" OK\n");
        }
        else
        {
            System.out.format(" FAIL\n");
            PASSED = false;
        }
    }

    // main method
    public static void main(String args[])
    {
        // wrap everything in a `try-catch` block
        try
        {
            // print a quick message
            System.out.println("======================================================");
            System.out.println("Performing Java tests...");
            System.out.println("------------------------------------------------------");
            System.out.println("Variable                 Calculated       Reference");
            System.out.println("------------------------------------------------------");

            // 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 = toRadians(srcLatDeg);
            double srcLonRad = toRadians(srcLonDeg);
            double rcvLatRad = toRadians(rcvLatDeg);
            double rcvLonRad = toRadians(rcvLonDeg);

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

            // 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[] = {"Pn", "Sn", "Pg", "Lg"};
            for (int i=0; i<4; i++)
            {
                // print status message
                if (i > 0)
                    System.out.println("");
                System.out.format("Computing values for %s phase...\n", phases[i]);

                // 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
                distRad               = slbm.getDistance();
                travelTimeSec         = slbm.getTravelTime();
                travelTimeUncertSec   = slbm.getTravelTimeUncertainty();
                travelTime1DUncertSec = slbm.getTravelTimeUncertainty1D();

                // 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 (SLBMException e)
        {
            // print info about the exception and exit
            e.printStackTrace();
            System.exit(1);
        }

        // catch other exceptions
        catch (Exception e)
        {
            // print info about the exception and exit
            e.printStackTrace();
            System.exit(1);
        }

        // return
        if (PASSED)
        {
            System.out.println("------------------------------------------------------");
            System.out.println("Java tests PASSED.");
            System.out.println("======================================================");
            System.exit(0);
        }
        else
        {
            System.out.println("------------------------------------------------------");
            System.out.println("Java tests FAILED.");
            System.out.println("======================================================");
            System.exit(1);
        }
    }
}
