/* This is part of the netCDF package. Copyright 2018 University
   Corporation for Atmospheric Research/Unidata See COPYRIGHT file for
   conditions of use. See www.unidata.ucar.edu for more info.

   Test small files.

   $Id: tst_small.c 2796 2014-10-28 03:40:29Z wkliao $
*/

#include "config.h"
#include <nc_tests.h>
#include "err_macros.h"
#include <netcdf.h>
#ifdef USE_PNETCDF
#include <netcdf_par.h>
#endif

/* Test everything for classic, 64-bit offset, 64-bit data files. If netcdf-4 is
 * included, that means another whole round of testing. */
#define NUM_FORMATS (5)

#define ATT_NAME "Atom"
#define MAX_LEN 7

#define ERR2 { \
    err++; \
    fprintf(stderr, "Sorry! Unexpected result, %s, line: %d (%s)\n", \
    __FILE__, __LINE__, nc_strerror(stat));                          \
    exit(1); \
}

#ifdef USE_PNETCDF
#define FMTCHECK {\
     int format; \
     nc_inq_format_extended(ncid,&format,NULL); \
     if (format == NC_FORMATX_PNETCDF) { \
       if (nc_var_par_access(ncid, NC_GLOBAL, NC_COLLECTIVE)) ERR;\
     }\
   }
#else
#define FMTCHECK
#endif


static int file_create(const char *filename, int cmode, int *ncid)
{
    int err;

    /* get the default file format */
    int default_format;
    nc_set_default_format(NC_FORMAT_CLASSIC, &default_format);
    /* set it back to the default */
    nc_set_default_format(default_format, NULL);

#ifdef USE_PNETCDF
    if (default_format == NC_FORMAT_CLASSIC ||
        default_format == NC_FORMAT_64BIT_OFFSET
#ifdef NETCDF_ENABLE_CDF5
        || default_format == NC_FORMAT_64BIT_DATA
#endif
        )
        err = nc_create_par(filename, cmode, MPI_COMM_WORLD, MPI_INFO_NULL, ncid);
    else
#endif
        err = nc_create(filename, cmode, ncid);

    return err;
}

static int file_open(const char *filename, int omode, int *ncid)
{
    int err;

    /* get the default file format */
    int default_format;
    err = nc_set_default_format(NC_FORMAT_CLASSIC, &default_format);
    /* set it back to the default */
    err = nc_set_default_format(default_format, NULL);

#ifdef USE_PNETCDF
    if (default_format == NC_FORMAT_CLASSIC ||
        default_format == NC_FORMAT_64BIT_OFFSET ||
        default_format == NC_FORMAT_64BIT_DATA)
        err = nc_open_par(filename, omode, MPI_COMM_WORLD, MPI_INFO_NULL, ncid);
    else
#endif
        err = nc_open(filename, omode, ncid);

    return err;
}

static int
test_small_atts(const char *testfile)
{
   int ncid;
   char att[MAX_LEN + 1], att_in[MAX_LEN + 1], source[MAX_LEN + 1] = "0123456";
   int ndims, nvars, natts, unlimdimid;
   size_t len_in;
   int f;

   /* Run this with and without fill mode. */
   for (f = 0; f < 2; f++)
   {
      /* Create small files with an attribute that grows by one each
       * time. */
      for (size_t t = 1; t < MAX_LEN; t++)
      {
	 /* Create null-terminated text string of correct length. */
	 strncpy(att, source, t);
         att[t] = '\0';

	 /* Create a file with one attribute. */
	 if (file_create(testfile, NC_CLOBBER, &ncid)) ERR;
	 if (nc_put_att_text(ncid, NC_GLOBAL, ATT_NAME, t + 1, att)) ERR;
	 if (f && nc_set_fill(ncid, NC_NOFILL, NULL)) ERR;
	 if (nc_close(ncid)) ERR;

	 /* Reopen the file and check it. */
	 if (file_open(testfile, NC_NOWRITE, &ncid)) ERR;
	 if (nc_inq(ncid, &ndims, &nvars, &natts, &unlimdimid)) ERR;
	 if (ndims != 0 && nvars != 0 && natts != 1 && unlimdimid != -1) ERR;
	 if (nc_inq_attlen(ncid, NC_GLOBAL, ATT_NAME, &len_in)) ERR;
	 if (len_in != t + 1) ERR;
	 if (nc_get_att_text(ncid, NC_GLOBAL, ATT_NAME, att_in)) ERR;
	 if (strncmp(att_in, att, t) != 0) ERR;
	 if (nc_close(ncid)) ERR;
      }
   }
   return 0;
}

