/***************************** 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
 *************************************/

#include "MvNetCDF.h"

#include <iomanip>
#include <sstream>

#include <assert.h>

#include "Tokenizer.h"

// Decimal precision to convert float/double number to string
#define MV_NC_PRECISION 17

// Static member, contains the currently open NetCDF files
CountMap MvNetCDF::countMap_;

// Static member, contains the behaviour options for NetCDF handling
MvNetCDFBehaviour MvNetCDF::options_;

//-----------------------------------------------------------
//  Class MvNcFile
//-----------------------------------------------------------

// Constructor
MvNcFile::MvNcFile(const std::string& path, int mode)
{
    // mode options:
    // a) open: NC_NOWRITE(0)/NC_WRITE/NC_SHARE/NC_WRITE|NC_SHARE
    // b) create:
    // NC_CLOBBER(0)/NC_NOCLOBBER/NC_SHARE/NC_64BIT_OFFSET/NC_64BIT_DATA/NC_NETCDF4/NC_CLASSIC_MODEL/NC_DISKLESS

    // First try to open the file
    // If it does not exist then try to create it
    in_define_mode_ = 0;
    ncStatus_ = nc_open(path.c_str(), mode, &ncId_);
    if (ncStatus_ != NC_NOERR) {
        ncStatus_ = nc_create(path.c_str(), mode, &ncId_);
        in_define_mode_ = 1;
    }
}

bool MvNcFile::defineMode()
{
    if (!isValid())
        return false;
    if (in_define_mode_)
        return true;
    if ((ncStatus_ = nc_redef(ncId_)) != NC_NOERR)
        return false;

    in_define_mode_ = 1;
    return true;
}

bool MvNcFile::dataMode()
{
    if (!isValid())
        return false;
    if (!in_define_mode_)
        return true;
    if ((ncStatus_ = nc_enddef(ncId_)) != NC_NOERR)
        return false;

    in_define_mode_ = 0;
    return true;
}

// FAMI20190222 Function nc_get_fill does not exist in the netcdf library.
// The alternative solution is call nc_set_fill twice.
int MvNcFile::getFillMode()
{
    int mode, old_mode;
    if ((ncStatus_ = nc_set_fill(ncId_, NC_FILL, &mode)) != NC_NOERR)
        return -1;

    if ((ncStatus_ = nc_set_fill(ncId_, mode, &old_mode)) != NC_NOERR)
        return -1;

    return mode;
}

int MvNcFile::setFillMode(int mode)
{
    int old_mode;
    ncStatus_ = nc_set_fill(ncId_, mode, &old_mode);
    return ncStatus_;
}

//-------------------------------------------------------------
//  class MvNcValues
//-------------------------------------------------------------

// Constructor
MvNcValues::MvNcValues(MvNcVar* nc) :
    vchar_(0),
    vshort_(0),
    vushort_(0),
    vint_(0),  // vlong_(0),
    vfloat_(0),
    vdouble_(0),
    vstring_(0)
{
    // Consistency check
    if (nc == 0) {
        ncStatus_ = NC_ENOTVAR;  // variable not found
        return;
    }

    // Instantiate a new object
    ncId_ = nc->ncId();
    id_ = nc->id();
    name_ = nc->name();
    type_ = nc->type();

    // Get dimension lenght
    long nval;
    if ((ncStatus_ = nc->getDimension(nval)) != NC_NOERR)
        return;

    // Get values
    nvalues_ = nval;
    if ((ncStatus_ = getValuesVar()) != NC_NOERR)
        return;

    return;
}

MvNcValues::MvNcValues(MvNcAtt* nc) :
    vchar_(0),
    vshort_(0),
    vushort_(0),
    vint_(0),  // vlong_(0),
    vfloat_(0),
    vdouble_(0),
    vstring_(0)
{
    // Consistency check
    if (nc == 0) {
        ncStatus_ = NC_ENOTATT;  // attribute not found
        return;
    }

    // Instantiate a new object
    ncId_ = nc->ncId();
    id_ = nc->id();
    name_ = nc->name();
    type_ = nc->type();

    // Get attribute lenght
    if ((ncStatus_ = nc_inq_attlen(ncId_, id_, name_.c_str(), &nvalues_)) !=
        NC_NOERR)
        return;

    // Get values
    if ((ncStatus_ = getValuesAtt()) != NC_NOERR)
        return;

    return;
}

// Destructor
MvNcValues::~MvNcValues()
{
    switch (type_) {
        case NC_CHAR:
            delete[](char*) values_;
            break;
        case NC_BYTE:
            delete[](signed char*) values_;
            break;
        case NC_SHORT:
            delete[](short*) values_;
            break;
        case NC_USHORT:
            delete[](ushort*) values_;
            break;
        case NC_INT:
            delete[](int*) values_;
            break;
        case NC_UINT:
            delete[](unsigned int*) values_;
            break;
        case NC_INT64:
            delete[](long*) values_;
            break;
        case NC_FLOAT:
            delete[](float*) values_;
            break;
        case NC_DOUBLE:
            delete[](double*) values_;
            break;
        case NC_STRING:
            delete[](std::string*) values_;
            break;
    }
}

std::string MvNcValues::as_string(long n) const  // NC_CHAR 2
{
    if (vchar_ && n == 0)
        return std::string(vchar_);

    assert(vstring_ != 0);
    return vstring_[n];
}

short MvNcValues::as_short(long n) const
{
    switch (type_) {
        case NC_SHORT:
            return *((short*)values_ + n);
        case NC_INT:
            return (short)(*((int*)values_ + n));
        case NC_FLOAT:
            return (short)(*((float*)values_ + n));
        case NC_DOUBLE:
            return (short)(*((double*)values_ + n));
        default:
            return 0;
    }
}

ushort MvNcValues::as_ushort(long n) const
{
    switch (type_) {
        case NC_USHORT:
            return *((ushort*)values_ + n);
        default:
            assert(false);
            return 0;
    }
}


int MvNcValues::as_int(long n) const
{
    switch (type_) {
        case NC_SHORT:
            return (int)(*((short*)values_ + n));
        case NC_INT:
            return *((int*)values_ + n);
        case NC_FLOAT:
            return (int)(*((float*)values_ + n));
        case NC_DOUBLE:
            return (int)(*((double*)values_ + n));
        default:
            return 0;
    }
}

unsigned int MvNcValues::as_uint(long n) const
{
    switch (type_) {
        case NC_UINT:
            return *((unsigned int*)values_ + n);
        default:
            return 0;
    }
}

long MvNcValues::as_int64(long n) const
{
    switch (type_) {
        case NC_INT64:
            return *((long*)values_ + n);
        default:
            return 0;
    }
}


float MvNcValues::as_float(long n) const
{
    switch (type_) {
        case NC_SHORT:
            return (float)(*((short*)values_ + n));
        case NC_INT:
            return (float)(*((int*)values_ + n));
        case NC_FLOAT:
            return *((float*)values_ + n);
        case NC_DOUBLE:
            return (float)(*((double*)values_ + n));
        default:
            return 0;
    }
}

double MvNcValues::as_double(long n) const
{
    switch (type_) {
        case NC_BYTE:
            return (double)(*((signed char*)values_ + n));
        case NC_SHORT:
            return (double)(*((short*)values_ + n));
        case NC_USHORT:
            return (double)(*((ushort*)values_ + n));
        case NC_INT:
            return (double)(*((int*)values_ + n));
        case NC_UINT:
            return (double)(*((unsigned int*)values_ + n));
        case NC_FLOAT:
            return (double)(*((float*)values_ + n));
        case NC_DOUBLE:
            // return *((double*)values_ + n*sizeof(double));
            return *((double*)values_ + n);
        default:
            return 0.;
    }
}

