/***************************** 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 <assert.h>

#include "MvNetCDF.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 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_FLOAT:
            delete[](float*) values_;
            break;
        case NC_DOUBLE:
            delete[](double*) values_;
            break;
        case NC_STRING:
            delete[](string*) values_;
            break;
    }
}

string MvNcValues::as_string(long n) const  // NC_CHAR 2
{
    if (vchar_ && n == 0)
        return 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;
    }
}

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_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_LONG:
            //         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 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 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_ = NULL;
}
#else
MvNcAtt::MvNcAtt(int ncid, int id, const 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_ = NULL;  // 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(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
string MvNcAtt::as_string(long n)
{
    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_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(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:
            cerr << "Invalid type !!" << 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_(NULL),ncVar_(ncvar),values_(NULL), isGlobal_(is_global), parent_(parent), isTime_(false)
{ 
  fillAttributes();
  storeFillValue();
  storeScaleFactorAndOffset();
  storeTimeInformation();
}
#else
MvNcVar::MvNcVar(int varId, const string& name, nc_type type,
                 bool is_global, MvNetCDF* parent) :
    MvNcBase(parent->ncId(), varId, name, type),
    edges_(NULL),
    values_(NULL),
    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()
{
    // cout << "In var destructor " << ncVar_->name() << endl;
    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 != NULL) {
        missingValueIndicator_    = att->as_double(0);
        hasMissingValueIndicator_ = true;
    }
}

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

        MvNcAtt* attOffset = getAttribute("add_offset");
        if (attOffset != NULL) {
            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>
    vector<std::string> tDateAndTime;
    Tokenizer tokenizer1(" T");
    tokenizer1(dateAndTimeString, tDateAndTime);
    std::string dateString = tDateAndTime[0];

    // split date into YMD
    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];
        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
        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 == NULL)
        nameAtt = getAttribute("long_name");

    if (nameAtt != NULL) {
        // 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 != NULL) {
            // parse the string
            std::string unitsString = units->as_string(0);
            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 != NULL) {
                    std::string refDateString = refDateAtt->as_string(0);
                    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
        string sname    = cname;
        MvNcAtt* tmpatt = new MvNcAtt(ncId_, id_, sname);
        if (!tmpatt || !tmpatt->isValid())
            return;

        attributes_.push_back(tmpatt);
    }
}

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

    for (unsigned int i = 0; i < attributes_.size(); i++)
        if (name == attributes_[i]->name())
            return attributes_[i];

    return NULL;
}

MvNcAtt* MvNcVar::getAttribute(unsigned int index)
{
    if (!isValid())
        return NULL;

    if (index <= (attributes_.size() - 1))
        return attributes_[index];
    else
        return NULL;
}

bool MvNcVar::getAttributeValues(MvNcAtt* att, vector<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, 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, 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 short* vals = (const short*)att->values()->base();
        ret_code =
            addAttribute(att->name(), att->values()->getNumberOfValues(), vals, type);
    }
    else if (type == NC_USHORT) {
        const ushort* vals = (const ushort*)att->values()->base();
        ret_code =
            addAttribute(att->name(), att->values()->getNumberOfValues(), vals, type);
    }
    else if (type == NC_LONG) {
        const nclong* vals = (const nclong*)att->values()->base();
        ret_code =
            addAttribute(att->name(), att->values()->getNumberOfValues(), vals, type);
    }
    else if (type == NC_FLOAT) {
        const float* vals = (const float*)att->values()->base();
        ret_code =
            addAttribute(att->name(), att->values()->getNumberOfValues(), vals, type);
    }
    else if (type == NC_DOUBLE) {
        const double* vals = (const double*)att->values()->base();
        ret_code =
            addAttribute(att->name(), att->values()->getNumberOfValues(), vals, type);
    }

    return ret_code;
}

bool MvNcVar::put_att(const 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 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 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 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 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 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(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];

        // 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];

        // 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;
            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 temporaries
        delete[] ptr;
        delete[] one_str;
    }
    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;

                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 string& name)
{
    if (!isValid())
        return false;

    for (unsigned int i = 0; i < attributes_.size(); i++) {
        if (name == attributes_[i]->name())
            return TRUE;
    }

    return FALSE;
}

void MvNcVar::getStringType(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 GRIB_API, tools/grib_to_netcdf.c,
// also Apache 2.0 Licence

bool MvNcVar::putAttributeWithType(const string& name, NcType nctype,
                                   double value)
{
    bool r = false;
    switch (nctype) {
        case NC_BYTE: {
            if (value < 0) {
                signed char val_char = (signed char)value;
                // F            r = ncVar_->add_att(name.c_str(), val_char);
                r = this->addAttribute(name, val_char);
            }
            else {
                unsigned char val_uchar = (unsigned char)value;
                //            r = ncVar_->add_att(name.c_str(), val_uchar);
                r = this->addAttribute(name, val_uchar);
            }
            break;
        }
        case NC_SHORT: {
            short int val_short = (short int)value;
            //        r = ncVar_->add_att(name.c_str(), val_short);
            r = this->addAttribute(name, val_short);
            break;
        }
        case NC_INT: {
            int val_int = (int)value;
            //        r = ncVar_->add_att(name.c_str(), val_int);
            r = this->addAttribute(name, val_int);
            break;
        }
        case NC_FLOAT: {
            float val_flt = (float)value;
            //        r = ncVar_->add_att(name.c_str(), val_flt);
            r = this->addAttribute(name, val_flt);
            break;
        }
        case NC_DOUBLE: {
            double 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 GRIB_API, 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);
                string sname    = cname;
                MvNcAtt* 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(vector<MvDate>& dates, const long* counts,
                         long nvals1)
{
    // get the values as doubles, then convert to dates
    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 NULL;

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

        // 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 NULL;
            }
            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 GRIB_API, 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 */
};

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

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

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

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

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

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