#define DIM1_NAME "Time"
#define DIM2_NAME "DataStrLen"
#define VAR_NAME "Times"
#define STR_LEN 19
#define NUM_VALS 2
#define NDIMS 2
#define TITLE " OUTPUT FROM WRF V2.0.3.1 MODEL"
#define ATT_NAME2 "TITLE"

/* Test a small file with an unlimited dimension. NOTE: Normally I
 * write a NULL terminator for my attributes and text strings, but
 * this reproduces a bug that a fortran user sent us. So string data
 * are written to the file without null terminators. - Ed */
static int
test_small_unlim(const char *testfile)
{
   int ncid, dimids[NDIMS], varid, stat;
   char data[NUM_VALS][STR_LEN + 1], data_in[NUM_VALS][STR_LEN];
   int ndims, nvars, natts, unlimdimid;
   size_t i, start[NDIMS], count[NDIMS];

   /* Create null-terminated text strings of correct length. */
   /*for (i = 0; i < NUM_VALS; i++)
     strcpy(data[i], source);*/
   strcpy(data[0], "2005-04-11_12:00:00");
   strcpy(data[1], "2005-04-11_13:00:00");

   /* Create a file with two dimensions, one unlimited, and one
    * var, and a global att. */
   if (file_create(testfile, NC_CLOBBER, &ncid)) ERR;
   if (nc_def_dim(ncid, DIM1_NAME, NC_UNLIMITED, dimids)) ERR;
   if (nc_def_dim(ncid, DIM2_NAME, STR_LEN, &dimids[1])) ERR;
   if (nc_def_var(ncid, VAR_NAME, NC_CHAR, 2, dimids, &varid)) ERR;
   if (nc_put_att_text(ncid, NC_GLOBAL, ATT_NAME2, strlen(TITLE), TITLE)) ERR;
   if (nc_enddef(ncid)) ERR;

   /* Write some records of var data. */
   count[0] = 1;
   count[1] = STR_LEN;
   start[1] = 0;

   FMTCHECK;

   for (start[0] = 0; start[0] < NUM_VALS; start[0]++)
      if ((stat=nc_put_vara_text(ncid, varid, start, count, data[start[0]]))!=NC_NOERR) ERR2;

   /* We're done! */
   if (nc_close(ncid)) ERR;

   /* Reopen the file and check it. */
   if (file_open(testfile, NC_NOWRITE, &ncid)) ERR;
   if (nc_inq(ncid, &ndims, &nvars, &natts, &unlimdimid)) ERR;
   if (ndims != 2 && nvars != 1 && natts != 0 && unlimdimid != 0) ERR;
   if (nc_get_var_text(ncid, varid, (char *)data_in)) ERR;
   for (i = 0; i < NUM_VALS; i++)
      if (strncmp(data[i], data_in[i], STR_LEN) != 0) ERR;
   if (nc_close(ncid)) ERR;
   return 0;
}

