// FARdb.h
//
//

#ifndef _FARDB_H_
#define _FARDB_H_

#include <iostream>
#include <fstream>
#include <string.h>
#include <vector>
#include <map>
#include <algorithm>
#include <functional>
#include <cstdlib>

namespace FAR {

using namespace std;

class Field : public string {

public:
  Field(void) : string("") {;}
  Field( const char* cs ) : string(cs) {;}
  Field( const string& s ) : string(s) {;}
  Field( const Field& f ) : string(f)  {;}
  operator double() const { return atof( c_str() ); }
};

class Record;
ostream& operator<<(ostream& os,const Record& rec);
class FARData;

class Record {

  vector<Field> fields;
  
  Field null_field;
  friend class FARData;
  
public:

  //Record(void): null_field("") {;}
  Record(int n=0):fields(n),null_field(""){;}
  Record( const Record& r ) : null_field("")
  { int i; for(i=0;i<r.fields.size();i++) fields.push_back( r[i] ); }
  Record( const vector<string>& sv ) : null_field("")
  { int i; for(i=0;i<sv.size();i++) fields.push_back( sv[i] ); }
  Record( const string& s ) : null_field("")
    { fields.push_back( s ); }
  int n_fields(void) const { return fields.size(); }
  bool field_exists( int f ) const { return f>=0 && f<fields.size(); }
  int add_field( const string& f )
    { fields.push_back( f ); return fields.size()-1; }
  void add_fields( vector<string>& ff )
  {
  vector<string>::const_iterator p=ff.begin();
  for( ; p!=ff.end(); p++ )
    fields.push_back( *p ); 
}
  
  void delete_field( int i )
    { if( i>=0 && i<fields.size() ) fields.erase( fields.begin()+i );  }

  void clear_field( int i ){ if( i>=0 && i<fields.size() ) fields[i] = ""; }
  
  Field& operator[]( int i )
    { return ( i>=0 && i<fields.size() )? fields[i]: null_field; }
  const Field& operator[]( int i ) const
    { return ( i>=0 && i<fields.size() )? fields[i]: null_field; }

  void diag_print(void) const;

};


typedef enum FARCmpType_e {

  FARSORT_NULL,
  FARSORT_ALPHA,
  FARSORT_ALPHA_NO_CASE,
  FARSORT_NUMERIC,
  FARSORT_REV_ALPHA,
  FARSORT_REV_ALPHA_NO_CASE,
  FARSORT_REV_NUMERIC

} FARCmpType;

class FARcompare_records_by_field {

  int n_cmp_f;
  int field_1;
  int field_2;
  FARCmpType cmp_type_1;
  FARCmpType cmp_type_2;
  
public:

  FARcompare_records_by_field( int f, FARCmpType ct )
     : n_cmp_f(1), field_1(f), cmp_type_1(ct)  {;}
  FARcompare_records_by_field( int f_1, FARCmpType ct_1,
                               int f_2, FARCmpType ct_2 )
     : n_cmp_f(2), field_1(f_1), cmp_type_1(ct_1),
                    field_2(f_2), cmp_type_2(ct_2)  {;}

  int operator() ( const Record& r1,  const Record& r2 )const
  {

  if( n_cmp_f < 1 ) return -1; // bad value for number of comparison fields
  
  if( n_cmp_f==1 )
    return cmp_rec_1( r1, r2 );
  else if( n_cmp_f==2 )
    return cmp_rec_2( r1, r2 );
  else
    return -1;
}

