/***************************************************************************
 *
 * Project:  OpenCPN
 *
 ***************************************************************************
 *   Copyright (C) 2013 by David S. Register                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.         *
 **************************************************************************/

#include <wx/filename.h>
#include <wx/tokenzr.h>

#include <math.h>

#include "TCDS_Ascii_Harmonic.h"

#ifndef M_PI
#define M_PI    ((2)*(acos(0.0)))
#endif

#define IFF_OPEN  0
#define IFF_CLOSE 1
#define IFF_SEEK  2
#define IFF_TELL  3
#define IFF_READ  4

typedef struct {
    void     *next;
    short int rec_start;
    char     *name;
} harmonic_file_entry;

/* Turn a time displacement of the form [-]HH:MM into the number of seconds. */
static int hhmm2seconds (char *hhmm)
{
    int h, m;
    char s;
    if (sscanf (hhmm, "%d:%d", &h, &m) != 2)
        return(0);
    if (sscanf (hhmm, "%c", &s) != 1)
        return(0);
    if (h < 0 || s == '-')
        m = -m;
    return h*3600 + m*60;
}

TCDS_Ascii_Harmonic::TCDS_Ascii_Harmonic()
{
    //  Initialize member variables
    m_IndexFile = NULL;

    m_cst_speeds = NULL;
    m_cst_nodes = NULL;
    m_cst_epochs = NULL;

    num_IDX = 0;
    num_nodes = 0;
    num_csts = 0;
    num_epochs = 0;
}

TCDS_Ascii_Harmonic::~TCDS_Ascii_Harmonic()
{
    free_data();

    m_msd_array.Clear();
}

TC_Error_Code TCDS_Ascii_Harmonic::LoadData(const wxString &data_file_path)
{
    if(m_IndexFile) IndexFileIO( IFF_CLOSE, 0 );

    m_indexfile_name = data_file_path;

    TC_Error_Code error_return = init_index_file();
    if(error_return != TC_NO_ERROR)
        return error_return;

    wxFileName f(data_file_path);
    m_harmfile_name = f.GetPath( wxPATH_GET_SEPARATOR | wxPATH_GET_VOLUME );
    m_harmfile_name += f.GetName();
    error_return = LoadHarmonicConstants(m_harmfile_name);

    //  Mark the index entries individually with invariant harmonic constants
    unsigned int max_index = GetMaxIndex();
    for(unsigned int i=0 ; i < max_index ; i++) {
        IDX_entry *pIDX = GetIndexEntry( i );
        if(pIDX) {
            pIDX->num_nodes = num_nodes;
            pIDX->num_csts = num_csts;
            pIDX->num_epochs = num_epochs;
            pIDX->m_cst_speeds = m_cst_speeds;
            pIDX->m_cst_nodes = m_cst_nodes;
            pIDX->m_cst_epochs = m_cst_epochs;
            pIDX->first_year = m_first_year;
            pIDX->m_work_buffer = m_work_buffer;
        }
    }

    return error_return;
}


IDX_entry *TCDS_Ascii_Harmonic::GetIndexEntry(int n_index)
{
    return &m_IDX_array[n_index];
}


