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

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

#ifndef MV_NETCDF_H
#define MV_NETCDF_H

#include <stdio.h>
#include <ctype.h>

#include <string.h>
#include "inc_iostream.h"
#include "inc_stl.h"

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

#include "netcdfcpp.h"
#include "Metview.h"


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

#define NETCDF_MISSING_VALUE  mars.grib_missing_value



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

typedef map <string, int > CountMap;

// Wrapper for NcFile.
// Only done to get global "variable" for the global attributes.
// This is a protected member of NcFile.
class MvNcFile : public NcFile
{
public:
  MvNcFile(const string &path,FileMode xx = ReadOnly) : 
    NcFile(path.c_str(),xx) {}
  virtual ~MvNcFile() {}
  NcVar *globalVariable() { return globalv; }
};


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

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


// Abstract class for common attribute/variable operations.
class MvNcBase
{
public:

  MvNcBase() : hasMissingValueIndicator_(false), missingValueIndicator_(-999),
               scaleFactor_(1), addOffset_(0) {};

  // General
  const char *name() { return delegate()->name(); }
  NcType type() { return delegate()->type(); }
  virtual NcBool isValid() { return delegate()->is_valid(); }

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

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


protected:

  bool   hasMissingValueIndicator_;
  double missingValueIndicator_;
  double scaleFactor_;
  double addOffset_;

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

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

};


// Wrapper for NcAtt
class MvNcAtt : public MvNcBase
{
public:

  virtual ~MvNcAtt();
  NcTypedComponent *delegate() { return ncAtt_; }

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

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


private:

  friend class MvNcVar;
  MvNcAtt(NcAtt *ncAtt);
  MvNcAtt(const MvNcAtt& aa);
  
  void tztrim(char*);
  void printValue(string &,double);

  NcAtt *ncAtt_;
  MvNcValues *values_;
};



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

class MvNetCDFBehaviour
{
public:
    MvNetCDFBehaviour();
   ~MvNetCDFBehaviour() {};

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

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

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

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

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

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


// Wrapper for NcVar
class MvNetCDF; // forward declaration

class MvNcVar : public MvNcBase
{
public:

  virtual ~MvNcVar();

  NcTypedComponent *delegate() {return ncVar_; }
  
  NcBool isValid() {
    if (isGlobal_ ) return true;
    else return delegate()->is_valid(); 
  }

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

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


  MvNetCDFBehaviour &options();

  void copyMissingValueAttributeIfNeededFrom(MvNcVar *from);


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


  MvDate processDate(double val);




  // Attributes
  int getNumberOfAttributes() { return ncVar_->num_atts(); }
  MvNcAtt* getAttribute(const string &);
  MvNcAtt* getAttribute(unsigned int index);

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

    return false;
  }
  
  template <class T> 
  bool getAttributeValues(unsigned int index, vector<T>& vec)
  {
    if ( !isValid() ) return false;
    
    if ( index <= (attributes_.size() - 1) )
      return getAttributeValues(attributes_[index],vec);
    
    return false;
  }

  // Two following functions inlined because of gcc template instantiation
  // problems. Should really be in cc file.
  template <class T>  NcBool addAttribute(const string& name ,T value)
    { 
      if ( !isValid() ) return false;
      
      if ( attributeExists(name) )
	{
	  cout << "Attribute already exists, not adding " << endl;
	  return 1;
	}
      
      NcBool ret_code = ncVar_->add_att(name.c_str(),value);
      
      if ( ret_code == TRUE )
	attributes_.push_back( new MvNcAtt(ncVar_->get_att(attributes_.size() ) ) );
      
      return ret_code;
    }

  template <class T>  NcBool addAttribute(const string& name, int nr, T *values)
    {
      if ( !isValid() ) return false;
      
      if ( attributeExists(name) )
	{
	  cout << "Attribute already exists, not adding " << endl;
	  return 1;
	}
      
      NcBool ret_code = ncVar_->add_att(name.c_str(),nr,values);
      
      if ( ret_code == TRUE )
        attributes_.push_back( new MvNcAtt(ncVar_->get_att(attributes_.size() ) ) );
      
      return ret_code;
    }

  NcBool addAttribute(MvNcAtt *att);
  NcBool attributeExists(const string& name);

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

  long *edges() 
  {
    if (!edges_ )
      edges_ =  ncVar_->edges();
    return edges_;
  }
  
  long numValsFromCounts(const long* counts)
  {
    long num_values = 1;
	int ndim = getNumberOfDimensions();

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

    return num_values;
  }
  

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

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

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

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

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

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


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