int MvNcValues::getValuesAtt()
{
    // Memory allocation
    if ((ncStatus_ = allocateMemory()) != NC_NOERR)
        return ncStatus_;

    // Assign data pointer
    if ((ncStatus_ = nc_get_att(ncId_, id_, name_.c_str(), values_)) != NC_NOERR)
        return ncStatus_;

    return ncStatus_;
}

int MvNcValues::getValuesVar()
{
    // Memory allocation
    if ((ncStatus_ = allocateMemory()) != NC_NOERR)
        return ncStatus_;

    // Assign data pointer
    if ((ncStatus_ = nc_get_var(ncId_, id_, values_)) != NC_NOERR)
        return ncStatus_;

    return ncStatus_;
}

int MvNcValues::allocateMemory()
{
    switch (type_) {
        case NC_SHORT: {
            vshort_ = new short[nvalues_];
            values_ = (void*)vshort_;
            break;
        }
        case NC_USHORT: {
            vushort_ = new ushort[nvalues_];
            values_ = (void*)vushort_;
            break;
        }

        case NC_INT: {
            vint_ = new int[nvalues_];
            values_ = (void*)vint_;
            break;
        }
        case NC_UINT: {
            vuint_ = new unsigned int[nvalues_];
            values_ = (void*)vuint_;
            break;
        }  //      case NC_LONG:
        case NC_INT64: {
            vint64_ = new long[nvalues_];
            values_ = (void*)vint64_;
            break;
        }
            //         vlong_ = new long[nvalues_];
            //         values_ = vlong_;
            //         break;
        case NC_FLOAT:
            vfloat_ = new float[nvalues_];
            values_ = (void*)vfloat_;
            break;
        case NC_DOUBLE:
            vdouble_ = new double[nvalues_];
            values_ = (void*)vdouble_;
            break;
        case NC_CHAR:
            vchar_ = new char[nvalues_ + 1];
            vchar_[nvalues_] = 0;
            values_ = (void*)vchar_;
            break;
        case NC_BYTE:
            vschar_ = new signed char[nvalues_ + 1];
            vschar_[nvalues_] = 0;
            values_ = (void*)vschar_;
            break;
        case NC_STRING:
            vstring_ = new std::string[nvalues_];
            values_ = (void*)vstring_;
            break;
        default:
            return NC_ENOMEM;
    }

    return NC_NOERR;
}

//-------------------------------------------------------
// Class MvNcBase
//-------------------------------------------------------

MvNcBase::MvNcBase() :
    hasMissingValueIndicator_(false),
    missingValueIndicator_(-999),
    scaleFactor_(1),
    addOffset_(0),
    ncId_(0),
    id_(0),
    name_(""),
    type_(0),
    ncStatus_(NC_NOERR){};

MvNcBase::MvNcBase(int ncid, int id, const std::string& name, nc_type type) :
    hasMissingValueIndicator_(false),
    missingValueIndicator_(-999),
    scaleFactor_(1),
    addOffset_(0),
    ncId_(ncid),
    id_(id),
    name_(name),
    type_(type),
    ncStatus_(NC_NOERR){};

//-------------------------------------------------------
// Class MvNcAtt
//-------------------------------------------------------

#if 0  // F
MvNcAtt::MvNcAtt(NcAtt *ncAtt) : ncAtt_(ncAtt)
{
  if ( ncAtt_ ) values_ = new MvNcValues(ncAtt_->values());
  else values_ = nullptr;
}
#else
MvNcAtt::MvNcAtt(int ncid, int id, const std::string& name) :
    MvNcBase(ncid, id, name, -1)
{
    // Check if it is a new or an existent attribute
    nc_type type;
    size_t len;
    int ret = nc_inq_att(ncId_, id_, name_.c_str(), &type, &len);
    if (ret == NC_ENOTATT) {
        values_ = nullptr;  // new attribute
        return;
    }
    else if (ret != NC_NOERR) {
        ncStatus_ = ret;  // error to instantiate an attribute
        return;
    }

    // Instantiate an existent attribute
    // Get attribute values
    type_ = type;
    values_ = new MvNcValues(this);
}
#endif

MvNcAtt::MvNcAtt(const MvNcAtt& aa) :
    MvNcBase(aa.ncId(), aa.id(), aa.name(), aa.type())
{
    values_ = aa.values_;
}

MvNcAtt::~MvNcAtt()
{
    delete values_;
}

// Return a string with the values separated by '/'.
NcBool MvNcAtt::getValues(std::string& str)
{
    if (!isValid())
        return false;
    double tmpval;
    str = "";
    if (type() == NC_CHAR)
        str = as_string(0);
    else {
        for (int j = 0; j < getNumberOfValues(); j++) {
            if (j > 0)
                str += "/";
            tmpval = as_double(j);
            printValue(str, tmpval);
        }
    }
    return true;
}

// Over-ride the base function when dealing with floating-point
// numbers because the in-built netCDF as_string function chops off
// any significant figures after about 10^6
std::string MvNcAtt::as_string(long n)
{
    std::ostringstream oss;
    switch (type()) {
        case NC_FLOAT: {
            oss << std::setprecision(MV_NC_PRECISION) << values()->as_float(n);
            return oss.str();
        }
        case NC_DOUBLE: {
            oss << std::setprecision(MV_NC_PRECISION) << values()->as_double(n);
            return oss.str();
        }
        case NC_SHORT: {
            oss << values()->as_short(n);
            return oss.str();
        }
        case NC_USHORT: {
            oss << values()->as_ushort(n);
            return oss.str();
        }
        case NC_INT: {
            oss << values()->as_int(n);
            return oss.str();
        }
        case NC_UINT: {
            oss << values()->as_uint(n);
            return oss.str();
        }
        case NC_INT64: {
            oss << values()->as_int64(n);
            return oss.str();
        }
        case NC_BYTE: {
            oss << values()->as_char(n);
            return oss.str();
        }

        default: {
            oss << MvNcBase::as_string(n).c_str();
            return oss.str();
        }
    }

    return {};
}

// Format attribute values in a sensible way
void MvNcAtt::printValue(std::string& str, double value)
{
    unsigned char uc;
    short ss;
    ushort us;
    int ii;
    char buf[50];
    float ff;
    double dd;

    switch (type()) {
        case NC_BYTE:
            uc = (unsigned char)value & 0377;
            if (isprint(uc))
                sprintf(buf, "'%c'", uc);
            else
                sprintf(buf, "'\\%o'", uc);
            break;
        case NC_SHORT:
            ss = (short)value;
            sprintf(buf, "%ds", ss);
            break;
        case NC_USHORT:
            us = (ushort)value;
            sprintf(buf, "%ds", us);
            break;
        case NC_INT:
            ii = (int)value;
            sprintf(buf, "%d", ii);
            break;
        case NC_FLOAT:
            ff = value;
            sprintf(buf, "%#.7gf", ff);
            tztrim(buf);  // trim trailing 0's after '.'
            break;
        case NC_DOUBLE:
            dd = value;
            sprintf(buf, "%#.15g", dd);
            tztrim(buf);
            break;
        default:
            std::cerr << "Invalid type !!" << std::endl;
    }

    str += buf;
}

// 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
void MvNcAtt::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;
}

//------------------------------------------------------
// MvNcVar
//------------------------------------------------------

#if 0  // F
MvNcVar::MvNcVar(NcVar *ncvar, bool is_global, MvNetCDF *parent): 
  edges_(nullptr),ncVar_(ncvar),values_(nullptr), isGlobal_(is_global), parent_(parent), isTime_(false)
{ 
  fillAttributes();
  storeFillValue();
  storeScaleFactorAndOffset();
  storeTimeInformation();
}
#else
MvNcVar::MvNcVar(int varId, const std::string& name, nc_type type,
                 bool is_global, MvNetCDF* parent) :
    MvNcBase(parent->ncId(), varId, name, type),
    edges_(nullptr),
    values_(nullptr),
    isGlobal_(is_global),
    parent_(parent),
    isTime_(false)
{
    // Initialise variables
    the_cur_ = new size_t[NC_MAX_DIMS];  // *** don't know num_dims() yet?
    for (int i = 0; i < NC_MAX_DIMS; i++)
        the_cur_[i] = 0;

    fillAttributes();
    storeFillValue();
    storeScaleFactorAndOffset();
    storeTimeInformation();
}
#endif