TC_Error_Code TCDS_Ascii_Harmonic::init_index_file()
{
    long int xref_start=0;

    num_IDX=0;

    m_abbreviation_array.clear();
    m_IDX_array.Clear();
    //   free_harmonic_file_list();
    int have_index = 0;
    int index_in_memory = 0;

    if (IndexFileIO(IFF_OPEN, 0)) {
        while (IndexFileIO(IFF_READ, 0)) {
            if ((index_line_buffer[0] == '#') || (index_line_buffer[0] <= ' '));  // Skip comment lines
            else if (!have_index && !xref_start) {
                if (!strncmp(index_line_buffer, "XREF", 4))
                    xref_start = IndexFileIO(IFF_TELL, 0);
            }
            else if (!have_index && !strncmp(index_line_buffer, "*END*", 5)) {
                if (m_abbreviation_array.empty()) {
                    IndexFileIO(IFF_CLOSE, 0);
                    return(TC_INDEX_FILE_CORRUPT); // missing at least some data so no valid index
                }
                // We're done with abbreviation list (and no errors)
                else have_index = 1;
            } // found *END* of cross reference

            else if (!have_index && xref_start) {
                wxString line( index_line_buffer, wxConvUTF8 );

                abbr_entry entry;

                wxStringTokenizer tkz(line, _T(" "));
                wxString token = tkz.GetNextToken();
                if(token.IsSameAs(_T("REGION"), FALSE))
                    entry.type = REGION;
                else if(token.IsSameAs(_T("COUNTRY"), FALSE))
                    entry.type = COUNTRY;
                else if(token.IsSameAs(_T("STATE"), FALSE))
                    entry.type = STATE;

                token = tkz.GetNextToken();
                entry.short_s = token;

                entry.long_s = line.Mid(tkz.GetPosition()).Strip();

                m_abbreviation_array.push_back(entry);

            }

            else if (have_index && (strchr("TtCcIUu", index_line_buffer[0]))) {
                // Load index file data .
                num_IDX++; // Keep counting entries for harmonic file stuff
                IDX_entry *pIDX = new IDX_entry;
                pIDX->source_data_type = SOURCE_TYPE_ASCII_HARMONIC;
                pIDX->pDataSource = NULL;

                index_in_memory   = TRUE;
                pIDX->Valid15 = 0;

                if(TC_NO_ERROR != build_IDX_entry(pIDX ) ) {
                }

                m_IDX_array.Add(pIDX);
            }

#if 0
            else if (have_index && (index_line_buffer[0] == 'H')) {
                // This is a new harmonic file name.
                sscanf(index_line, "Harmonic %s", s1);
                pHarmonic = harmonic_file_list;
                while (pHarmonic && pHarmonic->next)
                    pHarmonic = (harmonic_file_entry *)pHarmonic->next;
                pHarmonic_prev = pHarmonic;
                pHarmonic = (harmonic_file_entry *)malloc(sizeof(harmonic_file_entry));
                if (NULL == pHarmonic) {
                    //               no_mem_msg();
                    free_harmonic_file_list();
                }
                else {
                    if (!harmonic_file_list)
                        harmonic_file_list = pHarmonic;
                    else pHarmonic_prev->next = pHarmonic;
                    pHarmonic->next = NULL;
                    pHarmonic->rec_start = num_IDX;
                    if (allocate_copy_string(&pHarmonic->name,s1)) {
                        //                  no_mem_msg();
                        free_harmonic_file_list();
                    }
                }
            }
#endif
        } // while (more file)
        if (index_in_memory) IndexFileIO(IFF_CLOSE, 0); // All done with file
    } // index file can't be opened
    //   if (hwndBusy) DestroyWindow(hwndBusy);

//   max_IDX = num_IDX;
    return( TC_NO_ERROR );
}

// ----------------------------------
//   Decode an index data line into an IDX_entry
// ----------------------------------

