//- ****************************************************************************
//-
//- 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 <stdio.h>
#include "slbm_C_shell.h"
#include <stdlib.h>
#include "time.h"
#include "string.h"
//#include "curses.h"
#include <math.h>

void check_err(int err);
void print_error(int err);
char * getline_slbm(FILE * f);

double DEG_TO_RAD = 1.7453292519943300E-02;
double RAD_TO_DEG = 5.7295779513082300E+01;

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

    int simple_test(char* modelName, char* phase,
            double srclatDegrees, double srclonDegrees, double srcDepthKm,
            double rcvlatDegrees, double rcvlonDegrees, double rcvElevKm);

    char SlbmVersion[20];

    if (argc == 3)
    {
        // run the regresion test
        return regression_test(argv[1], argv[2]);
    }
    else if (argc == 4)
    {
        slbm_shell_create();
        printf("SLBM C reformat model\n");
        printf("Loading velocity model %s\n", argv[1]);
        check_err(slbm_shell_loadVelocityModel(argv[1]));
        printf("Saving  velocity model %s\n", argv[2]);
        check_err(slbm_shell_saveVelocityModelFormat(argv[2], atoi(argv[3])));
        printf("Done.");
    }
    else if (argc == 9)
    {
        // run the single path test.
        int i = 1;
        char* modelName = argv[i++];
        char* phase     = 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
    {
        slbm_shell_getVersion(SlbmVersion);
        printf("SLBM SLBM version %s\n\n", SlbmVersion);

        printf("Expecting either 2 or 8 command line parameters:\n\n");

        printf("2 parameters - run a regression test:\n");
        printf("    1: directory where slbm velocity model can be found\n");
        printf("    2: file containing the regression test data\n");
        printf("    If running this program from SLBM_Root, try:\n");
        printf("    bin/slbmtestc models/rstt201404um.geotess models/rstt201404um.regression.dat\n\n");

        printf("8 parameters - run a single path test:\n");
        printf("    1: directory where slbm velocity model can be found\n");
        printf("    2: phase (one of Pn, Sn, Pg, Lg)\n");
        printf("    3: source latitude in degees\n");
        printf("    4: source longitude in degrees\n");
        printf("    5: source depth in km below sea level\n");
        printf("    6: receiver latitude in degees\n");
        printf("    7: receiver longitude in degrees\n");
        printf("    8: receiver DEPTH (!!!) in km BELOW sea level\n");
        printf("    If running this program from SLBM_Root, try:\n");
        printf("    bin/slbmtestc models/rstt201404um.geotess Pn 10 20 5 15 25 -0.1\n\n");
        exit(1);
    }
    return 0;
}

