/***************************** LICENSE START ***********************************

 Copyright 2019 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END
 *************************************/

/* -*- C -*-
   Metview class interface to NetCDF.
*/

#pragma once

#ifdef AIX
/* There is a name clash with xlc own include files */
#undef name2
#undef implement
#undef declare
#endif

#include <iostream>

#include "Metview.h"
#include "netcdf.h"

// A note about NETCDF_MISSING_VALUE: this is currently set to
// mars.grib_missing_value so that we can efficiently copy netCDF values
// directly to a vector without having to do any conversion on the missing
// values. Some macro functions may make this assumption; therefore, if
// NETCDF_MISSING_VALUE is changed, then we would
// need to revise these functions so that they consider missing values
// more carefully.

#define NETCDF_MISSING_VALUE mars.grib_missing_value

// Definitions to help the translation from C++ to C interface
//------------------------------------------------------------
#define ncByte NC_BYTE
#define ncChar NC_CHAR      // 2
#define ncShort NC_SHORT    // 3
#define ncInt NC_INT        // 4
#define ncLong NC_LONG      //
#define ncFloat NC_FLOAT    // 5
#define ncDouble NC_DOUBLE  // 6
#define ncUShort NC_USHORT  // 8
#define ncUInt NC_UINT      // 9
#define ncInt64 NC_INT64    // 10
//------------------------------------------------------------

typedef int NcBool;
typedef nc_type NcType;
typedef signed char ncbyte;
typedef unsigned short ushort;

enum
{
    D_ISNUMBER = 1,
    D_ISSTRING = 2,
    D_ISANY = 3
};
// F enum { CDF_FILL = NcFile::Fill,CDF_NOFILL = NcFile::NoFill};
enum
{
    CDF_FILL = NC_FILL,
    CDF_NOFILL = NC_NOFILL
};

typedef std::map<std::string, int> CountMap;

// Wrapper to handle netcdf files.
// Only done to get global "variable" for the global attributes.
// F class MvNcFile : public NcFile
class MvNcFile
{
public:
    // Constructor
    // F   MvNcFile(const std::string &path,FileMode xx = ReadOnly) :
    // F            NcFile(path.c_str(),xx) {}
    // Constructor
    // "mode = 0" means NC_NOWRITE for opening or NC_CLOBBER for creating
    MvNcFile(const std::string&, int mode = 0);

    // Destructor
    virtual ~MvNcFile() {}

    // Functions members
    // F   NcVar *globalVariable() { return globalv; }

    int ncId() const { return ncId_; }

    bool isValid() const { return ncStatus_ == NC_NOERR; }
    int status() const { return ncStatus_; }

    // Handle file mode
    bool dataMode();
    bool defineMode();

    int sync() const { return nc_sync(ncId_); }

    int close() const { return nc_close(ncId_); }

    int getFillMode();
    int setFillMode(int);

    // Variables
    int ncId_;            // file id
    int ncStatus_;        // file status
    int in_define_mode_;  // 0 : data mode; 1: define mode
};

//--------------------------------------------------------------
// Class MvNcDim
// Wrapper for NcDim
//--------------------------------------------------------------
class MvNcDim
{
public:
    // Constructos/Destructors
    MvNcDim(int, int, const std::string&, int);
    ~MvNcDim() = default;

    // Class functions
    int ncId() { return ncId_; }

    int id() const { return dimId_; }

    int size() { return size_; }

    const char* name() { return name_.c_str(); }
    std::string sname() { return name_; }

    bool isUnlimited() const;

private:
    int ncId_;          // netcdf id
    int dimId_;         // dimension id
    int size_;          // dimension length
    std::string name_;  // dimension name
};