TC_Error_Code TCDS_Ascii_Harmonic::build_IDX_entry(IDX_entry *pIDX )
{
    int TZHr, TZMin ;
    char stz[80];

    pIDX->pref_sta_data = NULL;                     // no reference data yet
    pIDX->IDX_Useable = 1;                          // but assume data is OK

    pIDX->IDX_tzname = NULL;
    stz[0] = 0;

    if (7 != sscanf( index_line_buffer, "%c%s%lf%lf%d:%d%*c%[^\r\n]",
                     &pIDX->IDX_type,&pIDX->IDX_zone[0],&pIDX->IDX_lon,&pIDX->IDX_lat,&TZHr,&TZMin,
                     &pIDX->IDX_station_name[0])) return(TC_INDEX_ENTRY_BAD);

    if(TZHr < 0 && TZMin > 0 ) TZMin=-TZMin;  //correct for negative timezones with fractional hours (NewFoundland)
    pIDX->IDX_time_zone = TZHr*60 + TZMin;

    if (strchr("tcUu",index_line_buffer[0])) { // Substation so get second line of info
        IndexFileIO(IFF_READ, 0);

        if(index_line_buffer[0] == '^')                  // Opencpn special
        {
            if (11 != sscanf(index_line_buffer, "%*c%d %f %f %d %f %f %d %d %d %d%*c%[^\r\n]",
                             &pIDX->IDX_ht_time_off, &pIDX->IDX_ht_mpy, &pIDX->IDX_ht_off,
                             &pIDX->IDX_lt_time_off, &pIDX->IDX_lt_mpy, &pIDX->IDX_lt_off,
                             &pIDX->IDX_sta_num, &pIDX->IDX_flood_dir, &pIDX->IDX_ebb_dir,
                             &pIDX->IDX_ref_file_num, pIDX->IDX_reference_name))
                return(TC_INDEX_ENTRY_BAD);

            if(abs(pIDX->IDX_ht_time_off) > 1000)           // useable?
                pIDX->IDX_Useable = 0;

            if(abs(pIDX->IDX_flood_dir) > 360)           // useable?
                pIDX->IDX_Useable = 0;
            if(abs(pIDX->IDX_ebb_dir) > 360)           // useable?
                pIDX->IDX_Useable = 0;

            //    Fix up the secondaries which are identical to masters
            if(pIDX->IDX_ht_mpy == 0.0)
                pIDX->IDX_ht_mpy = 1.0;
            if(pIDX->IDX_lt_mpy == 0.0)
                pIDX->IDX_lt_mpy = 1.0;

        }
        else
        {
            if (9 != sscanf(index_line_buffer, "%*c%d %f %f %d %f %f %d %d%*c%[^\r\n]",
                            &pIDX->IDX_ht_time_off, &pIDX->IDX_ht_mpy, &pIDX->IDX_ht_off,
                            &pIDX->IDX_lt_time_off, &pIDX->IDX_lt_mpy, &pIDX->IDX_lt_off,
                            &pIDX->IDX_sta_num, &pIDX->IDX_ref_file_num, pIDX->IDX_reference_name))
            {
                // Had an error so try alternate with timezone name before ref file number
                if (10 != sscanf(index_line_buffer, "%*c%d %f %f %d %f %f %d %s %d%*c%[^\r\n]",
                                 &pIDX->IDX_ht_time_off, &pIDX->IDX_ht_mpy, &pIDX->IDX_ht_off,
                                 &pIDX->IDX_lt_time_off, &pIDX->IDX_lt_mpy, &pIDX->IDX_lt_off,
                                 &pIDX->IDX_sta_num, stz, &pIDX->IDX_ref_file_num, pIDX->IDX_reference_name))
                    return(TC_INDEX_ENTRY_BAD);

                if (NULL!=(pIDX->IDX_tzname = (char *)malloc(strlen(stz)+1)))
                    strcpy(pIDX->IDX_tzname, stz);
            }


        }           // else


        //  We only consider 1 reference file per index file
        pIDX->IDX_ref_file_num = 0;
        /*
                if (pIDX->IDX_ref_file_num <= 0)
                {   // Find harmonic reference file number
                    pIDX->IDX_ref_file_num= 0;
                    // Find reference station in index, if no index, it had better be in the first one
                    pIDXh = pIDX_first;
                    while (pIDXh!=NULL && strcmp(pIDXh->IDX_reference_name,pIDX->IDX_reference_name))
                        pIDXh = (IDX_entry *)pIDXh->IDX_next;

                    // Copy reference station harmonic file number
                    if (pIDXh!=NULL)
                        pIDX->IDX_ref_file_num = pIDXh->IDX_ref_file_num;
                }
        */
    }

    else
    {   // Reference stations have no offsets
        pIDX->IDX_ht_time_off = pIDX->IDX_lt_time_off = 0;
        pIDX->IDX_ht_mpy      = pIDX->IDX_lt_mpy = 1.0;
        pIDX->IDX_ht_off      = pIDX->IDX_lt_off = 0.0;
        pIDX->IDX_sta_num     = 0;
        strcpy(pIDX->IDX_reference_name, pIDX->IDX_station_name);

    }

    if( pIDX->IDX_ht_time_off ||
            pIDX->IDX_ht_off != 0.0 ||
            pIDX->IDX_lt_off != 0.0 ||
            pIDX->IDX_ht_mpy != 1.0 ||
            pIDX->IDX_lt_mpy != 1.0)
        pIDX->have_offsets = 1;

    pIDX->station_tz_offset = 0;            // ASCII Harmonic data is (always??) corrected to Ref Station TZ

    return(TC_NO_ERROR);
}


