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

// BufrPicker.cc, Jul2011/vk, based on ObsFilter by vk & br oct-94, rev vk 970729
//
//  Picks single or vector values from BUFR messages into a geopoints file.
//  'BufrPicker' is targeted for complicated BUFR messages where the requested
//  value(s) occur several times, with different coordinate values.


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

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

const char   mySeparator = '\t';             //-- 'tab'

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

const unsigned long BRIGHT_TEMP = 12163;     //-- default value to be picked

//__________________________________________________________ BufrPicker

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

 public:
	BufrPicker();
	~BufrPicker();

	void      serve( MvRequest&, MvRequest& );

 protected:
	void      getParams();
	void      getData();
	void      getParameterSpecifiers();
	MvRequest createRequest();
	void      pickValue( MvObs& anObs );
	void      add( MvObs& anObs, double aCoordValue );
	void      close();

 private:
	long              counter_;
 	MvRequest         in_;               //-- input request
 	MvRequest         data_;             //-- sub-request of input request
	MvObsSet*         inSet_;            //-- input observation set
	MvObsSetIterator* iter_;             //-- input iterator (no filtering)
	ofstream          outfile_;          //-- output geopoints file
	long              param_;            //-- value to be picked from requested "coordinates"
	long              param2_;           //-- second value, in case of geovector output
	vector<long>      coordDescr_;       //-- coordinate descriptors
	vector<double>    coordValue_;       //-- values for the given coordinates
	int               coordDescrCount_;  //-- number of coord descrs
	const char*       geoVectorType_;    //-- 'polar vector' or 'XY vector'
	ENextReturn       obsOrMsg_;         //-- for multisubset msgs (pick also with subset data?)
	boolean           includeMissingValues_;
	fortfloat         missingValue_;
	bool              failOnError_;
	double            pickedValue1_;
	double            pickedValue2_;
	bool              pickAll_;          //-- pick all values related to the innermost coordinate descriptor
};


//_________________________________________________________

BufrPicker::BufrPicker() : MvService( "BUFRPICKER" ),
   counter_( 0 ),
   inSet_( 0 ),
   iter_( 0 ),
   param_( BRIGHT_TEMP ),
   param2_( 0 ),
   coordDescrCount_( 0 ),
   geoVectorType_( 0 ),
   obsOrMsg_( NR_returnObs ),   //-- as a default pick all subsets (obs's)
   includeMissingValues_( false ),
   missingValue_( kBufrMissingValue ),
   failOnError_(true)         //-- this breaks backwards compatibility
{ }
//_________________________________________________________