// Construct from request
MvNetCDF::MvNetCDF(const MvRequest& r, const char mode) :
    ncStatus_(NC_NOERR),
    ncFile_(NULL),
    globalVar_(NULL)
{
    // 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 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 string& path, int imode)
{
    ncFile_ = new MvNcFile(path, imode);

    if (!ncFile_ || !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
    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 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++)
            dimIds[i] = dim[i]->id();
    }

    // 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 NULL;
    }

    // Create and save variable internal structure
    string sname    = name;
    MvNcVar* 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 string& name, NcType type, long dimsize0,
                               long dimsize1, long dimsize2, long dimsize3,
                               long dimsize4)
{
    if (!isValid())
        return NULL;

    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 string& name, NcType type,
                               vector<long>& dimsize, vector<string>& vname)
{
    if (!isValid())
        return NULL;

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

    MvNcDim* dim[5];
    vector<long>::size_type num;

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

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

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

    for (unsigned int i = 0; i < variables_.size(); i++)
        if (name == variables_[i]->name())
            return variables_[i];

    return NULL;
}

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

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

    return var->type();
}

void MvNetCDF::getStringTypeForVariable(const string& name, 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(NULL, 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(), NULL, &nvars, NULL, NULL);
    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, NULL, NULL, NULL);
        if (ncStatus_ != NC_NOERR) {
            delete[] varids;
            return;
        }

        string sname    = name;
        MvNcVar* 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 (unsigned int i = 0; i < variables_.size(); i++) {
        tmpvar   = variables_[i];
        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 string& name)
{
    if (!isValid())
        return false;

    for (unsigned int i = 0; i < variables_.size(); i++) {
        if (name == variables_[i]->name())
            return 1;
    }

    return 0;
}

MvNcDim* MvNetCDF::addDimension(const 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 NULL;

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

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

NcBool MvNetCDF::dimensionExists(const 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 string& name)
{
    int dimId;
    size_t lenp;

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

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

    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 NULL;

    // return the dimension structure
    return new MvNcDim(ncId(), index, 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
    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 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);
