/*********************************************************************
 *   Copyright 1993, UCAR/Unidata
 *   See netcdf/COPYRIGHT file for copying and redistribution conditions.
 *********************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "ncgen.h"
#include "genlib.h"

extern int netcdf_flag;
extern int c_flag;
extern int fortran_flag;

void
load_netcdf(void *rec_start) /* write out record from in-memory structure */
{
    int     idim;
    int     istat = 0;
    long    coords[H4_MAX_VAR_DIMS];
    long    edges[H4_MAX_VAR_DIMS];
    char   *charvalp   = NULL;
    short  *shortvalp  = NULL;
    nclong *longvalp   = NULL;
    float  *floatvalp  = NULL;
    double *doublevalp = NULL;

    /* load values into variable */

    switch (vars[varnum].type) {
        case NC_CHAR:
        case NC_BYTE:
            charvalp = (char *)rec_start;
            break;
        case NC_SHORT:
            shortvalp = (short *)rec_start;
            break;
        case NC_LONG:
            longvalp = (nclong *)rec_start;
            break;
        case NC_FLOAT:
            floatvalp = (float *)rec_start;
            break;
        case NC_DOUBLE:
            doublevalp = (double *)rec_start;
            break;
        default:
            break;
    }
    /* initialize coords to upper left corner (0,0,0,...) */
    if (vars[varnum].dims[0] == rec_dim) {
        coords[0] = netcdf_record_number;
        edges[0]  = 1;
    }
    else {
        coords[0] = 0;
        edges[0]  = dims[vars[varnum].dims[0]].size;
    }

    for (idim = 1; idim < vars[varnum].ndims; idim++) {
        coords[idim] = 0;
        edges[idim]  = dims[vars[varnum].dims[idim]].size;
    }

    switch (vars[varnum].type) {
        case NC_CHAR:
        case NC_BYTE:
            istat = ncvarput(ncid, varnum, coords, edges, (void *)charvalp);
            break;
        case NC_SHORT:
            istat = ncvarput(ncid, varnum, coords, edges, (void *)shortvalp);
            break;
        case NC_LONG:
            istat = ncvarput(ncid, varnum, coords, edges, (void *)longvalp);
            break;
        case NC_FLOAT:
            istat = ncvarput(ncid, varnum, coords, edges, (void *)floatvalp);
            break;
        case NC_DOUBLE:
            istat = ncvarput(ncid, varnum, coords, edges, (void *)doublevalp);
            break;
        default:
            break;
    }
    if (istat == -1)
        derror("error putting value for variable %s", vars[varnum].name);
}

/*
 * Remove trailing zeros (after decimal point) but not trailing decimal
 * point from ss, a string representation of a floating-point number that
 * might include an exponent part.
 */
/* ss - returned string representing dd */
static void
tztrim(char *ss)
{
    char *cp, *ep;

    cp = ss;
    if (*cp == '-')
        cp++;
    while (isdigit((int)*cp) || *cp == '.')
        cp++;
    if (*--cp == '.')
        return;
    ep = cp + 1;
    while (*cp == '0')
        cp--;
    cp++;
    if (cp == ep)
        return;
    while (*ep)
        *cp++ = *ep++;
    *cp = '\0';
    return;
}

