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

// OperaRadar.cc, Apr2012/vk
//
//  Extract and convert OPERA BUFR radar images to geopoints (and later
//  hopefully also to GRIB2).
//
// INPUT DATA (as of May 2012):
// ===========================
//
// - This software has been implemented and tested with OPERA data
//   available from ECFS (ec:/oparch/opera/...).
// - This software has not been tested with other types of OPERA data
//   [no test data available, except for "Odyssey" data that cannot
//    handled (see below)].
//
// - OPERA BUFR data requires custom BUFR tables that are installed in
//   directory share/metview/etc/ (those tables that are used to decode
//   OPERA data available in ECFS)
//
// - Users can direct Metview to use other OPERA BUFR tables by setting
//   env.variable OPERA_BUFR_TABLES to point to directory containing them.
//
// - Current version, based on the current ECMWF BUFR package, is not able
//   to handle new OPERA "Odyssey" data (example data available from
//   http://www.knmi.nl/opera/odc/ODIM_example_files/Odyssey.zip )
//   because the example data contains 'Extended delayed descriptor
//   replication factor' with a values greater than the hard coded
//   arrays in BUFR package can handle.
//
// CURRENT STATUS (as of May 2012):
// ===============================
//  Phase 1 (done): extract pixel values into unsigned char array
//                   test with pseudo geopoints
//  Phase 2 (done): convert to rainfall values and add mapping to
//                   real world geopoints
//  Phase 3 (future): add other output formats (GRIB2, PNG?,...)
//  Phase 4 (future): extract several images for animation
//

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

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

//__________________________________________________________ OperaRadar

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

 public:
    OperaRadar();
    ~OperaRadar();

    void      serve( MvRequest&, MvRequest& );

 protected:
    void      setOperaBufrTableDir();
    void      getData();
    MvRequest createRequest();
    bool      getIntensities();
    void      geopointsOutput( const unsigned char* img );

 private:
    MvRequest         in_;               //-- input request
    MvRequest         data_;             //-- sub-request of input request
    MvObsSet*         inSet_;            //-- input observation set
    MvObsSetIterator* iter_;             //-- input iterator (no filtering)
    MvObs             obs_;              //-- current observation
    vector<float>     intensity_;        //-- true rain intensities
    ofstream          outfile_;          //-- output geopoints file
    bool              includeMissingValues_;
    double            missingValue_;
    bool              failOnError_;
};


//_________________________________________________________

OperaRadar::OperaRadar() : MvService( "OPERA_RADAR_FILTER" ),
   inSet_( 0 ),
   iter_( 0 ),
   includeMissingValues_(false)
{ }
//_________________________________________________________

