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

// ObsFilter.cc, vk & br oct-94, rev vk 970729
//               qa-c++ vk 960725


#include "inc_iostream.h"
#include <time.h>
#include <vector>

#include "Metview.h"
#include "MvObs.h"
#include "MvException.h"

const char cTab = 9;                         // ascii code for 'tab'
const char cComma = ',';

const char cVectorPolar[] = "POLAR_VECTOR";
const char cVectorUV[]    = "XY_VECTOR";

enum eLevel { LVL_Surface, LVL_SingleLevel, LVL_ThicknessLayer, LVL_Occurrence, LVL_CoordinateValue, LVL_CoordinateRange};
const unsigned long TEMP_2M = 12004;

enum eOutput          { eBUFR,  eGEOPOINTS,  ePOLAR_VECTOR,  eXY_VECTOR,  eCSV };
string outTypeStr[] = { "BUFR", "GEOPOINTS", "POLAR_VECTOR", "XY_VECTOR", "CSV" };

//__________________________________________________________ ObsFilter

class ObsFilter : public MvService
{
 private:
	ObsFilter( const ObsFilter& anOther );
	void operator= ( const ObsFilter& anOther );

 public:
	ObsFilter();
	~ObsFilter();

	void      serve( MvRequest&, MvRequest& );

 protected:
	void      getParams( void );
	void      getData( void );
	void      getParameterSpecifiers( void );
	void      getTypes( void );
	void      getTimes( void );
	void      getWmoIds( void );
	void      getLocation( void );
	void      getCustom( void );
	MvRequest createRequest();
	void      add();
	void      close( void );
	float     surfaceValue( long parInd );
	float     pressureLevelValue( float level, long parInd );
	float     layerValue( float level1, float level2, long parInd );
	float     occurranceValue( int occurranceInd, long parIndex );
	float     coordinateLevelValue(long, float, long);
	float     coordinateRangeValue(long, float,float, long );

 private:
	long              _counter;
	MvRequest         _in;
	MvRequest         _data;
	MvObs             obs_;
	MvObsSet*         _inSet;
	MvObsSetIterator* _iter;
	eOutput           outType_;
	MvObsSet*         _outBufr;
	ofstream          _outFile;
	eLevel            _level;
	float             _firstLevel;
	float             _secondLevel;
	long              _occurrenceIndex;
	vector<long>      param_;
	ENextReturn       _obsOrMsg;   //-- for multisubset msgs (filtering also with subset data?)
	boolean           _includeMissingValues;
	fortfloat         _missingValue;
	bool              failOnError_;
	bool              failOnEmptyOutput_;
	long		  _coordLevelDescriptor;
	double		  _coordLevelValue;
};


//_________________________________________________________

ObsFilter::ObsFilter() : MvService( "OBSFILTER" ),
   _counter( 0 ),
   _inSet( 0 ),
   _iter( 0 ),
   _outBufr( 0 ),
   _level( LVL_Surface ),
   _firstLevel( 0. ),
   _secondLevel( 0. ),
   _occurrenceIndex( 0 ),
   _obsOrMsg( NR_returnMsg ),   //-- as a default do not study subsets (obs's)
   _includeMissingValues( false ),
   _missingValue( kBufrMissingValue ),
   failOnError_(true),         //-- this breaks backwards compatibility
   failOnEmptyOutput_(false)
{
}
//_________________________________________________________