//--------------------------------------------------------------
// Class MvNcValues
// Wrapper for NcValues
//--------------------------------------------------------------
#if 0
class MvNcValues
{
public:
  void *base() { return ncValues_->base(); }
  int getNumberOfValues() { return ncValues_->num(); }
  char   as_char  ( long n ) const { return  ncValues_->as_char(n);   }
  short  as_short ( long n ) const { return  ncValues_->as_short(n);  }
  long   as_long  ( long n ) const { return  ncValues_->as_long(n);   }
  float  as_float ( long n ) const { return  ncValues_->as_float(n);  }
  double as_double( long n ) const { return  ncValues_->as_double(n); }
  char*  as_string( long n ) const { return  ncValues_->as_string(n); }

private:
  friend class MvNcVar;
  friend class MvNcAtt;
  MvNcValues(NcValues *values) : ncValues_(values) {}
  ~MvNcValues() { delete ncValues_; }
  NcValues *ncValues_;
};
#else
class MvNcVar;
class MvNcAtt;

class MvNcValues
{
public:
    // Constructors
    MvNcValues(MvNcVar*);
    MvNcValues(MvNcAtt*);

    // Destructor
    ~MvNcValues();

    // Functions members
    void* base() { return values_; }
    int getNumberOfValues() { return nvalues_; }

    char as_char(long n) const
    {
        return vchar_[n];
    }

    short as_short(long) const;
    ushort as_ushort(long) const;
    int as_int(long) const;
    unsigned int as_uint(long) const;
    long as_int64(long n) const;
    float as_float(long) const;
    double as_double(long) const;
    std::string as_string(long) const;

    int as_long(long n) const
    {
        return as_int(n);
    }

private:
    friend class MvNcVar;
    friend class MvNcAtt;

    int allocateMemory();
    int getValuesAtt();
    int getValuesVar();

    int ncStatus_;
    int type_;
    int ncId_;
    int id_;
    std::string name_;

    size_t nvalues_;
    void* values_;
    char* vchar_;
    signed char* vschar_;
    short* vshort_;
    ushort* vushort_;
    int* vint_;
    unsigned int* vuint_;
    long* vint64_;
    //   long* vlong_;
    float* vfloat_;
    double* vdouble_;
    std::string* vstring_;
};
#endif


//--------------------------------------------------------------
// Class MvNcBase
// Abstract class for common attribute/variable operations
//--------------------------------------------------------------
class MvNcBase
{
public:
    // Constructors
    MvNcBase();
    MvNcBase(int, int, const std::string&, nc_type);

    // Destructors
    ~MvNcBase() {}

    // General
    // F  const char *name() { return delegate()->name(); }
    // F  NcType type() { return delegate()->type(); }
    // F  virtual NcBool isValid() { return delegate()->is_valid(); }
    std::string sname() const { return name_; }
    const char* name() const { return name_.c_str(); }
    nc_type type() const { return type_; }
    int ncId() const { return ncId_; }
    int id() const { return id_; }
    virtual bool isValid()
    {
        return ncStatus_ == NC_NOERR;
    }

    virtual int status()
    {
        return ncStatus_;
    }

    int getNumberOfValues() { return values()->getNumberOfValues(); }
    char as_char(long n) { return values()->as_char(n); }
    short as_short(long n) { return values()->as_short(n); }
    long as_long(long n) { return values()->as_long(n); }
    float as_float(long n) { return values()->as_float(n); }
    double as_double(long n) { return processValue(values()->as_double(n)); }
    // char*  as_string( long n ) { return values()->as_string(n); }
    std::string as_string(long n) { return values()->as_string(n); }
    MvDate as_date(long n) { return processDate(values()->as_double(n)); }

    // template <class T>
    // int get(std::vector<T> &vec) { return values()->get(vec); }

    virtual MvNcValues* values() = 0;
    // F  virtual NcTypedComponent *delegate() = 0;

protected:
    bool hasMissingValueIndicator_;
    double missingValueIndicator_;
    double scaleFactor_;
    double addOffset_;

    int ncId_;
    int id_;
    std::string name_;
    nc_type type_;
    int ncStatus_;

    virtual double processValue(double val) = 0;
    virtual MvDate processDate(double val) = 0;

    bool hasScaling() { return (scaleFactor_ != 1 || addOffset_ != 0); }
};

// ---------------------------------------------------------------------
// Class MvNcAtt
// Wrapper for NcAtt
// ---------------------------------------------------------------------