MvNcVar::MvNcVar(const MvNcVar& aa)
{
    ncId_ = aa.ncId_;
    id_ = aa.id_;
    name_ = aa.name_;
    type_ = aa.type_;
    attributes_ = aa.attributes_;
    values_ = aa.values_;
    isGlobal_ = aa.isGlobal_;

    int numd = getNumberOfDimensions();
    the_cur_ = new size_t[numd];
    for (int i = 0; i < numd; i++)
        the_cur_[i] = 0;
}

MvNcVar::~MvNcVar()
{
    // std::cout << "In var destructor " << ncVar_->name() << std::endl;
    std::vector<MvNcAtt*>::iterator ii;
    for (ii = attributes_.begin(); ii != attributes_.end(); ii++)
        delete (*ii);

    if (values_)
        delete values_;
    if (edges_)
        delete[] edges_;
    if (the_cur_)
        delete[] the_cur_;
}

void MvNcVar::storeFillValue()
{
    // if (options().detectMissingValues())
    MvNcAtt* att = getAttribute(options().missingValuesAttribute());
    if (att != nullptr) {
        missingValueIndicator_ = att->as_double(0);
        hasMissingValueIndicator_ = true;
    }
}

void MvNcVar::storeScaleFactorAndOffset()
{
    // if (options().scaleValues())
    {
        MvNcAtt* attScale = getAttribute("scale_factor");
        if (attScale != nullptr) {
            scaleFactor_ = attScale->as_double(0);
        }

        MvNcAtt* attOffset = getAttribute("add_offset");
        if (attOffset != nullptr) {
            addOffset_ = attOffset->as_double(0);
        }
    }
}

bool MvNcVar::parseDate(const std::string& dateAndTimeString, MvDate& date)
{
    // e.g. 1990-01-01 12:00:00
    // e.g. 2016-10-26T00:00:00Z
    // e.g. 1970-01-01T00:00:00+00:00
    // MvDate requires the input to its constructor to be the date in a particular
    // format. Although most examples of netCDF dates comply, some do not, so we
    // do some parsing here and construct a 'nice' date and time for it

    // split into <date> <time>
    std::vector<std::string> tDateAndTime;
    Tokenizer tokenizer1(" T");
    tokenizer1(dateAndTimeString, tDateAndTime);
    std::string dateString = tDateAndTime[0];

    // split date into YMD
    std::vector<std::string> tDate;
    Tokenizer tokenizer2("-");
    tokenizer2(dateString, tDate);

    // should be 3 components: Y,M,D
    if (tDate.size() != 3)
        return false;

    int y = atoi(tDate[0].c_str());
    int m = atoi(tDate[1].c_str());
    int d = atoi(tDate[2].c_str());

    int hours = 0, minutes = 0, seconds = 0;

    // is there a time component?
    if (tDateAndTime.size() > 1) {
        // there could be a time zone offset (e.g. 12:30:00+00:00), so remove what's
        // after the '+'
        std::string timeAndZoneString = tDateAndTime[1];
        std::vector<std::string> tTimeAndZone;
        Tokenizer tokenizer3("+");
        tokenizer3(timeAndZoneString, tTimeAndZone);
        std::string timeString =
            tTimeAndZone[0];  // this should just be the time without the zone

        // split time into HMS
        // some netCDFs can have the time as, e.g., 00:00.0, which causes problems
        // with the parser so we use 'atof' for parsing the seconds
        std::vector<std::string> tTime;
        Tokenizer tokenizer4(":");
        tokenizer4(timeString, tTime);

        hours = atoi(tTime[0].c_str());

        if (tTime.size() > 1) {
            minutes = atoi(tTime[1].c_str());
        }
        if (tTime.size() > 2) {
            seconds = (int)atof(tTime[2].c_str());
        }
    }

    // put everything into a string that MvDate can handle
    char newDateAndTimeString[1024];
    sprintf(newDateAndTimeString, "%02d-%02d-%02d %02d:%02d:%02d", y, m, d, hours,
            minutes, seconds);
    MvDate newDate(newDateAndTimeString);
    date = newDate;

    return true;
}

void MvNcVar::storeTimeInformation()
{
    // do not do this for global variables
    if (isGlobal_)
        return;

    bool isTimeVar = false;

    // look first in standard_name, then in long_name
    MvNcAtt* nameAtt = getAttribute("standard_name");
    if (nameAtt == nullptr)
        nameAtt = getAttribute("long_name");

    if (nameAtt != nullptr) {
        // has the attribute got the right value?
        std::string standardName = nameAtt->as_string(0);
        if (standardName == "time" || standardName == "Time")
            isTimeVar = true;
    }
    else {
        // no attribute we can check, - second attempt: the name of the var itself
        std::string varName(name());
        if (varName == "time")
            isTimeVar = true;
    }

    if (isTimeVar) {
        // the information we need to decode the time is in the units
        MvNcAtt* units = getAttribute("units");
        if (units != nullptr) {
            // parse the string
            std::string unitsString = units->as_string(0);
            std::vector<std::string> tokens;
            Tokenizer tokenizer(" ");
            tokenizer(unitsString, tokens);

            // string example: units="days since 1990-01-01 00:00:00" , some
            // components optional

            // alternative:    units="seconds", reference_date="2016-08-10 12:00:00"
            // - in this case, we convert to the first format for consistency of
            // parsing
            if (tokens.size() == 1) {
                MvNcAtt* refDateAtt = getAttribute("reference_date");
                if (refDateAtt != nullptr) {
                    std::string refDateString = refDateAtt->as_string(0);
                    std::vector<std::string> refDateTokens;
                    Tokenizer tokenizer1(" ");
                    tokenizer1(refDateString, refDateTokens);
                    tokens.push_back("since");
                    tokens.insert(tokens.end(), refDateTokens.begin(),
                                  refDateTokens.end());
                }
            }

            if (tokens.size() > 2 && tokens[1] == "since") {
                std::string timeUnits = tokens[0];

                if (timeUnits == "days")
                    timeScaleFactor_ = 1.0;
                else if (timeUnits == "hours")
                    timeScaleFactor_ = 1.0 / 24;
                else if (timeUnits == "minutes")
                    timeScaleFactor_ = 1.0 / (24 * 60);
                else if (timeUnits == "seconds")
                    timeScaleFactor_ = 1.0 / (24 * 60 * 60);
                else {
                    std::cout << "Did not recognise time unit " << timeUnits
                              << " in variable " << name() << std::endl;
                    return;
                }

                std::string refDateString = tokens[2];
                if (tokens.size() > 3)
                    refDateString += " " + tokens[3];

                // convert into MvDate
                MvDate refDate;
                bool ok = parseDate(refDateString, refDate);
                if (ok) {
                    refDate_ = refDate;
                    isTime_ = true;
                }

            }  // "since"

        }  // "units"

    }  // isTimeVar
}

MvNetCDFBehaviour& MvNcVar::options()
{
    return parent_->options();
}

void MvNcVar::copyMissingValueAttributeIfNeededFrom(MvNcVar* from)
{
    if (from) {
        // if 'from' has a missing value attribute, and 'this' doesn't, then
        // copy it across
        if (from->hasMissingValueIndicator_ && !this->hasMissingValueIndicator_ &&
            options().detectMissingValues()) {
            hasMissingValueIndicator_ = from->hasMissingValueIndicator_;
            missingValueIndicator_ = from->missingValueIndicator_;
            options().missingValuesAttribute(
                from->options().missingValuesAttribute());

            addAttribute(options().missingValuesAttribute(), missingValueIndicator_);
        }
    }
    else {
        std::cout << "Could not copy missing value attribute from NULL attribute"
                  << std::endl;
    }
}