/* generate C to put netCDF record from in-memory data */
static void
gen_load_c(void *rec_start)
{
    int     idim, ival;
    char   *val_string;
    char   *charvalp   = NULL;
    short  *shortvalp  = NULL;
    nclong *longvalp   = NULL;
    float  *floatvalp  = NULL;
    double *doublevalp = NULL;
    char    stmnt[C_MAX_STMNT];
    size_t  stmnt_len;
    char    s2[H4_MAX_NC_NAME + 2];

    /* initialize coords to upper left corner (rec_num,0,0,...) */
    cline("");
    sprintf(stmnt, "   {\t\t\t/* store %s */", vars[varnum].name);
    cline(stmnt);

    if (vars[varnum].ndims > 0) {
        sprintf(stmnt, "    static long %s_start[] = {", vars[varnum].name);
        if (vars[varnum].dims[0] == rec_dim)
            sprintf(s2, "%ld%s", netcdf_record_number, 0 < vars[varnum].ndims - 1 ? ", " : "};");
        else
            sprintf(s2, "%d%s", 0, 0 < vars[varnum].ndims - 1 ? ", " : "};");
        strcat(stmnt, s2);
        for (idim = 1; idim < vars[varnum].ndims; idim++) {
            sprintf(s2, "%d%s", 0, idim < vars[varnum].ndims - 1 ? ", " : "};");
            strcat(stmnt, s2);
        }
        cline(stmnt);

        /* initialize edge lengths from upper left corner */
        sprintf(stmnt, "    static long %s_edges[] = {", vars[varnum].name);
        if (vars[varnum].dims[0] == rec_dim)
            sprintf(s2, "%d%s", 1, 0 < vars[varnum].ndims - 1 ? ", " : "};");
        else
            sprintf(s2, "%ld%s", dims[vars[varnum].dims[0]].size, 0 < vars[varnum].ndims - 1 ? ", " : "};");
        strcat(stmnt, s2);
        for (idim = 1; idim < vars[varnum].ndims; idim++) {
            sprintf(s2, "%ld%s", dims[vars[varnum].dims[idim]].size,
                    idim < vars[varnum].ndims - 1 ? ", " : "};");
            strcat(stmnt, s2);
        }
        cline(stmnt);

        /* load variable with data values using static initialization */
        sprintf(stmnt, "    static %s %s[] = {", ncctype(vars[varnum].type), vars[varnum].name);

        stmnt_len = strlen(stmnt);
        switch (vars[varnum].type) {
            case NC_CHAR:
                val_string = cstrstr((char *)rec_start, var_len);
                sprintf(s2, "%s", val_string);
                strcat(stmnt, s2);
                free(val_string);
                break;
            default:
                switch (vars[varnum].type) {
                    case NC_BYTE:
                        charvalp = (char *)rec_start;
                        break;
                    case NC_SHORT:
                        shortvalp = (short *)rec_start;
                        break;
                    case NC_LONG:
                        longvalp = (nclong *)rec_start;
                        break;
                    case NC_FLOAT:
                        floatvalp = (float *)rec_start;
                        break;
                    case NC_DOUBLE:
                        doublevalp = (double *)rec_start;
                        break;
                    default:
                        break;
                }
                for (ival = 0; ival < var_len - 1; ival++) {
                    switch (vars[varnum].type) {
                        case NC_BYTE:
                            sprintf(s2, "%d, ", *charvalp++);
                            break;
                        case NC_SHORT:
                            sprintf(s2, "%d, ", *shortvalp++);
                            break;
                        case NC_LONG:
                            sprintf(s2, "%d, ", (int)*longvalp++);
                            break;
                        case NC_FLOAT:
                            sprintf(s2, "%.8g, ", (double)(*floatvalp++));
                            break;
                        case NC_DOUBLE:
                            sprintf(s2, "%#.16g", *doublevalp++);
                            tztrim(s2);
                            strcat(s2, ", ");
                            break;
                        default:
                            break;
                    }
                    stmnt_len += strlen(s2);
                    if (stmnt_len < C_MAX_STMNT)
                        strcat(stmnt, s2);
                    else {
                        cline(stmnt);
                        strcpy(stmnt, s2);
                        stmnt_len = strlen(stmnt);
                    }
                }
                for (; ival < var_len; ival++) {
                    switch (vars[varnum].type) {
                        case NC_BYTE:
                            sprintf(s2, "%d", *charvalp);
                            break;
                        case NC_SHORT:
                            sprintf(s2, "%d", *shortvalp);
                            break;
                        case NC_LONG:
                            sprintf(s2, "%d", (int)*longvalp);
                            break;
                        case NC_FLOAT:
                            sprintf(s2, "%.8g", (double)(*floatvalp));
                            break;
                        case NC_DOUBLE:
                            sprintf(s2, "%#.16g", *doublevalp++);
                            tztrim(s2);
                            break;
                        default:
                            break;
                    }
                    stmnt_len += strlen(s2);
                    if (stmnt_len < C_MAX_STMNT)
                        strcat(stmnt, s2);
                    else {
                        cline(stmnt);
                        strcpy(stmnt, s2);
                        stmnt_len = strlen(stmnt);
                    }
                }
                break;
        }
        strcat(stmnt, "};");
        cline(stmnt);
        sprintf(stmnt, "    ncvarput(ncid, %s_id, %s_start, %s_edges, (void *)%s);", vars[varnum].name,
                vars[varnum].name, vars[varnum].name, vars[varnum].name);
        cline(stmnt);
        cline("   }");
    }
    else { /* scalar variables */
        /* load variable with data values using static initialization */
        sprintf(stmnt, "    static %s %s = {", ncctype(vars[varnum].type), vars[varnum].name);

        switch (vars[varnum].type) {
            case NC_CHAR:
                val_string = cstrstr((char *)rec_start, var_len);
                sprintf(s2, "%s", val_string);
                strcat(stmnt, s2);
                free(val_string);
                break;
            case NC_BYTE:
                charvalp = (char *)rec_start;
                sprintf(s2, "%d", *charvalp);
                strcat(stmnt, s2);
                break;
            case NC_SHORT:
                shortvalp = (short *)rec_start;
                sprintf(s2, "%d", *shortvalp);
                strcat(stmnt, s2);
                break;
            case NC_LONG:
                longvalp = (nclong *)rec_start;
                sprintf(s2, "%d", (int)*longvalp);
                strcat(stmnt, s2);
                break;
            case NC_FLOAT:
                floatvalp = (float *)rec_start;
                sprintf(s2, "%.8g", (double)(*floatvalp));
                strcat(stmnt, s2);
                break;
            case NC_DOUBLE:
                doublevalp = (double *)rec_start;
                sprintf(s2, "%#.16g", *doublevalp++);
                tztrim(s2);
                strcat(stmnt, s2);
                break;
            default:
                break;
        }
        strcat(stmnt, "};");
        cline(stmnt);
        sprintf(stmnt, "    ncvarput1(ncid, %s_id, (long *)0, (void *)&%s);", vars[varnum].name,
                vars[varnum].name);
        cline(stmnt);
        cline("   }");
    }
}