int simple_test(char* modelName, char* phase,
        double srclatDegrees, double srclonDegrees, double srcDepthKm,
        double rcvlatDegrees, double rcvlonDegrees, double rcvDepthKm)
{
    double srclat, srclon, rcvlat, rcvlon;
    double distance, tt, tt_uncertainty, slow, sh_uncertainty, dtt_dlat, dtt_dlon, dtt_ddepth;
    char SlbmVersion[20];

    slbm_shell_getVersion(SlbmVersion);

    slbm_shell_create();

    printf("SLBM C Single Path Test -- SLBM version %s\n\n", SlbmVersion);

    printf("Loading velocity model %s\n\n", modelName);

    // load the velocity model.  if any errors happen, print message and exit
    check_err(slbm_shell_loadVelocityModel(modelName));

    printf("Phase  = %s\n", phase);
    printf("Source   Lat   = %9.4f deg\n",srclatDegrees);
    printf("Source   Lon   = %9.4f deg\n",srclonDegrees);
    printf("Source   Depth = %9.4f km\n",srcDepthKm);
    printf("Receiver Lat   = %9.4f deg\n",rcvlatDegrees);
    printf("Receiver Lon   = %9.4f deg\n",rcvlonDegrees);
    printf("Receiver Depth = %9.4f km\n\n",rcvDepthKm);

    srclat = srclatDegrees*DEG_TO_RAD;
    srclon = srclonDegrees*DEG_TO_RAD;
    rcvlat = rcvlatDegrees*DEG_TO_RAD;
    rcvlon = rcvlonDegrees*DEG_TO_RAD;

    // create a GreatCircle object at the requested location.
    // Note that source and receiver latitudes and longitudes are converted
    // from degrees to radians.
    check_err(slbm_shell_createGreatCircle(phase,
            &srclat, &srclon, &srcDepthKm,
            &rcvlat, &rcvlon, &rcvDepthKm));

    check_err(slbm_shell_getDistance(&distance));
    distance *= RAD_TO_DEG;

    printf("Distance       = %9.4f deg\n",distance);
    // retrieve the travel time, in seconds
    check_err(slbm_shell_getTravelTime(&tt));

    printf("Travel time    = %9.4f sec\n",tt );

    // retrieve the travel time uncertainty, in seconds
    check_err(slbm_shell_getTTUncertainty(&tt_uncertainty));

    printf("TT uncertainty = %9.4f sec\n",tt_uncertainty);

    // retrieve slowness
    check_err(slbm_shell_getSlowness(&slow));
    slow *= DEG_TO_RAD; // convert from sec/radian to sec/degrees

    printf("Slowness       = %9.4f sec/deg\n",slow);

    // retrieve the travel time uncertainty, in seconds
    check_err(slbm_shell_getSHUncertainty(&sh_uncertainty));
    sh_uncertainty *= DEG_TO_RAD; // convert from sec/radian to sec/degrees

    printf("SH uncertainty = %9.4f sec/deg\n",sh_uncertainty);

    // retrieve dtt_dlat
    check_err(slbm_shell_get_dtt_dlat(&dtt_dlat));
    dtt_dlat *= DEG_TO_RAD; // convert from sec/radian to sec/degrees

    printf("dtt/dlat       = %9.4f sec/deg\n",dtt_dlat);

    // retrieve dtt_dlon
    check_err(slbm_shell_get_dtt_dlon(&dtt_dlon));
    dtt_dlon *= DEG_TO_RAD; // convert from sec/radian to sec/degrees

    printf("dtt/dlon       = %9.4f sec/deg\n",dtt_dlon);

    // retrieve dtt_dlon
    check_err(slbm_shell_get_dtt_ddepth(&dtt_ddepth));

    printf("dtt/ddepth     = %9.4f sec/km\n\n",dtt_ddepth);

    printf("Done.\n");

    return 0;
}