class MvNcAtt : public MvNcBase
{
public:
    virtual ~MvNcAtt();
    // F  NcTypedComponent *delegate() { return ncAtt_; }

    // Values
    MvNcValues* values() { return values_; }
    NcBool getValues(std::string&);
    std::string as_string(long);

protected:
    double processValue(double val) { return val; }          // dummy function
    MvDate processDate(double /*val*/) { return MvDate(); }  // dummy function

private:
    friend class MvNcVar;

    // Constructors
    // F  MvNcAtt(NcAtt *ncAtt);
    MvNcAtt(int, int, const std::string&);
    MvNcAtt(const MvNcAtt& aa);

    void tztrim(char*);
    void printValue(std::string&, double);

    // F  NcAtt *ncAtt_;
    MvNcValues* values_;
};

// ---------------------------------------------------------------------
// Class MvNetCDFBehaviour
// Class to encapsulate the behavioural options of our netCDF interface,
// such as whether we treat missing values specially
// ---------------------------------------------------------------------

class MvNetCDFBehaviour
{
public:
    MvNetCDFBehaviour();
    ~MvNetCDFBehaviour() = default;

    void detectMissingValues(bool d) { detectMissingValues_ = d; }
    bool detectMissingValues() { return detectMissingValues_; }

    void scaleValues(bool s) { scaleValues_ = s; }
    bool scaleValues() { return scaleValues_; }

    void missingValuesAttribute(std::string a) { missingValueAttribute_ = a; }
    std::string missingValuesAttribute() { return missingValueAttribute_; }

    void rescaleToFit(bool r) { rescaleToFit_ = r; }
    bool rescaleToFit() { return rescaleToFit_; }

    void translateTime(bool t) { translateTime_ = t; }
    bool translateTime() { return translateTime_; }

private:
    bool detectMissingValues_;
    bool scaleValues_;
    bool rescaleToFit_;
    bool translateTime_;
    std::string missingValueAttribute_;
};

// ---------------------------------------------------------------------
// Class MvNcVar
// Wrapper for NcVar
// ---------------------------------------------------------------------

class MvNetCDF;  // forward declaration

class MvNcVar : public MvNcBase
{
public:
    virtual ~MvNcVar();

    // F  NcTypedComponent *delegate() {return ncVar_; }

#if 0  // F
  NcBool isValid() {
    if (isGlobal_ ) return true;
    else return delegate()->is_valid(); 
  }
#else
    bool isValid()
    {
        return (isGlobal_ ? true : MvNcBase::isValid());
    }
#endif

    void storeFillValue();
    void storeScaleFactorAndOffset();
    void storeTimeInformation();

    void getStringType(std::string&);
    bool isTime()
    {
        return isTime_;
    }

    MvNetCDFBehaviour& options();

    void copyMissingValueAttributeIfNeededFrom(MvNcVar* from);

    // it would be nice to allow this function for all types, but in reality
    // we only perform computations with doubles, so we can save the expense of
    // the template; also, our choice of the missing value indicator for
    // the unpacked values, NETCDF_MISSING_VALUE, is specific to
    // 'double' (the data type we unpack to, not the type we store in the netCDF
    // file), and it would be impossible to have one for all data types (e.g. what
    // would we use for char?).
    // template <class T>
    // T processValue(T val)
    double processValue(double val);

    MvDate processDate(double val);

    // Attributes
    // F  int getNumberOfAttributes() { return ncVar_->num_atts(); }
    int getNumberOfAttributes();

    MvNcAtt* getAttribute(const std::string&);
    MvNcAtt* getAttribute(unsigned int index);

    template <class T>
    bool getAttributeValues(const std::string& name, std::vector<T>& vec)
    {
        if (!isValid())
            return false;
        for (unsigned int i = 0; i < attributes_.size(); i++)
            if (name == attributes_[i]->name())
                return getAttributeValues(attributes_[i], vec);

        return false;
    }

    template <class T>
    bool getAttributeValues(unsigned int index, std::vector<T>& vec)
    {
        if (!isValid())
            return false;

        if (index <= (attributes_.size() - 1))
            return getAttributeValues(attributes_[index], vec);

        return false;
    }

