/***************************** 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 "netcdf.hh"
#include "Metview.h"

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:
  // 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  values()->as_double(n); }
  char* as_string( long n ) { return  values()->as_string(n); } 

  virtual MvNcValues *values() = 0;
  virtual NcTypedComponent *delegate() = 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);

private:

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

  NcAtt *ncAtt_;
  MvNcValues *values_;
};


// Wrapper for NcVar
class MvNcVar : public MvNcBase
{
public:

  virtual ~MvNcVar();

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

  void getStringType(string &);

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

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

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

  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); }
  
  template <class T> NcBool put(const T *vals,const long *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, int is_global = 0 );
  MvNcVar(const MvNcVar& vv);

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

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


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.

  // 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_;  
  
  vector<MvNcVar *> variables_;
  MvNcVar *globalVar_;
};

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

#endif