ObsFilter::~ObsFilter()
{
  delete _iter;
  delete _inSet;
  delete _outBufr;
}
//_________________________________________________________
void
ObsFilter :: getData( void )
{
  //-- D A T A --
  _in.getValue( _data, "DATA" );
  _data.print();
  //cout << " Path now: " << (char *)_data( "PATH" ) << endl;

  _inSet = new MvObsSet( _data );
  _iter = new MvObsSetIterator( *_inSet );

  //-- O U T P U T --
  outType_ = (eOutput)( (int)_in( "OUTPUT" ) );

  if( strcmp( _in("FAIL_ON_ERROR"), "NO" ) == 0 )
     failOnError_ = false;

  if( strcmp( _in("FAIL_ON_EMPTY_OUTPUT"), "YES" ) == 0 )
     failOnEmptyOutput_ = true;
}
//_________________________________________________________
void
ObsFilter :: getParameterSpecifiers( void )
{
  //-- P A R A M E T E R --
  int nrParams = _in.countValues( "PARAMETER" );

  if( nrParams == 0 )                        //-- first descriptor (parameter)
    param_.push_back( TEMP_2M );
  else
    param_.push_back( (int)_in( "PARAMETER" ) );

  if( nrParams > 1 )                         //-- if more descriptors (parameters)
  {
     for( int n=1; n<nrParams; ++n )
        param_.push_back( (int)_in( "PARAMETER", n ) );
  }
  if( (outType_ == ePOLAR_VECTOR || outType_ == eXY_VECTOR)  && nrParams < 2 )
     throw MvException( "ObsFilter: too few parameters defined. Wind data takes two parameters!!" );

  const char* myMissingData = _in( "MISSING_DATA" );
  if( myMissingData && strcmp( myMissingData, "INCLUDE" ) == 0  )
    {
      _includeMissingValues = true;
      _missingValue = (double)_in( "MISSING_DATA_VALUE" );
    }

  	//-- L E V E L --
  const char* levelStr = _in( "LEVEL" );
  if( levelStr && strcmp( levelStr, "SURFACE" ) == 0 )
    _level = LVL_Surface;
  else if( levelStr && strcmp( levelStr, "SINGLE" ) ==0 )
    _level = LVL_SingleLevel;
  else if( levelStr && strcmp( levelStr, "THICKNESS" ) ==0 )
    _level = LVL_ThicknessLayer;
  else if( levelStr && strcmp( levelStr, "OCCURRENCE" ) ==0 )
    _level = LVL_Occurrence;
  else if( levelStr && strcmp( levelStr, "DESCRIPTOR_VALUE" ) ==0 )
    _level = LVL_CoordinateValue;
  else if( levelStr && strcmp( levelStr, "DESCRIPTOR_RANGE" ) ==0 )
    _level = LVL_CoordinateRange;
  
  if( _in.countValues( "FIRST_LEVEL" ) > 0 )
    _firstLevel = (double)_in( "FIRST_LEVEL" );

  if( _in.countValues( "SECOND_LEVEL" ) > 0 )
    _secondLevel = (double)_in( "SECOND_LEVEL" );

  if( _in.countValues( "OCCURRENCE_INDEX" ) > 0 )
    _occurrenceIndex = (long)_in( "OCCURRENCE_INDEX" );
  
  if( _in.countValues( "LEVEL_DESCRIPTOR" ) > 0 )
    _coordLevelDescriptor = (long)_in( "LEVEL_DESCRIPTOR" );
  
}
//_________________________________________________________
void
ObsFilter :: getTypes( void )
{
  int i;

  // It requires that this parameter returns number(s)
  const char* obsTypeStr = (const char*)_in( "OBSERVATION_TYPES" );
  if( obsTypeStr )
  {
    long myType = -999;
    for( i = 0; i < _in.countValues( "OBSERVATION_TYPES" ); i++ )
    {
      myType = -999;
      if( ( myType = (long)_in( "OBSERVATION_TYPES", i ) ) >= 0 )
        _iter->setMessageType( (int)myType );
    }
  }

  // It requires that this parameter returns number(s)
  const char* obsSubTypeStr = (const char*)_in( "OBSERVATION_SUBTYPES" );
  if( obsSubTypeStr )
  {
    long mySubType = -999;
    for( i = 0; i < _in.countValues( "OBSERVATION_SUBTYPES" ); i++ )
    {
      mySubType = -999;
      if( ( mySubType = (long)_in( "OBSERVATION_SUBTYPES", i ) ) >= 0 )
        _iter->setMessageSubtype( (int)mySubType );
    }
  }
}
//_________________________________________________________
void
ObsFilter :: getTimes( void )
{
  const char* obsTimeStr = _in( "DATE_AND_TIME_FROM" );
  if( obsTimeStr && strcmp( obsTimeStr, "DATA" ) == 0 )
    _iter->useObsTime( true );

  const char* dateStr = _in( "DATE" );
  const char* timeStr = _in( "TIME" );
  if( dateStr && strcmp( dateStr, "ANY" ) == 0 &&
      timeStr && strcmp( timeStr, "ANY" ) == 0    )
  {
                                          // both DATE and TIME are ANY
  }                                       // TIME==ANY && DATE==value is illegal
  else
  {
    int  myTime = (int)_in( "TIME" );
    int  myDelta = (int)_in( "RESOLUTION_IN_MINS" );

	if( dateStr && strcmp( dateStr, "ANY" ) == 0 )  // only TIME is given
	{
	  myDelta = 100*(myDelta / 60) + myDelta % 60;  // minutes into HHMM
	  if( (myDelta % 100) != 0 )
	    _iter->setTimeRangeWithoutDate( myTime - myDelta - 40  // 40=100-60!!!
					  , myTime + myDelta );
	  else
	    _iter->setTimeRangeWithoutDate( myTime - myDelta
					  , myTime + myDelta );
	}
	else                                            // both TIME and DATE given
	{
	  long myDate = (int)_in( "DATE" );
	  TDynamicTime myObsTime( myDate, myTime );
	  _iter->setTimeRange( myObsTime, myDelta );
    }
  }
}
//_________________________________________________________
void
ObsFilter :: getWmoIds( void )
{
  int i;

	const char* blockStr = _in( "WMO_BLOCKS" );
	if( blockStr && strcmp( blockStr, "ANY" ) == 0 )
	{
	}
	else
	{
	  long myWmoBlock;
	  for( i = 0; i < _in.countValues( "WMO_BLOCKS" ); i++ )
	  {
	    if( ( myWmoBlock = (long)_in( "WMO_BLOCKS", i ) ) > 0 )
	      _iter->setWmoBlock( (int)myWmoBlock );
	  }
      _obsOrMsg = NR_returnObs;  //-- have to study each subset (obs)
	}

	const char* stationStr = _in( "WMO_STATIONS" );
	if( stationStr && strcmp( stationStr, "ANY" ) == 0 )
	{
	}
	else
	{
	  long myWmoStation;
	  for( i = 0; i < _in.countValues( "WMO_STATIONS" ); i++ ) // int i; ????
	  {
	    if( ( myWmoStation = (long)_in( "WMO_STATIONS", i ) ) > 0 )
	      _iter->setWmoStation( myWmoStation );
	  }
      _obsOrMsg = NR_returnObs;  //-- have to study each subset (obs)
	}
}
//_________________________________________________________
void
ObsFilter :: getLocation( void )
{
  const char* locFilterStr = _in( "LOCATION_FILTER" );
  if( locFilterStr && strcmp( locFilterStr, "AREA" ) == 0 )
  {
	const char* areaStr = _in( "AREA" );
	if( areaStr )
	{
	  if( _in.countValues( "AREA" ) == 4 )
	  {
	    float y1 = (double)_in( "AREA", 0 );
	    float x1 = (double)_in( "AREA", 1 );
	    float y2 = (double)_in( "AREA", 2 );
	    float x2 = (double)_in( "AREA", 3 );
	    MvLocation loc1( y1, x1 );
	    MvLocation loc2( y2, x2 );
	    _iter->setArea( loc1, loc2 );
        _obsOrMsg = NR_returnObs;  //-- have to study each subset (obs)
	  }
	  else
	    sendProgress( "ObsFilter: Errors in AREA-parameter, Area filter not set!" );
	    //cerr << " >>> Errors in 'AREA'-parameter?" << endl;
	}
  }


  if( locFilterStr && strcmp( locFilterStr, "LINE" ) == 0 )
  {
	const char* lineStr = _in( "LINE" );
	if( lineStr )
	{
	  if( _in.countValues( "LINE" ) == 4 )
	  {
	    float myDeltaInM = (double)_in( "DELTA_IN_KM" ) * 1000;
	    float y1 = (double)_in( "LINE", 0 );
	    float x1 = (double)_in( "LINE", 1 );
	    float y2 = (double)_in( "LINE", 2 );
	    float x2 = (double)_in( "LINE", 3 );
	    MvLocation loc1( y1, x1 );
	    MvLocation loc2( y2, x2 );
	    _iter->setXSectionLine( loc1, loc2, myDeltaInM );
        _obsOrMsg = NR_returnObs;  //-- have to study each subset (obs)
	  }
	  else
	    sendProgress( "ObsFilter: Errors in LINE-parameter, Line filter not set!" );
	}
  }
}
//_________________________________________________________
void
ObsFilter :: getCustom( void )
{
  const char* customFilterStr = _in( "CUSTOM_FILTER" );
  long  customDescriptor = (long)_in( "CUSTOM_PARAMETER" );
  float customFirstValue = 0;
  float customSecondValue = 0;
  int mySelectValueCount = _in.countValues( "CUSTOM_VALUES" );
  if( mySelectValueCount > 0 )
    customFirstValue = (double)_in( "CUSTOM_VALUES", 0 );
  if( mySelectValueCount > 1 )
    customSecondValue = (double)_in( "CUSTOM_VALUES", 1 );

  if( customFilterStr && strcmp( customFilterStr, "VALUE" ) == 0 )
  {
    for( int i = 0; i < mySelectValueCount; i++ )
    {
      _iter->select( customDescriptor, (double)_in( "CUSTOM_VALUES", i ) );
    }
    _obsOrMsg = NR_returnObs;  //-- have to study each subset (obs)
  }

  if( customFilterStr && strcmp( customFilterStr, "RANGE" ) == 0 )
  {
    if( mySelectValueCount == 2 )
    {
      _iter->selectRange( customDescriptor, customFirstValue, customSecondValue );
      _obsOrMsg = NR_returnObs;  //-- have to study each subset (obs)
    }
    else
      cerr << ">>> 'Custom Filter/Range: nr of values != 2 !!!" << endl;
  }

  if( customFilterStr && strcmp( customFilterStr, "EXCLUDE" ) == 0 )
  {
    if( mySelectValueCount == 2 )
    {
      _iter->excludeRange( customDescriptor, customFirstValue, customSecondValue );
      _obsOrMsg = NR_returnObs;  //-- have to study each subset (obs)
    }
    else
      cerr << ">>> 'Custom Filter/ExcludeRange: nr of values != 2 !!!" << endl;
  }
}
//_________________________________________________________
void
ObsFilter :: getParams( void )
{
  _obsOrMsg = NR_returnMsg;  //-- set msg level as default

  //-- these have no effect on _obsOrMsg --
  getData();
  getParameterSpecifiers();
  getTypes();

  //-- these may have an effect on _obsOrMsg --
  getTimes();
  getWmoIds();
  getLocation();
  getCustom();

  if( outType_ == eBUFR )
  {
     if( _obsOrMsg == NR_returnObs )
        cerr << "-- Filtering with data values requested, have to study each subset (obs) --\n"
             << endl;
     else
        cerr << "-- Filtering on msg level (only section header info used) --\n"
             << endl;
  }
  else
     _obsOrMsg = NR_returnObs;    //-- for geopoints always on obs level!!!
}
//_________________________________________________________
MvRequest
ObsFilter :: createRequest()
{
   switch( outType_ )
   {
      case eBUFR:                           //-- BUFR
      {
	 MvRequest x( "BUFR" );             //-- BUFR return request
	 x( "TEMPORARY" ) = 1;
	 x( "PATH" ) = marstmp();
	 _outBufr = new MvObsSet( x, "w" );

	 return x;
      }

      case eCSV:                            //-- CSV
      {
	 char* fileName = marstmp();

	 MvRequest x( "TABLE" );            //-- CSV return request (TABLE)
	 x( "TEMPORARY" ) = 1;
	 x( "PATH" ) = fileName;

	 _outFile.open( fileName, ios::out );
                                            //-- construct CSV header line
	 for( unsigned int p = 0; p < param_.size(); ++p )
	 {
	    if( p > 0 )
	       _outFile << cComma;
	    _outFile << "'" << param_[p] << "'";
	 }
	 _outFile << endl;

	 return x;
      }

      case eGEOPOINTS:                      //-- any GEOPOINTS
      case ePOLAR_VECTOR:
      case eXY_VECTOR:
      {
	 char* fileName = marstmp();

	 MvRequest x( "GEOPOINTS" );        //-- GEOPOINTS return request
	 x( "TEMPORARY" ) = 1;
	 x( "PATH" ) = fileName;

	 _outFile.open( fileName, ios::out );

	 _outFile << "#GEO" << endl;        //-- construct GEOPOINTS header

	 if( outType_ == ePOLAR_VECTOR || outType_ == eXY_VECTOR )
	   _outFile << "#FORMAT " << outTypeStr[ outType_ ] << endl;

	 if ( outType_ == eGEOPOINTS )
           _outFile << "PARAMETER = " << param_[0] << endl;
	 else
           _outFile << "PARAMETER = " << param_[0] << cTab << param_[1] << endl;

	 _outFile << "#lat" << cTab << "long" << cTab
	          << "level" << cTab << "date" << cTab
	          << "time" << cTab;
	 if( outType_ == eGEOPOINTS )
	   _outFile << "value";
	 else
	   _outFile << "speed|u" << cTab << "direction|v";
	 _outFile << endl;

	 _outFile << "#DATA" << endl;

	 return x;
      }
   }

   MvRequest x( "ERROR" );                   //-- internal error if here
   return x;
}
//_________________________________________________________
float
ObsFilter::surfaceValue( long parInd )
{
   return obs_.value( param_[parInd] );
}
//_________________________________________________________
float
ObsFilter::pressureLevelValue( float level, long parInd )
{
   return obs_.valueByPressureLevel( level, param_[parInd] );
}
//_________________________________________________________
float
ObsFilter::layerValue( float level1, float level2, long parInd )
{
   return obs_.valueByLayer( level1, level2, param_[parInd] );
}
//_________________________________________________________
float
ObsFilter::occurranceValue( int occurranceInd, long parIndex )
{
   return obs_.valueByOccurrence( occurranceInd, param_[parIndex] );
}
//_________________________________________________________
float
ObsFilter::coordinateLevelValue(long coordDesc, float level, long parInd )
{
   return obs_.valueByLevel(coordDesc,level,param_[parInd]);
}
//_________________________________________________________
float
ObsFilter::coordinateRangeValue(long coordDesc, float level1,float level2, long parInd )
{
   return obs_.valueByLevelRange(coordDesc,level1,level2,param_[parInd]);
}