/* Test a small file with a fixed dimension. */
static int
test_small_fixed(const char *testfile)
{
   int ncid, dimids[NDIMS], varid;
   char data[NUM_VALS][STR_LEN + 1], data_in[NUM_VALS][STR_LEN];
   int ndims, nvars, natts, unlimdimid;
   size_t i, start[NDIMS], count[NDIMS];

   /* Create null-terminated text strings of correct length. */
   /*for (i = 0; i < NUM_VALS; i++)
     strcpy(data[i], source);*/
   strcpy(data[0], "2005-04-11_12:00:00");
   strcpy(data[1], "2005-04-11_13:00:00");

   /* Create a file with two dimensions, one unlimited, and one
    * var, and a global att. */
   if (file_create(testfile, NC_CLOBBER, &ncid)) ERR;
   if (nc_def_dim(ncid, DIM1_NAME, NUM_VALS, dimids)) ERR;
   if (nc_def_dim(ncid, DIM2_NAME, STR_LEN, &dimids[1])) ERR;
   if (nc_def_var(ncid, VAR_NAME, NC_CHAR, NDIMS, dimids, &varid)) ERR;
   if (nc_put_att_text(ncid, NC_GLOBAL, ATT_NAME2, strlen(TITLE), TITLE)) ERR;
   if (nc_enddef(ncid)) ERR;

   /* Write some records of var data. */
   count[0] = 1;
   count[1] = STR_LEN;
   start[1] = 0;
   for (start[0] = 0; start[0] < NUM_VALS; start[0]++)
      if (nc_put_vara_text(ncid, varid, start, count, data[start[0]])) ERR;

   /* We're done! */
   if (nc_close(ncid)) ERR;

   /* Reopen the file and check it. */
   if (file_open(testfile, NC_NOWRITE, &ncid)) ERR;
   if (nc_inq(ncid, &ndims, &nvars, &natts, &unlimdimid)) ERR;
   if (ndims != 2 && nvars != 1 && natts != 0 && unlimdimid != -1) ERR;
   if (nc_get_var_text(ncid, varid, (char *)data_in)) ERR;
   for (i = 0; i < NUM_VALS; i++)
      if (strncmp(data[i], data_in[i], STR_LEN) != 0) ERR;
   if (nc_close(ncid)) ERR;
   return 0;
}

/* Test a small file with one var. */
static int
test_small_one(const char *testfile)
{
   int ncid, dimid, varid;
   char data = 'h', data_in;
   int ndims, nvars, natts, unlimdimid;
   size_t start[NDIMS], count[NDIMS];

   /* Create a file with one unlimited dimensions, and one var. */
   if (file_create(testfile, NC_CLOBBER, &ncid)) ERR;
   if (nc_def_dim(ncid, DIM1_NAME, NC_UNLIMITED, &dimid)) ERR;
   if (nc_def_var(ncid, VAR_NAME, NC_CHAR, 1, &dimid, &varid)) ERR;
   if (nc_enddef(ncid)) ERR;

   /* Write one record of var data, a single character. */
   count[0] = 1;
   start[0] = 0;

   FMTCHECK;

   if (nc_put_vara_text(ncid, varid, start, count, &data)) ERR;

   /* We're done! */
   if (nc_close(ncid)) ERR;

   /* Reopen the file and check it. */
   if (file_open(testfile, NC_NOWRITE, &ncid)) ERR;
   if (nc_inq(ncid, &ndims, &nvars, &natts, &unlimdimid)) ERR;
   if (ndims != 1 && nvars != 1 && natts != 0 && unlimdimid != 0) ERR;
   if (nc_get_var_text(ncid, varid, &data_in)) ERR;
   if (data_in != data) ERR;
   if (nc_close(ncid)) ERR;
   return 0;
}

#define ONE_DIM 1
#define MAX_RECS 10