  int cmp_rec_1(const Record& r1, const Record& r2 ) const
  {
  if( field_1 < 0 ) return -1;   // bad value for field selector
  
  if( field_1 >= r1.n_fields() )
  {
    if( field_1 >= r2.n_fields() )
      return 0;
    else
      return -1;
  }
  if( field_1 >= r2.n_fields() )
  {
    if( field_1 >= r1.n_fields() )
      return 0;
    else
      return +1;
  }

  switch( cmp_type_1 )
  {
    case FARSORT_ALPHA:
//    cout << "comparing:field: " << field<< " <" << r1[field].c_str() <<"> <" << r2[field].c_str() << ">" << endl;

    case FARSORT_ALPHA_NO_CASE:
      return r1[field_1] < r2[field_1];  // NOT NOT NOT right!!!!!
      break;

    case FARSORT_NUMERIC:
      return atof( r1[field_1].c_str() ) < atof( r2[field_1].c_str() );
      break;
      
    case FARSORT_REV_ALPHA:
      return r2[field_1] < r1[field_1];
      break;

    case FARSORT_REV_NUMERIC:
      return atof( r2[field_1].c_str() ) < atof( r1[field_1].c_str() );
      break;

    default:
      return r1[field_1] < r2[field_1];
      break;
  }

}

  int cmp_rec_2(const Record& r1, const Record& r2 ) const {}

};

typedef enum FARWriteFileMode_e {

  FARWRITEFILE_NEW,
  FARWRITEFILE_APPEND

} FARWriteFileMode;


typedef enum FARReadFileMode_e {

  FARREADFILE_REPLACE,
  FARREADFILE_APPEND

} FARReadFileMode;


class FARData {

// data
  vector<Record> records;
  int nfieldsmax;             // largest number of fields in a record

  map<string,int> field_by_name; // fieldname -> field number mappings (no checks!)

// private static functions

  static void trim_whitespace( string& s )
  {
  string::iterator p1 = s.begin();
  string::iterator p2 = s.end();

  while( s.size() > 0 && isspace(s[0]) )
    s.erase(0,1);
  while( s.size() > 0 && isspace(s[s.size()-1]) )
    s.erase(s.size()-1);
}
  static bool is_comment_line( const char* s );
  
public:
  FARData(int n=0) : records(n), nfieldsmax(0) {;}
  ~FARData(void) { delete_all_records(); }
  
// public static functions
  static int xtokenize_string( const char *s, const char *delimiters, vector<string>& tokens );
  static int tokenize_string(const char *s, const char *delimiters,
                                  vector<string>& token_list )
  {
    string token;
  int s_len = strlen(s);

  if( s_len == 0 )
    return 0;

  enum state_e {
    token_not_started, token_started_normal,
    token_started_quoted, token_ended_quoted } state;

  int ip = -1;
  state = token_not_started;

  while( ip++ < s_len )
    switch( state )
    {
    case token_not_started:
      if( !isspace(s[ip]) )
        if( s[ip] == '"' )
          state = token_started_quoted;
        else if( s[ip] == ',' )          // another comma already!
        {
          trim_whitespace( token );
          token_list.push_back(token);
          token.erase();
          state = token_not_started;
        } else
        {
          state = token_started_normal;
          token += s[ip];
        }
      break;
    
    case token_started_normal:
      if( strchr( delimiters, s[ip] ) != NULL )  // if delimiter ...
      {
        trim_whitespace( token );
        token_list.push_back(token);
        token.erase();
        state = token_not_started;
      } else
        token += s[ip];
      break;

    case token_started_quoted:
      if( s[ip] == '"' )
      {
        ip++;
        if( s[ip] == '"' )  // double quote
        {
          token += '"';
        } else
        {
          ip--;
          trim_whitespace( token );
          token_list.push_back(token);
          token.erase();
          state = token_ended_quoted;
        }
      } else
      {
        token += s[ip];
      }
      break;

    case token_ended_quoted:
     if( strchr( delimiters, s[ip] ) != NULL )  // if delimiter ...
       state = token_not_started;
     break;
    }

  if( state == token_started_normal || state == token_started_quoted )
  {
    trim_whitespace( token );
    token_list.push_back(token);
    token.erase();
  }


  }
// ----

  void update_nfieldsmax( int n_f ){ if( n_f > nfieldsmax ) nfieldsmax = n_f; }
  
  int n_fields_max(void) const {return nfieldsmax; }
  int n_records(void) const {return records.size(); }
  int add_record( const Record& rec )
    { records.push_back( rec ); update_nfieldsmax( rec.n_fields() );
      return records.size()-1;}
  
  void xfer_record( int r, FARData& db ){ db.add_record( records[r] ); delete_record(r);}
  
