//
//  CDFDataConfigurationMap.h
//
//  Qdos - Data Configuration Map
//
//

#ifndef QDOS_DATA_CONFIGURATION_MAP_H_
#define QDOS_DATA_CONFIGURATION_MAP_H_

#include <cstdio>
#include <string>
#include <vector>
#include <map>
#ifdef HAVE_SSTREAM_H
#include <sstream>
#else
#include <strstream>
#endif
#include <fstream>
#include <stack>
#include <set>
#include <iterator>
#include <algorithm>

#include "Exception.h"
#include "Var.h"
#include "Viostream.h"
#include "Naming.h"
#include "QdTime.h"
#include "FARdb.h"
#include "DataAvailability.h"
#include "DCMSpec.h"
#include "DataSource.h"

namespace QDOS {

typedef FAR::FARData CDF_DCMFileData;
typedef FAR::Record CDF_DCMRecord;
typedef FAR::Field CDF_DCMField;

class CDF_DCMWarning
{
public:
    CDF_DCMWarning(string msg)
    { cout << "CDFDataConfigurationMapWarning: " << msg << endl;}
};   

class CDF_DCMException : public Exception {
public:
    CDF_DCMException(ExceptionMsg msg )
      : Exception( msg <<  " [CDF_DCMException]" )
    {Exception& e=*this;e.print_msg();}
};

// Entry corresponding to "data,cdf" line

class CDF_DCMEntry;

Vostream& operator<<(Vostream&,const CDF_DCMEntry&);
Vistream& operator>>(Vistream&, CDF_DCMEntry&);

class CDF_DCMEntry {

  friend Vostream& operator<<(Vostream&,const CDF_DCMEntry&);
  friend Vistream& operator>>(Vistream&, CDF_DCMEntry&);

  string _data_source_name;
  double _data_priority;
  string _variable_name;
  string _time_tags_variable_name;
  string _time_tags_type;
  string _file_name;
  int _num_items;
  DataAvailability _data_availability;

  friend class CDF_DCMEntryMap;
  friend class CDF_DCMDataInterval;
  friend class CDF_DCMDataIntervalList;
  friend class CDFDataBaseDataSource;

public:
  CDF_DCMEntry(){;}
  CDF_DCMEntry( const char* data_source_name, double priority,
    const char* data_var_name,
    const char* time_tags_var, const char* time_tags_type,
    const char* file_name, double time_interval_start,
    double time_interval_end, int num_items,
    const char* data_avail="" );

  bool is_data_available(const TimeInterval& tint) const;
  bool is_data_available(const Time& t) const;
  TimeInterval get_time_interval() const;
  void diag_print() const
  { cout << "CDF_DCMEntry for : " << _data_source_name <<
      " priority: " << _data_priority <<
      " variable name: " << _variable_name <<
      " time tags name: " << _time_tags_variable_name <<
      " time tags type: " << _time_tags_type <<
      " file: " << _file_name <<
      " num items: " << _num_items << endl;
      _data_availability.diag_print();
   }

};

// class for ... DCM entry (ie file and var name specification)
//              PLUS: time interval to be used from that entry

class CDF_DCMDataInterval {

  CDF_DCMEntry _dcm_entry;
  TimeInterval _time_interval;
  
  friend class CDF_DCMDataIntervalList;
  friend class CDFDataBaseDataSource;

public:
    CDF_DCMDataInterval( const CDF_DCMEntry& dcm_entry,
                         const TimeInterval& time_interval )
      :  _time_interval(time_interval),_dcm_entry(dcm_entry) {;}

    CDF_DCMDataInterval( const CDF_DCMDataInterval& a )
      :  _time_interval(a._time_interval),_dcm_entry(a._dcm_entry) {;}

};

class CDF_DCMDataIntervalList : public vector<CDF_DCMDataInterval>
{
public:
    int get_num_entries() const
    {
      int num=0;
      for(int i=0;i<size();i++)
         num+=(*this)[i]._dcm_entry._num_items;
      return num;
    }

