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

#include "inc_iostream.h"
#include "inc_string.h"
#include "inc_stl.h"
#include "Metview.h"
#include "MvTimeSerie.h"
#include "MvLocation.h"
#include "MvDate.h"
#include "MvGeoPoints.h"
#include "MvObs.h"


const double cBIGDOUBLE = 3.4E35;
const char   cTAB[]     = "\t";
const string cGRIB("GRIB");
const string cGPTS("GEOPOINTS");
const string cBUFR("BUFR");

const long cBufrType    = 252;
const long cBufrSubtype =   0;


class TimeSerie : public MvService
{
protected:
	TimeSerie(char* a) : MvService(a) {};

	void    init(MvRequest&);
	void    do_geopoints();
	string  ref_string( int param, int level, int number
	                  , const MvLocation& loca, const char* expver );
	void    set_basedate( int );

	virtual void do_the_job() = 0;

public:
	TimeSerie() : MvService("TIMESERIES") {};
	~TimeSerie() { delete serie_; };
	void serve(MvRequest&,MvRequest&);
protected:
	MvRequest    data_;
	MvRequest    station_;
	string       name_;
	MvRequest    axis_;               //-- dummy
	MvRequest    graph_;
	MvTimeSerie* serie_;
	MvDate       base_date_;
	bool         base_date_set_;
	bool         scaling_;
	double       scaleAdd_;
	double       scaleMul_;
	int          message_;
        double       area_[4];
};


class FieldPointTimeSerie : public TimeSerie
{
public:
	FieldPointTimeSerie() : TimeSerie("FIELDPOINT_TIMESERIES") {};
	virtual void do_the_job();
};


class FieldMeanTimeSerie : public TimeSerie
{
public:
	FieldMeanTimeSerie() : TimeSerie("FIELDMEAN_TIMESERIES") {};
	virtual void do_the_job();
};


class GeoPointTimeSerie : public TimeSerie
{
public:
	GeoPointTimeSerie() : TimeSerie("GEOPOINT_TIMESERIES") {};
	virtual void do_the_job();
};


class BufrTimeSerie : public TimeSerie
{
public:
	BufrTimeSerie() : TimeSerie("BUFR_TIMESERIES") {};
	virtual void do_the_job();
protected:
	long extractDate( MvObs& obs );
};

