//  CDFDataSource.h
//
//  Qdos - data interfacing
//
//  David Burgess
//  June 1999, March 2000
//

#ifndef QDOS_CDFDATASOURCE_H
#define QDOS_CDFDATASOURCE_H

#include <vector>
#include <string>
#include <map>

#include "Exception.h"
#include "DataSource.h"
#include "DataAvailability.h"
#include "DataBuffer.h"
#include "Naming.h"
#include "QdTime.h"
#include "FARdb.h"
#include "DCMSpec.h"
#include "cdf.h"
#define GLOBAL_ATTRIBUTES "global_attributes"
#define VARIABLE_ATTRIBUTES "variable_attributes"

namespace QDOS {

class CDFException : public DataSourceException {
protected:
  CDFstatus cdf_status;
public:
  CDFException( CDFstatus s, ExceptionMsg m="" )
    : DataSourceException(m<<"[CDFException]"), cdf_status(s) {;}
  void print_msg(void) {
   Exception::print_msg();
   cout << "CDFException::print_msg: CDFstatus: " << cdf_status << endl;}
};

class CDFFileBadOpen : public CDFException {
public:
  CDFFileBadOpen( CDFstatus s, ExceptionMsg m="" )
    : CDFException(s,m<<" [CDFFileBadOpen]") {;}
};
class CDFFileBadRead : public CDFException {
public:
  CDFFileBadRead( CDFstatus s, ExceptionMsg m="" )
    : CDFException(s,m<<" [CDFFileBadRead]") {;}
};
class CDFFileNoSuchVariable : public CDFException {
public:
  CDFFileNoSuchVariable( CDFstatus s, ExceptionMsg m="" )
    : CDFException(s,m<<" [CDFFileNoSuchVariable]") {;}
};
class CDFFileNoSuchAttribute : public CDFException {
public:
  CDFFileNoSuchAttribute( CDFstatus s, ExceptionMsg m="" )
    : CDFException(s,m<<" [CDFFileNoSuchAttribute]") {;}
};
class CDF_TSNoTimeVariable : public CDFException {
public:
  CDF_TSNoTimeVariable( CDFstatus s, ExceptionMsg m="" )
    : CDFException(s,m<<" [CDF_TSNoTimeVariable]") {;}
};
class CDF_TSTimeTagsNonConformant : public CDFException {
public:
  CDF_TSTimeTagsNonConformant( CDFstatus s, ExceptionMsg m="" )
    : CDFException(s,m<<" [CDF_TSTimeTagsNonConformant]") {;}
};
class CDF_TSDataNonConformant : public CDFException {
public:
  CDF_TSDataNonConformant( CDFstatus s, ExceptionMsg m="" )
    : CDFException(s,m<<" [CDF_TSDataNonConformant]") {;}
};


// ++++++++++++++++++++++++++++++++++++++++++++++++++++ class CDFFileDataSource
// ----------------------------------------------------------------------------
class CDFFileDataSource
  : public DataSource, RecordStructuredDataSource {

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ nested classes
 protected:  
 class CDFTypeInfo {  // info common to var, gattr and vattr
  public:
    long data_type;        // CDF data type
    long num_elems;        // CDF number of "elements"
    int  num_bytes_elt;   //  number of bytes for one "item" of this type

    void calc_bytes_elt(void) ;
    string data_type_srep(void);
  };

  class CDFVarInfo;
  
  class CDFVarBuffer {
  public:
    void *p;
    CDFVarBuffer( const CDFVarInfo& v, int size=1 );
    ~CDFVarBuffer(void) { if( p!=0 ) free(p);  };
  };

  class CDFVarInfo : public CDFTypeInfo {
// the following use the cdf nomenclature
    string name;
    long rec_vary;
    long max_rec_num;
    long num_dims;
    long dim_sizes[CDF_MAX_DIMS];
    long dim_varys[CDF_MAX_DIMS];
// number of elements (ie numbers) in a (multi-dimensional) variable
    int   num_elts;
    int   num_bytes_var; //  number of bytes for all of a variable

    friend class CDFFileDataSource;
    friend class CDFFileDataSource::CDFVarBuffer;
    friend class CDF_TSFileDataSource;
    friend class CDFDataBaseDataSource;

  public:
// default constructor uses currently selected CDF file
    CDFVarInfo( const string& var_name );
//    ~CDFVarInfo(void) { cout << "CDFVarInfo destrucutopr" << endl; }
    
    string var_info_srep(void);
  };

  class CDFGAttrInfo : public CDFTypeInfo {
    const CDFFileDataSource& cdf;
  public:
    string _gattr_name;
    long attr_num;
    long max_entry_num;
    long num_entries;

    CDFGAttrInfo(
      const CDFFileDataSource& cdf_file_ds, const string& gattr_name);    
    string gattr_info_srep(void);    
  };

  class CDFGAttrBuffer {
  public:
    void *p;

    CDFGAttrBuffer( const CDFGAttrInfo& g );
    CDFGAttrBuffer( long cdf_data_type, long num_elements );
    ~CDFGAttrBuffer(void) { if( p!=0 ) free(p);  };
  };

  class CDFVAttrInfo : public CDFTypeInfo {
    const CDFFileDataSource& cdf;
  public:
    string _vattr_name;
    string _var_name;
    long attr_num;
    long var_num;
     
    CDFVAttrInfo(
      const CDFFileDataSource& cdf_file_ds,
      const string& var_name, const string& vattr_name);
    string vattr_info_srep(void);    
  };

  class CDFVAttrBuffer {
  public:
    void *p;

    CDFVAttrBuffer( const CDFVAttrInfo& a );
    ~CDFVAttrBuffer(void) { if( p!=0 ) free(p);  };
  };

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ end nested classes
  CDFid cdf_id;      // file id from CDF lib
  long cdf_majority; // ROW_MAJOR (1st dim slowest) or COL_MAJOR
  long cdf_version;  // version number of writing library
  long cdf_release;  // release number of writing library
  long cdf_release_increment; // increment of writing library
  
protected:
  bool open_status;  
  string file_name;  // name of CDF file
  
  DataNamesTable_var data_names_table;
  static DataNamesTable_var attr_names_table;

public:
  bool cdf_variable_exists( const string& var_name ) const;
  bool cdf_vattribute_exists( 
         const string& var_name, const string& attr_name ) const;
  bool cdf_vattribute_exists( int i_var, int i_attr ) const;
  bool cdf_gattribute_exists( const string& attr_name ) const;
  bool cdf_gattr_entry_exists( int i_attr, int i_entry ) const;

  int get_num_cdf_variables(void) const;
  string get_cdf_variable_name( int i_var ) const;
  int get_cdf_variable_number( const string& var_name ) const;

  int get_num_cdf_attributes(void) const;
  string get_cdf_attribute_name( int i_attr ) const;
  int get_cdf_attribute_number( const string& attr_name ) const;
  int get_cdf_attribute_scope( int i_attr ) const;
  int get_cdf_attribute_scope( const string& attr_name ) const;
  void get_gattr_data_type( int i_attr, int i_entry,
                            long& data_type, long &num_elems ) const;

  string get_ptr_attr_name(const string&) const;
  string get_reg_attr_name(const string&) const;
  static int num_bytes_for_cdf_data_type( long data_type );

  void create_data_names_table(void);

// so that these classes can use the utility functions 
  friend class CDFVAttrInfo;
  friend class CDFGAttrInfo;  
  friend class CDFGAttrBuffer;  
  friend class CDFVAttrBuffer; 

  friend class CDFDataBaseDataSource; 

public:

  CDFFileDataSource( string file_name )
    : open_status(false) { open( file_name );}
  ~CDFFileDataSource(void) { close(); }

  void open( const string& cdf_file_name );
  void close();
  void select( void ) const;  // CDFlib select this object's cdf_id

  DataNamesTable_var get_data_names(void);
  DataBuffer_var get_data( const string& data_name );
  DataBuffer_var get_data( const string& data_name, int rec_start, int num_rec );
    
  DataBuffer_var get_data_by_variable(
    const string& var_name, int rec_start=0, int num_rec=-1 );
  DataBuffer_var get_var_cdf_data_type( const string& var_name );

  DataBuffer_var get_vattr_data(
                 const string& var_name, const string& vattr_name );
  DataBuffer_var get_vattr_cdf_data_type( 
                 const string& var_name, const string& vattr_name );
  DataBuffer_var get_gattr_data( const string& gattr_name );
  DataBuffer_var get_gattr_cdf_data_type( const string& gattr_name );
};

typedef Var<CDFFileDataSource> CDFFileDataSource_var;

// ++++++++++++++++++++++++++++++++++++++++++++++++++ class CDFTimeVariableInfo
// ----------------------------------------------------------------------------

class CDFTimeVariableInfo {