    // Two following functions inlined because of gcc template
    // instantiation problems. Should really be in cc file.
    template <class T>
    bool addAttribute(const std::string& name, T value)
    {
        if (!isValid())
            return false;

        if (attributeExists(name))
            return true;

        // Add attribute to the netcdf file
        // F     NcBool ret_code = ncVar_->add_att(name.c_str(),value);
        if (!put_att(name.c_str(), value))
            return false;

        // Add attribute to an internal structure
        // F        attributes_.push_back( new
        // MvNcAtt(ncVar_->get_att(attributes_.size() ) ) );
        MvNcAtt* attr = new MvNcAtt(ncId_, id_, name);
        attributes_.push_back(attr);

        return true;
    }

    template <class T>
    bool addAttribute(const std::string& name, int nr, T* values, NcType attrType)
    {
        if (!isValid())
            return false;

        if (attributeExists(name)) {
            return true;
        }

        // Add attribute to the netcdf file
        int ret_code = nc_put_att(ncId_, id_, name.c_str(), attrType, nr, values);
        if (ret_code == NC_NOERR) {
            MvNcAtt* attr = new MvNcAtt(ncId_, id_, name);
            attributes_.push_back(attr);
        }

        return ret_code == NC_NOERR;
    }

    bool addAttribute(MvNcAtt* att);

    bool put_att(const std::string&, const char*);
    bool put_att(const std::string&, const short);
    bool put_att(const std::string&, const int);
    bool put_att(const std::string&, const long);
    bool put_att(const std::string&, const float);
    bool put_att(const std::string&, const double);

    NcBool attributeExists(const std::string& name);

    // Dimensions
    // F  int   getNumberOfDimensions() { return ncVar_->num_dims(); }
    int getNumberOfDimensions();
    // F  NcDim *getDimension(int index){ return ncVar_->get_dim(index); }
    MvNcDim* getDimension(int);
    int getDimension(long&);

    long* edges();

    long numValsFromCounts(const long* counts)
    {
        long num_values = 1;
        int ndim = getNumberOfDimensions();

        for (int i = 0; i < ndim; i++)
            num_values *= counts[i];

        return num_values;
    }

    // Values
    template <class T>
    int get(std::vector<T>& vals, const long* counts, long nvals1 = 0L)
    {
        if (!isValid())
            return false;

        long num_values = 1;
        long nvals = nvals1;
        int ndim = getNumberOfDimensions();
        int i;

        vals.erase(vals.begin(), vals.end());
        if (ndim > 0) {
            for (i = 0; i < ndim; i++)
                num_values *= counts[i];

            if (nvals > 0 && nvals < num_values) {
                size_t* len = new size_t[ndim];
                for (i = 0; i < ndim; i++)
                    len[i] = 1;

                long np = 1;
                for (i = ndim - 1; i >= 0; i--) {
                    if (counts[i] >= nvals) {
                        len[i] = nvals;
                        np *= nvals;
                        break;
                    }
                    else {
                        len[i] = counts[i];
                        nvals = (nvals / counts[i]) + 1;
                        np *= len[i];
                    }
                }

                vals.resize(np);
                // F ret_val = ncVar_->get(&vals.front(),len);
                ncStatus_ = get_vara(vals, len);
            }
            else {
                vals.resize(num_values);
                // F ret_val = ncVar_->get(&vals.front(),counts);
                ncStatus_ = get_vara(vals, (size_t*)counts);
            }
        }
        else {  // Scalar
            T* scalarval = (T*)values()->base();
            if (scalarval)
                vals.push_back(scalarval[0]);
        }

        // Handle missing values?
        // We replace values which are, for example, equal to _FillValue
        // with our own missing value indicator so that the calling
        // routines can use them more easily.
        // This conversion also allows us to scale the original data
        // properly (the _FillValue is in the range of packed values,
        // not scaled values).
        // Also check whether to apply a scaling factor.
        if (ncStatus_ == NC_NOERR) {
            if ((hasMissingValueIndicator_ && options().detectMissingValues()) ||
                (hasScaling() && options().scaleValues())) {
                for (size_t n = 0; n < vals.size(); n++)
                    vals[n] = processValue(vals[n]);
            }
        }

        return ncStatus_;
    }