bool MvNcVar::isIntegerType()
{
    NcType t = type();
    return (t == NC_BYTE || t == NC_CHAR || t == NC_SHORT || t == NC_USHORT || t == NC_LONG);
}

void MvNcVar::fillAttributes()
{
    if (!isValid())
        return;

    // Clean memory and get number of attributes
    attributes_.clear();
    int natts = getNumberOfAttributes();

    // Get all attributes info from the netcdf file
    // and save them in memory (MvNcAtt)
    char cname[NC_MAX_NAME];
    for (int i = 0; i < natts; i++) {
        // Get attribute name
        if ((ncStatus_ = nc_inq_attname(ncId_, id_, i, cname)) != NC_NOERR)
            return;

        // Save info
        std::string sname = cname;
        auto* tmpatt = new MvNcAtt(ncId_, id_, sname);
        if (!tmpatt) {
            return;
        }
        else if (!tmpatt->isValid()) {
            delete tmpatt;
            return;
        }
        attributes_.push_back(tmpatt);
    }
}

MvNcAtt* MvNcVar::getAttribute(const std::string& name)
{
    if (!isValid())
        return nullptr;

    for (auto& attribute : attributes_)
        if (name == attribute->name())
            return attribute;

    return nullptr;
}

MvNcAtt* MvNcVar::getAttribute(unsigned int index)
{
    return isValid() && index < attributes_.size() ? attributes_[index] : nullptr;
}

bool MvNcVar::getAttributeValues(MvNcAtt* att, std::vector<std::string>& vec)
{
    // Wipe out the vector
    vec.erase(vec.begin(), vec.end());
    for (int i = 0; i < att->getNumberOfValues(); i++)
        vec.push_back(att->as_string(i));

    return att->getNumberOfValues() > 0;
}

bool MvNcVar::getAttributeValues(MvNcAtt* att, std::vector<double>& vec)
{
    // Wipe out the vector
    vec.erase(vec.begin(), vec.end());
    for (int i = 0; i < att->getNumberOfValues(); i++)
        vec.push_back(att->as_double(i));

    return att->getNumberOfValues() > 0;
}

bool MvNcVar::getAttributeValues(MvNcAtt* att, std::vector<long>& vec)
{
    // Wipe out the vector
    vec.erase(vec.begin(), vec.end());
    for (int i = 0; i < att->getNumberOfValues(); i++)
        vec.push_back(att->as_long(i));

    return att->getNumberOfValues() > 0;
}

bool MvNcVar::addAttribute(MvNcAtt* att)
{
    if (!isValid())
        return false;

    if (attributeExists(att->name()))
        return true;

    bool ret_code = false;
    NcType type = att->type();
    if (type == NC_BYTE || type == NC_CHAR) {
        const char* vals = (const char*)att->values()->base();
        ret_code =
            addAttribute(att->name(), att->values()->getNumberOfValues(), vals, type);
    }
    else if (type == NC_SHORT) {
        const auto* vals = (const short*)att->values()->base();
        ret_code =
            addAttribute(att->name(), att->values()->getNumberOfValues(), vals, type);
    }
    else if (type == NC_USHORT) {
        const auto* vals = (const ushort*)att->values()->base();
        ret_code =
            addAttribute(att->name(), att->values()->getNumberOfValues(), vals, type);
    }
    else if (type == NC_LONG) {
        const auto* vals = (const nclong*)att->values()->base();
        ret_code =
            addAttribute(att->name(), att->values()->getNumberOfValues(), vals, type);
    }
    else if (type == NC_FLOAT) {
        const auto* vals = (const float*)att->values()->base();
        ret_code =
            addAttribute(att->name(), att->values()->getNumberOfValues(), vals, type);
    }
    else if (type == NC_DOUBLE) {
        const auto* vals = (const double*)att->values()->base();
        ret_code =
            addAttribute(att->name(), att->values()->getNumberOfValues(), vals, type);
    }

    return ret_code;
}

bool MvNcVar::put_att(const std::string& name, const char* val)
{
    if (!parent_->defineMode())
        return false;

    ncStatus_ = nc_put_att_text(ncId_, id_, name.c_str(), strlen(val), val);

    return ncStatus_ == NC_NOERR;
}

bool MvNcVar::put_att(const std::string& name, short val)
{
    if (!parent_->defineMode())
        return false;

    ncStatus_ = nc_put_att_short(ncId_, id_, name.c_str(), (nc_type)NC_SHORT, 1, &val);

    return ncStatus_ == NC_NOERR;
}

bool MvNcVar::put_att(const std::string& name, int val)
{
    if (!parent_->defineMode())
        return false;

    ncStatus_ = nc_put_att_int(ncId_, id_, name.c_str(), (nc_type)NC_INT, 1, &val);

    return ncStatus_ == NC_NOERR;
}

bool MvNcVar::put_att(const std::string& name, long val)
{
    if (!parent_->defineMode())
        return false;

    ncStatus_ = nc_put_att_long(ncId_, id_, name.c_str(), (nc_type)NC_LONG, 1, &val);

    return ncStatus_ == NC_NOERR;
}

bool MvNcVar::put_att(const std::string& name, float val)
{
    if (!parent_->defineMode())
        return false;

    ncStatus_ = nc_put_att_float(ncId_, id_, name.c_str(), (nc_type)NC_FLOAT, 1, &val);

    return ncStatus_ == NC_NOERR;
}

bool MvNcVar::put_att(const std::string& name, double val)
{
    if (!parent_->defineMode())
        return false;

    ncStatus_ = nc_put_att_double(ncId_, id_, name.c_str(), (nc_type)NC_DOUBLE, 1, &val);

    return ncStatus_ == NC_NOERR;
}

#if 0  // F
NcBool MvNcVar::put(MvNcVar *var) {
  if (!isValid() || !var->isValid())
    return false;

  NcBool ret_code = false;
  NcType type = var->type();
  const long *edges = var->edges();
  void *base = var->values()->base();

  if (type == NC_BYTE || type == NC_CHAR)
    ret_code = put((const char *)base, edges);
  else if (type == NC_SHORT)
    ret_code = put((const short *)base, edges);
  else if (type == NC_LONG)
    ret_code = put((const nclong *)base, edges);
  else if (type == NC_FLOAT)
    ret_code = put((const float *)base, edges);
  else if (type == NC_DOUBLE)
    ret_code = put((const double *)base, edges);

  return ret_code;
}
#else
int MvNcVar::put(MvNcVar* var)
{
    if (!isValid() || !var->isValid())
        return false;

    void* base = var->values()->base();

    switch (var->type()) {
        case NC_BYTE:
        case NC_CHAR:
            //        return put(vchar_);
        case NC_SHORT:
            return put((const short*)base);
        case NC_USHORT:
            return put((const ushort*)base);
        case NC_LONG:
            //         return put(vlong_);
        case NC_FLOAT:
            //         return put(vfloat_);
        case NC_DOUBLE:
            return put((const double*)base);
        default:
            return !NC_NOERR;
    }

    return !NC_NOERR;
}
#endif

bool MvNcVar::put_vara(const char* vals, size_t* counts)
{
    if (parent_->dataMode())
        ncStatus_ = nc_put_vara_text(ncId_, id_, the_cur_, counts, vals);
    else
        ncStatus_ = parent_->status();

    return ncStatus_ == NC_NOERR;
}

bool MvNcVar::put_vara(const short* vals, size_t* counts)
{
    if (parent_->dataMode())
        ncStatus_ = nc_put_vara_short(ncId_, id_, the_cur_, counts, vals);
    else
        ncStatus_ = parent_->status();

    return ncStatus_ == NC_NOERR;
}