//    Load the Harmonic Constant Invariants
TC_Error_Code TCDS_Ascii_Harmonic::LoadHarmonicConstants(const wxString &data_file_path)
{
    FILE *fp;
    char linrec[linelen];
    char junk[80];
    int a, b;

    free_data();

    fp = fopen (data_file_path.mb_str(), "r");
    if (NULL == fp)
        return TC_FILE_NOT_FOUND;

    read_next_line (fp, linrec, 0);

    if (1 != sscanf (linrec, "%d", &num_csts))
        goto error;
    if (num_csts <= 0 || num_csts > 1000000) // 100 % arbitrary roughly twice the harmonic lines number
        goto error;

    m_cst_speeds = (double *) malloc (num_csts * sizeof (double));
    m_work_buffer = (double *) malloc (num_csts * sizeof (double));

    /* Load constituent speeds */
    for (a=0; a<num_csts; a++) {
        read_next_line (fp, linrec, 0);
        sscanf (linrec, "%s %lf", junk, &(m_cst_speeds[a]));
        m_cst_speeds[a] *= M_PI / 648000; /* Convert to radians per second */
    }

    /* Get first year for nodes and epochs */
    read_next_line (fp, linrec, 0);
    sscanf (linrec, "%d", &m_first_year);

    /* Load epoch table */
    read_next_line (fp, linrec, 0);
    if (1 != sscanf (linrec, "%d", &num_epochs))
        goto error;
    if (num_epochs <= 0 || num_epochs > 1000000)
        goto error;

    m_cst_epochs = (double **) malloc (num_csts * sizeof (double *));
    for (int i=0; i<num_csts; i++)
        m_cst_epochs[i] = (double *) malloc (num_epochs * sizeof (double));

    for (int i=0; i<num_csts; i++)
    {
        if(1 != fscanf (fp, "%s", linrec))
            goto error;
        for (int b=0; b<num_epochs; b++)
        {
            if(1 != fscanf (fp, "%lf", &(m_cst_epochs[i][b])))
                goto error;
            m_cst_epochs[i][b] *= M_PI / 180.0;
        }
    }


    /* Sanity check */
    if(1 != fscanf (fp, "%s", linrec))
        goto error;
    skipnl (fp);

    /* Load node factor table */
    read_next_line (fp, linrec, 0);
    if(1 != sscanf (linrec, "%d", &num_nodes))
        goto error;
    if (num_nodes <= 0 || num_nodes > 1000000)
        goto error;

    m_cst_nodes = (double **) malloc (num_csts * sizeof (double *));
    for (int a=0; a<num_csts; a++)
        m_cst_nodes[a] = (double *) malloc (num_nodes * sizeof (double));

    for (int a=0; a<num_csts; a++) {
        int ignore = fscanf (fp, "%s", linrec);
        for (b=0; b<num_nodes; b++)
            ignore = fscanf (fp, "%lf", &(m_cst_nodes[a][b]));
    }

    fclose(fp);

    return TC_NO_ERROR;

error:
    fclose(fp);
    return TC_HARM_FILE_CORRUPT;
}