	return ret_val;
}

  template <class T>  NcBool get( vector<T>& vals, long c0=0, long c1=0,
				  long c2=0, long c3=0, long c4=0 )
    {
      long counts[5];
      counts[0] = c0; counts[1] = c1;counts[2] = c2;
      counts[3] = c3; counts[4] = c4;
      
      return get(vals,counts);
    }


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


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

  // Records
  NcBool setCurrent(long c0=-1, long c1=-1, long c2=-1,
		 long c3=-1, long c4=-1) 
    { return ncVar_->set_cur(c0,c1,c2,c3,c4); }

  NcBool setCurrent(long* cur) { return ncVar_->set_cur(cur); }



  // packValues
  // handle any processing that needs to be done before encoding the values in netCDF
  // e.g. handle missing values and scaling factors

  template <class T>
  void packValues(T *vals, const long *counts)
  {
    bool isInt = isIntegerType();
    bool doScale = hasScaling() && options().scaleValues();
    bool doMissing = hasMissingValueIndicator_ && options().detectMissingValues();
    if (doMissing || doScale)
    {
      long n = numValsFromCounts(counts);

      if (doScale)
        recomputeScalingIfNecessary(vals, n);

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

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

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

  NcBool putAttributeWithType(const string& name, NcType nctype, double value);


  template <class T> NcBool put(const T *vals,const long *counts) 
    { return ncVar_->put(vals,counts); }
  
  template <class T> NcBool packAndPut(T *vals,const long *counts) 
  {
    packValues(vals, counts);
    return ncVar_->put(vals, counts);
  }


  template <class T>
  NcBool put (const T *vals,long c0=0,long c1=0,long c2=0,long c3=0,long c4=0)
    { return ncVar_->put(vals,c0,c1,c2,c3,c4); }

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

  NcBool put(MvNcVar *var);
  
private:
  friend class MvNetCDF;
  MvNcVar(NcVar *ncvar, bool is_global, MvNetCDF *parent);
  MvNcVar(const MvNcVar& vv);

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


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

  bool isIntegerType();

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

  void fillAttributes();

  template <class T> NcBool put_rec(const T *vals,long i= -1) 
    {
      if ( i >= 0 ) return ncVar_->put_rec(vals,i); 
      else return ncVar_->put_rec(vals);
    }
  
  long *edges_;
  NcVar *ncVar_;
  vector<MvNcAtt *> attributes_;
  MvNcValues *values_;
  bool isGlobal_;
  MvNetCDF *parent_;
  bool isTime_;
  MvDate refDate_;
  double timeScaleFactor_;




  #define NC_TYPES 7
  struct nc_types_values {
    double nc_type_max;
    double nc_type_min;
    double nc_type_missing;
  };
  
  static nc_types_values nc_type_values_[NC_TYPES];
};

// end of class MvNcVar



class MvNetCDF
{
public:

  MvNetCDF();                              // Empty constructor
  MvNetCDF(const string &, const char mode = 'r');   // From file name
  MvNetCDF(const MvRequest&, const char mode = 'r'); //From request
  virtual ~MvNetCDF();
  
  bool isValid() { return ncFile_->is_valid(); }  // Check if file is OK.

  static MvNetCDFBehaviour &options() {return options_;}

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

  // Variables
  int    getNumberOfVariables() { return ncFile_->num_vars(); }
  MvNcVar* getVariable(const string &);
  MvNcVar* getVariable(int index) { if (index == -1) return globalVar_; else return variables_[index]; }
  MvNcVar *addVariable(const string & name,NcType type, int size, const NcDim **dim);
  MvNcVar *getGlobalVariable() { return globalVar_; }

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

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

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

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

      return currVar;
    }
      
  template <class T>
  NcBool getDataForVariable(vector<T> &vec,const string &name, long *given_edges = 0)
    {
      if ( !isValid() ) return false;
      
      long *edges;
      MvNcVar *var = getVariable(name);
      if ( !var ) return false;
      
      if ( !given_edges ) edges = var->edges();
      else edges = given_edges;
      
      return var->get(vec,edges);
    }

  NcType getTypeForVariable(const string & name);
  void   getStringTypeForVariable(const string &name, string &);

  // Dimensions
  int   getNumberOfDimensions() { return ncFile_->num_dims(); }
  NcDim *getDimension(const string &name) { return ncFile_->get_dim(name.c_str() ); }
  NcDim *getDimension(int index)        { return ncFile_->get_dim(index); }
  NcDim *addDimension(const string&,long s=0);
  NcBool  dimensionExists(const string&);
  
  // Attributes.
  int getNumberOfAttributes() { return ncFile_->num_atts(); } 
  MvNcAtt* getAttribute(int i) { return globalVar_->getAttribute(i); }
  MvNcAtt* getAttribute(const string &name) 
    { return globalVar_->getAttribute(name.c_str() ); }

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

  template <class T>
  NcBool addAttribute(const string& name, T val) 
    { return globalVar_->addAttribute(name.c_str() ,val); }

  template <class T>
  NcBool addAttribute(const string& name, int n, const T *val) 
    { return globalVar_->addAttribute(name.c_str() ,n,val); }

  NcBool addAttribute(MvNcAtt *att) { return globalVar_->addAttribute(att); }
  NcBool attributeExists(const string& name) { return globalVar_->attributeExists(name); }

  // File operations.
  const string& path() { return path_; }
  NcBool sync() { return ncFile_->sync(); }
  NcBool close() { return ncFile_->close(); }
  int getFillMode() { return ncFile_->get_fill(); }
  NcBool setFillMode(int mode = CDF_FILL) 
    { return ncFile_->set_fill((NcFile::FillMode)mode); }
  void init(const string& path,const char mode = 'r');

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

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

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

  // the behaviour options should be global
  static MvNetCDFBehaviour options_;

  vector<MvNcVar *> variables_;
  MvNcVar *globalVar_;
};

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

#endif