bool MvNcVar::put_vara(const ushort* vals, size_t* counts)
{
    if (parent_->dataMode())
        ncStatus_ = nc_put_vara_ushort(ncId_, id_, the_cur_, counts, vals);
    else
        ncStatus_ = parent_->status();

    return ncStatus_ == NC_NOERR;
}

bool MvNcVar::put_vara(const int* vals, size_t* counts)
{
    if (parent_->dataMode())
        ncStatus_ = nc_put_vara_int(ncId_, id_, the_cur_, counts, vals);
    else
        ncStatus_ = parent_->status();

    return ncStatus_ == NC_NOERR;
}

bool MvNcVar::put_vara(const float* vals, size_t* counts)
{
    if (parent_->dataMode())
        ncStatus_ = nc_put_vara_float(ncId_, id_, the_cur_, counts, vals);
    else
        ncStatus_ = parent_->status();

    return ncStatus_ == NC_NOERR;
}

bool MvNcVar::put_vara(const double* vals, size_t* counts)
{
    if (parent_->dataMode())
        ncStatus_ = nc_put_vara_double(ncId_, id_, the_cur_, counts, vals);
    else
        ncStatus_ = parent_->status();

    return ncStatus_ == NC_NOERR;
}


// Specialize template for Cached. It's convenient to use a vector of
// Cached when retrieving string variables, but it most be treated
// differently than the primitive types.
template <>
NcBool MvNcVar::get(std::vector<Cached>& vals, const long* counts, long nvals)
{
    if (!isValid())
        return false;

    NcBool ret_val = NC_NOERR;
    int i;
    int num_values = 1;

    vals.erase(vals.begin(), vals.end());

    // This is the length of the variable strings
    if (getNumberOfDimensions() > 0) {
        long last_dim = counts[getNumberOfDimensions() - 1];
        for (i = 0; i < getNumberOfDimensions(); i++)
            num_values *= counts[i];

        // Allocate char array to hold all the values
        char* ptr = new char[num_values];

        // F      ret_val = ncVar_->get(ptr,counts);
        ret_val = nc_get_var(ncId_, id_, ptr);
        if (ret_val == NC_NOERR) {
            int num_values1;
            int nelems = (num_values / last_dim);
            vals.resize(nelems);
            if (nvals > 0 && nvals < nelems)
                num_values1 = nvals * last_dim;
            else
                num_values1 = num_values;

            i = 0;
            int j = 0;

            // Allocate space to hold one string. NetCDF does not
            // nullterminate strings, so the 0 should be added
            char* one_str = new char[last_dim + 1];

            while (i < num_values1) {
                strncpy(one_str, &ptr[i], last_dim);
                one_str[last_dim] = 0;
                vals[j] = one_str;
                i += last_dim;
                ++j;
            }

            delete[] one_str;
        }

        // Delete temporaries
        delete[] ptr;
    }
    else {
        char* scalarval = (char*)values()->base();
        if (scalarval) {
            char xxx[2];
            sprintf(xxx, "%1c", scalarval[0]);
            xxx[1] = '\0';
            if (scalarval)
                vals.push_back(Cached(xxx));
        }
    }

    return ret_val;
}

bool MvNcVar::setCurrent(long c0, long c1, long c2, long c3, long c4)
{
    // F return ncVar_->set_cur(c0,c1,c2,c3,c4);
    long t[6];
    t[0] = c0;
    t[1] = c1;
    t[2] = c2;
    t[3] = c3;
    t[4] = c4;
    t[5] = -1;
    int num_dims = getNumberOfDimensions();
    for (int j = 0; j < 6; j++) {
        // find how many parameters were used
        int i;
        if (t[j] == -1) {
            if (num_dims < j)
                return false;  // too many for variable's dimensionality

            for (i = 0; i < j; i++) {
                MvNcDim* ncdim = getDimension(i);
                if (!ncdim) {
                    return false;
                }
                else if (t[i] >= ncdim->size() && !ncdim->isUnlimited()) {
                    return false;  // too big for dimension
                }
                the_cur_[i] = t[i];
            }

            for (i = j; i < num_dims; i++)
                the_cur_[i] = 0;

            return true;
        }
    }

    return true;
}

bool MvNcVar::setCurrent(long* cur)
{
    // F     return ncVar_->set_cur(cur);
    for (int i = 0; i < getNumberOfDimensions(); i++) {
        MvNcDim* ncdim = getDimension(i);
        if (!ncdim)
            return false;

        if (cur[i] >= ncdim->size() && !ncdim->isUnlimited())
            return false;

        the_cur_[i] = cur[i];
    }

    return true;
}

NcBool MvNcVar::attributeExists(const std::string& name)
{
    if (!isValid())
        return false;

    for (auto& attribute : attributes_) {
        if (name == attribute->name())
            return TRUE;
    }

    return FALSE;
}

void MvNcVar::getStringType(std::string& strtype)
{
    if (type() == NC_BYTE)
        strtype = "ncbyte ( signed char )";
    else if (type() == NC_CHAR)
        strtype = "char";
    else if (type() == NC_SHORT)
        strtype = "short";
    else if (type() == NC_USHORT)
        strtype = "ushort";
    else if (type() == NC_LONG)
        strtype = "nclong ( int )";
    else if (type() == NC_FLOAT)
        strtype = "float";
    else if (type() == NC_DOUBLE)
        strtype = "double";
}

// putAttributeWithType
// - exists so that we can have a 'double' number and write an attribute of,
// e.g. short, with that value Adapted from ecCodes, tools/grib_to_netcdf.c,
// also Apache 2.0 Licence

bool MvNcVar::putAttributeWithType(const std::string& name, NcType nctype,
                                   double value)
{
    bool r = false;
    switch (nctype) {
        case NC_BYTE: {
            if (value < 0) {
                auto val_char = (signed char)value;
                // F            r = ncVar_->add_att(name.c_str(), val_char);
                r = this->addAttribute(name, val_char);
            }
            else {
                auto val_uchar = (unsigned char)value;
                //            r = ncVar_->add_att(name.c_str(), val_uchar);
                r = this->addAttribute(name, val_uchar);
            }
            break;
        }
        case NC_SHORT: {
            auto val_short = (short int)value;
            //        r = ncVar_->add_att(name.c_str(), val_short);
            r = this->addAttribute(name, val_short);
            break;
        }
        case NC_INT: {
            auto val_int = (int)value;
            //        r = ncVar_->add_att(name.c_str(), val_int);
            r = this->addAttribute(name, val_int);
            break;
        }
        case NC_FLOAT: {
            auto val_flt = (float)value;
            //        r = ncVar_->add_att(name.c_str(), val_flt);
            r = this->addAttribute(name, val_flt);
            break;
        }
        case NC_DOUBLE: {
            auto val_dbl = (double)value;
            // F        r = ncVar_->add_att(name.c_str(), val_dbl);
            r = addAttribute(name, val_dbl);
            break;
        }
        default:
            // std::cout << "putAttributeWithType(...): Unknown netcdf type " << nctype;
            break;
    }
    return r;
}

//
// Adapted from ecCodes, tools/grib_to_netcdf.c, also Apache 2.0 Licence
//