//_________________________________________________________
void
ObsFilter :: add()
{
  if( outType_ == eBUFR )                   //-- BUFR
  {
     if( _counter == 0 )                    //-- BUFR: first time
     {
       //-- changed 2011-12-30/vk:
       //--  o target BUFR file is set to contain single subsets, but this is in effect
       //--     only when filtering using data values from BUFR Section 4
       //--  o this setting has no effect when filtering only with metadata values in
       //--     BUFR Section 1; in such cases multisubset messages are copied as they
       //--     are (metadata is common to all subsets in a multisubset msg)
        _outBufr->setSubsetMax(1); //-- obs_.msgSubsetCount() );
     }

     //-- if filtering requires values from observation then multisubset
     //-- messages have to be filtered on observation (subset) level !
     if( _obsOrMsg == NR_returnMsg )
     {
        _outBufr->write( obs_ );           //-- BUFR: filtering with "meta data" only
        _counter += obs_.msgSubsetCount(); //-- BUFR: i.e. write the whole message!
     }
     else
     {
        _outBufr->add( obs_ );             //-- BUFR: filtering also with data values
        _counter++;                         //-- BUFR: write current subset only!
     }
  }
  else if( outType_ == eCSV )               //-- CSV
  {
     ostringstream os;
     bool abortOnMissing = false;
     for( unsigned int p = 0; p < param_.size(); ++p )
     {
        if( p > 0 )
           os << cComma;
	float val;
	switch( _level )
	{
	 case LVL_Surface:
	    val = surfaceValue( p );
	    break;

	 case LVL_SingleLevel:
	    val = pressureLevelValue( _firstLevel, p );
	    break;

	 case LVL_ThicknessLayer:
	    val = layerValue( _firstLevel, _secondLevel, p );
	    break;

	 case LVL_Occurrence:                //-- CSV: this does not make much sence here !!!
	    val = occurranceValue( _occurrenceIndex, p );
	    break;
	 	 
	 case LVL_CoordinateValue:
            val = coordinateLevelValue( _coordLevelDescriptor,_firstLevel, p);
            break;
            
         case LVL_CoordinateRange:
            val = coordinateRangeValue( _coordLevelDescriptor,_firstLevel, _secondLevel, p);    
      	    break; 
	  
	 default:
	   break;         
	}

	if( val == kBufrMissingValue )      //-- CSV: missing value?
	{
	   if( _includeMissingValues )
	   {
	      val = _missingValue;          //-- CSV: chage to user defined missing value
	   }
	   else
	   {
	      abortOnMissing = true;        //-- CSV: force ignoring the whole obs
	      break;
	   }
	}
        os << val;                          //-- CSV: write value or user defined missing value
     }
     if( ! abortOnMissing )
     {
        _outFile << os.str() << endl;
        _counter++;
     }
  }
  else                                      //-- GEOPOINTS or GEOVECTORS
  {
    float myValue = 0;
    float myDirec = 0;  //-- for geovectors

    switch( _level )
    {
    case LVL_Surface:
      myValue = surfaceValue( 0 );
      if( outType_ > eGEOPOINTS )
	myDirec = surfaceValue( 1 );
      break;

    case LVL_SingleLevel:
      myValue = pressureLevelValue( _firstLevel, 0 );
      if( outType_ > eGEOPOINTS )
	myDirec = pressureLevelValue( _firstLevel, 1 );
      break;

    case LVL_ThicknessLayer:
      myValue = layerValue( _firstLevel, _secondLevel, 0 );
      if( outType_ > eGEOPOINTS )
	myDirec = layerValue( _firstLevel, _secondLevel, 1 );
      break;

    case LVL_Occurrence:
      myValue = occurranceValue( _occurrenceIndex, 0 );
      if( outType_ > eGEOPOINTS )
	myDirec = occurranceValue( _occurrenceIndex, 1 );
      break;

    case LVL_CoordinateValue:
      myValue = coordinateLevelValue( _coordLevelDescriptor,_firstLevel, 0);
      if( outType_ > eGEOPOINTS )
	myDirec = coordinateLevelValue( _coordLevelDescriptor,_firstLevel, 1);
      break;   
      
    case LVL_CoordinateRange:
      myValue = coordinateRangeValue( _coordLevelDescriptor,_firstLevel, _secondLevel, 0);
      if( outType_ > eGEOPOINTS )
	myDirec = coordinateRangeValue( _coordLevelDescriptor,_firstLevel, _secondLevel, 1);
      break;   
      
    default:
      	break;      
   }	

    if( myValue != kBufrMissingValue || _includeMissingValues )
    {
      MvLocation  myLoc = obs_.location();
      TStaticTime myTime = obs_.obsTime();

      _outFile << myLoc.latitude() << cTab;
      _outFile << myLoc.longitude() << cTab;

      _outFile << _firstLevel << cTab;

      _outFile << myTime.GetYear();
      _outFile.width(2); _outFile.fill( '0' );
      _outFile << myTime.GetMonth();
      _outFile.width( 2 ); _outFile.fill( '0' );
      _outFile << myTime.GetDay() << cTab;
      _outFile.width( 2 ); _outFile.fill( '0' );
      _outFile << myTime.GetHour();
      _outFile.width( 2 ); _outFile.fill( '0' );
      _outFile << myTime.GetMin() << cTab;

      if( obs_.elementValueType( param_[0] ) == kEVT_string )
	{
	  //-- string value has only basic retrieve...
	  _outFile << obs_.stringValue( param_[0] );
	}
      else
	{
	  if( _includeMissingValues && myValue == kBufrMissingValue )
	    {
	      myValue = _missingValue;
	    }
	  _outFile << myValue;

	  if( outType_ > eGEOPOINTS )
	    _outFile << cTab << myDirec;  //-- should we check against missing value?
	}
      _outFile << endl;

      _counter++;
    }
  }
}
//_________________________________________________________
void
ObsFilter :: close( void )
{
  if( outType_ == eBUFR )
     _outBufr->close();
  else
     _outFile.close();
}
//_________________________________________________________

