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

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

////////////////////// 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,int is_global): 
  edges_(NULL),ncVar_(ncvar),values_(NULL), isGlobal_(is_global)
{ 
  fillAttributes(); 
}

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::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";
}
////////////////////// End MvNcVar ///////////////////


////////////////////////////////////  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));
  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);
      variables_.push_back(tmpvar);
    }

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


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

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