/* Test a small file with one record var, which grows. */
static int
test_one_growing(const char *testfile)
{
   int ncid, dimid, varid;
   char data[MAX_RECS], data_in;
   size_t start[ONE_DIM], count[ONE_DIM], index[ONE_DIM], len_in;
   int r, f;

   /* Create some phoney data. */
   for (data[0] = 'a', r = 1; r < MAX_RECS; r++)
      data[r] = data[r - 1] + 1;

   /* Run this with and without fill mode. */
   for (f = 0; f < 2; f++)
   {
      /* Create a file with one unlimited dimensions, and one var. */
      if (file_create(testfile, NC_CLOBBER, &ncid)) ERR;
      if (nc_def_dim(ncid, DIM1_NAME, NC_UNLIMITED, &dimid)) ERR;
      if (nc_def_var(ncid, VAR_NAME, NC_CHAR, 1, &dimid, &varid)) ERR;
      if (nc_close(ncid)) ERR;

      /* Normally one would not close and reopen the file for each
       * record, but I am giving the library a little work-out here... */
      for (size_t r = 0; r < MAX_RECS; r++)
      {
	 /* Write one record of var data, a single character. */
	 if (file_open(testfile, NC_WRITE, &ncid)) ERR;
         if (f) {
             int format;
             nc_inq_format_extended(ncid,&format,NULL);
             if (format == NC_FORMATX_PNETCDF) {
                 /* in PnetCDF, nc_set_fill() can only be called in define mode */
                 if (nc_redef(ncid)) ERR;
                 if (nc_set_fill(ncid, NC_NOFILL, NULL)) ERR;
                 if (nc_enddef(ncid)) ERR;
             }
             else { /* test_netcdf4 */
                 if (nc_set_fill(ncid, NC_NOFILL, NULL)) ERR;
             }
         }
	 count[0] = 1;
	 start[0] = r;
	 FMTCHECK;
	 if (nc_put_vara_text(ncid, varid, start, count, &data[r])) ERR;
	 if (nc_close(ncid)) ERR;

	 /* Reopen the file and check it. */
	 if (file_open(testfile, NC_NOWRITE, &ncid)) ERR;
	 if (nc_inq_dimlen(ncid, 0, &len_in)) ERR;
	 if (len_in != r + 1) ERR;
	 index[0] = r;
	 if (nc_get_var1_text(ncid, 0, index, &data_in)) ERR;
	 if (data_in != data[r]) ERR;
	 if (nc_close(ncid)) ERR;
      } /* Next record. */
   }
   return 0;
}

#define ONE_DIM 1
#define MAX_RECS 10

/* Test a small file with one record var, which grows, and has
 * attributes. */
static int
test_one_growing_with_att(const char *testfile)
{
   int ncid, dimid, varid;
   char data[MAX_RECS], data_in;
   char att_name[NC_MAX_NAME + 1];
   size_t start[ONE_DIM], count[ONE_DIM], index[ONE_DIM], len_in;
   int r;

   /* Create a file with one unlimited dimensions, and one var. */
   if (file_create(testfile, NC_CLOBBER, &ncid)) ERR;
   if (nc_def_dim(ncid, DIM1_NAME, NC_UNLIMITED, &dimid)) ERR;
   if (nc_def_var(ncid, VAR_NAME, NC_CHAR, 1, &dimid, &varid)) ERR;
   if (nc_close(ncid)) ERR;

   /* Create some phoney data. */
   for (data[0] = 'a', r = 1; r < MAX_RECS; r++)
      data[r] = data[r - 1] + 1;

   /* Normally one would not close and reopen the file for each
    * record, nor add an attribute each time I add a record, but I am
    * giving the library a little work-out here... */
   for (size_t r = 0; r < MAX_RECS; r++)
   {
      /* Write one record of var data, a single character. */
      if (file_open(testfile, NC_WRITE, &ncid)) ERR;
      count[0] = 1;
      start[0] = r;

      FMTCHECK;

      if (nc_put_vara_text(ncid, varid, start, count, &data[r])) ERR;
      snprintf(att_name, sizeof(att_name), "a_%d", data[r]);
      if (nc_redef(ncid)) ERR;
      if (nc_put_att_text(ncid, varid, att_name, 1, &data[r])) ERR;
      if (nc_close(ncid)) ERR;

      /* Reopen the file and check it. */
      if (file_open(testfile, NC_NOWRITE, &ncid)) ERR;
      if (nc_inq_dimlen(ncid, 0, &len_in)) ERR;
      if (len_in != r + 1) ERR;
      index[0] = r;
      if (nc_get_var1_text(ncid, 0, index, &data_in)) ERR;
      if (data_in != data[r]) ERR;
      if (nc_get_att_text(ncid, varid, att_name, &data_in)) ERR;
      if (data_in != data[r]) ERR;
      if (nc_close(ncid)) ERR;
   } /* Next record. */
   return 0;
}

#define VAR_NAME2 "var2"
#define NUM_VARS 2