void
ObsFilter :: serve( MvRequest& in, MvRequest& out )
{
   cout << "\n\nO b s F i l t e r, MAX_KDLEN=" << MAX_KDLEN << endl;
   time_t myStartTime;
   time( &myStartTime );
   clock_t myStartCpu = clock();

   // The input request needs to be expanded with flag EXPAND_LAST_NAME.
   // This is required by functions getTypes and getCustom.
   _in = in.ExpandRequest("ObsFilterDef","ObsFilterRules",EXPAND_LAST_NAME);
   _in.print();

   getParams();                          //-- new _inSet & _iter
   cout << " " << *_iter << endl;

   MvRequest x = createRequest();
   _counter = 0;                         //-- m a i n   l o o p
   try
   {
     while( (obs_ = (*_iter)(_obsOrMsg) ) )
     {
       add();
     }
     close();
   }
   catch ( MvException& e )
   {
     marslog( LOG_WARN, "ObsFilter: %s", e.what() );
     if( failOnError_ )
         throw MvException( "ObsFilter failed" );
   }

   time_t myEndTime;
   time( &myEndTime );
   double wallClockTime = difftime( myEndTime, myStartTime );
   clock_t myEndCpu = clock();
   double cpuTime =
        ( (double)( myEndCpu - myStartCpu ) ) / (double)CLOCKS_PER_SEC; // CLK_TCK;
   cout << " Tick Tack for " << wallClockTime << " seconds ("
        << cpuTime << " CPU secs)\n";

   cout << " Filtered " << _counter
        << " observations (out of " << _inSet->obsCount() << ")" << endl;

   sendProgress( "Filtered %d observations (out of %d) into a %s file in %d secs (cpu %d secs)"
               , _counter, _inSet->obsCount(), outTypeStr[(int)outType_].c_str()
               , (int)wallClockTime, (int)cpuTime );

   delete _iter;
   _iter = 0;

   delete _inSet;
   _inSet = 0;

   if( _counter == 0 && failOnEmptyOutput_ )
       throw MvException( "Abort: Fail-On-Empty-Output requested!" );

   out = x;
   out.print();
}
//_________________________________________________________

int
main( int argc, char **argv )
{
	MvApplication theApp( argc, argv );
	ObsFilter     filter;

	theApp.run();
}
