/*
 File :       fes.c
 Brief :      FES core module.
 Author :     CLS
 */
#include <stdio.h>
#include <malloc.h>
#include <string.h>

#include "fes.h"

#include "error.h"
#include "fes_int.h"
#include "ini.h"
#include "grid.h"
#include "prediction.h"

#ifdef _WIN32
#define snprintf _snprintf
#endif

/* Default variable name for the amplitude data */
#define AMPLTIUDE   "amplitude"

/* Default variable name for the phase data */
#define PHASE       "phase"

/* Default value for the phase lag */
#define PHASE_LAG   0

/* Default variable and dimension name for latitudes */
#define LATITUDE    "latitude"

/* Default variable and dimension name for longitude */
#define LONGITUDE   "longitude"

/*
 _get_key

 Get the key name in the parameter file

 type     Type of tide
 name     Wave name
 key      Key

 Returns a pointer to a static string that contains the key name.
 */
static char* _get_key(
        const fes_enum_tide_type tide,
        const char* const name,
        const char* const key)
{
    static char buffer[MAX_PATH];

    snprintf(buffer, sizeof(buffer), "%s_%s_%s", tide == FES_RADIAL ? "RADIAL"
            : "TIDE", name, key);

    return buffer;
}

/*
 */
int fes_new(
        FES* handle,
        const fes_enum_tide_type tide,
        const fes_enum_access mode,
        const char* const path)
{
    int rc;
    int ix;
    int n = 0;
    fes_handler* fes;
    void* ini;

    /* Allocate handle */
    if ((fes = calloc(1, sizeof(fes_handler))) == NULL)
    {
        set_fes_error(fes, FES_NO_MEMORY);
        return 1;
    }

    /* set handle value */
    *handle = fes;

    /* Set waves properties */
    set_waves(fes->waves);

    /* Set type of tide */
    fes->type = tide;

    /* Open configuration file */
    if (ini_open(fes, path, &ini))
        return 1;

    /* Determines the number of grid has to be loaded into memory */
    for (ix = 0; ix < N_WAVES; ix++)
    {
        /* Get key name for current wave */
        if (ini_get_string(ini,
                _get_key(fes->type, fes->waves[ix].name, "FILE"), NULL) != NULL)
            fes->grid.n_grids++;
    }

    /* Allocate grids buffer */
    if (mode == FES_IO)
    {
        /* to read data from grids */
        if ((fes->grid.file = calloc(fes->grid.n_grids, sizeof(fes_cdf_file)))
                == NULL)
        {
            set_fes_error(fes, FES_NO_MEMORY);
            return 1;
        }
    }
    else /* FES_MEM */
    {
        /* to read data from memory */
        if ((fes->grid.buffer = calloc(fes->grid.n_grids,
                sizeof(fes_float_complex*))) == NULL)
        {
            set_fes_error(fes, FES_NO_MEMORY);
            return 1;
        }
    }

    if ((fes->grid.waveIndex = calloc(fes->grid.n_grids, sizeof(int))) == NULL)
    {
        set_fes_error(fes, FES_NO_MEMORY);
        return 1;
    }

    /* Loading grids */
    for (ix = 0; ix < N_WAVES; ix++)
    {
        fes_cdf_file file;
        const char* filename = ini_get_string(ini, _get_key(fes->type,
                fes->waves[ix].name, "FILE"), NULL);

        if (filename == NULL)
        {
            /* Wave computed by admittance or not computed */
            fes->waves[ix].dynamic = 0;
            continue;
        }

        /* Wave computed dynamically */
        fes->waves[ix].dynamic = 1;

        /* If the data are read from a grid so the wave is not computed by
         admittance. */
        fes->waves[ix].admittance = 0;

        /* Reading the name of the variable who contains the latitudes. */
        strncpy(file.lat, ini_get_string(ini, _get_key(fes->type,
                fes->waves[ix].name, "LATITUDE"), LATITUDE), sizeof(file.lat));

        /* Reading the name of the variable who contains the longitude. */
        strncpy(file.lon, ini_get_string(ini, _get_key(fes->type,
                fes->waves[ix].name, "LONGITUDE"), LONGITUDE), sizeof(file.lon));

        /* Reading the name of the variable who contains the amplitudes. */
        strncpy(file.amp, ini_get_string(ini, _get_key(fes->type,
                fes->waves[ix].name, "AMPLITUDE"), AMPLTIUDE), sizeof(file.amp));

        /* Reading the name of the variable who contains the phases. */
        strncpy(file.pha, ini_get_string(ini, _get_key(fes->type,
                fes->waves[ix].name, "PHASE"), PHASE), sizeof(file.pha));

        /* Grids contains phase lag ? */
        file.phase_lag = ini_get_integer(ini, _get_key(fes->type,
                fes->waves[ix].name, "PHASE_LAG"), PHASE_LAG);

        /* loading netCDF grid */
        if ((rc = load_grid(filename, n, &file, fes)) != 0)
            return rc;

        fes->grid.waveIndex[n] = ix;

        n++;
    }

    ini_close(ini);

    /* Set wave order 2 to compute long-period equilibrium ocean tides */
    set_w2nd(fes->waves, fes->w2nd);

    return 0;
}

/*
 */
void fes_delete(void* handle)
{
    int ix;
    fes_handler* fes = handle;

    if (fes != NULL)
    {
        for (ix = 0; ix < fes->grid.n_grids; ix++)
        {
            if (fes->grid.file != NULL)
                nc_close(fes->grid.file[ix].id);

            if (fes->grid.buffer != NULL)
                free(fes->grid.buffer[ix]);

        }
        free(fes->grid.file);
        free(fes->grid.buffer);
        free(fes->grid.waveIndex);
        free(fes);
    }
}

/*
 */
int fes_core(
        FES handle,
        const double lat,
        const double lon,
        const double time,
        double* h,
        double* h_long_period)
{
    int i;
    double phi;
    double t1 = julian_centuries(time);
    double delta;
    fes_handler* fes = handle;

    delta = (t1 - fes->nodal_time) * 3.6525E+04 * 24.0;

    if (fabs(delta) > 24.0)
    {
        compute_nodal_corrections(t1, fes->waves);

        fes->nodal_time = t1;
        delta = 0.0;
    }

    if (interp(fes, lat, lon))
        return 1;

    admittance(fes->waves);

    if (fes->is_data)
    {
        *h = 0.0;

        if (fes->type == FES_TIDE)
        {
            lpe_minus_n_waves((const float (*)[N_COEFS])(fes->w2nd), time,
                    lat, h_long_period);
        }
        else
            *h_long_period = 0;

        for (i = 0; i < N_WAVES; i++)
        {
            double tide;

            phi = fmod(fes->waves[i].freq * delta + fes->waves[i].v0u, 2.0
                    * M_PI);

            if (phi < 0.0)
                phi = phi + 2.0 * M_PI;

            tide = fes->waves[i].f * (fes->waves[i].c.re * cos(phi)
                    + fes->waves[i].c.im * sin(phi));

            if (fes->waves[i].type == SP_TIDE)
                *h += tide;
            else
                *h_long_period += tide;
        }
    }
    else
    {
        set_fes_error(fes, FES_NO_DATA);
        return 1;
    }

    return 0;
}
