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

 Copyright 2012 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 "Tokenizer.h"

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

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


// 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 */
        { 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 */
};



////////////////////// MvNcAtt ///////////////////////
MvNcAtt::MvNcAtt(NcAtt *ncAtt) : ncAtt_(ncAtt)
{
  if ( ncAtt_ ) values_ = new MvNcValues(ncAtt_->values());
  else values_ = NULL;
}

MvNcAtt::MvNcAtt(const MvNcAtt& aa)
{
  ncAtt_ = aa.ncAtt_; 
  values_ = aa.values_;
}

// Return a string with the values separated by '/'.
NcBool MvNcAtt::getValues(string & str )
{
  if ( ! isValid() ) return false;
  double tmpval;
  str = "";
  if (type() == ncChar ) 
    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;
}



// the purpose of this function is to 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.

char* MvNcAtt::as_string(long n)
{
    switch (type())
    {
        case NC_FLOAT:
        {
            char *outstring = new char[64]; 
            sprintf(outstring, "%f", values()->as_float(n));
            return outstring;
        }
        case NC_DOUBLE:
        {
            char *outstring = new char[64]; 
            sprintf(outstring, "%f", values()->as_double(n));
            return outstring;
        }
    
        default:
        {
            return MvNcBase::as_string(n);
        }
    }
}


// Format attribute values in a sensible way
void MvNcAtt::printValue(string &str, double value)
{
  unsigned char uc;
  short ss;
  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_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;
}

MvNcAtt::~MvNcAtt()
{
  //cout << "In attribute destructor  " << name() << endl;
 delete ncAtt_; delete values_; 
}


////////////////////////////// MvNcVar //////////////////////////////
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();
}

MvNcVar::MvNcVar(const MvNcVar & aa) 
{
  ncVar_                    = aa.ncVar_;
  attributes_               = aa.attributes_;
  values_                   = aa.values_;
  isGlobal_                 = aa.isGlobal_;
}

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_;
}


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 == ncByte || t == ncChar || t == ncShort || t == ncLong);
}


void MvNcVar::fillAttributes()
{
  if ( !isValid() ) return;
  
  for (int i = 0; i < getNumberOfAttributes(); i++)
    {
      MvNcAtt *tmpatt = new MvNcAtt(ncVar_->get_att(i) );
      attributes_.push_back(tmpatt);
    }
}

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

  for (unsigned int i = 0;i < attributes_.size();i++ )
    if ( ! strcmp(name.c_str(),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;
}

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

  if ( attributeExists(att->name() ) )
    {
      cout << "Attribute already exists, not adding " << endl;
      return 1;
    }
  
  NcBool ret_code = false;
  NcType type = att->type();

  if ( type == ncByte || type == ncChar ) 
    {
      const char *vals = (const char *) att->values()->base();
      ret_code =  addAttribute(att->name(),att->values()->getNumberOfValues(),
			       vals);
    }
  else if ( type == ncShort ) 
    {
      const short *vals = (const short *) att->values()->base();
      ret_code =  addAttribute(att->name(),att->values()->getNumberOfValues(),
			       vals);
    }
  else if ( type == ncLong ) 
    {
      const nclong *vals = (const nclong *) att->values()->base();
      ret_code =  addAttribute(att->name(),att->values()->getNumberOfValues(),
			       vals);
    }
  else if ( type == ncFloat ) 
    {
      const float *vals = (const float *) att->values()->base();
      ret_code =  addAttribute(att->name(),att->values()->getNumberOfValues(),
			  vals);
    }
  else if ( type == ncDouble ) 
    {
      const double *vals = (const double *) att->values()->base();
      ret_code =  addAttribute(att->name(),att->values()->getNumberOfValues(),
			  vals);
    }

  return ret_code;
}

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 == ncByte || type == ncChar ) 
    ret_code = put((const char *)base, edges);
  else if ( type == ncShort ) 
    ret_code = put((const short *)base, edges);
  else if ( type == ncLong ) 
    ret_code = put((const nclong *)base, edges);
  else if ( type == ncFloat ) 
    ret_code = put((const float *)base, edges);
  else if ( type == ncDouble ) 
    ret_code = put((const double *)base, edges);

  return ret_code;
}


