text: /******************************************************************************
   *
   * Project:  netCDF read/write Driver
   * Purpose:  GDAL bindings over netCDF library.
   * Author:   Winor Chen <wchen329 at wisc.edu>
   *
   ******************************************************************************
   * Copyright (c) 2019, Winor Chen <wchen329 at wisc.edu>
   *
   * Permission is hereby granted, free of charge, to any person obtaining a
   * copy of this software and associated documentation files (the "Software"),
   * to deal in the Software without restriction, including without limitation
   * the rights to use, copy, modify, merge, publish, distribute, sublicense,
   * and/or sell copies of the Software, and to permit persons to whom the
   * Software is furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included
   * in all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   * DEALINGS IN THE SOFTWARE.
   ****************************************************************************/
  #ifndef __NETCDFVIRTUAL_H__
  #define __NETCDFVIRTUAL_H__
  #include <map>
  #include <memory>
  #include <string>
  #include <vector>
  #include "netcdfsg.h"
  #include "netcdf.h"
  
  // netCDF Virtual
  // Provides a layer of "virtual ncID"
  // that can be mapped to a real netCDF ID
  namespace nccfdriver
  {
      // Exceptions
      class SG_Exception_NVOOB : public SG_Exception
      {
          std::string err_msg;
  
          public:
              explicit SG_Exception_NVOOB(const char* dsname) :
                  err_msg(std::string("An attempt to read an undefined ID from ") + std::string(dsname) + std::string(" was made")) {}
              const char* get_err_msg() override { return this->err_msg.c_str(); }
      };
  
      class SG_Exception_DupName: public SG_Exception
      {
          std::string err_msg;
  
          public:
              SG_Exception_DupName(const char * keyn, const char* dsname) :
                  err_msg(std::string("The key ") + std::string(keyn) + std::string(" already exists in") + std::string(dsname)) {}
              const char* get_err_msg() override { return this->err_msg.c_str(); }
      };
  
      class SG_Exception_BadMapping : public SG_Exception
      {
          std::string err_msg;
  
          public:
              SG_Exception_BadMapping(const char* key, const char* where) :
                  err_msg(std::string(key) + std::string(" not found in ") + std::string(where))
              {}
  
              const char* get_err_msg() override { return this->err_msg.c_str(); }
      };
  
      class SG_Exception_VWrite_Failure : public SG_Exception
      {
          std::string err_msg;
  
          public:
              SG_Exception_VWrite_Failure(const char* where, const char* type) :
                  err_msg(std::string("Failed to write ") + std::string(type) + std::string(" to ") + std::string(where))
              {}
  
              const char* get_err_msg() override { return this->err_msg.c_str(); }
      };
  
      /* netCDFVAttribute
       * -
       * Contains attribute name and data.
       * Central to derived types are reimplementations of vsync
       */
      class netCDFVAttribute
      {
          public:
              /* vsync(...)
               * Implementation: Given the REAL ncID and REAL variable ID
               * Write the attribute to the variable
               */
              virtual void vsync(int realncid, int realvarid) = 0;
  
              /*  ~netCDFVAttribute()
               * Virtual destructor
               */
              virtual ~netCDFVAttribute() { }
      };
  
      template <class VClass, nc_type ntype> class netCDFVGeneralAttribute : public netCDFVAttribute
      {
          std::string name;
          VClass value;
  
          public:
              netCDFVGeneralAttribute<VClass, ntype>(const char* a_name, const VClass* a_value) :
                  name(a_name),
                  value(*a_value)
              {}
  
              void vsync(int realncid, int realvarid) override
              {
                  if(nc_put_att(realncid, realvarid, name.c_str(), ntype, 1, &value) != NC_NOERR)
                  {
                      throw SG_Exception_VWrite_Failure("variable", "attribute");
                  }
              }
      };
  
      /* netCDFVTextAttribute
       * -
       * Attribute that has a text string value
       */
      class netCDFVTextAttribute : public netCDFVAttribute
      {
          std::string name;
          std::string value;
  
          public:
              netCDFVTextAttribute(const char* a_name, const char* a_value) :
                  name(a_name),
                  value(a_value)
              {}
  
              void vsync(int realncid, int realvarid) override;
      };
  
      typedef netCDFVGeneralAttribute<signed char, NC_BYTE> netCDFVByteAttribute;
      typedef netCDFVGeneralAttribute<int, NC_INT> netCDFVIntAttribute;
      typedef netCDFVGeneralAttribute<double, NC_DOUBLE> netCDFVDoubleAttribute;
      typedef netCDFVGeneralAttribute<float, NC_FLOAT> netCDFVFloatAttribute;
  
      /* netCDFVDimension
       * -
       * Contains the real dim id, real dimension name, and dimension length
       */
      class netCDFVDimension
      {
          friend class netCDFVID;
  
          std::string real_dim_name;
          int r_did = INVALID_DIM_ID;
          int v_did;
          size_t dim_len;
          bool valid = true;
  
          protected:
              void setRealID(int realID) { this->r_did = realID; }
              void invalidate();
              void setLen(size_t len) { this->dim_len = len; }
          public:
              netCDFVDimension(const char * name, size_t len, int dimid) :
                               real_dim_name(name),
                               v_did(dimid),
                               dim_len(len)
                               {}
  
              std::string& getName() { return this->real_dim_name; }
              size_t getLen() { return this->dim_len; }
              int getRealID() { return this->r_did; }
              int getVirtualID() { return this->v_did; }
              bool isValid() { return this->valid; }
      };
  
      /* netCDFVVariable
       * -
       * Contains the variable name, variable type, etc.
       */
      class netCDFVVariable
      {
          friend class netCDFVID;
  
          std::string real_var_name;
          nc_type ntype;
          int r_vid = INVALID_VAR_ID;
          int ndimc;
          std::unique_ptr<int, std::default_delete<int[]>> dimid;
          std::vector<std::shared_ptr<netCDFVAttribute>> attribs;
          bool valid = true;
  
          protected:
              std::vector<std::shared_ptr<netCDFVAttribute>>& getAttributes() { return attribs; }
              void invalidate();
              void setRealID(int realID) { this->r_vid = realID; }
          public:
              netCDFVVariable(const char * name, nc_type xtype, int ndims, const int* dimidsp);
              std::string& getName() { return real_var_name; }
              int getRealID() { return r_vid; }
              nc_type getType() { return ntype; }
              int getDimCount() { return this->ndimc; }
              const int* getDimIds() { return this->dimid.get(); }
              bool isValid() { return this->valid; }
      };
  
      /* netCDFVID
       * -
       * A netCDF ID that sits on top of an actual netCDF ID
       * And manages actual interaction with the real netCDF file
       *
       * A bit difference is that netCDFVID
       * doesn't have fixed dim sizes, until defines are committed
       *
       * Also, virtual attributes only exist until the variable is committed. Use "real" attributes and "real" IDs
       * for a variable after its been commited.
       *
       * ** Do not mix netCDF virtual dim and variable IDs with regular netCDF dim (a.k.a. "real")
       * ids and variable ids. They are NOT necessarily compatible, and must be translated first, to be
       * used in this manner **
       *
       * The netCDFVID can also be used in what is called "direct mode" and the netCDFVID will just act as a wrapper to the netCDF Library.
       * In such a case netCDFVID should take real IDs, not real ones. However, the big advantages of using netCDFVID (such as quick dim resizing)
       * are no longer are available.
       */
      class netCDFVID
      {
          int & ncid; // ncid REF. which tracks ncID changes that may be made upstream
          int dimTicket = 0;
          int varTicket = 0;
          bool directMode = true;
  
          std::vector<netCDFVVariable> varList;
          std::vector<netCDFVDimension> dimList;
  
          std::map<std::string, int> nameDimTable;
          std::map<std::string, int> nameVarTable;
  
          public:
              // Each of these returns an ID, NOT an error code
  
              /* nc_def_vdim(...)
               * Defines a virtual dim given the parameters NAME and LENGTH.
               * Returns: virtual dimID
               */
              int nc_def_vdim(const char * name, size_t dimlen); // for dims that don't already exist in netCDF file
  
              /* nc_def_vvar(...)
               * Defines a virtual var given the parameters NAME, NC TYPE, NUMBER OF DIMS, and DIM IDS
               * The dim IDs in dimidsp given are to be virtual dim IDs, using real dim IDs is undefined
               */
              int nc_def_vvar(const char * name, nc_type xtype, int ndims, const int* dimidsp);
  
              /* nc_del_vdim(...)
               * Delete a virtual dimension
               * NOTES:
               *     This doesn't work on committed IDs.
               *     Also the dimension (for now) will be only invalidated, doesn't completely *delete* it in memory.
               */
              void nc_del_vdim(int dimid);
  
              /* nc_del_vvar(...)
               * Delete a virtual variable
               * NOTES:
               *     This doesn't work on committed IDs.
               *     Also the variable (for now) will be only invalidated, doesn't completely *delete* it in memory.
               */
              void nc_del_vvar(int varid);
  
              /* nc_resize_vdim(...)
               * Change the size of a virtual dim to the given size.
               * NOTE: if the dim has committed using nc_vmap then this has no effect.
               */
              void nc_resize_vdim(int dimid, size_t dimlen); // for dims that haven't been mapped to physical yet
  
              /* nc_set_define_mode()
               * Convenience function for setting the ncid to define mode
               */
              void nc_set_define_mode();
  
              /* nc_set_data_mode()
               * Convenience function for setting the ncid to data mode
               */
              void nc_set_data_mode();
  
              /* nc_vmap()
               * Maps virtual IDs to real physical ID if that mapping doesn't already exist
               * This is required before writing data to virtual IDs that do not exist yet in the netCDF file
               */
              void nc_vmap();
  
              /* void enableFullVirtualMode
               * Enables full virtual mode (i.e. allows netCDFVID to use its full capabilities).
               */
              void enableFullVirtualMode() { this->directMode = false; }
  
              // Attribute function(s)
              template<class attrC, class attrT> void nc_put_vatt_generic(int varid, const char* name, const attrT* value)
              {
  
                  if(varid >= static_cast<int>(varList.size()) || varid < 0)
                  {
                      throw SG_Exception_NVOOB("virtual variable collection");
                  }
  
                  netCDFVVariable& v = virtualVIDToVar(varid);
                          v.getAttributes().push_back(std::shared_ptr<netCDFVAttribute>(new attrC(name, value)));
              }
  
              void nc_put_vatt_text(int varid, const char * name, const char * value);
              void nc_put_vatt_int(int varid, const char* name, const int* value);
              void nc_put_vatt_double(int varid, const char* name, const double* value);
              void nc_put_vatt_float(int varid, const char* name, const float* value);
              void nc_put_vatt_byte(int varid, const char* name, const signed char* value);
  
              // Writing Functions
              template<class out_T> void nc_put_vvar_generic(int varid, const size_t* index, const out_T* value)
              {
                  int rvarid = !directMode ? virtualVIDToVar(varid).getRealID() : varid;
  
                  if(rvarid == INVALID_VAR_ID)
                      return; // invalidated variable, specific condition that Scribe relies on
  
                  if(nc_put_var1(ncid, rvarid, index, value) != NC_NOERR)
                  {
                      throw SG_Exception_VWrite_Failure("variable", "datum");
                  }
              }
  
              template<class outArr_T> void nc_put_vvara_generic(int varid, const size_t* index, const size_t* count, const outArr_T* value)
              {
                  int rvarid = !directMode ? virtualVIDToVar(varid).getRealID() : varid;
  
                  if (rvarid == INVALID_VAR_ID)
                      return; // invalidated variable, specific condition that Scribe relies on
  
                  if (nc_put_vara(ncid, rvarid, index, count, value) != NC_NOERR)
                  {
                      throw SG_Exception_VWrite_Failure("variable", "data array");
                  }
              }
  
              void nc_put_vvar1_text(int varid, const size_t* index, const char* value);
              void nc_put_vvara_text(int varid, const size_t* start, const size_t* index, const char* value);
  
  #ifdef NETCDF_HAS_NC4
              void nc_put_vvar1_string(int varid, const size_t* index, const char** value);
  #endif
  
              // Equivalent "enquiry" functions
              netCDFVVariable& virtualVIDToVar(int virtualID); // converts a virtual var ID to a real ID
              netCDFVDimension& virtualDIDToDim(int virtualID); // converts a virtual dim ID to a real ID
              int nameToVirtualVID(std::string& name);
              int nameToVirtualDID(std::string& name);
              bool virtualVarNameDefined(std::string& nm) { return nameVarTable.count(nm) > 0; }
  
              // Constructor
              explicit netCDFVID(int & ncid_in) : ncid(ncid_in) {}
          };
  
  }
  #endif
  