template <class T>
void MvNcVar::recomputeScalingIfNecessary(T* vals, long n)
{
    // Only consider repacking if we're allowed
    if (!options().rescaleToFit())
        return;

    NcType idx = type();  // makes an assumption about the values of ncByte, etc

    if (idx == NC_DOUBLE)  // would not be able to repack doubles anyway
        return;

    double max = -DBL_MAX;
    double min = DBL_MAX;
    double midrange = 0;
    int64_t scaled_max = 0;
    int64_t scaled_min = 0;
    int64_t scaled_midrange = 0;
    double ao = 0.0, sf = 0.0;
    double x;

    // Compute the min and max unpacked values
    bool doMissing = hasMissingValueIndicator_ && options().detectMissingValues();
    if (doMissing) {
        for (long j = 0; j < n; ++j) {
            if (vals[j] != NETCDF_MISSING_VALUE) {
                if (vals[j] > max)
                    max = vals[j];
                if (vals[j] < min)
                    min = vals[j];
            }
        }
    }
    else {
        for (long j = 0; j < n; ++j) {
            if (vals[j] > max)
                max = vals[j];
            if (vals[j] < min)
                min = vals[j];
        }
    }

    // Test whether the min and max of the packed values are out of
    // range of the packed data type
    // - note that if we have a negative scale factor, then the
    // following formula will result in swapped min/max packed values,
    // so we need to test that both are within the upper and lower
    // bounds of the packed data type
    double maxPacked = (max - addOffset_) / scaleFactor_;
    double minPacked = (min - addOffset_) / scaleFactor_;
    if (minPacked < nc_type_values_[idx].nc_type_min ||
        maxPacked > nc_type_values_[idx].nc_type_max ||
        maxPacked < nc_type_values_[idx].nc_type_min ||
        minPacked > nc_type_values_[idx].nc_type_max) {
        // yes - we will need to repack
        midrange = (max + min) / 2.0;
        sf = (double)((max - min) / (double)(nc_type_values_[idx].nc_type_max -
                                             nc_type_values_[idx].nc_type_min));
        ao = ((max + min) - sf * (nc_type_values_[idx].nc_type_min +
                                  nc_type_values_[idx].nc_type_max)) /
             2;

        // Prevent divide by zero later.
        // Constant field grib has max == min
        if (min == max)
            sf = 1.0;

        x = ((midrange - ao));
        x /= sf;

        scaled_max = rint((max - ao) / sf);
        scaled_min = rint((min - ao) / sf);
        scaled_midrange = rint((midrange - ao) / sf);

        max = scaled_max * sf + ao;
        min = scaled_min * sf + ao;
        midrange = scaled_midrange * sf + ao;

        if (scaleFactor_ != sf || addOffset_ != ao) {
            scaleFactor_ = sf;
            addOffset_ = ao;

            // can't use the MvNcVar version because it will not
            // overwrite an existing attribute
            // F NcBool ret_code1 = ncVar_->add_att("scale_factor",sf);
            // F NcBool ret_code2 = ncVar_->add_att("add_offset",ao);
            NcBool ret_code1 =
                nc_put_att_double(ncId_, id_, "scale_factor", NC_DOUBLE, 1, &sf);
            NcBool ret_code2 =
                nc_put_att_double(ncId_, id_, "add_offset", NC_DOUBLE, 1, &ao);
            bool ret_code3 = false;

            if (hasMissingValueIndicator_) {
                missingValueIndicator_ = nc_type_values_[idx].nc_type_missing;
                ret_code3 = putAttributeWithType(options().missingValuesAttribute(),
                                                 idx, missingValueIndicator_);
            }

            if (ret_code1 == TRUE || ret_code2 == TRUE || ret_code3) {
                // F attributes_.push_back(new MvNcAtt(ncVar_->get_att(attributes_.size())));
                int natts;
                ncStatus_ = nc_inq_natts(id_, &natts);
                char cname[NC_MAX_NAME];
                ncStatus_ = nc_inq_attname(ncId_, id_, natts - 1, cname);
                std::string sname = cname;
                auto* tmpatt = new MvNcAtt(ncId_, id_, sname);
                attributes_.push_back(tmpatt);
            }
        }
    }
}

MvDate MvNcVar::processDate(double val)
{
    MvDate d1(refDate_);
    d1 += (val * timeScaleFactor_);
    return d1;
}

// Values as dates
NcBool MvNcVar::getDates(std::vector<MvDate>& dates, const long* counts,
                         long nvals1)
{
    // get the values as doubles, then convert to dates
    std::vector<double> vals;
    NcBool ret = get(vals, counts, nvals1);
    dates.resize(vals.size());

    for (size_t n = 0; n < vals.size(); n++) {
        dates[n] = processDate(vals[n]);
    }
    return ret;
}

int MvNcVar::getNumberOfAttributes()
{
    int natts;
    ncStatus_ = nc_inq_varnatts(ncId_, id_, &natts);
    return natts;
}

int MvNcVar::getNumberOfDimensions()
{
    int ndim = -1;
    ncStatus_ = nc_inq_varndims(ncId_, id_, &ndim);

    return ndim;
}

MvNcDim* MvNcVar::getDimension(int dimId)
{
    // Find all Dimension info for this variable
    int ndim;
    int dims[NC_MAX_DIMS];
    if ((ncStatus_ = nc_inq_var(ncId_, id_, 0, 0, &ndim, dims, 0)) != NC_NOERR)
        return 0;

    if (dimId < 0 || dimId >= ndim)
        return 0;

    // Get the i-th Dimension info for this variable
    return parent_->getDimension(dims[dimId]);
}

int MvNcVar::getDimension(long& dim)
{
    long* edges1 = edges();
    dim = numValsFromCounts(edges1);
    return NC_NOERR;
}

long* MvNcVar::edges()
{
    if (!edges_)
    // F      edges_ =  ncVar_->edges();
    {
        // get dimension IDs
        int ndim = getNumberOfDimensions();
        if (ncStatus_ != NC_NOERR)
            return nullptr;

        int* dimIds = new int[ndim];
        if ((ncStatus_ = nc_inq_vardimid(ncId_, id_, dimIds)) != NC_NOERR)
            return nullptr;

        // Get dimension lenghts
        edges_ = new long[ndim];
        size_t len;
        for (int i = 0; i < ndim; i++) {
            if ((ncStatus_ = nc_inq_dimlen(ncId_, dimIds[i], &len)) != NC_NOERR) {
                delete[] edges_;
                edges_ = 0;
                return nullptr;
            }
            edges_[i] = len;
        }
    }

    return edges_;
}

double MvNcVar::processValue(double val)
{
    // 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).
    if (hasMissingValueIndicator_ && options().detectMissingValues()) {
        if (val == missingValueIndicator_)
            return NETCDF_MISSING_VALUE;
    }

    // scaling?
    if (hasScaling() && options().scaleValues())
        val = (val * scaleFactor_) + addOffset_;

    return val;  // 'normal' situation - just return the value as given
}

// From ecCodes, tools/grib_to_netcdf.c, also Apache 2.0 Licence
MvNcVar::nc_types_values MvNcVar::nc_type_values_[NC_TYPES] = {
    /* In some occasions, SHRT_MIN-2 for the minimum value, makes ncview display
missing values for -32766, while NC_FILL_SHORT=-32767, and SHRT_MIN=-32768 */
    {0, 0, 0}, /* NC_NAT,   'Not A Type' (c.f. NaN) */
    {0x7f, NC_FILL_BYTE + 1,
     NC_FILL_BYTE},                         /* NC_BYTE,   signed 1 byte integer */
    {0xff, NC_FILL_CHAR + 1, NC_FILL_CHAR}, /* NC_CHAR,   ISO/ASCII character */
    {0x7fff, NC_FILL_SHORT + 1,
     NC_FILL_SHORT}, /* NC_SHORT,  signed 2 byte integer */
    {0x7ffffff, NC_FILL_INT + 1,
     NC_FILL_INT}, /* NC_INT,    signed 4 byte integer */
    {0x7ffffff, NC_FILL_INT + 1,
     NC_FILL_INT}, /* NC_LONG,   alias of NC_INT */
    {FLT_MAX, -FLT_MAX,
     NC_FILL_FLOAT}, /* NC_FLOAT,  single precision floating point number */
    {DBL_MAX, -DBL_MAX,
     NC_FILL_DOUBLE}, /* NC_DOUBLE, double precision floating point number */
    {0xffff, 0,
     NC_FILL_USHORT}, /* NC_USHORT,  unsigned 2 byte integer */
    {0xfffffff, 0,
     NC_FILL_UINT}, /* NC_UINT,  unsigned 4 byte integer */
    {0xfffffff, 0,
     NC_FILL_UINT},
    /* NC_INT64,  signed 8 byte integer */};