// Now in header file
#if 0
template <class T> 
NcBool MvNcVar::get(vector<T>& vals, const long *counts)
{
  if ( !isValid() ) return false;

  NcBool ret_val;
  int i;
  int num_values = 1;
  
  vals.erase(vals.begin(),vals.end());

  if ( getNumberOfDimensions() >  0 )
    {
      for (i=0;i < getNumberOfDimensions();i++)
	num_values *= counts[i];
      
      T *ptr = new T[num_values];
      
      ret_val = ncVar_->get(ptr,counts);
      if ( ret_val )
	for (i=0;i < num_values;i++)
	  vals.push_back(ptr[i]);
      
      delete [] ptr;
    }
  else 
    {  // Scalar
      T *scalarval = (T*)values()->base();
      if ( scalarval )
	vals.push_back(scalarval[0]);
    }
  return ret_val;
}
#endif

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

		ret_val = ncVar_->get(ptr,counts);

		if ( ret_val )
		{
			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;
}

// Now in header file
#if 0 
template <class T> 
NcBool MvNcVar::get(vector<T>& vals, long c0, long c1,
		    long c2, long c3, long c4 ) 
{
  long counts[5];
  counts[0] = c0; counts[1] = c1;counts[2] = c2;
  counts[3] = c3; counts[4] = c4;
  
  return get(vals,counts);
}
#endif

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

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

void MvNcVar::getStringType(string& strtype)
{
  if ( type() == ncByte )
    strtype = "ncbyte ( unsigned char)";
  else if ( type() == ncChar )
    strtype = "char";
  else if ( type() == ncShort )
    strtype = "short";
  else if ( type() == ncLong )
    strtype = "nclong ( int )";
  else if ( type() == ncFloat  )
    strtype = "float";
  else if ( type() == ncDouble )
    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

NcBool MvNcVar::putAttributeWithType(const string& name, NcType nctype, double value)
{
    NcBool r = 0;
    switch(nctype)
    {
    case ncByte: {
        if (value < 0)
        {
            signed char val_char = (signed char) value;
            r = ncVar_->add_att(name.c_str(), val_char);
        }
        else
        {
            unsigned char val_uchar = (unsigned char) value;
            r = ncVar_->add_att(name.c_str(), val_uchar);
        }
        break;
    }
    case ncShort: {
        short int val_short = (short int) value;
        r = ncVar_->add_att(name.c_str(), val_short);
        break;
    }
    case ncInt: {
        int val_int = (int) value;
        r = ncVar_->add_att(name.c_str(), val_int);
        break;
    }
    case ncFloat: {
        float val_flt = (float) value;
        r = ncVar_->add_att(name.c_str(), val_flt);
        break;
    }
    case ncDouble: {
        double val_dbl = (double) value;
        r = ncVar_->add_att(name.c_str(), 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 == ncDouble)  // 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;
        //cout <<  "rescale netcdf: max_int: " <<  nc_type_values_[idx].nc_type_max
        //     << " min_int: " << nc_type_values_[idx].nc_type_min << endl;


        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;

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

        //grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: idx is: %d", idx);
        //grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: max: %lf, min: %lf, midrange: %lf, scale factor: %lf, add_offset: %lf", max, min, midrange, sf, ao);

        x = ((midrange - ao));
        //grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: x=%lf", x);
        x /= sf;
        //grib_context_log(ctx, GRIB_LOG_DEBUG, "grib_to_netcdf: x=%lf", x);

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

        //cout << "rescale netcdf: scaled_max: " <<  scaled_max
        //     << " scaled_min: "                << scaled_min
        //     << " scaled_midrange: "           << scaled_midrange
        //     << " x: "                         << x  << endl;

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

        //cout << "rescale netcdf: max: " << max
        //     << " min: "                 << min
        //     << " midrange: "            << midrange << endl;


        if (scaleFactor_ != sf || addOffset_ != ao)
        {
            std::cout << "Applying new scaling factor to netCDF variable to fit packed type" << std::endl;
            scaleFactor_ = sf;
            addOffset_   = ao;

            // can't use the MvNcVar version because it will not overwrite an existing attribute
            NcBool ret_code1 = ncVar_->add_att("scale_factor", sf);
            NcBool ret_code2 = ncVar_->add_att("add_offset",   ao);
            NcBool 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 == TRUE)
                attributes_.push_back( new MvNcAtt(ncVar_->get_att(attributes_.size())) );
      
        }
    }
}


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



////////////////////// End MvNcVar ///////////////////




////////////////////////////////////  MvNetCDFBehaviour //////////////////

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



/////////////////////////////////  End MvNetCDFBehaviour ////////////////


////////////////////////////////////  MVNetCDF //////////////////

// Empty constructor
MvNetCDF::MvNetCDF() : ncFile_(NULL) {}

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

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

  init(path,mode);
}