  string time_variable_name;
  double scaling_to_seconds;
  double offset_seconds_epoch_0AD;

  friend class CDF_TSFileDataSource;

public:
  CDFTimeVariableInfo(void)
    : time_variable_name( "" ), scaling_to_seconds( 0.001 ),
      offset_seconds_epoch_0AD( 0.0 ) {;}
  CDFTimeVariableInfo( const string& tv_name,
                       double scaling=0.001, double offset = 0.0 )
    : time_variable_name( tv_name ), scaling_to_seconds( scaling ),
      offset_seconds_epoch_0AD( offset ) {;}
  CDFTimeVariableInfo( const CDFTimeVariableInfo& a ) 
    : time_variable_name( a.time_variable_name ),
      scaling_to_seconds( a.scaling_to_seconds ),
      offset_seconds_epoch_0AD( a.offset_seconds_epoch_0AD ) {;}

  double seconds0AD( double x ) const
    { return scaling_to_seconds*x + offset_seconds_epoch_0AD; }

}; // end class CDFTimeVariableInfo


// +++++++++++++++++++++++++++++++++++++++++++++++++ class CDF_TSFileDataSource
// ----------------------------------------------------------------------------

class CDF_TSFileDataSource
  : public CDFFileDataSource, public TimeStructuredDataSource {

  map<string,CDFTimeVariableInfo> time_variable_table;
  map<string,IgnoreNumericDataFunctor_var> numeric_ignoration_table;

public:
  CDF_TSFileDataSource( string cdf_file_name );
  ~CDF_TSFileDataSource(void) { CDFFileDataSource::close(); }

  void set_ISTP_time_variables_default( void );

  void set_time_variable( const string& variable_name,
    const string& time_variable_name,
    double scaling=0.001, double offset = 0.0 );

  void diag_print(void);

  DataBuffer_var get_data_by_variable(
    const string& var_name,
    const TimeInterval& req_time_interval,
    int subsample_cycle=1, int subsample_offset=0 );


  DataBuffer_var get_data( const string& data_name ) 
    { return CDFFileDataSource::get_data( data_name ); }
  DataBuffer_var get_data( const string& data_name, int rec_start, int num_rec ) 
    { return CDFFileDataSource::get_data( data_name, rec_start, num_rec ); }

  DataBuffer_var
    get_data( const string& data_name, const TimeInterval& time_interval );

  void set_numeric_ignoration_functor(
     string var_name, IgnoreNumericDataFunctor_var ignore_functor )
    { numeric_ignoration_table[var_name] = ignore_functor; }

  void get_data_availability_by_variable( const string& variable_name,
    DataAvailability& data_availability );

  TimeInterval get_time_range_by_variable( DataBuffer_var file_ttags_buff_var );
  
  void get_dcm_entries( FAR::FARData& dcm_entries,
                              const string& data_name_root,
                              const string& file_name_root,
                              double data_availability_resolution)
							  {
							  //  dcm_entries.clear();

  time_t clock = time(0);
  string now_time = asctime(localtime(&clock));
// get rid of the trailing newline!
  now_time.erase(24);

  FAR::Record comment;
  comment.add_field("comment");
  comment.add_field("make_dcm_entry " + file_name);
  comment.add_field(now_time);
  dcm_entries.add_record(comment);

  int nvar = get_num_cdf_variables();
//  int nattr= get_num_cdf_attributes();
  for( int ivar=0; ivar<nvar; ivar++ )
  {
    try{
      string var_name = get_cdf_variable_name(ivar);
      CDFVarInfo cdf_var_info( var_name );

      // No entries for variables without an associated time variable
      if( time_variable_table.count( var_name ) == 0 )
      {
        //cout << "No time variable for cdf variable: " << var_name << endl;
        continue;
      }
      CDFTimeVariableInfo time_var_info = time_variable_table[ var_name ];
      //CDFVarInfo cdf_time_var_info( time_var_info.time_variable_name );

      DataAvailability da;
      da.set_resolution( data_availability_resolution );
      get_data_availability_by_variable( var_name, da );
      TimeInterval time_range = da.get_time_interval();

      char cbuff[50]; sprintf(cbuff,"%d", cdf_var_info.max_rec_num+1);
      string rec_num_str = cbuff;

      const int rec_sz(DCM_CDF_TS_DATA_NUM_FLDS);
      vector<string> flds(rec_sz);
      FAR::Record rec(flds);

      rec[(int)DCM_RECORD_TYPE_FLD]=string(DCM_DATA_RECORD);
      rec[(int)DCM_DATA_TYPE_FLD]=string(DCM_CDF_TS_DATA);
      // we need to pass in priority as an argument also
      rec[(int)DCM_CDF_TS_DATA_PRIORITY_FLD]="0";
      rec[(int)DCM_CDF_TS_DATA_DATASOURCE_FLD]=data_name_root + var_name;
      rec[(int)DCM_CDF_TS_DATA_VAR_NAME_FLD]=var_name;
      rec[(int)DCM_CDF_TS_DATA_TIME_VAR_NAME_FLD]=\
        time_var_info.time_variable_name;
      rec[(int)DCM_CDF_TS_DATA_TIME_TYPE_FLD]="cdf_epoch";
      Name file_name_name(file_name);
      rec[(int)DCM_CDF_TS_DATA_FILE_NAME_FLD]=file_name_root + \
        file_name_name.get_component(file_name_name.num_components()-1).get_id();
      rec[(int)DCM_CDF_TS_DATA_TIME_INT_START_FLD]=\
        time_range.start().iso_srep();
      rec[(int)DCM_CDF_TS_DATA_TIME_INT_END_FLD]=\
        time_range.end().iso_srep();
      rec[(int)DCM_CDF_TS_DATA_NUM_ITEMS_FLD]=rec_num_str;
      rec[(int)DCM_CDF_TS_DATA_AVAILABILITY_FLD]=da.srep();
      dcm_entries.add_record(rec);
    }
    catch(Exception& e)
    { e.print_msg(); continue;}

  }

}

}; // end class CDF_TSFileDataSource

typedef Var<CDF_TSFileDataSource> CDF_TSFileDataSource_var;

} // end namespace QDOS


#endif //  #ifndef QDOS_CDFDATASOURCE_H