  void delete_record( int r )
    { if( r>=0 && r<records.size() ) records.erase( records.begin() + r ); }
    
  void delete_all_records(void)
    { records.clear(); }
  
  void delete_field( int f );
  
  Record& operator[](int i){ return records[i]; }
  const Record& operator[](int i) const { return records[i]; }

    void append_from_file( string file_name, string separator="," )
  {
  const int n_line_buf = 5000;
  char line_buf[n_line_buf];
  std::ifstream infile( file_name.c_str() );

  if( !infile )  // baddd!
  {
     cout << " FAR database error: input file could not be opened.\n" ;
     return;
  }
// readline, eol discarded
  while( !infile.eof() )
  {
    infile.getline( line_buf, n_line_buf );

// Apr 14, 1999:
//    decided to drop idea of comment lines! because of wanting
//    to preserve them when writing out a database to a file
//   
//    if( is_comment_line( line_buf ) )
//      continue;

// create a new empty record 
   records.push_back( Record() );

//   cout << line_buf << endl;
     
     vector<string> tokens;
     int n_tokens = tokenize_string( line_buf, ",", tokens );
//     for( int i=0; i < tokens.size(); i++ )
//       cout << "token "<<i<<": <"<<tokens[i].c_str()<<">"<<endl;
     records[records.size()-1].add_fields( tokens );
  }
  infile.close();
  }
  
  void read_from_file( string file_name,
         FARReadFileMode read_mode=FARREADFILE_REPLACE, string separator="," )
		 {
		  switch( read_mode )
		  {
		  default:
		  case FARREADFILE_REPLACE:
		    delete_all_records();
		    append_from_file( file_name, separator );
		    break;
		  case FARREADFILE_APPEND:
		    append_from_file( file_name, separator );
		    break;
		  }
}

  void write_to_file( string file_name = "FARdata.csv",
      FARWriteFileMode write_mode=FARWRITEFILE_NEW, string separator="," )
	  {

// probably gnu c++ specific!

  ios::openmode open_mode;
  switch( write_mode )
  {
    default:
    case FARWRITEFILE_NEW:
      open_mode = ios::out;
      break;
    case FARWRITEFILE_APPEND:
      open_mode = ios::app;
      break;
  }

  std::ofstream outfile( file_name.c_str(), open_mode );

  if( !outfile )  // baddd!
  {
     cout << " FAR database error: output file could not be opened.\n" ;
     return;
  }

  for(int ir=0; ir<records.size(); ir++ )
  {
    for( int ifld=0; ifld<records[ir].n_fields(); ifld++ )
    {
      if( ifld != 0 )
        outfile << separator.c_str();
      int spos = records[ir][ifld].find( separator );
      if( spos == string::npos )
        outfile << records[ir][ifld].c_str();
      else
        outfile << "\"" << records[ir][ifld].c_str() << "\"";
    }

    outfile << endl;
  }
  outfile.close();
}

  void sort_records_by_field( const FARcompare_records_by_field& cmp )
    { stable_sort( records.begin(), records.end(), cmp ); }

  int match_record_by_field( int field, string s, int r_start=0, int r_end=-1 ) const
  {
  if( r_end == -1 ) r_end = records.size()-1;
  if( r_end<0 || r_start<0 ) return -1;
  if( r_end >= records.size() || r_start >= records.size()  ) return -1;
  if( r_start > r_end ) return -1;
  if( field < 0 ) return -1;

  bool match_found = false;
  int r;
  for( r=r_start; r <= r_end; r++ )
  {
    if( field >= records[r].n_fields() )
      continue;
    if( records[r][field] == s )
    {
      match_found = true; break;
    }
  }
  return match_found ? r : -1;
}
  int match_record_by_field( int field, double x, int r_start=0, int r_end=-1 ) const;
  
  void set_field_name( int f, string field_name ) { field_by_name[field_name]=f; }
  int  get_field_number( string field_name ){ return field_by_name[field_name]; }
  
  void diag_print(void) const;

};


} // end module FAR

#endif // _FARDB_H_