BufrPicker::~BufrPicker()
{
  delete iter_;
  delete inSet_;
  coordDescr_.clear();
  coordValue_.clear();
}
//_________________________________________________________
void
BufrPicker::getData()
{
  //-- 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 --
  const char* outStr = in_( "OUTPUT" );
  if( outStr && ( strcmp( outStr, cVectorPolar ) == 0  ||
		( strcmp( outStr, cVectorUV ) == 0 ) ) )
    {
      if( strcmp( outStr, cVectorPolar ) == 0 )
	geoVectorType_ = cVectorPolar;
      else
	geoVectorType_ = cVectorUV;
    }

  if( strcmp( in_("FAIL_ON_ERROR"), "NO" ) == 0 )
     failOnError_ = false;
}
//_________________________________________________________
void
BufrPicker::getParameterSpecifiers()
{
  //-- P A R A M E T E R --

  if( in_.countValues( "PARAMETER" ) > 0 )
    param_ = (int)in_( "PARAMETER" );

  if( geoVectorType_ && in_.countValues( "PARAMETER" ) > 1 )
    param2_ = (int)in_( "PARAMETER", 1 );

  const char* myMissingData = in_( "MISSING_DATA" );
  if( myMissingData && strcmp( myMissingData, "INCLUDE" ) == 0  )
    {
      includeMissingValues_ = true;
      missingValue_ = (double)in_( "MISSING_DATA_VALUE" );
    }

  //-- Check coordinate descriptors and coordinate values

  coordDescrCount_ = in_.countValues( "COORDINATE_DESCRIPTORS" );
  if( coordDescrCount_ < 1 )
     throw MvException( "No coordinate descriptors given" );

  if( in_.countValues( "COORDINATE_VALUES" ) != coordDescrCount_ )
     throw MvException( "Different number of coordinate descriptors and values" );

  //-- OK, now get coordinate descriptors and coordinate values

  pickAll_ = false;
  int innermostCoordIndex = coordDescrCount_ - 1;
  for( int i=0; i < coordDescrCount_; ++i )
  {
     coordDescr_.push_back( (long)in_( "COORDINATE_DESCRIPTORS", i ) );

     string coordValStr = (const char*)in_( "COORDINATE_VALUES", i );  //-- to check for "ALL"

     if( coordValStr == "ALL" && i < innermostCoordIndex )       //-- 'ALL' not allowed for outer coords
     {
        marslog( LOG_EROR, "Value ALL allowed _only_ for the innermost coordinate descriptor" );
     }
     else if( coordValStr == "ALL" && i == innermostCoordIndex ) //-- 'ALL' allowed for the innermost
     {
        pickAll_ = true;
        coordValue_.push_back( -9999.99 );                       //-- dummy value
        cout << coordDescr_[ i ] << ":\tALL" << endl;
     }
     else                                                        //-- OK for rest of the cases
     {
        coordValue_.push_back( (double)in_( "COORDINATE_VALUES", i ) );
        cout << coordDescr_[ i ] << ":\t" << coordValue_[ i ] << endl;
     }
  }
}
//_________________________________________________________
void
BufrPicker::getParams()
{
  getData();
  getParameterSpecifiers();
}
//_________________________________________________________
MvRequest
BufrPicker::createRequest()
{
    char* fileName = marstmp();

	MvRequest x( "GEOPOINTS" );
	x( "TEMPORARY" ) = 1;
	x( "PATH" ) = fileName;

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

	outfile_ << "#GEO" << endl;

	if( geoVectorType_ )
	  outfile_ << "#FORMAT " << geoVectorType_ << endl;

	if ( geoVectorType_ )
          outfile_ << "PARAMETER = " << param_ << mySeparator << param2_ << endl;
	else
          outfile_ << "PARAMETER = " << param_ << endl;

        outfile_ << "#lat" << mySeparator << "long" << mySeparator
                 << "level" << mySeparator << "date" << mySeparator
		 << "time" << mySeparator;
	if( geoVectorType_ )
	  outfile_ << "speed|u" << mySeparator << "direction|v";
	else
	  outfile_ << "value";
	outfile_ << endl;

	outfile_ << "#DATA" << endl;

	return x;
}
//__________________________________________________________________
//
// Note: 'void' function - failure is indicated by missing value(s)
// because user may have requested to return missing values as well.
//
void
BufrPicker::pickValue( MvObs& anObs )
{
    int startIndex = 0;                      //-- coordinate interval start index in the msg
    anObs.setFirstDescriptor();              //-- (implicitly) force descriptors to be expanded

                                             //-- "tighten in" 'startIndex' with outer coordinates
    for( int i=0; i < (coordDescrCount_ - 1); ++i )
    {
       //--
       //-- Method 'specifierIndex':
       //-- look for a coordinate descriptor with the given value,
       //-- starting from the given index, returning the index of the coordinate point
       //--
       startIndex = anObs.specifierIndex( coordDescr_[ i ]
                                        , coordValue_[ i ]
                                        , startIndex );
       if( startIndex < 0 )
       {
          cerr << "BufrPicker: No match for coordinate " << coordDescr_[ i ]
               << " with value " << coordValue_[ i ] << endl;
//          marslog( LOG_WARN, "BufrPicker: No match for coordinate %d", coordDescr_[ i ]);
          pickedValue1_ = kBufrMissingValue;
          pickedValue2_ = kBufrMissingValue;
          add( anObs, coordValue_[ coordDescrCount_ - 1 ]  );
          return;                            //-- no match for this coordinate => give up
       }
    }

    //--
    //-- We have reached the innnermost coordinate interval, lets pick the value(s)
    //--
    if( pickAll_ )                           //-- shall we pick all values for the innermost coordinate?
    {
       long coordDescr   = coordDescr_[ coordDescrCount_ - 1 ];
       long prevCoordDescr = coordDescrCount_ > 1
                           ? coordDescr_[ coordDescrCount_ - 2 ]
                           : -999;                  //-- use dummy if only one coordinate level

       int    currentIndex = startIndex + 1;        //-- set start position after the previous coordinate location

       //-- NOTE: startIndex runs 0,1,2,..., BUT index for operator[] should run 1,2,3,...
       double currentValue = anObs[ currentIndex+1 ]; //-- adjust internal pointer to start position
       long   currentDescr = anObs.currentDescriptor();

       bool cont = true;
       while( cont )
       {
          currentDescr = anObs.currentDescriptor();
          if( currentDescr == prevCoordDescr )
             break;                          //-- outer coordinate value changed => break

          if( currentDescr == coordDescr )
          {                                  //-- found new inner coordinate value
             double currentCoordValue = anObs.currentValue();

             anObs.setNextDescriptor();      //-- advance to the next data
             ++currentIndex;

             pickedValue1_ = kBufrMissingValue;
             pickedValue2_ = kBufrMissingValue;
                                             //-- loop until next innermost coordinate value
             while( anObs.currentDescriptor() != coordDescr )
             {
                currentDescr = anObs.currentDescriptor();
                if( currentDescr == param_ )
                   pickedValue1_ = anObs.currentValue(); //-- main data found

                if( currentDescr == param2_ )
                   pickedValue2_ = anObs.currentValue(); //-- secondary data found (only if vector)

                cont = anObs.setNextDescriptor();
                ++currentIndex;
                if( ! cont )
                {                            //-- no more data available
                    add( anObs, currentCoordValue );//-- add last data value(s) for current innermost coordinate
                    break;                   //-- end-of-msg reached => break
                }
             }
             add( anObs, currentCoordValue );//-- add data value(s) for current innermost coordinate
             anObs[ currentIndex + 1 ];      //-- revert internal pointer
          }
          else
          {
             cont = anObs.setNextDescriptor();
             ++currentIndex;
          }
       }
    }
    else                                     //-- just pick the value for the single given innermost coordinate
    {
	//-- Method 'valueBySpecifier':
	//--  o  first looks for a coordinate descriptor with the given value
	//--  o     starts looking from 'startIndex'
	//--  o  if found then looks for descriptor 'param_'
	//--  o     starts looking from where the given coordinate descriptor was found
	//--  o  returns the value of descr 'param_' (or a missing value)
	//--
	pickedValue1_ = anObs.valueBySpecifier( coordDescr_[ coordDescrCount_ - 1 ]
						, coordValue_[ coordDescrCount_ - 1 ]
						, param_
						, startIndex );

	if( param2_ > 0 )                    //-- geovector ?
	{
	  pickedValue2_ = anObs.valueBySpecifier( coordDescr_[ coordDescrCount_ - 1 ]
						, coordValue_[ coordDescrCount_ - 1 ]
						, param2_
						, startIndex );
	}
	else
	  pickedValue2_ = 0;                 //-- a 'non-missing' value

	add( anObs, coordValue_[ coordDescrCount_ - 1 ] );
    }

    return;
}
//_________________________________________________________
void
BufrPicker::add( MvObs& anObs, double aCoordValue )
{
    if( pickedValue1_ == kBufrMissingValue && ! includeMissingValues_ )
       return;

    if( geoVectorType_ && pickedValue2_ == kBufrMissingValue && ! includeMissingValues_  )
       return;

    MvLocation  myLoc = anObs.location();
    TStaticTime myTime = anObs.obsTime();

    outfile_ << myLoc.latitude() << mySeparator;
    outfile_ << myLoc.longitude() << mySeparator;

                                             //-- use innermost coordinate value as "level"
    outfile_ << aCoordValue << mySeparator;

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

    if( pickedValue1_ == kBufrMissingValue )
       pickedValue1_ = missingValue_;        //-- swap to the requested missing value

    outfile_ << pickedValue1_;

    if( geoVectorType_ )                     //-- two values for geovectors
    {
       if( pickedValue2_ == kBufrMissingValue )
          pickedValue2_ = missingValue_;     //-- swap to the requested missing value

       outfile_ << mySeparator << pickedValue2_;
    }

    outfile_ << endl;

    counter_++;
}
//_________________________________________________________
void
BufrPicker::close()
{
     outfile_.close();
}
//_________________________________________________________