OperaRadar::~OperaRadar()
{
  delete iter_;
  delete inSet_;
}
//_________________________________________________________
void
OperaRadar::setOperaBufrTableDir()
{
   const int overWrite = 1;
   int ok = 0;
   const char* operaTables = getenv( "OPERA_BUFR_TABLES" );
   if( operaTables )
   {
      ok = setenv( "BUFR_TABLES", operaTables, overWrite );
      marslog( LOG_INFO, "OperaRadarFilter: using user given BUFR tables " );
   }
   else
   {
      string mvShareDir = getenv( "METVIEW_DIR_SHARE" );
      mvShareDir += "/etc/";
      ok = setenv( "BUFR_TABLES", mvShareDir.c_str(), overWrite );
      marslog( LOG_INFO, "OperaRadarFilter: using Metview provided OPERA BUFR tables " );
   }
   cout << "Looking OPERA BUFR files from dir " << getenv("BUFR_TABLES") << endl;
   //-- test ok...
}
//_________________________________________________________
void
OperaRadar::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_ );


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


    if( strcmp( in_("FAIL_ON_ERROR"), "NO" ) == 0 )
        failOnError_ = false;
}
//_________________________________________________________
MvRequest
OperaRadar::createRequest()
{
   char* fileName = marstmp();

   outfile_.open( fileName, ios::out );
   outfile_ << "#GEO\n";
   outfile_ << "#FORMAT XYV\n";
   outfile_ << "#DATA\n";

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

   return x;
}
//_________________________________________________________
bool
OperaRadar::getIntensities()
{
   const long radarRainfallIntensity = 21036;
   intensity_.clear();

   int cnt = 0;
   float myRainfall = obs_.value( radarRainfallIntensity );
   while( myRainfall != kBufrMissingValue )
   {
      intensity_.push_back( myRainfall );
      cnt++;
      myRainfall = obs_.nextValue();
   }
   cout << "Rain intensity values count: " << cnt << endl;
}
//_________________________________________________________
void
OperaRadar::geopointsOutput( const unsigned char* img )
{
   double proj = obs_.value( 29201 );        //-- 5 == Lambert...
   if( proj != 5 )
   {
      throw MvException( "Radar data is in unimplemented projection" );
   }

   double lat  = obs_.value( 29194 );        //-- Latitude Origin [Degree]
   double lon  = obs_.value( 29193 );        //-- Longitude Origin [Degree]
cout << "lat/lon: " << lat << "/" << lon << endl;

   long a = obs_.value( 29199 );             //-- semi-major axis of rotation ellipsoid [Meters]
   long b = obs_.value( 29200 );             //-- semi-minor axis of rotation ellipsoid [Meters]
cout << "a/b: " << a << "/" << b << endl;

   double x_offset = obs_.value( 29195 );    //-- X-Offset (of NW corner of NW pixel) [Meters]
   double y_offset = obs_.value( 29196 );    //-- Y-Offset (of NW corner of NW pixel) [Meters]
cout << "x_offset/y_offset: " << x_offset << "/" << y_offset << endl;

   long dx = obs_.value( 5033 );             //-- "pixel" X size [Meters]
   long dy = obs_.value( 6033 );             //-- "pixel" Y size [Meters]

   ostringstream proj4Init;                  //-- Proj4 initialisation string
#if 1
   proj4Init << "+proj=laea +lat_0=" << lat
             << " +lon_0=" << lon
             << " +a=" << a
             << " +b=" << b
             << ends;
#else
   double e2 = 1 - double(b*b) / double(a*a);
   proj4Init << "+proj=laea +lat_0=" << lat
             << " +lon_0=" << lon
             << " +a=" << a
             << " +es=" << e2
             << ends;
#endif
cout << "proj4Init: " << proj4Init.str() << endl;

   projPJ pj = pj_init_plus( proj4Init.str().c_str() );
   if( ! pj )
   {
      throw MvException( "Geographical projection (Proj4) initialisation failed" );
   }

   projUV origin;                            //-- origin coordinates
   origin.v = lat * DEG_TO_RAD;
   origin.u = lon * DEG_TO_RAD;
   origin   = pj_fwd( origin, pj );          //-- compute projected origin coordinates
cout << "origin.u/origin.v: " << origin.u << "/" << origin.v << endl;

   int ncols = obs_.value( 30021 );          //-- number of pixels on a row
   int nrows = obs_.value( 30022 );          //-- number of pixels on a column

   projUV NW_corner = origin;                //-- start with projected Origin
   NW_corner.u += x_offset;                  //-- compute projected NW corner
   NW_corner.v += y_offset;

   const char* tab = "\t";
   int imgInd = 0;
   int gptCount = 0;
   for( int row=0; row < nrows; row++ )      //-- loop through pixels
   for( int col=0; col < ncols;  col++ )
   {
      float val = intensity_[ img[imgInd++] ];//-- convert pixel value to true rainfall
      if( val > 0 || includeMissingValues_)   //-- skip zero valued pixels
      {
         projUV cur = NW_corner;             //-- start with projected NW corner coordinates
         cur.u += col*dx;                    //-- move to the current pixel location
         cur.v -= row*dy;
         cur = pj_inv( cur, pj );            //-- convert back to lat/lon world

         if (includeMissingValues_ && val == 0) //-- convert zero to user-defined missing value
            val = missingValue_;

         outfile_ << cur.u*RAD_TO_DEG << tab << cur.v*RAD_TO_DEG << tab << val << endl;
         ++gptCount;
      }
   }
   cout << "OperaRadar::geopointsOutput: input pixel count = " << imgInd << endl;
   cout << "OperaRadar::geopointsOutput: geopoints created = " << gptCount << endl;

   outfile_.close();
}
//_________________________________________________________

void
OperaRadar::serve( MvRequest& in, MvRequest& out )
{
   cout << "\n\nO p e r a R a d a r F i l t e r" << endl;

   in_ = in;
   in_.print();

   setOperaBufrTableDir();

   getData();

   MvRequest x = createRequest();
   obs_ = (*iter_)();                        //-- <aki>: testing with the first image only

   try
   {
     unsigned char* radimg = obs_.OperaRadarImage();
     getIntensities();                       //-- for converting pixel values to rain intensities
     geopointsOutput( radimg );
   }
   catch ( MvException& e )
   {
     marslog( LOG_WARN, "OperaRadarFilter: %s", e.what() );
     if( failOnError_ )
         throw MvException( "OperaRadarFilter failed" );
   }

   delete iter_;
   delete inSet_;

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

int
main( int argc, char **argv )
{
	MvApplication theApp( argc, argv );
	OperaRadar    radar;

	theApp.run();
}