    template <class T>
    int get(std::vector<T>& vals, long c0 = 0, long c1 = 0, long c2 = 0,
            long c3 = 0, long c4 = 0)
    {
        // Save input dimensions
        size_t counts[5];
        counts[0] = (size_t)c0;
        counts[1] = (size_t)c1;
        counts[2] = (size_t)c2;
        counts[3] = (size_t)c3;
        counts[4] = (size_t)c4;

        // Reserve memory
        int len = 1;
        for (int i = 0; i < 5; i++) {
            if (counts[i] == 0)
                break;
            len *= counts[i];
        }
        vals.resize(len);

        // Save values
        return get_vara(vals, counts);
    }

    int get(std::vector<double>& vals)
    {
        return nc_get_var_double(ncId_, id_, &vals.front());
    }

    int get_vara(std::vector<char>& vals, size_t* counts)
    {
        return nc_get_vara_text(ncId_, id_, the_cur_, counts, (char*)&vals.front());
    }

    int get_vara(std::vector<signed char>& vals, size_t* counts)
    {
        return nc_get_vara_schar(ncId_, id_, the_cur_, counts, (signed char*)&vals.front());
    }

    int get_vara(std::vector<short>& vals, size_t* counts)
    {
        return nc_get_vara_short(ncId_, id_, the_cur_, counts, (short*)&vals.front());
    }

    int get_vara(std::vector<int>& vals, size_t* counts)
    {
        return nc_get_vara_int(ncId_, id_, the_cur_, counts, (int*)&vals.front());
    }

    int get_vara(std::vector<float>& vals, size_t* counts)
    {
        return nc_get_vara_float(ncId_, id_, the_cur_, counts, (float*)&vals.front());
    }

    int get_vara(std::vector<double>& vals, size_t* counts)
    {
        return nc_get_vara_double(ncId_, id_, the_cur_, counts, (double*)&vals.front());
    }

    /*
   template <class T>
   int get_vara1(std::vector<T> &vals, size_t *counts)
   {
for (int i = 0; i < getNumberOfDimensions(); i++)
std::cout << the_cur_[i] << " " << counts[i] << std::endl;
ncStatus_=nc_get_var_double(ncId_,id_,(double*)&vals.front());
      switch(type_)
      {
         case NC_DOUBLE:
            return ncStatus_ = nc_get_vara_double(ncId_,id_,the_cur_,counts,(double*)&vals.front());
         case NC_FLOAT:
            return ncStatus_ = nc_get_vara_float(ncId_,id_,the_cur_,counts,(float*)&vals.front());
          case NC_INT:
            return ncStatus_ = nc_get_vara_int(ncId_,id_,the_cur_,counts,(int*)&vals.front());
          case NC_SHORT:
            return ncStatus_ = nc_get_vara_short(ncId_,id_,the_cur_,counts,(short*)&vals.front());
          case NC_BYTE:
            return ncStatus_ = nc_get_vara_schar(ncId_,id_,the_cur_,counts,(signed char*)&vals.front());
          case NC_CHAR:
             return ncStatus_ = nc_get_vara_text(ncId_,id_,the_cur_,counts,(char*)&vals.front());
          default:
             return !NC_NOERR;
      }

      return ncStatus_;
   }
*/

    // Values as dates
    NcBool getDates(std::vector<MvDate>&, const long*, long nvals1 = 0L);

    MvNcValues* values()
    {
        checkValues();
        return values_;
    }

    // Records
    bool setCurrent(long c0 = -1, long c1 = -1, long c2 = -1, long c3 = -1, long c4 = -1);

    bool setCurrent(long*);