/* Test a small file with two record vars, which grow, and has
 * attributes added. */
static int
test_two_growing_with_att(const char *testfile)
{
   int ncid, dimid, varid[NUM_VARS];
   char data[MAX_RECS], data_in;
   char att_name[NC_MAX_NAME + 1];
   size_t start[ONE_DIM], count[ONE_DIM], index[ONE_DIM], len_in;
   int v, r;

   /* Create a file with one unlimited dimensions, and one var. */
   if (file_create(testfile, NC_CLOBBER, &ncid)) ERR;
   if (nc_def_dim(ncid, DIM1_NAME, NC_UNLIMITED, &dimid)) ERR;
   if (nc_def_var(ncid, VAR_NAME, NC_CHAR, 1, &dimid, &varid[0])) ERR;
   if (nc_def_var(ncid, VAR_NAME2, NC_CHAR, 1, &dimid, &varid[1])) ERR;
   if (nc_close(ncid)) ERR;

   /* Create some phoney data. */
   for (data[0] = 'a', r = 1; r < MAX_RECS; r++)
      data[r] = data[r - 1] + 1;

   /* Normally one would not close and reopen the file for each
    * record, nor add an attribute each time I add a record, but I am
    * giving the library a little work-out here... */
   for (size_t r = 0; r < MAX_RECS; r++)
   {
      /* Write one record of var data, a single character. */
      if (file_open(testfile, NC_WRITE, &ncid)) ERR;
#ifdef USE_PNETCDF
      {int format;
      nc_inq_format_extended(ncid,&format,NULL);
      if (format == NC_FORMATX_PNETCDF) {
          if (nc_var_par_access(ncid, NC_GLOBAL, NC_COLLECTIVE)) ERR;
      }}
#endif
      count[0] = 1;
      start[0] = r;
      snprintf(att_name, sizeof(att_name), "a_%d", data[r]);
      for (v = 0; v < NUM_VARS; v++)
      {
	 if (nc_put_vara_text(ncid, varid[v], start, count, &data[r])) ERR;
	 if (nc_redef(ncid)) ERR;
	 if (nc_put_att_text(ncid, varid[v], att_name, 1, &data[r])) ERR;
	 if (nc_enddef(ncid)) ERR;
      }
      if (nc_close(ncid)) ERR;

      /* Reopen the file and check it. */
      if (file_open(testfile, NC_NOWRITE, &ncid)) ERR;
      if (nc_inq_dimlen(ncid, 0, &len_in)) ERR;
      if (len_in != r + 1) ERR;
      index[0] = r;
      for (v = 0; v < NUM_VARS; v++)
      {
	 if (nc_get_var1_text(ncid, varid[v], index, &data_in)) ERR;
	 if (data_in != data[r]) ERR;
      }
      if (nc_close(ncid)) ERR;
   } /* Next record. */
   return 0;
}

/* Test a small file with one var and one att. */
static int
test_one_with_att(const char *testfile)
{
   int ncid, dimid, varid;
   char data = 'h', data_in;
   int ndims, nvars, natts, unlimdimid;
   size_t start[NDIMS], count[NDIMS];

   /* Create a file with one unlimited dimensions, and one var. */
   if (file_create(testfile, NC_CLOBBER, &ncid)) ERR;
   if (nc_def_dim(ncid, DIM1_NAME, NC_UNLIMITED, &dimid)) ERR;
   if (nc_def_var(ncid, VAR_NAME, NC_CHAR, 1, &dimid, &varid)) ERR;
   if (nc_put_att_text(ncid, NC_GLOBAL, ATT_NAME, 1, &data)) ERR;
   if (nc_enddef(ncid)) ERR;

   /* Write one record of var data, a single character. */
   count[0] = 1;
   start[0] = 0;

   FMTCHECK;

   if (nc_put_vara_text(ncid, varid, start, count, &data)) ERR;

   /* We're done! */
   if (nc_close(ncid)) ERR;

   /* Reopen the file and check it. */
   if (file_open(testfile, NC_NOWRITE, &ncid)) ERR;
   if (nc_inq(ncid, &ndims, &nvars, &natts, &unlimdimid)) ERR;
   if (ndims != 1 && nvars != 1 && natts != 0 && unlimdimid != 0) ERR;
   if (nc_get_var_text(ncid, varid, &data_in)) ERR;
   if (data_in != data) ERR;
   if (nc_get_att_text(ncid, NC_GLOBAL, ATT_NAME, &data_in)) ERR;
   if (data_in != data) ERR;
   if (nc_close(ncid)) ERR;
   return 0;
}