    DataNamesTable_var get_file_names()
    { 
      DataNamesTable_var names=new DataNamesTable;
      for(int i=0;i<size();i++)
      {
          const CDF_DCMDataInterval& data_int=(*this)[i];
          try{
            string file_name(data_int._dcm_entry._file_name);
            int offset=0;
            if(file_name.substr(0,1)=="/")
              offset=1;
            names->bindx(file_name.substr(offset),"");
          }
          catch(NamingException& ne)
          {;}
      }
      return names;
    }

    void diag_print()
    {
        for(int i=0;i<size();i++)
        {
            const CDF_DCMDataInterval& data_int=(*this)[i];
            cout << "[ " <<  data_int._time_interval.start() << " , " << \
            data_int._time_interval.end() << " ) : "
	     << data_int._dcm_entry._file_name << endl;
        }
    }
};     


typedef map<int,CDF_DCMEntry*> CDF_DCMEntryTable;
typedef multimap<Time,int> CDF_DCMDatesTable;

class CDF_DCMEntryMap;

Vostream& operator<<(Vostream&,const CDF_DCMEntryMap&);
Vistream& operator>>(Vistream&, CDF_DCMEntryMap&);

class CDF_DCMEntryMap 
{
  friend Vostream& operator<<(Vostream&,const CDF_DCMEntryMap&);
  friend Vistream& operator>>(Vistream&, CDF_DCMEntryMap&);
  typedef map<int,CDF_DCMEntry*>::iterator EntryIter;
  typedef map<int,CDF_DCMEntry*>::const_iterator EntryCIter;
  typedef map<Time,int>::iterator DatesIter;

  CDF_DCMEntryTable _entry_table;
  CDF_DCMDatesTable _startdates;
  CDF_DCMDatesTable _enddates;
  vector<double> _priorities;
  TimeInterval _time_range;
  vector<string> _data_info;
  
public:
  ~CDF_DCMEntryMap();
  int add_entry( CDF_DCMEntry* cdf_data_entry );  
  void get_data_intervals(CDF_DCMDataIntervalList& filelist);
  void get_data_intervals(CDF_DCMDataIntervalList& filelist,
      const TimeInterval& tint);
  double get_priority(int entrynum)
  { return _priorities[-1+entrynum];}
  void get_time_range(TimeInterval& tint) const
  { tint=_time_range;}
  void add_data_info(const string& str)
  {_data_info.push_back(str);}
  void get_data_info(vector<string>& str)
  {  str=_data_info; }
  int next_entry(const TimeInterval& tint); 
  bool is_data_available(const TimeInterval& tint) const;
  bool is_data_available(const Time& t) const;
  void diag_print() const
  { for(EntryCIter itr=_entry_table.begin();itr!=_entry_table.end();itr++)
      itr->second->diag_print();
  }
};

// Entry corresponding to "data,cdf:ts:metadata" line
class CDF_DCMMetaDataEntry;
Vostream& operator<<(Vostream&,const CDF_DCMMetaDataEntry&);
Vistream& operator>>(Vistream&, CDF_DCMMetaDataEntry&);
class CDF_DCMMetaDataEntry
{
  friend Vostream& operator<<(Vostream&,const CDF_DCMMetaDataEntry&);
  friend Vistream& operator>>(Vistream&, CDF_DCMMetaDataEntry&);
  string _data_source_name;
  string _file_data_source_name;
  string _file_type;
  string _file_name;

// not sure if this is needed below ?
  friend class CDFDataBaseDataSource;

public:
    CDF_DCMMetaDataEntry(){;}
    CDF_DCMMetaDataEntry(const char* meta_name, const char* file_type,\
      const char* file_name, const char* file_var_name);
    string get_data_source_name()
    { return _data_source_name;}
    string get_file_variable_name()
    { return _file_data_source_name;}
    string get_file_type()
    {return _file_type;}
    string get_file_name()
    {return _file_name;}

};

inline Vostream& operator<<(Vostream& v_os,const CDF_DCMMetaDataEntry& cdf_dcmentry)
{ v_os << cdf_dcmentry._data_source_name;
  v_os << cdf_dcmentry._file_data_source_name;
  v_os << cdf_dcmentry._file_type;
  v_os << cdf_dcmentry._file_name;
  return v_os;
}

inline Vistream& operator>>(Vistream& v_is, CDF_DCMMetaDataEntry& cdf_dcmentry)
{ v_is >> cdf_dcmentry._data_source_name;
  v_is >> cdf_dcmentry._file_data_source_name;
  v_is >> cdf_dcmentry._file_type;
  v_is >> cdf_dcmentry._file_name;
  return v_is;
}

typedef vector<string> CDF_DCMFileStack;
typedef map<string,string> CDF_DCMAliasTable;

typedef enum CDFDCMReadStatus_e
{
  CDFDCM_SUCCESS=0,
  CDFDCM_FAILURE=1
} CDFDCMReadStatus;

// input file, with aliases, etc
class CDFDataConfigurationMap;

class CDF_DCMFile
{
  string _file_name;
  double _data_priority_offset;
  ifstream _istr;
  ostream _ostr;
  CDF_DCMAliasTable& _alias_table;
  CDF_DCMFileStack& _open_files;
  CDFDataConfigurationMap* _data_map;
  CDFDCMReadStatus _read_status;
  CDF_DCMFileData _file_data;