    // Handle any processing that needs to be done before encoding the
    // values in netCDF, e.g. handle missing values and scaling factors
    template <class T>
    void packValues(T* vals, const long* counts)
    {
        bool isInt = isIntegerType();
        bool doScale = hasScaling() && options().scaleValues();
        bool doMissing =
            hasMissingValueIndicator_ && options().detectMissingValues();
        if (doMissing || doScale) {
            long n = numValsFromCounts(counts);

            if (doScale)
                recomputeScalingIfNecessary(vals, n);

            for (long i = 0; i < n; i++) {
                // replace our internal missing value indicator with the
                // variable-specific one
                if (doMissing && (vals[i] == NETCDF_MISSING_VALUE))
                    vals[i] = missingValueIndicator_;
                else if (doScale) {
                    vals[i] = (vals[i] - addOffset_) /
                              scaleFactor_;  // unscale our (non-missing) values

                    if (isInt)  // if this is an integer type then round to nearest value
                    {
                        if (vals[i] >= 0)
                            vals[i] = (long)(vals[i] + 0.499);
                        else
                            vals[i] = (long)(vals[i] - 0.499);
                    }
                }
            }
        }
    }

    template <class T>
    void recomputeScalingIfNecessary(T* vals, long n);

    bool putAttributeWithType(const std::string&, NcType, double);

#if 0
  template <class T> NcBool put(const T *vals, const long *counts) {
      return ncVar_->put(vals,counts);
  }
#else
    //   int putv(std::vector<short>& vals)
    //      { return nc_put_var_short(ncId_,id_,&vals.front()); }
    //   int putv(std::vector<double>& vals)
    //      { return nc_put_var_double(ncId_,id_,&vals.front()); }
    int putv(const char* vals)
    {
        return nc_put_var_text(ncId_, id_, vals);
    }
    int putv(const int* vals)
    {
        return nc_put_var_int(ncId_, id_, vals);
    }
    int putv(const short* vals)
    {
        return nc_put_var_short(ncId_, id_, vals);
    }
    int putv(const ushort* vals)
    {
        return nc_put_var_ushort(ncId_, id_, vals);
    }
    int putv(const float* vals)
    {
        return nc_put_var_float(ncId_, id_, vals);
    }
    int putv(const double* vals)
    {
        return nc_put_var_double(ncId_, id_, vals);
    }
#endif

    template <class T>
    NcBool packAndPut(T* vals, const long* counts)
    {
        packValues(vals, counts);
        // F    return ncVar_->put(vals, counts);
        bool ret = put_vara(vals, (size_t*)counts);
        return ret;
    }

    template <class T>
    bool put(const T* vals, long c0 = 0, long c1 = 0, long c2 = 0,
             long c3 = 0, long c4 = 0)
    {
        // F     return ncVar_->put(vals,c0,c1,c2,c3,c4);
        if (c0 == 0 && c1 == 0 && c2 == 0 && c3 == 0 && c4 == 0)
            return putv(vals);
        else {
            // Save input dimensions
            size_t counts[5];
            counts[0] = (size_t)c0;
            counts[1] = (size_t)c1;
            counts[2] = (size_t)c2;
            counts[3] = (size_t)c3;
            counts[4] = (size_t)c4;

            // Save values
            return put_vara(vals, counts);
        }

        return true;
    }

    bool put_vara(const char*, size_t*);
    bool put_vara(const short*, size_t*);
    bool put_vara(const ushort*, size_t*);
    bool put_vara(const int*, size_t*);
    bool put_vara(const float*, size_t*);
    bool put_vara(const double*, size_t*);

    template <class T>
    bool put(const std::vector<T>& vecvals, long c0 = 0, long c1 = 0,
             long c2 = 0, long c3 = 0, long c4 = 0)
    {
        T* vals = new T[vecvals.size()];
        for (typename std::vector<T>::size_type i = 0; i < vecvals.size(); i++)
            vals[i] = vecvals[i];

        // F bool retVal = ncVar_->put(vals,c0,c1,c2,c3,c4);
        // ncStatus_ = nc_put_var(ncId_, id_, vals);
        bool ret = put(vals, c0, c1, c2, c3, c4);
        delete[] vals;
        return ret;
    }

    int put(MvNcVar* var);

private:
    friend class MvNetCDF;