// note: fillvalue for NC_INT64 should be NC_FILL_INT64 but this does not fit into a double.
// this is ok for now, because we only need this type for attributes rather than data

//--------------------------------------------------------------
// MvNetCDFBehaviour
//--------------------------------------------------------------

MvNetCDFBehaviour::MvNetCDFBehaviour()
{
    detectMissingValues(true);
    scaleValues(true);
    rescaleToFit(true);
    translateTime(true);
    missingValuesAttribute("_FillValue");
}

//--------------------------------------------------------------
// Class MVNetCDF
//--------------------------------------------------------------

// Empty constructor
MvNetCDF::MvNetCDF() :
    ncStatus_(NC_NOERR),
    ncFile_(nullptr),
    globalVar_(nullptr)
{
}

// Construct from a path and char opening mode ('r' by default)
MvNetCDF::MvNetCDF(const std::string& path, const char mode) :
    ncStatus_(NC_NOERR),
    ncFile_(nullptr),
    globalVar_(nullptr)
{
    init(path, mode);
}

// Construct from a path and int opening mode (e.g. NC_NOWRITE)
MvNetCDF::MvNetCDF(const std::string& path, int mode) :
    ncStatus_(NC_NOERR),
    ncFile_(nullptr),
    globalVar_(nullptr)
{
    init(path, mode);
}

// Construct from request
MvNetCDF::MvNetCDF(const MvRequest& r, const char mode) :
    ncStatus_(NC_NOERR),
    ncFile_(nullptr),
    globalVar_(nullptr)
{
    // Get the PATH from request
    const char* path = get_value(r, "PATH", 0);

    init(path, mode);
}

// Function to initialize MvNetCDF using mode as char
void MvNetCDF::init(const std::string& path, const char mode)
{
    int imode = (mode == 'w' || mode == 'u') ? NC_WRITE : NC_NOWRITE;
    init(path, imode);
}

// Function to initialize MvNetCDF
void MvNetCDF::init(const std::string& path, int imode)
{
    ncFile_ = new MvNcFile(path, imode);
    if (!ncFile_->isValid()) {
        ncStatus_ = ncFile_->status();
        return;
    }

    path_ = path;
    countMap_[path_]++;

    fillVariables();
}

// Destructor
// Just deletes it's MvNcFile pointer and clean memory
// if it is the last instance that has this file open.
MvNetCDF::~MvNetCDF()
{
    if (!isValid())
        return;

    if (--countMap_[path_] != 0)
        return;

    // Clean memory
    std::vector<MvNcVar*>::iterator ii;
    for (ii = variables_.begin(); ii != variables_.end(); ii++)
        delete (*ii);

    delete globalVar_;

    delete ncFile_;
}

bool MvNetCDF::dataMode()
{
    if (ncFile_->dataMode())
        return true;
    else {
        ncStatus_ = ncFile_->status();
        return false;
    }
}

int MvNetCDF::defineMode()
{
    if (ncFile_->defineMode())
        return true;
    else {
        ncStatus_ = ncFile_->status();
        return false;
    }
}

MvNcVar* MvNetCDF::addVariable(const std::string& name, int type, int size,
                               const MvNcDim** dim)
{
    // If variable already exists then return it
    if (variableExists(name))
        return getVariable(name);

    int* dimIds = nullptr;

    // Get dimension info
    if (size > 0) {
        dimIds = new int[size];
        for (int i = 0; i < size; i++) {
            if (dim[i] != nullptr)
                dimIds[i] = dim[i]->id();
            else {
                delete[] dimIds;
                return nullptr;
            }
        }
    }

    // Add variable to the netcdf file
    if (!ncFile_->defineMode())
        return nullptr;

    int varId;
    int status = nc_def_var(ncId(), name.c_str(), type, size, dimIds, &varId);
    if (status != NC_NOERR) {
        if (dimIds) {
            delete[] dimIds;
        }
        return nullptr;
    }

    // Create and save variable internal structure
    std::string sname = name;
    auto* tmpvar = new MvNcVar(varId, sname, type, false, this);
    variables_.push_back(tmpvar);

    if (dimIds) {
        delete[] dimIds;
    }

    return tmpvar;
}

// Convenience function, add a variable by given name,type and dimension(s)
MvNcVar* MvNetCDF::addVariable(const std::string& name, NcType type, long dimsize0,
                               long dimsize1, long dimsize2, long dimsize3,
                               long dimsize4)
{
    if (!isValid())
        return nullptr;

    if (variableExists(name))
        return getVariable(name);

    MvNcDim* dim[5];
    int number = 0;
    char dim_name[NC_MAX_NAME + 1];

    if (dimsize0 >= 0) {
        sprintf(dim_name, "%s_%d", name.c_str(), number + 1);
        dim[number++] = addDimension(dim_name, dimsize0);
    }
    if (dimsize1 >= 0) {
        sprintf(dim_name, "%s_%d", name.c_str(), number + 1);
        dim[number++] = addDimension(dim_name, dimsize1);
    }
    if (dimsize2 >= 0) {
        sprintf(dim_name, "%s_%d", name.c_str(), number + 1);
        dim[number++] = addDimension(dim_name, dimsize2);
    }
    if (dimsize3 >= 0) {
        sprintf(dim_name, "%s_%d", name.c_str(), number + 1);
        dim[number++] = addDimension(dim_name, dimsize3);
    }
    if (dimsize4 >= 0) {
        sprintf(dim_name, "%s_%d", name.c_str(), number + 1);
        dim[number++] = addDimension(dim_name, dimsize4);
    }

    return addVariable(name, type, number, (const MvNcDim**)dim);
}

// Convenience function, add a variable by given name,type,
// dimension(s) and dimension(s)'s names
MvNcVar* MvNetCDF::addVariable(const std::string& name, NcType type,
                               std::vector<long>& dimsize, std::vector<std::string>& vname)
{
    if (!isValid())
        return nullptr;

    if (variableExists(name))
        return getVariable(name);

    MvNcDim* dim[5] = {nullptr};

    assert(dimsize.size() <= 5);
    if (dimsize.size() > 5) {
        return nullptr;
    }

    // Add dimensions
    for (size_t num = 0; num < dimsize.size(); num++) {
        MvNcDim* pdim = addDimension(vname[num], dimsize[num]);
        if (!pdim) {
            for (auto v : dim) {
                if (v != nullptr) {
                    delete v;
                }
            }
            return nullptr;
        }
        dim[num] = pdim;
    }

    return addVariable(name, type, dimsize.size(), (const MvNcDim**)dim);
}

// Get variable by name
MvNcVar* MvNetCDF::getVariable(const std::string& name)
{
    if (!isValid())
        return nullptr;

    for (auto& variable : variables_)
        if (name == variable->name())
            return variable;

    return nullptr;
}

// Convenience function. Get a variable's type by giving the name.
NcType MvNetCDF::getTypeForVariable(const std::string& name)
{
    if (!isValid())
        return (NcType)0;
    ;

    MvNcVar* var = getVariable(name);
    if (!var)
        return (NcType)0;

    return var->type();
}

void MvNetCDF::getStringTypeForVariable(const std::string& name, std::string& strtype)
{
    if (!isValid())
        return;

    MvNcVar* var = getVariable(name);
    if (var)
        var->getStringType(strtype);
}