  friend int DCMyyparse();
  friend void DCMyyerror(const char *s);

public:
  CDF_DCMFile( CDFDataConfigurationMap* data_map, string& file_name,
    CDF_DCMAliasTable& alias_table, CDF_DCMFileStack& open_files,
    double data_priority=0 );
  ~CDF_DCMFile();

  void open();
  CDF_DCMFileData& file_data(){return _file_data;}
  string file_name(){return _file_name;}
  void close();

  CDFDCMReadStatus read_status() const { return _read_status;}

  double data_priority(){ return _data_priority_offset; }
  string get_curdir();
  const char* get_alias(const char* name);

};

// Should these both be vars below ?
typedef NamingContext<CDF_DCMEntryMap*> CDF_DCMDataSourceTable;
typedef Var<CDF_DCMDataSourceTable> CDF_DCMDataSourceTable_var;
typedef NamingContext<CDF_DCMMetaDataEntry*> CDF_DCMMetaDataTable;
typedef Var<CDF_DCMMetaDataTable> CDF_DCMMetaDataTable_var;

Vostream& operator<<(Vostream&,const CDFDataConfigurationMap&);
Vistream& operator>>(Vistream&, CDFDataConfigurationMap&);

class  CDFDataConfigurationMap:public VarTarget
{
  friend Vostream& operator<<(Vostream&,const CDFDataConfigurationMap&);
  friend Vistream& operator>>(Vistream&, CDFDataConfigurationMap&);

  CDF_DCMDataSourceTable_var _data_source_table;
  CDF_DCMMetaDataTable_var _meta_data_table;
  string _search_path;
  vector<string> _dcm_filenames;
  DataNamesTable_var _cdf_filenames;

  public:
  CDFDataConfigurationMap(string searchpath=DCM_SPATH) : \
      _search_path(searchpath)
  {_cdf_filenames=new DataNamesTable;
   _data_source_table=new CDF_DCMDataSourceTable;
   _meta_data_table=new CDF_DCMMetaDataTable;
  }

  ~CDFDataConfigurationMap();

  // abstract class for processing records in file_data
  class ProcessRecord
  {
  public:
      virtual void operator()(CDFDataConfigurationMap&,\
          CDF_DCMFile&,int,CDF_DCMAliasTable&,CDF_DCMFileStack&)=0;
  };

  // class for processing comments
  class ProcessComment:public ProcessRecord
  {
  public:
      void operator()(CDFDataConfigurationMap&,\
          CDF_DCMFile&,int,CDF_DCMAliasTable&,CDF_DCMFileStack&){;}
  };

  // class for processing alias lines
  class ProcessAlias:public ProcessRecord
  {
  public:
      void operator()(CDFDataConfigurationMap&,\
          CDF_DCMFile&,int,CDF_DCMAliasTable&,CDF_DCMFileStack&);
  };

  // class for processing unalias records
  class ProcessUnAlias:public ProcessRecord
  {
  public:
      void operator()(CDFDataConfigurationMap&,\
          CDF_DCMFile&,int,CDF_DCMAliasTable&,CDF_DCMFileStack&);
  };

  // class for processing merge records
  class ProcessMerge:public ProcessRecord
  {
  public:
      void operator()(CDFDataConfigurationMap&,\
          CDF_DCMFile&,int,CDF_DCMAliasTable&,CDF_DCMFileStack&);
  };