/*
 * Add to a partial Fortran statement, checking if it's too long.  If it is too
 * long, output the first part of it as a single statement with continuation
 * characters and start a new (probably invalid) statement with the remainder.
 * This will cause a Fortran compiler error, but at least all the information
 * will be available.
 */
/* s     - source string of stement being built */
/* t     - string to be appended to source */
/* slenp - pointer to length of source string */
static void
fstrcat(char *s, const char *t, size_t *slenp)
{
    *slenp += strlen(t);

    if (*slenp >= FORT_MAX_STMNT) {
        derror("FORTRAN statement too long: %s", s);
        fline(s);
        strcpy(s, t);
        *slenp = strlen(s);
    }
    else {
        strcat(s, t);
    }
}

static void
gen_load_fortran(void *rec_start) /* make Fortran to put record */
{
    int     idim, ival;
    char   *val_string;
    char   *charvalp;
    short  *shortvalp;
    nclong *longvalp;
    float  *floatvalp;
    double *doublevalp;
    char    stmnt[FORT_MAX_STMNT];
    size_t  stmnt_len;
    char    s2[H4_MAX_NC_NAME + 2];

    /* initialize coords to upper left corner (1,1,...,rec_num) */
    sprintf(stmnt, "* store %s", vars[varnum].name);
    fline(stmnt);

    if (vars[varnum].ndims > 0) {
        for (idim = 1; idim < vars[varnum].ndims; idim++) {
            sprintf(stmnt, "corner(%d) = 1", idim);
            fline(stmnt);
        }
        if (vars[varnum].dims[0] == rec_dim) {
            sprintf(stmnt, "corner(%d) = %d", idim, (int)(netcdf_record_number + 1));
            fline(stmnt);
        }
        else {
            sprintf(stmnt, "corner(%d) = 1", idim);
            fline(stmnt);
        }
        for (idim = vars[varnum].ndims - 1; idim > 0; idim--) {
            sprintf(stmnt, "edges(%d) = %d", vars[varnum].ndims - idim,
                    (int)dims[vars[varnum].dims[idim]].size);
            fline(stmnt);
        }
        if (vars[varnum].dims[0] == rec_dim) {
            sprintf(stmnt, "edges(%d) = 1", vars[varnum].ndims - idim);
            fline(stmnt);
        }
        else {
            sprintf(stmnt, "edges(%d) = %d", vars[varnum].ndims - idim, (int)dims[vars[varnum].dims[0]].size);
            fline(stmnt);
        }
    }
    else { /* scalar variables */
        fline("corner(1) = 1");
        fline("edges(1) = 1");
    }

    /* load variable with data values  */
    if (vars[varnum].type != NC_CHAR) {
        sprintf(stmnt, "data %s /", vars[varnum].name);
        stmnt_len = strlen(stmnt);
        switch (vars[varnum].type) {
            case NC_BYTE:
                charvalp = (char *)rec_start;
                for (ival = 0; ival < var_len - 1; ival++) {
                    val_string = fstring(NC_BYTE, (void *)charvalp++, 0);
                    sprintf(s2, "%s, ", val_string);
                    fstrcat(stmnt, s2, &stmnt_len);
                    free(val_string);
                }
                val_string = fstring(NC_BYTE, (void *)charvalp++, 0);
                fstrcat(stmnt, val_string, &stmnt_len);
                free(val_string);
                break;
            case NC_SHORT:
                shortvalp = (short *)rec_start;
                for (ival = 0; ival < var_len - 1; ival++) {
                    sprintf(s2, "%d, ", *shortvalp++);
                    fstrcat(stmnt, s2, &stmnt_len);
                }
                sprintf(s2, "%d", *shortvalp);
                fstrcat(stmnt, s2, &stmnt_len);
                break;
            case NC_LONG:
                longvalp = (nclong *)rec_start;
                for (ival = 0; ival < var_len - 1; ival++) {
                    sprintf(s2, "%d, ", (int)*longvalp++);
                    fstrcat(stmnt, s2, &stmnt_len);
                }
                sprintf(s2, "%d", (int)*longvalp);
                fstrcat(stmnt, s2, &stmnt_len);
                break;
            case NC_FLOAT:
                floatvalp = (float *)rec_start;
                for (ival = 0; ival < var_len - 1; ival++) {
                    sprintf(s2, "%.8g, ", (double)(*floatvalp++));
                    fstrcat(stmnt, s2, &stmnt_len);
                }
                sprintf(s2, "%.8g", (double)(*floatvalp));
                fstrcat(stmnt, s2, &stmnt_len);
                break;
            case NC_DOUBLE:
                doublevalp = (double *)rec_start;
                for (ival = 0; ival < var_len - 1; ival++) {
                    sprintf(s2, "%#.16g", *doublevalp++);
                    tztrim(s2);
                    fstrcat(s2, ", ", &stmnt_len);
                    fstrcat(stmnt, s2, &stmnt_len);
                }
                sprintf(s2, "%#.16g", *doublevalp++);
                tztrim(s2);
                fstrcat(stmnt, s2, &stmnt_len);
                break;
            default:
                break;
        }
        fstrcat(stmnt, "/", &stmnt_len);
        fline(stmnt);

        sprintf(stmnt, "call ncvpt(ncid, %sid, corner, edges, %s, iret)", vars[varnum].name,
                vars[varnum].name);
    }
    else { /* for strings, call ncvptc() */
        long dimprod = 1;

        val_string = fstrstr((char *)rec_start, var_len);
        sprintf(stmnt, "%s = %s", vars[varnum].name, val_string);
        free(val_string);
        stmnt_len = strlen(stmnt);
        fstrcat(stmnt, " // char(0)", &stmnt_len);
        fline(stmnt);
        for (idim = vars[varnum].ndims - 1; idim > 0; idim--)
            dimprod *= dims[vars[varnum].dims[idim]].size;
        if (vars[varnum].dims[0] != rec_dim)
            dimprod *= dims[vars[varnum].dims[0]].size;

        sprintf(stmnt, "call ncvptc(ncid, %sid, corner, edges, %s, %ld, iret)", vars[varnum].name,
                vars[varnum].name, dimprod);
    }
    fline(stmnt);
}

/* invoke netcdf calls (or generate C or Fortran code) to load netcdf variable
 * from in-memory data.  Assumes following global variables set from yacc
 * parser:
 * int varnum        - number of variable to be loaded.
 * struct vars[varnum] - structure containing info on variable, specifically
 *                     name, type, ndims, dims, fill_value, has_data
 * int rec_dim       - id of record dimension, or -1 if none
 * struct dims[]     - structure containing name and size of dimensions.
 * int netcdf_record_number - number of current record for this variable.
 */
/* rec_start - points to data to be loaded  */
void
put_variable(void *rec_start)
{
    if (netcdf_flag)
        load_netcdf(rec_start); /* put variable values (one record's worth) */
    if (c_flag)                 /* create C code to put values */
        gen_load_c(rec_start);
    if (fortran_flag) /* create Fortran code to put values */
        gen_load_fortran(rec_start);
}
