/***************************** 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 "Datacoverage.h"


/////////// Checks whether an obs is satelite or not.
int is_sat(int subtype)
{
  return  ( (subtype >= 61 && subtype <= 65) ||
	   (subtype >= 71 && subtype <= 75)  ||
	   (subtype >= 82 && subtype <= 88)  ||
	   (subtype >= 121 && subtype <= 127)  ||
	   subtype == 51 || subtype == 53 );
}

// Default constructor.
CoverageService::CoverageService() : MvService("DATACOVERAGE"),
				     nr_total(0),
				     cutoff(0),
				     check_time(),
				     in_time(),
				     numbers(10,0),
				     plot_ss(1),
				     subtype_nrs(),
				     descriptors(),
				     late_check(0)
{}


void CoverageService::serve(MvRequest& in, MvRequest& out)
{
  char *fname = marstmp();
  Cached plotss;
  long ymd,hour;
  int i = 0;

  // Print incoming request.
  in.print();

  MvObsSet *in_set;
  MvRequest symb;
  MvRequest mars_in;
  MvRequest data_req;

  // Reset members to defaults.
  descriptors.erase(descriptors.begin(),descriptors.end() );
  subtype_nrs.erase(subtype_nrs.begin(),subtype_nrs.end() );
  plot_ss = 1;
  nr_total = cutoff = late_check = 0;


  in_req = in;

  // Get any types given by user. This is useful if data is
  // read from a file containing types that should not be
  // processed ( could be filtered by ObsFilter instead).
  long mytype = -1;
  if  ( in_req.countValues("SUBTYPE") > 0 )
    {
      sendProgress("Filtering by SUBTYPE field is no longer supported.");
      sendProgress("Use ObsFilter prior to calling Datacoverage");
    }
  in_req.getValue(data_req, "DATA");

  // Get the symbol values.
  symb = in_req.getSubrequest("SYMBOL");
  const char* verb = symb.getVerb();
  if( ! verb )
    {
      symb = MvRequest( "PSYMB" );
      symb( "SYMBOL_TYPE" ) = "MARKER";
    }


  // Check if reference date, time and and cutoff for plotting late obs. is given.
  const char *chour,*cymd;
  chour   = in_req("CHECKING_TIME");
  cymd    = in_req("CHECKING_DATE");

  if ( chour && cymd )
    {
      hour = atol(chour);
      ymd  = atol(cymd);
      late_check = 1;
      TDynamicTime tmp_time(ymd,hour);
      in_time = tmp_time;
      cutoff = (int)in("CUTOFF_TIME_IN_MINUTES");
      if ( cutoff > 0 )
	{
	  check_time = in_time;
	  check_time.ChangeByMinutes(cutoff);
	}
    }
  else
    {
      sendProgress("Datacoverage: Late observations not plotted.");
      sendProgress("Datacoverage: To plot late obs, set values for \
'Checking Date/Time' and Cutoff.");
    }


  // Check if all subsets should be plotted for multi subset messages.
  in_req.getValue(plotss,"PLOT_SUBSETS");
  if ( plotss == Cached("OFF") ) plot_ss = 0;


  // Check if any descriptors is given for quality checking. Default is
  // to plot the lowest in the whole message. If any descriptors are given,
  // only the quality of these will be checked, and the lowest value used.
  for ( i = 0; i < in_req.countValues("DESCRIPTORS_TO_CHECK");i++ )
    descriptors.push_back((long)in_req("DESCRIPTORS_TO_CHECK",i));



  // Construct the MvObsSet from the data request.
  in_set = new MvObsSet( data_req );

  // If write_values is OK, complete the out request.
  if ( write_values(fname,*in_set) )
    {
      MvRequest geo( "GEOPOINTS" );
      geo( "TEMPORARY" ) = 1;
      geo( "PATH" ) = fname;
      geo("ORIGINAL_BUFR_FILE") = data_req("PATH");

      generate_info_str(geo,"TITLE");

      if ( (const char *)in_req("_NAME") )
	geo("_NAME") = in_req("_NAME");

      set_legend_values(symb);

      // Add some text to output request.
      MvRequest text("PTEXT");
      text( "TEXT_AUTOMATIC" ) = Cached( "NO");
      text( "TEXT_USER" )      = Cached( "YES");
      text( "TEXT_COLOUR" )  = "BLACK";

      generate_info_str(text, "TEXT_LINE_1");

      text("LEGEND_TEXT_MAXIMUM_HEIGHT") = 0.2;
      text("LEGEND_TEXT_QUALITY") = "MEDIUM";

      int columnCount = in_req( "LEGEND_COLUMN_COUNT" );
      if( columnCount > 0 )
	text( "LEGEND_COLUMN_COUNT" ) = columnCount;

      out = geo + symb + text;

    }

  // Print outgoing request
  out.print();
} //------------------------------------------------ End serve

//------------------------------------------------------------------ write_values
// Writes values to geopoints file.
int CoverageService::write_values(char *fname,MvObsSet &obs_set)
{
  MvObsSetIterator obs_iter (obs_set);
  MvObs obs;
  ofstream outfile;
  int nr_subsets;
  int i;

  outfile.open(fname,ios::out);
  outfile << "#GEO" << endl;
  outfile << "#DATA" << endl;

  const long lat_fine  = 5001;
  const long lat_coarse = 5002;
  const long lon_fine = 6001;
  const long lon_coarse = 6002;
  fortfloat f_long, f_lat;

  int symbol;
  int sec2_msg = 0;
  while ( obs = obs_iter(NR_returnMsg) )
    {

      ObsValues values(obs,cutoff,check_time,plot_ss,descriptors,late_check);

      if ( !sec2_msg && !obs.hasSection2() )
     	{
	  sendProgress("Datacoverage: Some messages have no section 2, plotted as Unknown");
	  sec2_msg = 1;
	}

      if ( plot_ss )
	nr_subsets = obs.msgSubsetCount();
      else
	nr_subsets = 1;

      i = 1;

      for(;;)
	{
	  // Use descriptor for coarse or fine lat/lon, whatever is found.
	  if ( (f_lat = obs.value(lat_fine) ) ==  kBufrMissingValue)
	    f_lat = obs.value(lat_coarse);
      	  if ( (f_long = obs.value(lon_fine) ) ==  kBufrMissingValue)
	    f_long = obs.value(lon_coarse);


	  symbol = values.get_symbol(obs,subtype_nrs);
	  numbers[symbol]++;

	  //cout << f_lat << "\t\t" << f_long << "\t\t" << symbol << endl;
	  outfile << f_lat << " " << f_long ;
	  outfile << " 0  0 0 " << symbol << endl;
	  nr_total++;
	  if ( i++ == nr_subsets ) break;
	  obs = obs_iter();
	}
    }

  // Close geopoints file.
  outfile.close();
  return 1;
} //------------------------------------------------------ End write_values


//-------------------------------------------------------- set_legend_values
// Set the legend values based on what the subtypes are.
void CoverageService::set_legend_values(MvRequest &s)
{
  char buf[150],lg_txt_buf[30];

  s("LEGEND") = "ON";
  s("LEGEND_TITLE") = "OFF";
  s("LEGEND_TEXT_COLOUR") = "BLACK";
  s("LEGEND_TEXT_QUALITY")= "HIGH";
  s("LEGEND_TEXT_COMPOSITION")= "USER_TEXT_ONLY";


  int txt_nr = 0;

  // Check is late observations are plotted.
  if ( late_check )
    sprintf(buf,"LATE : %d",numbers[LATE]);
  else
    sprintf(buf,"LATE NOT PLOTTED");

  sprintf(lg_txt_buf,"LEGEND_USER_TEXT_%d",++txt_nr);
  s(lg_txt_buf)= buf;


  // Check the subtype values to see if all is well.
  map<int,int,less<int> >::iterator ii = subtype_nrs.begin();
  int stype = (*ii).first;
  int found_sat = 0, found_conv = 0;

  for (; ii != subtype_nrs.end(); ii++ )
    {

      if ( is_sat((*ii).first) ) found_sat = 1;
      else found_conv = 1;
    }

  if ( found_sat && found_conv )
    {
      sendProgress("Datacoverage: Plotting of satelite and conventional together not supported");
      sendProgress("Datacoverage: Results are not meaningful, satelite data plotted by ident");
    }


  // Use the type of the first type found when generating the legend.
  if ( is_sat(stype ) )
    {
      // Satelite, loop thru info from headerfile to plot the legend.
      for (int i = 0; i < NUMBER(ident_info); i++ )
	{
	  if ( stype >= ident_info[i].st_min &&
	      stype <= ident_info[i].st_max )
	    {
	      sprintf(lg_txt_buf,"LEGEND_USER_TEXT_%d",ident_info[i].flag);
	      sprintf(buf," %s : %d",ident_info[i].txt,
		      numbers[ident_info[i].flag]);
	      s(lg_txt_buf)= buf;
	      txt_nr++;
	    }
	}
    }
  else
    {
      // Conventional, legends should have qc ranges.
      s("LEGEND_TITLE") = "ON";
      if ( descriptors.size() > 0 )
	{
	  char tmpstr[10];
	  sprintf(buf,"Confidence values for data descriptors : ");
	  for (int i = 0; i < descriptors.size();i++)
	    {
	      if ( i > 0 ) strcat(buf,"/");
	      sprintf(tmpstr,"%d",descriptors[i]);
	      strcat(buf,tmpstr);
	    }
	}
      else
	sprintf(buf,"Confidence values for all descriptors");

      s("LEGEND_TITLE_TEXT") = buf;

      sprintf(buf,"%% CONF GT 70: %d",numbers[2]);txt_nr++;
      s("LEGEND_USER_TEXT_2") = buf;
      sprintf(buf,"%% CONF 70-50: %d",numbers[3]);txt_nr++;
      s("LEGEND_USER_TEXT_3") = buf;
      sprintf(buf,"%% CONF 50-30: %d",numbers[4]);txt_nr++;
      s("LEGEND_USER_TEXT_4") = buf;
      sprintf(buf,"%% CONF LT 30: %d",numbers[5]);txt_nr++;
      s("LEGEND_USER_TEXT_5") = buf;
    }


  // Fill all legend entries up to UNKNOWN with text not used.
  for (int j = txt_nr; j < (UNKNOWN -1 );j++)
    {
      sprintf(buf,"(not used)");
      sprintf(lg_txt_buf,"LEGEND_USER_TEXT_%d",++txt_nr);
      s(lg_txt_buf)= buf;
    }
  sprintf(buf,"UNKNOWN : %d",numbers[UNKNOWN]);
  sprintf(lg_txt_buf,"LEGEND_USER_TEXT_%d",++txt_nr);
  s(lg_txt_buf)= buf;

  check_symbol_table_modes( s, txt_nr );

 } //----------------------------------------------- End set_legend_values

//----------------------------------------------------- check_symbol_table_mode

void CoverageService::check_symbol_table_modes( MvRequest& symb, int table_size )
{
  cout << "original PSYMB:" << endl;
  symb.print();
  MvRequest s = symb;

  int cnt = symb.countValues( "SYMBOL_TABLE_MODE" );
  if( cnt==0 )
    {
      s( "SYMBOL_TABLE_MODE" ) = "ON";
      sendProgress( "SYMBOL_TABLE_MODE not set, using default values" );
    }

  check_one_symbol_table( s, symb, "SYMBOL_MIN_TABLE", default_min_table, table_size );
  check_one_symbol_table( s, symb, "SYMBOL_MAX_TABLE", default_max_table, table_size );
  check_one_symbol_table( s, symb, "SYMBOL_MARKER_TABLE", default_marker_table, table_size );
  check_one_symbol_table( s, symb, "SYMBOL_HEIGHT_TABLE", default_height_table, table_size );

  cnt = symb.countValues( "SYMBOL_COLOUR_TABLE" );
  if( cnt != table_size )
    {
      if( cnt > 0 )
	s.setValue( "SYMBOL_COLOUR_TABLE", (const char*)symb("SYMBOL_COLOUR_TABLE") );
      else
	s.setValue( "SYMBOL_COLOUR_TABLE", default_colour_table[0] );

      //-- not to access beyond table size --
      int def_size = sizeof( default_min_table ) / sizeof( default_min_table[0] );

      for( int i=1; i < table_size; ++i )
	{
	  if( i < cnt )
	    s.addValue( "SYMBOL_COLOUR_TABLE", (const char*)symb("SYMBOL_COLOUR_TABLE", i) );
	  else
	    s.addValue( "SYMBOL_COLOUR_TABLE", default_colour_table[ i % def_size ] );
	}
    }

  symb = s;
  cout << "adjusted PSYMB:" << endl;
  symb.print();
}

void CoverageService::check_one_symbol_table( MvRequest& new_req,
					      MvRequest& old_req,
					      const char* param,
					      double* defaults,
					      int table_size )
{
  int cnt = old_req.countValues( param );
  if( cnt != table_size )
    {
      if( cnt > 0 )
	new_req.setValue( param, (double)old_req(param) );
      else
	new_req.setValue( param, defaults[0] );

      //-- not to access beyond table size --
      int def_size = sizeof( default_min_table ) / sizeof( default_min_table[0] );

      for( int i=1; i < table_size; ++i )
	{
	  if( i < cnt )
	    new_req.addValue( param, (double)old_req(param, i) );
	  else
	    new_req.addValue( param, defaults[ i % def_size ] );
	}
    }
}

//------------------------------------------------------- generate_info_str
// Generate the title string.
void CoverageService::generate_info_str(MvRequest &t,char *mag_dir)
{

  char buf[300];
  ostrstream ost(buf,300);
  int i = 0;

  // If checking time given, print time and cutoff.
  if ( late_check )
    ost << "Ref.date: " << format_date(in_time) << " Cutoff : " << cutoff;
  else
    ost << "No ref.date ";

  ost << " Types : " ;


  // Plot the obs.types. Only types having data are plotted( with nr. of values).
  map<int,int,less<int> >::iterator ii;
  for (i=0,ii = subtype_nrs.begin();ii != subtype_nrs.end();ii++,i++)
    {
      if (i > 0 ) ost << "/";
      ost << (*ii).first << "(" << (*ii).second << ")";
    }

  ost << "  Total : " << nr_total << " Plotted at ";
  TDynamicTime current;
  ost << format_date(current) << ends;

  t(mag_dir)  = buf;
} //------------------------------------------------ End generate_info_str

//--------------------------------------------------------- format_date
// Return a date as a formatted string.
char * CoverageService::format_date(const TDynamicTime &tt)
{
  static char buf[30];

  sprintf(buf,"%d-%02d-%02d %02d:%02d", tt.GetYear(), tt.GetMonth(),
      tt.GetDay(), tt.GetHour(), tt.GetMin() ); //-- ISO date format
  return buf;
} //-------------------------------------------------------- End format_date


//////////////////////////////// Class ObsValues //////////////////
ObsValues::ObsValues( MvObs &obs,int cutoff,TDynamicTime c_time,int pss,
		     vector<long> &descs,int late_check) : check_time(c_time),
		     plot_ss(pss),descriptors(descs)

{
  long bword = 32;
  long wordnr,dd,hh,mm,ss,qc;
  long skip,extract,ierr;

  itype = obs.messageType();
  isbtype = obs.messageSubtype();
  const long satid_desc = 1007;
  ident = 0;
  sec2_conf = -1;
  late = 0;

  if (  obs.hasSection2() )
    {
      long *sec2 = ( long *)obs.section2Ptr();

      if (is_sat(isbtype) )
	ident = obs.value(satid_desc);
      else
	ident = obs.WmoIdentNumber();

      // Unpack needed values from section 2.
      // Day,hour,minute and sec
      wordnr = 9; skip = 16;extract = 6;
      unpack(bword,sec2 + wordnr,&dd,wordnr,skip,extract,ierr);
      extract = 5;
      unpack(bword,sec2 + wordnr,&hh,wordnr,skip,extract,ierr);
      extract = 6;
      unpack(bword,sec2 + wordnr,&mm,wordnr,skip,extract,ierr);
      unpack(bword,sec2 + wordnr,&ss,wordnr,skip,extract,ierr);

      // Lowest quality control.
      wordnr = 12; skip = 0; extract = 8;
      unpack(bword,sec2 + wordnr,&qc,wordnr,skip,extract,ierr);
      sec2_conf = (int)qc;
      //cout << "QC from sec 2 is " << sec2_conf << endl;


      if ( late_check && cutoff > 0 )
	{
	  TDynamicTime obs_time = obs.msgTime();
	  TDynamicTime reb_time(obs_time.GetYear(),obs_time.GetMonth(),dd,hh,mm,ss);
	  if ( reb_time.GetHour() < obs_time.GetHour() &&
	      obs_time.DifferenceInMinutes(reb_time) > 10 )
	    reb_time.ChangeByDays(1);

	  //cout << "Obs_time "; obs_time.Print();
	  //cout << " Reb time "; reb_time.Print(); cout  << endl;

	  if ( reb_time > check_time )
	    {
	      late = 1;
	    }
	}
    }

}

int ObsValues::get_symbol( MvObs &obs,map<int,int,less<int> > & stypes)
{
  int iflag = UNKNOWN;

  // Update the numbers for all subsets.
  stypes[isbtype]++;

  if ( late )
    return LATE;

  if( is_sat( isbtype ) )
    {
      // match subtype and ident against the satelite types
      // and set iflag to matching flag.
      for ( int i = 0; i < NUMBER(ident_info); i++ )
	{
	  if ( isbtype >= ident_info[i].st_min &&
	       isbtype <= ident_info[i].st_max )
	    {
	      if ( ident >= ident_info[i].i_min &&
		   ident <= ident_info[i].i_max )
		{
		  iflag = ident_info[i].flag;
		  break;
		}
	    }
	}
    }
  else
    {
      // Not a satelite type, plotting conf. values.
      int qc = get_conf_value(obs);
      if ( qc < 0 ) iflag = UNKNOWN;
      else if ( qc >= 70 ) iflag = 2;
      else if (qc < 70 && qc >= 50 ) iflag=3;
      else if (qc < 50 && qc >= 30 ) iflag=4;
      else if (qc < 30 && qc >= 0  ) iflag=5;
    }

  return iflag;

} //------------------------------------------------------- End get_symbol

// Get the confidence value to use, either for given descriptors or for whole
// message.
int ObsValues::get_conf_value( MvObs &obs)
{
  int lowest = 101,tmp_val;

  if ( !plot_ss && sec2_conf >= 0 )
    lowest = sec2_conf;

  else if ( descriptors.size() > 0 )
    {
      int found = 0;
      for ( int i = 0; i < descriptors.size();i++ )
	{
	  // Just used to set current descriptor.
	  obs.elementValueType(descriptors[i]);

	  if ( obs.hasConfidences() )
	    tmp_val = obs.confidence();

	  if ( tmp_val < lowest && tmp_val >= 0 )
	    {
	      found = 1;
	      lowest = tmp_val;
	    }
	}

      if ( !found ) // The given descriptor(s) does not exist,plot as unknown.
	lowest = -1;

    }
  else  // No descriptors given, plot lowest in message.
    {
      boolean ok = obs.setFirstDescriptor();

      while( ok )
	{

	  if ( obs.hasConfidences() )
	    tmp_val = obs.confidence();

	  if ( tmp_val < lowest && tmp_val >= 0 )
	    lowest = tmp_val;

	  ok = obs.setNextDescriptor();
	}
    }

  if ( lowest > 100 )   // No confidences found, use one from sec2;
    lowest = sec2_conf;

  return lowest;
}

//--------------------------------------------------------- unpack
// Unpack section 2 key values
void ObsValues::unpack(long &bword,long *src,long *target,
                          long &wordnr, long &skip,long &extract,
                          long &ierr)
{
  long kw;

  if ( extract > bword )
    {
      cerr << "Number of bits to extract longer than computer word" << endl;
      ierr = 3;
      return;
    }

  gbyte_(src,target,skip,extract);

  skip += extract;

  if ( skip > bword)
    {
      kw = skip / bword;
      skip = skip - ( kw * bword);
      wordnr += kw;
    }
} //------------------------------------------------------- End unpack

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

  CoverageService Datacoverage;

  Datacoverage.saveToPool(false);

  theApp.run();

  return 0;
}