  // class for processing data records
  class ProcessData:public ProcessRecord
  {
  public:
      void operator()(CDFDataConfigurationMap&,\
          CDF_DCMFile&,int,CDF_DCMAliasTable&,CDF_DCMFileStack&);
   };

  // class for processing data info records
  class ProcessDataInfo:public ProcessRecord
  {
  public:
      void operator()(CDFDataConfigurationMap&,\
          CDF_DCMFile&,int,CDF_DCMAliasTable&,CDF_DCMFileStack&);
   };
  // class for processing data records
  class ProcessDataDir:public ProcessRecord
  {
  public:
      void operator()(CDFDataConfigurationMap&,\
          CDF_DCMFile&,int,CDF_DCMAliasTable&,CDF_DCMFileStack&);
   };

  // Valid Record Types in CDF_DCMFile and appropriate functor for processing
  // a record of this type
  class CDF_DCMRecordTypes
  {
      map<string,ProcessRecord*> _recordtype;
  public:
      CDF_DCMRecordTypes();
      ~CDF_DCMRecordTypes();
      ProcessRecord* get_process_recordtype(string& str)
      { return _recordtype[str];}
      void set_process_recordtype(string str,ProcessRecord* proc_rec)
      { _recordtype[str]=proc_rec;}

  };

  CDF_DCMDataSourceTable_var get_data_source_table()
    { return _data_source_table;}
  CDF_DCMMetaDataTable_var get_meta_data_table()
    { return _meta_data_table;}
  string get_filename(const string& file, const string& searchpath,\
      CDF_DCMAliasTable& alias_table);

  CDFDCMReadStatus read_file( string file_name, double data_priority = 0 );
  CDFDCMReadStatus read_merge_file( const string& file_name, \
      double data_priority,\
      CDF_DCMAliasTable& alias_table, CDF_DCMFileStack& open_files,\
      string curdir=DCM_NULL_STR);
  // process data in file_data
  CDFDCMReadStatus process_file_data(CDF_DCMFile& cdf_dcm_file,\
      CDF_DCMAliasTable& alias_table, CDF_DCMFileStack& open_files);