TC_Error_Code TCDS_Ascii_Harmonic::LoadHarmonicData(IDX_entry *pIDX)
{
    Station_Data *psd = NULL;

    //    Look in the index first
    if(pIDX->pref_sta_data)
        return TC_NO_ERROR;         //easy


    // Try the member array of "already-looked-at" master stations
    for(unsigned int i=0 ; i < m_msd_array.GetCount() ; i++)
    {
        psd = &m_msd_array[i];
        //    In the following comparison, it is allowed that the sub-station reference_name may be
        //          a pre-subset of the master station name.
        //          e.g  IDX_refence_name:  The Narrows midchannel New York
        //                            as found in HARMONIC.IDX
        //                 psd_station_name:      The Narrows, Midchannel, New York Harbor, New York Current
        //                            as found in HARMONIC
        if( (!slackcmp(psd->station_name, pIDX->IDX_reference_name)) && (toupper(pIDX->IDX_type) == psd->station_type) )
        {
            pIDX->pref_sta_data = psd;                // save for later
            return TC_NO_ERROR;
        }
    }


    //    OK, have to read and create from the raw file
    psd = NULL;

    //    If reference station was recently sought, and not found, don't bother
    //            if(!strcmp(pIDX->IDX_reference_name, plast_reference_not_found->mb_str()))
    if(m_last_reference_not_found.IsSameAs(wxString(pIDX->IDX_reference_name, wxConvUTF8)))
        return TC_MASTER_HARMONICS_NOT_FOUND;

    //    Clear for this looking
    m_last_reference_not_found.Clear();

    //    Find and load appropriate constituents
    FILE *fp;
    char linrec[linelen];

    fp = fopen (m_harmfile_name.mb_str(), "r");
    if (fp == 0)
        return TC_MASTER_HARMONICS_NOT_FOUND;

    while (read_next_line (fp, linrec, 1))
    {
        nojunk (linrec);
        int curonly = 0;
        if (curonly)
            if (!strstr (linrec, "Current"))
                continue;
        //    See the note above about station names
        //                  if(!strncmp(linrec, "Rivi", 4))
        //                        int ggl = 4;

        if (slackcmp (linrec, pIDX->IDX_reference_name))
            continue;

        //    Got the right location, so load the data

        psd = new Station_Data;

        psd->amplitude = (double *)malloc(num_csts * sizeof(double));
        psd->epoch     = (double *)malloc(num_csts * sizeof(double));
        psd->station_name = (char *)malloc(strlen(linrec) +1);

        char junk[80];
        int a;
        strcpy (psd->station_name, linrec);

        //    Establish Station Type
        wxString caplin(linrec, wxConvUTF8);
        caplin.MakeUpper();
        if(caplin.Contains(_T("CURRENT")))
            psd->station_type = 'C';
        else
            psd->station_type = 'T';



        /* Get meridian */
        read_next_line (fp, linrec, 0);
        psd->meridian = hhmm2seconds (linrec);
        psd->zone_offset = 0;
        
        /* Get tzfile, if present */
        if (sscanf (nojunk(linrec), "%s %s", junk, psd->tzfile) < 2)
            strcpy (psd->tzfile, "UTC0");

        /* Get DATUM and units */
        read_next_line (fp, linrec, 0);
        if (sscanf (nojunk(linrec), "%lf %s", &(psd->DATUM), psd->unit) < 2)
            strcpy (psd->unit, "unknown");

        if ((a = findunit (psd->unit)) == -1)
        {
            // Nonsense....
            //                        strcpy (psd->units_abbrv, psd->unit);
            //                        strcpy (psd->units_conv, known_units[a].name);
        }

        psd->have_BOGUS = (findunit(psd->unit) != -1) && (known_units[findunit(psd->unit)].type == BOGUS);

        int unit_c;
        if (psd->have_BOGUS)
            unit_c = findunit("knots");
        else
            unit_c = findunit(psd->unit);

        if (unit_c != -1)
        {
            strcpy (psd->units_conv,       known_units[unit_c].name);
            strcpy (psd->units_abbrv,      known_units[unit_c].abbrv);
        }

        /* Get constituents */
        double loca, loce;
        for (a=0; a<num_csts; a++)
        {
            read_next_line (fp, linrec, 0);
            sscanf (linrec, "%s %lf %lf", junk, &loca, &loce);
            //          loc_epoch[a] *= M_PI / 180.0;
            psd->amplitude[a] = loca;
            psd->epoch[a] = loce * M_PI / 180.;
        }

        break;
    }
    fclose (fp);

    if(!psd) {
        m_last_reference_not_found = wxString(pIDX->IDX_reference_name, wxConvUTF8);
        return TC_MASTER_HARMONICS_NOT_FOUND;
    }
    else {
        m_msd_array.Add(psd);                     // add it to the member array
        pIDX->pref_sta_data = psd;
        return TC_NO_ERROR;
    }

}