void
BufrPicker::serve( MvRequest& in, MvRequest& out )
{
   cout << "\n\nB u f r P i c k e r" << endl;
   time_t myStartTime;
   time( &myStartTime );
   clock_t myStartCpu = clock();

   in_ = in;
   in_.print();

   getParams();                          //-- new inSet_ & iter_

   MvRequest x = createRequest();
   MvObs     myObs;

   counter_ = 0;                         //-- m a i n   l o o p
   try
   {
     while( (myObs = (*iter_)(obsOrMsg_) ) )
     {
        pickValue( myObs );
     }
     close();
   }
   catch ( MvException& e )
   {
     marslog( LOG_WARN, "BufrPicker: %s", e.what() );
     if( failOnError_ )
         throw MvException( "BufrPicker 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";

   if( pickAll_ )
       sendProgress( "Picked %d values (out of %d reports) into GeoPoints file in %d secs (cpu %d secs)"
                   , counter_, inSet_->obsCount(), (int)wallClockTime, (int)cpuTime );
   else
       sendProgress( "Picked %d observations (out of %d) into GeoPoints file in %d secs (cpu %d secs)"
                   , counter_, inSet_->obsCount(), (int)wallClockTime, (int)cpuTime );

   delete iter_;
   iter_ = 0;

   delete inSet_;
   inSet_ = 0;

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

int
main( int argc, char **argv )
{
	MvApplication theApp( argc, argv );
	BufrPicker     picker;

	theApp.run();
}