    // Constructors
    // F  MvNcVar(NcVar *ncvar, bool is_global, MvNetCDF *parent);
    MvNcVar(int, const std::string&, nc_type, bool, MvNetCDF*);  // F
    MvNcVar(const MvNcVar&);

    // Handle attributes

    // Initialize attributes structure from the netcdf file
    void fillAttributes();

    bool getAttributeValues(MvNcAtt*, std::vector<std::string>&);
    bool getAttributeValues(MvNcAtt*, std::vector<double>&);
    bool getAttributeValues(MvNcAtt*, std::vector<long>&);

    bool parseDate(const std::string& dateAndTimeString, MvDate& date);

    bool isIntegerType();

    void checkValues()
    {
        if (!values_)
            // F        values_ = new MvNcValues(ncVar_->values());
            values_ = new MvNcValues(this);
    }

    template <class T>
    NcBool put_rec(const T* vals, long i = -1)
    {
        // F      if ( i >= 0 ) return ncVar_->put_rec(vals,i);
        // F      else return ncVar_->put_rec(vals);
        if (i >= 0) {
            std::cout << "MvNetCDF ERROR: IMPLEMENT THIS FUNCTION" << std::endl;
            // return ncVar_->put_rec(vals,i);
            return 0;
        }
        else
            return nc_put_var(vals);
    }

    // Variables
    long* edges_;
    size_t* the_cur_;
    std::vector<MvNcAtt*> attributes_;
    MvNcValues* values_;
    bool isGlobal_;
    MvNetCDF* parent_;
    bool isTime_;
    MvDate refDate_;
    double timeScaleFactor_;

#if 0  // F
  NcVar *ncVar_;
#else
    //  int varType_;
    //  std::string varName_;

#endif

#define NC_TYPES 11
    struct nc_types_values
    {
        double nc_type_max;
        double nc_type_min;
        double nc_type_missing;
    };

    static nc_types_values nc_type_values_[NC_TYPES];
};

//--------------------------------------------------------------
// Class MvNetCDF
//--------------------------------------------------------------

class MvNetCDF
{
public:
    // Constructors
    MvNetCDF();
    MvNetCDF(const std::string&, const char mode = 'r');  // from file name
    MvNetCDF(const std::string&, int mode);               // e.g. NC_NOWRITE
    MvNetCDF(const MvRequest&, const char mode = 'r');    // from request

    // Destructor
    virtual ~MvNetCDF();

    // Functions
    bool isValid()
    //     { return ncFile_->is_valid(); }  // Check if file is OK
    {
        return ncStatus_ == NC_NOERR;
    }  // Check if file is OK

    int status()
    {
        return ncStatus_;
    }

    int ncId() { return ncFile_->ncId(); }

    // Handle file mode
    bool dataMode();
    int defineMode();

    static MvNetCDFBehaviour& options() { return options_; }

    // Create MvRequest.
    MvRequest getRequest();
    // Write file from MvRequest

    // Variables
    int getNumberOfVariables() { return variables_.size(); }

    MvNcVar* getVariable(const std::string&);
    MvNcVar* getVariable(int index)
    {
        if (index == -1)
            return globalVar_;
        else
            return variables_[index];
    }

    MvNcVar* getGlobalVariable() { return globalVar_; }

    // F  MvNcVar *addVariable(const std::string & name,NcType type, int size, const
    // NcDim **dim);
    MvNcVar* addVariable(const std::string& name, int type, int size,
                         const MvNcDim** dim);

    // Will create a dimension of same name, with value dim, and
    // use this when adding the variable. Used for normal one-dim variables where
    // dimsize2 = 0, or for strings, where dimsize2 is length of string.
    MvNcVar* addVariable(const std::string& name, int type, long dimsize0,
                         long dimsize1 = -1, long dimsize2 = -1,
                         long dimsize3 = -1, long dimsize4 = -1);

    MvNcVar* addVariable(const std::string& name, int type, std::vector<long>& dimsize,
                         std::vector<std::string>& vname);