  void get_data_source_names(vector<string>& str)
  { _data_source_table->list_names_r(str);}
  void get_meta_data_source_names(vector<string>& str)
  { _meta_data_table->list_names_r(str);}
  void get_dcm_filenames(vector<string>& fnames)
  { fnames=_dcm_filenames;}
  void get_data_intervals( const string& data_source_name,
      const TimeInterval& time_interval, CDF_DCMDataIntervalList& file_list );
  void get_data_info(const string& data_source_name,vector<string>& str,\
    const TimeInterval& time_interval);
  DataNamesTable_var get_file_names()
  { return _cdf_filenames;}
  DataNamesTable_var get_file_names(const string& data_source_name,\
    const TimeInterval& time_interval)
  { 
    if(data_source_name=="" && time_interval.empty())
      return get_file_names();
    else if(data_source_name=="")
    {
      vector<string> names;
      get_data_source_names(names);
      CDF_DCMDataIntervalList file_list;
      for(int i=0;i<names.size();i++)
        get_data_intervals(names[i],time_interval,file_list);
      return file_list.get_file_names();
    }
    else if(time_interval.empty())
    {
      TimeInterval tint;
      get_time_range(data_source_name,tint);
      CDF_DCMDataIntervalList file_list;
      get_data_intervals(data_source_name,tint,file_list);
      return file_list.get_file_names();
    }
    else
    {
      CDF_DCMDataIntervalList file_list;
      get_data_intervals(data_source_name,time_interval,file_list);
      return file_list.get_file_names();
    }
  }  
  int get_num_entries(const string& data_source_name,\
    const TimeInterval& time_interval)
  { 
    CDF_DCMDataIntervalList file_list;
    get_data_intervals(data_source_name,time_interval,file_list);
    return file_list.get_num_entries();
  } 
  void get_time_range(TimeInterval& tint) 
  { vector<string> names; 
    get_data_source_names(names);
    TimeInterval ltint;
    for(int i=0;i<names.size();i++)
    {
        get_time_range(names[i],ltint);
        tint=(i==0)?ltint:tint.int_union(ltint);
    }
  }
  void get_time_range( const string& data_source_name, TimeInterval& tint );
  bool is_data_available(const string& data_source_name,\
      const TimeInterval& tint);
  bool is_data_available(const string& data_source_name,const Time& t);
  CDF_DCMMetaDataEntry* get_metadata_entry(const string& data_source_name);
  CDF_DCMEntryMap* get_data_entrymap(const string& data_source_name);
  void replace(string& str, CDF_DCMAliasTable& alias_table);
  void add_filename(const string& cdf_file,const string& dcm_file)
  {
    #ifdef DIAG
    cout << "adding " << cdf_file << " for " << dcm_file << endl;
    #endif
    int offset=0;
    if(cdf_file.substr(0,1)=="/")
      offset=1;
    try{ _cdf_filenames->bindx(cdf_file.substr(offset),dcm_file);}
    catch(NamingException& e)
    {
      #ifdef DIAG
      e.print_msg();
      #endif
    }
  }

}; // end class CDFDataConfigurationMap

typedef Var<CDFDataConfigurationMap> CDFDataConfigurationMap_var;

inline Vostream& operator<<(Vostream& v_os,\
  const CDF_DCMEntry& cdf_dcm_entry)
{ 
   v_os << cdf_dcm_entry._data_source_name;
   v_os << cdf_dcm_entry._data_priority;
   v_os << cdf_dcm_entry._variable_name;
   v_os << cdf_dcm_entry._time_tags_variable_name;
   v_os << cdf_dcm_entry._time_tags_type;
   v_os << cdf_dcm_entry._file_name;
   v_os << cdf_dcm_entry._num_items;
   v_os << cdf_dcm_entry._data_availability;
   return v_os;
}

inline Vistream& operator>>(Vistream& v_is,\
  CDF_DCMEntry& cdf_dcm_entry)
{ 
   v_is >> cdf_dcm_entry._data_source_name;
   v_is >> cdf_dcm_entry._data_priority;
   v_is >> cdf_dcm_entry._variable_name;
   v_is >> cdf_dcm_entry._time_tags_variable_name;
   v_is >> cdf_dcm_entry._time_tags_type;
   v_is >> cdf_dcm_entry._file_name;
   v_is >> cdf_dcm_entry._num_items;
   v_is >> cdf_dcm_entry._data_availability;
   return v_is;
}

inline Vostream& operator<<(Vostream& v_os,\
  const CDF_DCMEntryMap& dcm_entry_map)
{ 
   v_os << dcm_entry_map._entry_table;
   v_os << dcm_entry_map._startdates;
   v_os << dcm_entry_map._enddates;
   v_os << dcm_entry_map._priorities;
   v_os << dcm_entry_map._time_range;
   v_os << dcm_entry_map._data_info;
   return v_os;
}

inline Vistream& operator>>(Vistream& v_is,\
  CDF_DCMEntryMap& dcm_entry_map)
{ 
   v_is >> dcm_entry_map._entry_table;
   v_is >> dcm_entry_map._startdates;
   v_is >> dcm_entry_map._enddates;
   v_is >> dcm_entry_map._priorities;
   v_is >> dcm_entry_map._time_range;
   v_is >> dcm_entry_map._data_info;
   return v_is;
}

inline Vostream& operator<<(Vostream& v_os,\
  const CDFDataConfigurationMap& cdf_dcm)
{ 
   v_os << cdf_dcm._data_source_table;
   v_os << cdf_dcm._meta_data_table;
   v_os << cdf_dcm._cdf_filenames;
   v_os << cdf_dcm._dcm_filenames;
   return v_os;
}

inline Vistream& operator>>(Vistream& v_is, CDFDataConfigurationMap& cdf_dcm)
{ 
   v_is >> cdf_dcm._data_source_table;
   v_is >> cdf_dcm._meta_data_table;
   v_is >> cdf_dcm._cdf_filenames;
   v_is >> cdf_dcm._dcm_filenames;
   return v_is;
}

} // end namespace QDOS
                                                                                                   
# endif // QDOS_DATA_CONFIGURATION_MAP_H_