int regression_test(char* modelDirectory, char* dataFile)
{
    char* line;

    FILE* fin;

    char phase[3];

    double slat, slon, sdepth, rlat, rlon, rdepth,
    distance0, distance, tt0, tt, tt_uncertainty0, tt_uncertainty, slow0, slow;

    double tolerance = 0.02;

    int err, nrecords=0, ndifferent=0;

    char SlbmVersion[20];

    slbm_shell_getVersion(SlbmVersion);

    printf("SLBM C Regression Test -- SLBM version %s\n", SlbmVersion);

    slbm_shell_create();

    printf("Loading velocity model %s\n", modelDirectory);

    // load the velocity model.  if any errors happen, print message and exit
    check_err(slbm_shell_loadVelocityModel(modelDirectory));

    // open the file with regression data
    printf("Loading regression data from %s\n\n", dataFile);

    if ((fin = fopen(dataFile, "r")) == NULL)
    {
        printf("ERROR: Unable to open file %s", dataFile);
        return -1;
    }

    // read comment lines until an empty line is encountered
    line = getline_slbm(fin);
    while (strlen(line) > 0)
    {
        if (strstr(line, "LINEAR"))
            slbm_shell_setInterpolatorType("LINEAR");

        printf("%s\n", line);
        line = getline_slbm(fin);
    }

    // read records from the input file until end of 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
    while (fscanf(fin,"%s %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",
            phase, &slat, &slon, &sdepth, &rlat, &rlon, &rdepth,
            &distance0, &tt0, &tt_uncertainty0, &slow0) != EOF)
    {
        // count records
        ++nrecords;

        // convert source and receiver lat, lon from degrees to radians
        slat *= DEG_TO_RAD;
        slon *= DEG_TO_RAD;
        rlat *= DEG_TO_RAD;
        rlon *= DEG_TO_RAD;

        // call create_great_circle to compute travel times.
        // err will be zero if successful, positive error code if an error happens.
        err = slbm_shell_createGreatCircle(phase,
                &slat, &slon, &sdepth, &rlat, &rlon, &rdepth);

        if (err)
        {
            // an error occurred creating the great circle.
            // Respond by setting all regression values for this record
            // to -999 and do not retrieve values for tt, slowness, etc.
            // Presumably the same error happened when the regression data were generated.
            distance = tt = tt_uncertainty = slow = -999.;

            // if you want to see the error messages, uncomment following line:
            //print_error(slbm_shell_getTravelTime(&tt));
        }
        else
        {
            // great circle successfully created.  Retrieve values for distance,
            // travel time, tt uncertainty and slowness.  If any of the method
            // calls returns non-zero error code, set all the values to -999.
            if (slbm_shell_getDistance(&distance))
                distance = tt = tt_uncertainty = slow = -999.;
            else
            {
                distance *= RAD_TO_DEG;

                if (slbm_shell_getTravelTime(&tt))
                    distance = tt = tt_uncertainty = slow = -999.;
                else
                {

                    if (slbm_shell_getTTUncertainty(&tt_uncertainty))
                        distance = tt = tt_uncertainty = slow = -999.;
                    else
                    {
                        if (slbm_shell_getSlowness(&slow))
                            distance = tt = tt_uncertainty = slow = -999.;
                        else
                            slow *= DEG_TO_RAD;
                    }
                }
            }
        }

        // if previously stored travel time and new computed travel time don't agree,
        // print a message.
        if (fabs(distance-distance0) > tolerance
                || fabs(tt-tt0) > tolerance
                || fabs(tt_uncertainty-tt_uncertainty0) > tolerance
                || fabs(slow-slow0) > tolerance)
        {
            ++ndifferent;

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


            printf("%2s %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, slat*RAD_TO_DEG, slon*RAD_TO_DEG, sdepth,
                    rlat*RAD_TO_DEG, rlon*RAD_TO_DEG, rdepth,
                    distance0, distance, tt0, tt, tt_uncertainty0, tt_uncertainty, slow0, slow);
        }
    }

    // print results
    printf("\n%d of %d travel times differences exceeded tolerance of %f seconds\n\n",
            ndifferent, nrecords, tolerance);

    if (ndifferent == 0 && nrecords > 0)
        printf("PASSED\n\n");
    else
        printf("FAILED\n\n");

    return 0;
}

void check_err(int err)
{
    if( err )
    {
        print_error(err);
        exit(1);
    }
}

void print_error(int err)
{
    char errmsg[1000];
    if( err )
    {
        slbm_shell_getErrorMessage( errmsg );
        printf( "ERROR %d%s\n\n", err, errmsg);
    }
}

/**
 * This function reads characters from a FILE until \n is found
 * and returns a pointer to the resulting char*.
 * Copied from http://sucs.org/Knowledge/Help
 * look for link "Reading an arbitrarily long line in C"
 */
char * getline_slbm(FILE * f)
{
    size_t size = 0;
    size_t len  = 0;
    size_t last = 0;
    char * buf  = NULL;
    do
    {
        size += BUFSIZ; /* BUFSIZ is defined as "the optimal read size for this platform" */
        buf = (char*)realloc(buf,size); /* realloc(NULL,n) is the same as malloc(n) */
        /* Actually do the read. Note that fgets puts a terminal '\0' on the
        end of the string, so we make sure we overwrite this */
        fgets(buf+last,size,f);
        len = strlen(buf);
        last = len - 1;
    } while (!feof(f) && buf[last]!='\n');
    /* delete \n at end of string */
    buf[last] = '\0';
    return buf;
}