// Read the values in the NetCDF file and fill in a request
// Global attributes are given filled in "NETCDF" request, dimensions
// are given in a "DIMENSIONS" subrequest and variables are given
// in a "VARIABLES" subrequest. Separate variables also are given
// in subrequests of "VARIABLES", with separate fields for the variable's
// dimensions and attributes.
MvRequest MvNetCDF::getRequest()
{
    if (!isValid())
        return MvRequest(nullptr, false);

    MvRequest r("NETCDF");

    // Get the variables.

    r.setValue("PATH", path_.c_str());

    // Subrequest, contains info about dimensions.
    MvRequest dim("DIMENSIONS");
    reqGetDimensions(dim);

    // Subrequest, contains info about variables.
    MvRequest var("VARIABLES");
    reqGetVariables(var);

    // Global attributes are added straight to the request, not in
    // subrequest.
    reqGetAttributes(r);

    // Add the subrequests.
    r.setValue("DIMENSIONS", dim);
    r.setValue("VARIABLES", var);

    // Construct a new and return it.
    return MvRequest(r);
}

// Get all dimensions from file and fill into given request
void MvNetCDF::reqGetDimensions(MvRequest& r)
{
    if (!isValid())
        return;

    MvNcDim* tmpdim;
    for (int i = 0; i < getNumberOfDimensions(); i++) {
        tmpdim = getDimension(i);
        r.setValue(tmpdim->name(), tmpdim->size());
    }
}

// Fill in all the variables for a NetCDF file
void MvNetCDF::fillVariables()
{
    if (!isValid())
        return;

    // Inquire number of variable from the file
    int nvars;
    ncStatus_ = nc_inq(ncId(), nullptr, &nvars, nullptr, nullptr);
    if (ncStatus_ != NC_NOERR)
        return;

    // Inquire variable ids
    int* varids = new int[nvars];  // or NC_MAX_VARS
    int nvars1;
    ncStatus_ = nc_inq_varids(ncId(), &nvars1, varids);
    if (ncStatus_ != NC_NOERR) {
        delete[] varids;
        return;
    }

    // Get variables info and save them
    char name[NC_MAX_NAME];
    nc_type type;
    for (int i = 0; i < nvars; i++) {
        // Inquire about a variable name and type
        ncStatus_ = nc_inq_var(ncId(), varids[i], name, &type, nullptr, nullptr, nullptr);
        if (ncStatus_ != NC_NOERR) {
            delete[] varids;
            return;
        }

        std::string sname = name;
        auto* tmpvar = new MvNcVar(varids[i], sname, type, false, this);
        variables_.push_back(tmpvar);
    }

    // Get global attributes
    globalVariable();

    delete[] varids;
}

// Get the variables and fill in given request.
void MvNetCDF::reqGetVariables(MvRequest& r)
{
    if (!isValid())
        return;

    int num_dim, num_attr;
    int j, k;
    MvNcVar* tmpvar;
    MvNcAtt* tmpatt;
    MvNcDim* tmpdim;

    for (auto& variable : variables_) {
        tmpvar = variable;
        num_dim = tmpvar->getNumberOfDimensions();
        num_attr = tmpvar->getNumberOfAttributes();

        // For each variable, add subrequest with dimensions and attributes
        MvRequest var_req(tmpvar->name());

        for (j = 0; j < num_dim; j++) {
            tmpdim = tmpvar->getDimension(j);
            var_req.addValue("DIMENSIONS", tmpdim->size());
        }

        // Add any attributes to subrequest
        if (num_attr > 0) {
            for (j = 0; j < num_attr; j++) {
                tmpatt = tmpvar->getAttribute(j);
                if (tmpatt->type() == NC_CHAR)
                    var_req.addValue(tmpatt->name(), tmpatt->as_string(0).c_str());
                else
                    for (k = 0; k < tmpatt->getNumberOfValues(); k++)
                        var_req.addValue(tmpatt->name(), tmpatt->as_string(k).c_str());
            }
        }

        // Add subrequest for variable to given request.
        r.setValue(tmpvar->name(), var_req);
    }
}

// Fill global attributes into request
void MvNetCDF::reqGetAttributes(MvRequest& r)
{
    if (!isValid())
        return;

    int i, j;
    const char* req_name;
    for (i = 0; i < getNumberOfAttributes(); i++) {
        MvNcAtt* tmpatt = getAttribute(i);
        req_name = tmpatt->name();

        if (tmpatt->type() == NC_CHAR)
            r.addValue(req_name, tmpatt->as_string(0).c_str());
        else
            for (j = 0; j < tmpatt->getNumberOfValues(); j++)
                r.addValue(req_name, tmpatt->as_string(j).c_str());
    }
}

NcBool MvNetCDF::variableExists(const std::string& name)
{
    if (!isValid())
        return false;

    for (auto& variable : variables_) {
        if (name == variable->name())
            return 1;
    }

    return 0;
}

MvNcDim* MvNetCDF::addDimension(const std::string& name, long size)
{
    // Dimension already exists?
    if (dimensionExists(name))
        return getDimension(name);

    // If size = 0 then length is unlimited
    size_t len = size ? size : NC_UNLIMITED;

    // Create a dimension
    if (!ncFile_->defineMode())
        return nullptr;

    int dimId;
    if ((ncStatus_ = nc_def_dim(ncId(), name.c_str(), len, &dimId)) != NC_NOERR)
        return nullptr;

    // Create an internal dimension structure
    return new MvNcDim(ncId(), dimId, name, len);
}

NcBool MvNetCDF::dimensionExists(const std::string& name)
{
    if (!isValid())
        return false;

    for (int i = 0; i < getNumberOfDimensions(); i++) {
        if (name == getDimension(i)->name())
            return true;
    }

    return false;
}

int MvNetCDF::getNumberOfDimensions()
{
    int ndim = -1;
    ncStatus_ = nc_inq_ndims(ncId(), &ndim);

    return ndim;
}

MvNcDim* MvNetCDF::getDimension(const std::string& name)
{
    int dimId;
    size_t lenp;

    // Get dimension id
    if ((ncStatus_ = nc_inq_dimid(ncId(), name.c_str(), &dimId)) != NC_NOERR)
        return nullptr;

    // Get dimension length
    if ((ncStatus_ = nc_inq_dimlen(ncId(), dimId, &lenp)) != NC_NOERR)
        return nullptr;

    return new MvNcDim(ncId(), dimId, name, lenp);
}

MvNcDim* MvNetCDF::getDimension(int index)
{
    // Find the name and length of a dimension
    char cname[NC_MAX_NAME + 1];
    size_t lenp;
    if ((ncStatus_ = nc_inq_dim(ncId(), index, cname, &lenp)) != NC_NOERR)
        return nullptr;

    // return the dimension structure
    return new MvNcDim(ncId(), index, std::string(cname), lenp);
}

int MvNetCDF::getNumberOfAttributes()
{
    int natts;
    ncStatus_ = nc_inq_natts(ncId(), &natts);
    return natts;
}

// Get global attributes and store them within a MvNcVar structure
int MvNetCDF::globalVariable()
{
    // Create a variable structure
    std::string name = "global_attr";
    globalVar_ = new MvNcVar(NC_GLOBAL, name, 0, true, this);

    return ncStatus_;
}

//--------------------------------------------------------------
// Class MvNcDim
// Wrapper for NcDim
//--------------------------------------------------------------

MvNcDim::MvNcDim(int ncId, int dimId, const std::string& name, int size) :
    ncId_(ncId),
    dimId_(dimId),
    size_(size),
    name_(name) {}

bool MvNcDim::isUnlimited() const
{
    int recdim = -1;
    nc_inq_unlimdim(ncId_, &recdim);
    return dimId_ == recdim;
}

// required so that we can put the function definition in the .cc file
template void MvNcVar::recomputeScalingIfNecessary<double>(double* vals,
                                                           long n);