//_____________________________________________________________________________
void
TimeSerie::init( MvRequest& in )
{
  data_     =  in("DATA");
  station_  =  in("STATION");
  //axis_   =  in("VERTICAL_AXIS");
  graph_    =  in("ATTRIBUTES");

  base_date_set_ = false;

  if( (const char*)in("NAME") )      //-- NAME for geopoints and BUFR
    name_ = (const char*)in("NAME");
  else
    name_ = "Curve without a name";

  if( (const char*)in("SCALING") )
    {
      scaleAdd_ = in("SCALING");
      if( in.countValues("SCALING") < 2 )
	scaleMul_ = 1;
      else
	scaleMul_ = in("SCALING",1);
    }
  else
    {
      scaleAdd_ = 0;
      scaleMul_ = 1;
    }
  scaling_ = scaleAdd_ != 0 || scaleMul_ != 1 ? true : false;

  if( (const char*)in("MESSAGE") )   //-- for BUFR only
     message_ = (int)in("MESSAGE");
  else
     message_ = 1;

  if( (const char*)in("AREA") )
    {
      for( int i = 0; i < 4; ++i )
         area_[i] = in("AREA",i);
    }

  serie_   =  new MvTimeSerie( axis_, graph_ );

  //-- if no graph attributes given then set a timeseries to return a request
  //-- containing only 'HorAxis+Curve', otherwise 'HorAxis+Curve+GraphAttr'
  if( graph_ )
    serie_->setReqFilter( eTsReqsCurveAndGraph );
  else
    serie_->setReqFilter( eTsReqsCurveOnly );
}
//_____________________________________________________________________________
void
TimeSerie::set_basedate( int bd )
{
  if( ! base_date_set_ )
    {
      base_date_ = MvDate( (double)bd );
      base_date_set_ = true;

      serie_->setDate( bd, 0 );
    }
#if 0
  else if( MvDate( (double)bd ) < base_date_ )
    {
      cerr << "we should give error msg because another basedate is smaller" << endl;
      marslog( LOG_EROR, "TimeSerie::set_basedate: different basedate!!" );
    }
#endif
}
//_____________________________________________________________________________
void
TimeSerie::serve( MvRequest& in, MvRequest& out )
{
  cout << "--------------- INPUT REQUEST ------------------------" << endl;
  in.print();
  cout << "----------- end INPUT REQUEST ------------------------" << endl;

  init( in );

  do_the_job();

  //-- this one increases legend texts to be visible/readable --//
  MvRequest biggerLegText("PTEXT");
  biggerLegText("TEXT_LEGEND_MAGICS_STYLE") = "ON";
  biggerLegText("LEGEND_COLUMN_COUNT") = 2;
  biggerLegText("LEGEND_ENTRY_MAXIMUM_WIDTH") = 12.0;

  out = serie_->getRequest() + biggerLegText;

  cout << "--------------- OUTPUT REQUEST -----------------------" << endl;
  out.print();
  cout << "----------- end OUTPUT REQUEST -----------------------" << endl;
}
//_____________________________________________________________________________
string
TimeSerie::ref_string( int param, int level, int number
                     , const MvLocation& loca, const char* expver )
{
  std::ostringstream oref;
  oref << param
       << "-"
       << level
       << "-"
       << number
       << "-"
       << loca
       << "-"
       << expver;

  return oref.str();
}
//_____________________________________________________________________________
void
FieldPointTimeSerie::do_the_job()
{
  int  param  = -1;
  int  level  = -1;
  int  number = -1;

  MvRequest  last_station;

  MvFieldSet          fs(data_);
  MvFieldSetIterator  iter(fs);            //-- fieldset iterator

  iter.sort("LEVELIST");
  iter.sort("NUMBER");
  iter.sort("PARAM");

  MvField field;
  while(field = iter())                    //-- iterate through all fields
    {
      MvFieldExpander expand(field);
      MvRequest r = field.getRequest();

      int         req_date   = r("DATE");
      int         req_time   = r("TIME");
      double      req_step   = r("STEP");
      const char* req_expver = r("EXPVER");

      set_basedate( req_date );

      //-- compute time offset between base date and current data date --//
      MvDate cur_date( (double)req_date + (double)req_time/2400.0 + (double)req_step/24 );
      double x_offset = base_date_.time_interval_hours( cur_date );

      cout << req_date << cTAB
           << req_time << cTAB
	   << req_step << cTAB
	   << x_offset; //-- << endl;

      int    req_par  = r("PARAM");
      int    req_lev  = r("LEVELIST");
      int    req_num  = r("NUMBER");

      //-- check if new time series --//
      if (req_par != param || req_lev != level || req_num != number)
	{
	  param  = req_par;
	  level  = req_lev;
	  number = req_num;
	  string par_name = (const char*)field.marsName();

	  while (station_)
	    {
	      string   station_name =  (const char*)station_("NAME");
	      const char* icon_name =  station_("_CLASS");
	      if (icon_name)
		last_station = station_;

	      double lat = station_("LATITUDE");
	      double lon = station_("LONGITUDE");
	      MvLocation loca( lat, lon );

	      std::ostringstream otit;
	      otit << par_name.c_str();

	      if( level )
		otit << ", level "
		     << level;

	      otit << ", "
		   << station_name.c_str()
		   << " "
		   << loca
	           << ends;

	      string title = otit.str();
	      string ref   = ref_string( param, level, number, loca, req_expver );

	      serie_->createNewGroup( ref.c_str(), title.c_str() );

	      serie_->addIconInfo( ref.c_str(), "DATA", data_ );
	      serie_->addIconInfo( ref.c_str(), "STATION", last_station );

	      //serie_->setTitle( ref.c_str(), title.c_str() );

	      station_.advance();
	    }
	  station_.rewind();
	}

      //-- pick the point value, for each location --//
      while( station_ )
	{
	  double lat = station_("LATITUDE");
	  double lon = station_("LONGITUDE");
	  MvLocation loca( lat, lon );
	  string ref = ref_string( param, level, number, loca, req_expver );
	  cout << cTAB << ref.c_str() << endl;

	  double  y = field.interpolateAt( lon, lat );
	  if ( y < cBIGDOUBLE )
	    {
              if( scaling_ )
	         y = ( y + scaleAdd_ ) * scaleMul_;

	      serie_->addPoint( ref.c_str(), x_offset, y );
	    }

	  station_.advance();
	}
      station_.rewind();
    }
}
//_____________________________________________________________________________
void
FieldMeanTimeSerie::do_the_job()
{
  int  param  = -1;
  int  level  = -1;
  int  number = -1;

  MvRequest  last_station;

  MvFieldSet          fs(data_);
  MvFieldSetIterator  iter(fs);            //-- fieldset iterator

  iter.sort("LEVELIST");
  iter.sort("NUMBER");
  iter.sort("PARAM");

  MvField field;
  while(field = iter())                    //-- iterate through all fields
    {
      MvFieldExpander expand(field);
      MvRequest r = field.getRequest();

      int         req_date   = r("DATE");
      int         req_time   = r("TIME");
      double      req_step   = r("STEP");
      const char* req_expver = r("EXPVER");

      set_basedate( req_date );

      //-- compute time offset between base date and current data date --//
      MvDate cur_date( (double)req_date + (double)req_time/2400.0 + (double)req_step/24 );
      double x_offset = base_date_.time_interval_hours( cur_date );

      cout << req_date << cTAB
           << req_time << cTAB
	   << req_step << cTAB
	   << x_offset; //-- << endl;

      int    req_par  = r("PARAM");
      int    req_lev  = r("LEVELIST");
      int    req_num  = r("NUMBER");

      MvLocation loca( area_[0], area_[1] );

      //-- check if new time series --//
      if (req_par != param || req_lev != level || req_num != number)
	{
	  param  = req_par;
	  level  = req_lev;
	  number = req_num;
	  string par_name = (const char*)field.marsName();

	      std::ostringstream otit;
	      otit << par_name.c_str();

	      if( level )
		otit << ", level "
		     << level;

	      otit << ", area("
		   << area_[0] << ","
		   << area_[1] << ","
		   << area_[2] << ","
		   << area_[3] << ")"
	           << ends;

	      string title = otit.str();

	      string ref   = ref_string( param, level, number, loca, req_expver );

	      serie_->createNewGroup( ref.c_str(), title.c_str() );

	      serie_->addIconInfo( ref.c_str(), "DATA", data_ );
	}

      string ref = ref_string( param, level, number, loca, req_expver );
      cout << cTAB << ref.c_str() << endl;

      double  y = field.integrate( area_[0], area_[1], area_[2], area_[3] );
      if ( y < cBIGDOUBLE )
        {
           if( scaling_ )
           y = ( y + scaleAdd_ ) * scaleMul_;

           serie_->addPoint( ref.c_str(), x_offset, y );
        }
    }
}
//_____________________________________________________________________________
void
GeoPointTimeSerie::do_the_job()
{
  const char* myPath = data_("PATH" );
  MvGeoPoints gpts( myPath );

  int cnt = gpts.count();
  if( cnt < 2 )
    {
      setError( 131, "Empty geopoints file" );
    }

  if( gpts.format() == eGeoString )
    {
      setError( 132, "Geopoints format 'XYV' not supported!" );
    }
  if( gpts.format() == eGeoString )
    {
      setError( 133, "String valued geopoints are not supported!" );
    }

  MvGeoP1 gp1 = gpts[0];
  set_basedate( gp1.date() );

  serie_->setDate( gp1.date(), 0 );

  list<string> ref_list;
  list<string>::iterator lit;

  for( int i = 0; i<cnt; ++i )
    {
      MvGeoP1 gp = gpts[ i ];
      MvDate cur_date( (double)gp.date() + (double)gp.time()/2400 );
      double x_offset = base_date_.time_interval_hours( cur_date );

      cout << gp.date() << cTAB << gp.time() << cTAB << x_offset; //-- << endl;

      double lat = gp.lat_y();
      double lon = gp.lon_x();
      MvLocation loca( lat, lon );
      string ref = ref_string( 0, (int)gp.height(), 0, loca, "NA" );
      cout << cTAB << ref.c_str() << endl;

      if( find( ref_list.begin(), ref_list.end(), ref ) == ref_list.end() )
	{
	  ref_list.push_back( ref );

	  cout << "New serie point at " << loca << endl;

          ostringstream otit;
          if( name_ != "AUTOMATIC" )
	     otit << name_;
          else
	     otit << "Point "
	          << loca;

	  string title = otit.str();

	  serie_->createNewGroup( ref.c_str(), title.c_str() );
	  serie_->addIconInfo( ref.c_str(), "DATA", data_ );
	}

      double y = gp.value();
      if( scaling_ )
	 y = ( y + scaleAdd_ ) * scaleMul_;

      serie_->addPoint( ref.c_str(), x_offset, y );

    }
}
//_____________________________________________________________________________
void
BufrTimeSerie::do_the_job()
{
  MvObsSet obsSet( data_ );
  MvObsSetIterator obsIter( obsSet );

  obsIter.setMessageType( cBufrType );
  obsIter.setMessageSubtype( cBufrSubtype );

  MvObs obs = obsIter();
  if( message_ > 1 )
     for( int i=1; i < message_; ++i )       //-- skip until the requested msg
        obs = obsIter();

  if( obs )
    {
      int myDate = extractDate( obs );       //-- extract date info
      int myTime = 100*obs.intValue( 4004 ); //-- multiply to make format HHMM

      set_basedate( myDate );
      serie_->setDate( myDate, myTime );

      double lat = obs.value( 5001 );        //-- extract location
      double lon = obs.value( 6001 );
      MvLocation loca( lat, lon );

      int    startIndex = 9;                 //-- check if xtra level coord descr
      double mayBeLevel = obs[ startIndex ];
      long   currDescr  = obs.currentDescriptor();

      string levelInfo("");
      if( currDescr == 7004 || currDescr == 7007 )
      {
         ostringstream oslev;                //-- level: add level info into default title

	 if( currDescr == 7004 )             //-- convert Pa to hPa
	    oslev << mayBeLevel/100 << " hPa ";
	 else
	    oslev << mayBeLevel << " m ";    //-- hardcoded unit (obs.unit() would return "M")

	 levelInfo = oslev.str();

         ++startIndex;                       //-- adjust data starting index
      }

      double parval = obs[ startIndex + 1 ]; //-- extract parameter name
      string parnam = obs.name();

      //-- non-orthodoxic but valid use of ref_string parameters --//
      string ref = ref_string( myDate, myTime, currDescr, loca, parnam.c_str() );

      string title;
      if( name_ != "AUTOMATIC" )
	{
	  title = name_;                     //-- user provided title
	}
      else
	{
	  ostringstream os;
	  os << levelInfo                    //-- automatic title
	     << parnam
	     << " "
	     << loca;
	  title = os.str();
	}

      serie_->createNewGroup( ref.c_str(), title.c_str() );

      int mySteps = obs.numberOfLevels( 4024 );  //-- 0'04'024 time period of displacement
      for( int s=0; s<mySteps; ++s )
	{
	  int x_offset = (int)obs[ startIndex + 2*s ];
	  double y_val = obs[ startIndex + 2*s + 1 ];

	  if( y_val != kBufrMissingValue )
	  {
	     if( scaling_ )
		y_val = ( y_val + scaleAdd_ ) * scaleMul_;

	     serie_->addPoint( ref.c_str(), x_offset, y_val );
	  }
	}
    }
}
//_____________________________________________________________________________
long
BufrTimeSerie::extractDate( MvObs& obs )
{
  long y = obs.intValue( 4001 );
  long m = obs.intValue( 4002 );
  long d = obs.intValue( 4003 );

  return 10000*y + 100*m + d;
}
//_____________________________________________________________________________
int
main( int argc, char **argv )
{
  MvApplication theApp(argc,argv);

  FieldPointTimeSerie fieldpointtimeserie;
  FieldMeanTimeSerie  fieldmeantimeserie;
  GeoPointTimeSerie   geotimeserie;
  BufrTimeSerie       bufrtimeserie;

  theApp.run();
}
