/* 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 netcdf-4 enum types.

   Ed Hartnett
*/

#include <nc_tests.h>
#include "err_macros.h"
#include "netcdf.h"

#define FILE_NAME "tst_enums.nc"
#define DIM_LEN 4
#define NUM_MEMBERS 4
#define DIM_NAME "dim"
#define BASE_SIZE 20
#define VAR_NAME "Advice"
#define TYPE_NAME "Mysterous_Word"

int
main(int argc, char **argv)
{
   int ncid;
   nc_type typeid;
   int i;
   char name_in[NC_MAX_NAME+1];
   int ntypes, typeids[1] = {0};
   nc_type base_nc_type, base_nc_type_in;
   size_t nfields_in, num_members, base_size_in;
   int class_in;

   printf("\n*** Testing netcdf-4 enum type.\n");
   printf("*** testing creation of enum type...");
   {
      int value_in;
      /* Can't use the same name twice! */
      char member_name[NUM_MEMBERS][NC_MAX_NAME + 1] = {"Mene1", "Mene2",
                                                        "Tekel", "Upharsin"};
      int member_value[NUM_MEMBERS] = {0, 99, 81232, 12};

      /* Create a file. */
      if (nc_create(FILE_NAME, NC_NETCDF4, &ncid)) ERR;

      /* Create an enum type. */
      if (nc_def_enum(ncid, NC_INT, TYPE_NAME, &typeid)) ERR;
      for (i = 0; i < NUM_MEMBERS; i++)
         if (nc_insert_enum(ncid, typeid, member_name[i],
                            &member_value[i])) ERR;

      /* Check it out. */
      if (nc_inq_user_type(ncid, typeid, name_in, &base_size_in, &base_nc_type_in,
                           &nfields_in, &class_in)) ERR;
      if (strcmp(name_in, TYPE_NAME) || base_size_in != sizeof(int) ||
          base_nc_type_in != NC_INT || nfields_in != NUM_MEMBERS || class_in != NC_ENUM) ERR;
      if (nc_inq_enum(ncid, typeid, name_in, &base_nc_type, &base_size_in, &num_members)) ERR;
      if (strcmp(name_in, TYPE_NAME) || base_nc_type != NC_INT ||
          num_members != NUM_MEMBERS) ERR;
      for (i = 0; i < NUM_MEMBERS; i++)
      {
         if (nc_inq_enum_member(ncid, typeid, i, name_in, &value_in)) ERR;
         if (strcmp(name_in, member_name[i]) || value_in != member_value[i]) ERR;
         if (nc_inq_enum_ident(ncid, typeid, member_value[i], name_in)) ERR;
         if (strcmp(name_in, member_name[i])) ERR;
      }

      /* Write the file. */
      if (nc_close(ncid)) ERR;

      /* Reopen the file. */
      if (nc_open(FILE_NAME, NC_NOWRITE, &ncid)) ERR;

      /* Get type info. */
      if (nc_inq_typeids(ncid, &ntypes, typeids)) ERR;
      if (ntypes != 1 || !typeids[0]) ERR;

      /* Check it out. */
      if (nc_inq_user_type(ncid, typeids[0], name_in, &base_size_in, &base_nc_type_in,
                           &nfields_in, &class_in)) ERR;
      if (strcmp(name_in, TYPE_NAME) || base_size_in != sizeof(int) ||
          base_nc_type_in != NC_INT || nfields_in != NUM_MEMBERS || class_in != NC_ENUM) ERR;
      if (nc_inq_type(ncid, typeids[0], name_in, &base_size_in)) ERR;
      if (strcmp(name_in, TYPE_NAME) || base_size_in != sizeof(int)) ERR;
      if (nc_inq_enum(ncid, typeids[0], name_in, &base_nc_type, &base_size_in, &num_members)) ERR;
      if (strcmp(name_in, TYPE_NAME) || base_nc_type != NC_INT || num_members != NUM_MEMBERS) ERR;
      for (i = 0; i < NUM_MEMBERS; i++)
      {
         if (nc_inq_enum_member(ncid, typeid, i, name_in, &value_in)) ERR;
         if (strcmp(name_in, member_name[i]) || value_in != member_value[i]) ERR;
         if (nc_inq_enum_ident(ncid, typeid, member_value[i], name_in)) ERR;
         if (strcmp(name_in, member_name[i])) ERR;
      }

      if (nc_close(ncid)) ERR;
   }

   SUMMARIZE_ERR;

#define NUM_BRADYS 9
#define BRADYS "Bradys"
#define BRADY_DIM_LEN 3
#define ATT_NAME "brady_attribute"

   printf("*** testing enum attribute...");
   {
      char brady_name[NUM_BRADYS][NC_MAX_NAME + 1] = {"Mike", "Carol", "Greg", "Marsha",
                                                      "Peter", "Jan", "Bobby", "Whats-her-face",
                                                      "Alice"};
      unsigned char brady_value[NUM_BRADYS] = {8, 7, 6 , 5, 4, 3, 2, 1, 0};
      unsigned char data[BRADY_DIM_LEN] = {0, 4, 8};
      unsigned char value_in;

      /* Create a file. */
      if (nc_create(FILE_NAME, NC_NETCDF4, &ncid)) ERR;

      /* Create an enum type based on unsigned bytes. */
      if (nc_def_enum(ncid, NC_UBYTE, BRADYS, &typeid)) ERR;
      for (i = 0; i < NUM_BRADYS; i++)
         if (nc_insert_enum(ncid, typeid, brady_name[i],
                            &brady_value[i])) ERR;

      /* Check it out. */
      if (nc_inq_user_type(ncid, typeid, name_in, &base_size_in, &base_nc_type_in,
                           &nfields_in, &class_in)) ERR;
      if (strcmp(name_in, BRADYS) || base_size_in != 1 ||
          base_nc_type_in != NC_UBYTE || nfields_in != NUM_BRADYS || class_in != NC_ENUM) ERR;
      if (nc_inq_enum(ncid, typeid, name_in, &base_nc_type, &base_size_in, &num_members)) ERR;
      if (strcmp(name_in, BRADYS) || base_nc_type != NC_UBYTE || base_size_in != 1 ||
          num_members != NUM_BRADYS) ERR;
      for (i = 0; i < NUM_BRADYS; i++)
      {
         if (nc_inq_enum_member(ncid, typeid, i, name_in, &value_in)) ERR;
         if (strcmp(name_in, brady_name[i]) || value_in != brady_value[i]) ERR;
         if (nc_inq_enum_ident(ncid, typeid, brady_value[i], name_in)) ERR;
         if (strcmp(name_in, brady_name[i])) ERR;
      }

      /* Write an att of this enum type. */
      if (nc_put_att(ncid, NC_GLOBAL, ATT_NAME, typeid, BRADY_DIM_LEN, data)) ERR;

      /* Close the file. */
      if (nc_close(ncid)) ERR;

      /* Reopen. */
      if (nc_open(FILE_NAME, NC_NOWRITE, &ncid)) ERR;

      /* Check it out. */
      if (nc_inq_user_type(ncid, typeid, name_in, &base_size_in, &base_nc_type_in,
                           &nfields_in, &class_in)) ERR;
      if (strcmp(name_in, BRADYS) || base_size_in != 1 ||
          base_nc_type_in != NC_UBYTE || nfields_in != NUM_BRADYS || class_in != NC_ENUM) ERR;
      if (nc_inq_enum(ncid, typeid, name_in, &base_nc_type, &base_size_in, &num_members)) ERR;
      if (strcmp(name_in, BRADYS) || base_nc_type != NC_UBYTE || base_size_in != 1 ||
          num_members != NUM_BRADYS) ERR;
      for (i = 0; i < NUM_BRADYS; i++)
      {
         if (nc_inq_enum_member(ncid, typeid, i, name_in, &value_in)) ERR;
         if (strcmp(name_in, brady_name[i]) || value_in != brady_value[i]) ERR;
         if (nc_inq_enum_ident(ncid, typeid, brady_value[i], name_in)) ERR;
         if (strcmp(name_in, brady_name[i])) ERR;
      }

      if (nc_close(ncid)) ERR;
   }

   SUMMARIZE_ERR;

#define TYPE2_NAME "cloud_type"
#define DIM2_NAME "station"
#define DIM2_LEN 5
#define VAR2_NAME "primary_cloud"
#define VAR2_RANK 1
#define ATT2_NAME "_FillValue"
#define ATT2_LEN  1

   printf("*** testing enum fill value ...");
   {
      int dimid, varid;
      size_t num_members_in;
      int class_in;
      unsigned char value_in;
      unsigned char att_value_in;

      enum clouds {             /* a C enumeration */
         CLEAR=0,
         CUMULONIMBUS=1,
         STRATUS=2,
         STRATOCUMULUS=3,
         CUMULUS=4,
         ALTOSTRATUS=5,
         NIMBOSTRATUS=6,
         ALTOCUMULUS=7,
         CIRROSTRATUS=8,
         CIRROCUMULUS=9,
         CIRRUS=10,
         MISSING=255};

      struct {
         char *name;
         unsigned char value;
      } cloud_types[] = {
         {"Clear", CLEAR},
         {"Cumulonimbus", CUMULONIMBUS},
         {"Stratus", STRATUS},
         {"Stratocumulus", STRATOCUMULUS},
         {"Cumulus", CUMULUS},
         {"Altostratus", ALTOSTRATUS},
         {"Nimbostratus", NIMBOSTRATUS},
         {"Altocumulus", ALTOCUMULUS},
         {"Cirrostratus", CIRROSTRATUS},
         {"Cirrocumulus", CIRROCUMULUS},
         {"Cirrus", CIRRUS},
         {"Missing", MISSING}
      };
      int var_dims[VAR2_RANK];
      unsigned char att_val;
      unsigned char cloud_data[DIM2_LEN] = {
         CLEAR, STRATUS, CLEAR, CUMULONIMBUS, MISSING};
      unsigned char cloud_data_in[DIM2_LEN];

      if (nc_create(FILE_NAME, NC_CLOBBER | NC_NETCDF4, &ncid)) ERR;

      /* Create an enum type. */
      if (nc_def_enum(ncid, NC_UBYTE, TYPE2_NAME, &typeid)) ERR;
      num_members = (sizeof cloud_types) / (sizeof cloud_types[0]);
      for (i = 0; i < num_members; i++)
         if (nc_insert_enum(ncid, typeid, cloud_types[i].name,
                            &cloud_types[i].value)) ERR;

      /* Declare a station dimension */
      if (nc_def_dim(ncid, DIM2_NAME, DIM2_LEN, &dimid)) ERR;
      /* Declare a variable of the enum type */
      var_dims[0] = dimid;
      if (nc_def_var(ncid, VAR2_NAME, typeid, VAR2_RANK, var_dims, &varid)) ERR;
      /* Create and write a variable attribute of the enum type */
      att_val = MISSING;
      if (nc_put_att(ncid, varid, ATT2_NAME, typeid, ATT2_LEN, &att_val)) ERR;
      if (nc_enddef(ncid)) ERR;
      /* Store some data of the enum type */
      if(nc_put_var(ncid, varid, cloud_data)) ERR;
      /* Write the file. */
      if (nc_close(ncid)) ERR;

      /* Check it out. */

      /* Reopen the file. */
      if (nc_open(FILE_NAME, NC_NOWRITE, &ncid)) ERR;

      if (nc_inq_user_type(ncid, typeid, name_in, &base_size_in, &base_nc_type_in,
                           &nfields_in, &class_in)) ERR;
      if (strcmp(name_in, TYPE2_NAME) ||
          base_size_in != sizeof(unsigned char) ||
          base_nc_type_in != NC_UBYTE ||
          nfields_in != num_members ||
          class_in != NC_ENUM) ERR;
      if (nc_inq_enum(ncid, typeid, name_in,
                      &base_nc_type_in, &base_size_in, &num_members_in)) ERR;
      if (strcmp(name_in, TYPE2_NAME) ||
          base_nc_type_in !=  NC_UBYTE ||
          num_members_in != num_members) ERR;
      for (i = 0; i < num_members; i++)
      {
         if (nc_inq_enum_member(ncid, typeid, i, name_in, &value_in)) ERR;
         if (strcmp(name_in, cloud_types[i].name) ||
             value_in != cloud_types[i].value) ERR;
         if (nc_inq_enum_ident(ncid, typeid, cloud_types[i].value,
                               name_in)) ERR;
         if (strcmp(name_in, cloud_types[i].name)) ERR;
      }
      if (nc_inq_varid(ncid, VAR2_NAME, &varid)) ERR;

      if (nc_get_att(ncid, varid, ATT2_NAME, &att_value_in)) ERR;
      if (att_value_in != MISSING) ERR;

      if(nc_get_var(ncid, varid, cloud_data_in)) ERR;
      for (i = 0; i < DIM2_LEN; i++) {
         if (cloud_data_in[i] != cloud_data[i]) ERR;
      }

      if (nc_close(ncid)) ERR;
   }

   SUMMARIZE_ERR;
   printf("*** testing enum interuptus...");
   {
      unsigned char ubyte_value = 42;
#define GEEKY_NAME "Galadriel"

      /* Create a file. */
      if (nc_create(FILE_NAME, NC_NETCDF4, &ncid)) ERR;

      /* Create an enum type based on unsigned bytes. */
      if (nc_def_enum(ncid, NC_UBYTE, GEEKY_NAME, &typeid)) ERR;

      /* Check it out. */
      if (nc_inq_user_type(ncid, typeid, name_in, &base_size_in, &base_nc_type_in,
                           &nfields_in, &class_in)) ERR;
      if (strcmp(name_in, GEEKY_NAME) || base_size_in != 1 ||
          base_nc_type_in != NC_UBYTE || nfields_in != 0 || class_in != NC_ENUM) ERR;
      if (nc_inq_enum(ncid, typeid, name_in, &base_nc_type, &base_size_in, &num_members)) ERR;
      if (strcmp(name_in, GEEKY_NAME) || base_nc_type != NC_UBYTE || base_size_in != 1 ||
          num_members != 0) ERR;

      /* Close the file. */
      if (nc_close(ncid) != NC_EINVAL) ERR;

      if (nc_redef(ncid)) ERR;
      if (nc_insert_enum(ncid, typeid, "name", &ubyte_value)) ERR;
      if (nc_close(ncid)) ERR;
   }
   SUMMARIZE_ERR;
   printf("*** testing enum ncdump...");
#define ENUM_NAME "cargo"
#define ENUM_VAR_NAME "in_the_hold_of_the_Irish_Rover"
#define NUM_ENUM_FIELDS 8
#define DIMSIZE 4
#define NDIMS1 1
   {
      int ncid, dimid, v1id, typeid;
      int f;
      size_t start[NDIMS1] = {0}, count[NDIMS1] = {1};
      char field_name[NUM_ENUM_FIELDS][NC_MAX_NAME + 1] = {"bags of the best Sligo rags", "barrels of bones",
                                                           "bails of old nanny goats' tails", "barrels of stones",
                                                           "dogs", "hogs", "barrels of porter",
                                                           "sides of old blind horses hides"};
      unsigned long long field_value[NUM_ENUM_FIELDS] = {1000000, 2000000, 3000000, 4000000,
                                                         5000000, 6000000, 7000000, 8000000};
      unsigned long long data = 1000000, data_in;

      /* Create a netcdf-4 file. */
      /*nc_set_log_level(3);*/
      if (nc_create(FILE_NAME, NC_NETCDF4, &ncid)) ERR;

      /* Create a dimension. */
      if (nc_def_dim(ncid, DIM_NAME, DIMSIZE, &dimid)) ERR;

      /* Create an enum type. */
      if (nc_def_enum(ncid, NC_UINT64, ENUM_NAME, &typeid)) ERR;
      for (f = 0; f < NUM_ENUM_FIELDS; f++)
         if (nc_insert_enum(ncid, typeid, field_name[f], &field_value[f])) ERR;

      /* Create one var. */
      if (nc_def_var(ncid, ENUM_VAR_NAME, typeid, NDIMS1, &dimid, &v1id)) ERR;

      /* Write metadata to file. */
      if (nc_enddef(ncid)) ERR;

      /* Write phoney data. */
      if (nc_put_vara(ncid, v1id, start, count, &data)) ERR;

      if (nc_sync(ncid)) ERR;

      /* Read phoney data. */
      if (nc_get_vara(ncid, v1id, start, count, &data_in)) ERR;
      if (data_in != data) ERR;

      /* Close the netcdf file. */
      if (nc_close(ncid)) ERR;
   }
   SUMMARIZE_ERR;
   FINAL_RESULTS;
}