/*---------------------------------
 * Low level Index file I/O
 *-------------------------------*/


long TCDS_Ascii_Harmonic::IndexFileIO(int func, long value) {
    char *str;

    switch ( func ) {
        // Close either/both if open
    case IFF_CLOSE :
        if (m_IndexFile) fclose(m_IndexFile);
        m_IndexFile = NULL;
        return(0);

        // Open
    case IFF_OPEN :
        m_IndexFile = fopen( m_indexfile_name.mb_str(), "rt");
        if (m_IndexFile == NULL) return(0);
        return(1);

        // Return file pointer only happens with master file
    case IFF_TELL :
        return(ftell(m_IndexFile));

        // Seek
    case IFF_SEEK :
        return(fseek(m_IndexFile,value,SEEK_SET));

        // Read until EOF .
    case IFF_READ :
        str = fgets( index_line_buffer, 1024, m_IndexFile);

        if (str != NULL)
            return(1);
        else return(0);

    }
    return(0);
}

/* Read a line from the harmonics file, skipping comment lines */
int TCDS_Ascii_Harmonic::read_next_line (FILE *fp, char linrec[linelen], int end_ok)
{
    do {
        if (!fgets (linrec, linelen, fp)) {
            if (end_ok)
                return 0;
            else {
                exit (-1);
            }
        }
    } while (linrec[0] == '#' || linrec[0] == '\r' || linrec[0] == '\n');
    return 1;
}

/* Remove lingering carriage return, but do nothing else */
int TCDS_Ascii_Harmonic::skipnl (FILE *fp)
{
    char linrec[linelen];
    if(NULL == fgets (linrec, linelen, fp))
        return 0;
    return 1;
}

/* Get rid of trailing garbage in buffer */
char * TCDS_Ascii_Harmonic::nojunk (char *line)
{
    char *a;
    a = &(line[strlen(line)]);
    while (a > line)
        if (*(a-1) == '\n' || *(a-1) == '\r' || *(a-1) == ' ')
            *(--a) = '\0';
        else
            break;
    return line;
}

/* Slackful strcmp; 0 = match.  It's case-insensitive and accepts a
 *   prefix instead of the entire string.  The second argument is the
 *   one that can be shorter. Second argument can contain '?' as wild
 *   card character.
 */
int TCDS_Ascii_Harmonic::slackcmp (char *a, char *b)
{
    int c, cmp, n;
    n = strlen (b);
    if ((int)(strlen (a)) < n)
        return 1;
    for (c=0; c<n; c++)
    {
        if(b[c] == '?')
            continue;

        cmp = ((a[c] >= 'A' && a[c] <= 'Z') ? a[c] - 'A' + 'a' : a[c])
              -
              ((b[c] >= 'A' && b[c] <= 'Z') ? b[c] - 'A' + 'a' : b[c]);
        if (cmp)
            return cmp;
    }
    return 0;
}

void TCDS_Ascii_Harmonic::free_cst()
{
    free(m_cst_speeds);
    m_cst_speeds = NULL;
}
void TCDS_Ascii_Harmonic::free_nodes()
{
    int a;
    if (num_csts && m_cst_nodes)
        for(a=0; a<num_csts; a++)
            free(m_cst_nodes[a]);
    free(m_cst_nodes);

    m_cst_nodes = NULL;
}

void TCDS_Ascii_Harmonic::free_epochs()
{
    int a;
    if (num_csts && m_cst_epochs)
        for(a=0; a<num_csts; a++)
            free(m_cst_epochs[a]);
    free(m_cst_epochs);

    m_cst_epochs = NULL;
}

/* free harmonics data */
void TCDS_Ascii_Harmonic::free_data ()
{
    free_nodes();
    free_epochs();
    free_cst();
}