// Function to initialize MvNetCDF.
void MvNetCDF::init(const string& path,const char mode)
{
  NcFile::FileMode fmode =NcFile::ReadOnly;
  if ( mode ==  'w' ) fmode = NcFile::Replace;
  else if ( mode == 'u' ) fmode = NcFile::Write;
  ncFile_ = new MvNcFile(path,fmode);

  if ( !isValid() ) 
    return;
  path_ = path;
  countMap_[path_]++;

  fillVariables();
}

// Destructor, just deletes it's NcFile pointer if it's the last instance
// that has this file open.
MvNetCDF::~MvNetCDF()
{
  if ( !isValid() ) return;

  if ( --countMap_[path_] == 0 )
    {
      vector<MvNcVar *>::iterator ii;
      
      for ( ii = variables_.begin(); ii != variables_.end();ii++)
	delete (*ii);
      
      delete globalVar_;

      delete ncFile_;

    }
  else 
    cout << " Count " << countMap_[path_] << " not deleting" << endl;
}

MvNcVar* MvNetCDF::addVariable(const string &name,NcType type, int size,
			       const NcDim **dim)
{
  if ( variableExists(name) )
    {
      cout << "Variable already exists, returning existing: " << name.c_str() <<  endl;
      return getVariable(name);
    }

  MvNcVar *tmpvar = new MvNcVar(ncFile_->add_var(name.c_str(),type,size,dim), false, this);
  variables_.push_back(tmpvar);
  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) )
    {
      cout << "Variable already exists, returning existing: " << name.c_str() << endl;
      return getVariable(name);
    }

  
  NcDim *dim[5];
  int number = 0;
  char dim_name[100];

  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 NcDim**)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) )
    {
      cout << "Variable already exists, returning existing: " << name.c_str() << endl;
      return getVariable(name);
    }

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

  // Add dimensions
  for (num = 0; num < dimsize.size(); num++)
      dim[num] =  addDimension(vname[num].c_str(),dimsize[num]);

  return addVariable(name,type,num,(const NcDim**)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 ( ! strcmp(name.c_str(),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;
  NcDim *tmpdim;
  for (int i = 0; i < ncFile_->num_dims();i++)
    {
      tmpdim = ncFile_->get_dim(i);
      r.setValue(tmpdim->name(),tmpdim->size());
    }   
}

// Fill in all the variables for a NetCDF file.
void MvNetCDF::fillVariables()
{ 
  if ( !isValid() ) return;
  
  int i;
  for (i = 0; i < ncFile_->num_vars(); i++)
    {
      NcVar* nctmp = ncFile_->get_var(i);
      MvNcVar *tmpvar = new MvNcVar(nctmp, false, this);
      variables_.push_back(tmpvar);
    }

  globalVar_ = new MvNcVar(ncFile_->globalVariable(), true, this);
}


// 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;
  NcDim *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() == ncChar ) 
		var_req.addValue(tmpatt->name(),tmpatt->as_string(0));
	      else
		for ( k = 0; k < tmpatt->getNumberOfValues();k++ )
		  var_req.addValue(tmpatt->name(),tmpatt->as_string(k));
	    }
	}
      // 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 = (const char *)tmpatt->name();

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

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

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

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

  // size = 0 means unlimited dimension
  if ( size )
     return ncFile_->add_dim(name.c_str(),size);
  else
     return ncFile_->add_dim(name.c_str());
}

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

  for (int i = 0; i < getNumberOfDimensions(); i++)
  {
      if ( ! strcmp(name.c_str(),getDimension(i)->name()) )
	return 1;
  }

  return 0;
}


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


////////////////////////////// End MvNetCDF functions. //////////////