    template <class T>
    MvNcVar* addVariableWithData(const std::string& name, int type, std::vector<T>& vec)
    {
        MvNcVar* currVar = addVariable(name, type, vec.size());
        if (!currVar)
            return 0;

        // Add the data
        T* array = new T[vec.size()];
        for (int i = 0; i < vec.size(); i++)
            array[i] = vec[i];

        currVar->put(array, currVar->edges());
        delete[] array;

        return currVar;
    }

    template <class T>
    NcBool getDataForVariable(std::vector<T>& vec, const std::string& name,
                              long* given_edges = 0)
    {
        if (!isValid())
            return false;

        long* edges;
        MvNcVar* var = getVariable(name);
        if (!var)
            return false;

        if (!given_edges)
            edges = var->edges();
        else
            edges = given_edges;

        return var->get(vec, edges);
    }

    NcType getTypeForVariable(const std::string&);
    void getStringTypeForVariable(const std::string&, std::string&);

    //-------------------- Dimensions ---------------------------
    // F  int getNumberOfDimensions() { return ncFile_->num_dims(); }
    int getNumberOfDimensions();

    // F  NcDim *getDimension(const std::string &name) { return
    // ncFile_->get_dim(name.c_str() ); }
    MvNcDim* getDimension(const std::string&);

    // F  NcDim *getDimension(int index) { return ncFile_->get_dim(index); }
    MvNcDim* getDimension(int);

    // F  NcDim *addDimension(const std::string&,long s=0);
    MvNcDim* addDimension(const std::string&, long s = 0);
    NcBool dimensionExists(const std::string&);


    //-------------------- Attributes ---------------------------
    // F  int getNumberOfAttributes() { return ncFile_->num_atts(); }
    int getNumberOfAttributes();

    MvNcAtt* getAttribute(int i) { return globalVar_->getAttribute(i); }

    MvNcAtt* getAttribute(const std::string& name)
    {
        return globalVar_->getAttribute(name);
    }

    template <class T>
    bool getAttributeValues(const std::string& name, std::vector<T>& vec)
    {
        return globalVar_->getAttributeValues(name, vec);
    }

    template <class T>
    bool getAttributeValues(unsigned int index, std::vector<T>& vec)
    {
        return globalVar_->getAttributeValues(index, vec);
    }

    template <class T>
    bool addAttribute(const std::string& name, T val)
    {
        return globalVar_->addAttribute(name, val);
    }

    template <class T>
    bool addAttribute(const std::string& name, int n, const T* val)
    {
        return globalVar_->addAttribute(name, n, val);
    }

    bool addAttribute(MvNcAtt* att) { return globalVar_->addAttribute(att); }

    NcBool attributeExists(const std::string& name)
    {
        return globalVar_->attributeExists(name);
    }

    // Instantiate global attributes
    int globalVariable();

    // File operations
    const std::string& path() { return path_; }
    int sync() { return ncFile_->sync(); }
    int close() { return (ncFile_) ? ncFile_->close() : 0; }

    int getFillMode() { return ncFile_->getFillMode(); }

    int setFillMode(int mode = NC_FILL) { return ncFile_->setFillMode(mode); }

    // Initialize/create netcdf file
    void init(const std::string& path, const char mode = 'r');
    void init(const std::string& path, int mode = NC_NOWRITE);

private:
    void fillVariables();
    NcBool variableExists(const std::string& name);

    // Used to fill in the request and check it against predefined values.
    void reqGetDimensions(MvRequest&);
    void reqGetVariables(MvRequest&);
    void reqGetAttributes(MvRequest&);

    // Variables
    int ncStatus_;
    MvNcFile* ncFile_;
    std::string path_;
    std::vector<MvNcVar*> variables_;
    MvNcVar* globalVar_;

    // To find out if more than one MvNetCDF accesses the same NetCDF file.
    // ncFile_ only deleted if mapCount_[path_] == 0.
    static CountMap countMap_;

    // The behaviour options should be global
    static MvNetCDFBehaviour options_;
};

// Define specialization
template <>
NcBool MvNcVar::get(std::vector<Cached>& vals, const long* counts, long nvals);
