/***************************** 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();
}