int
main(int argc, char **argv)
{
   int i;
   char testfile[NC_MAX_NAME + 1];

#ifdef USE_PNETCDF
   MPI_Init(&argc, &argv);
#endif

   printf("\n*** Testing small files.\n");
   /*nc_set_log_level(3);*/

   /* Go thru formats and run all tests for each of two (for netCDF-3
    * only builds), or 4 (for netCDF-4 builds) different formats. */
   for (i = NUM_FORMATS; i >= 1; i--)
   {
      switch (i)
      {
	 case NC_FORMAT_CLASSIC:
	    nc_set_default_format(NC_FORMAT_CLASSIC, NULL);
	    printf("Switching to netCDF classic format.\n");
	    strcpy(testfile, "tst_small_classic.nc");
	    break;
	 case NC_FORMAT_64BIT_OFFSET:
	    nc_set_default_format(NC_FORMAT_64BIT_OFFSET, NULL);
	    printf("Switching to 64-bit offset format.\n");
	    strcpy(testfile, "tst_small_64bit.nc");
	    break;
#ifdef NETCDF_ENABLE_CDF5
	 case NC_FORMAT_CDF5:
	    nc_set_default_format(NC_FORMAT_CDF5, NULL);
	    printf("Switching to 64-bit data format.\n");
	    strcpy(testfile, "tst_small_cdf5.nc");
	    break;
#else
      case NC_FORMAT_CDF5:
        continue;
#endif
#ifdef USE_HDF5
	 case NC_FORMAT_NETCDF4_CLASSIC:
	    nc_set_default_format(NC_FORMAT_NETCDF4_CLASSIC, NULL);
	    strcpy(testfile, "tst_small_netcdf4_classic.nc");
	    printf("Switching to netCDF-4 format (with NC_CLASSIC_MODEL).\n");
	    break;
	 case NC_FORMAT_NETCDF4: /* actually it's _CLASSIC. */
	    nc_set_default_format(NC_FORMAT_NETCDF4, NULL);
	    strcpy(testfile, "tst_small_netcdf4.nc");
	    printf("Switching to netCDF-4 format.\n");
	    break;
#else
	 case NC_FORMAT_NETCDF4_CLASSIC:
	 case NC_FORMAT_NETCDF4:
	    continue; /* loop i */
#endif
	 default:
	    printf("Unexpected format!\n");
	    return 2;
      }

      printf("*** testing simple small file with a global attribute...");
      test_small_atts(testfile);
      SUMMARIZE_ERR;

      printf("*** testing simple small file with fixed dimensions...");
      test_small_fixed(testfile);
      SUMMARIZE_ERR;

      printf("*** testing simple small file with an unlimited dimension...");
      test_small_unlim(testfile);
      SUMMARIZE_ERR;

      printf("*** testing small file with one variable...");
      test_small_one(testfile);
      SUMMARIZE_ERR;

      printf("*** testing small file with one variable and one att...");
      test_one_with_att(testfile);
      SUMMARIZE_ERR;

      printf("*** testing small file with one record variable, which grows...");
      test_one_growing(testfile);
      SUMMARIZE_ERR;

      printf("*** testing small file with one growing record "
	     "variable, with attributes added...");
      test_one_growing_with_att(testfile);
      SUMMARIZE_ERR;

      printf("*** testing small file with two growing record "
	     "variables, with attributes added...");
      test_two_growing_with_att(testfile);
      SUMMARIZE_ERR;
   }

#ifdef USE_PNETCDF
   MPI_Finalize();
#endif
   FINAL_RESULTS;
}
