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

#include <fstream>
#include <functional>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

#include "fstream_mars_fix.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),
    plot_ss_(1),
    cutoff(0),
    late_check(0),
    numbers_(10, 0),
    subtype_nrs_(),
    descriptors(),
    check_time(),
    in_time()
{
    // empty
}

void CoverageService::serve(MvRequest& in, MvRequest& out)
{
    char* fname = marstmp();
    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).
    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
    if ((const char*)in_req("PLOT_SUBSETS") &&
        strcmp((const char*)in_req("PLOT_SUBSETS"), "OFF") == 0)
        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;

    // Open and initialise geopoints file
    outfile.open(fname, std::ios::out);
    outfile << "#GEO" << std::endl;
    outfile << "#DATA" << std::endl;

    // Main loop (all messages)
    int i;
    int symbol;
    int sec2_msg = 0;
    double f_long, f_lat;
    while (obs = obs_iter(NR_returnMsg)) {
        // Get information from one message
        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;
        }

        // Check subsets
        nr_subsets = plot_ss_ ? obs.msgSubsetCount() : 1;

        // Get values from one message
        i = 1;
        for (;;) {
            // Find latitude/longitude values
            if ((f_lat = obs.value("latitude")) == kBufrMissingValue)
                continue;
            if ((f_long = obs.value("longitude")) == kBufrMissingValue)
                continue;

            // Get symbol value
            symbol = values.get_symbol(obs, subtype_nrs_);
            numbers_[symbol]++;

            // Write values in the geopoints file
            outfile << f_lat << " " << f_long;
            outfile << " 0  0 0 " << symbol << std::endl;

            // Check message
            nr_total_++;
            if (i++ == nr_subsets)
                break;

            // Go to the next message
            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.
    auto 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 (size_t 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 (size_t i = 0; i < descriptors.size(); i++) {
                if (i > 0)
                    strcat(buf, "/");
                sprintf(tmpstr, "%ld", 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)
{
    std::cout << "original PSYMB:" << std::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;
    std::cout << "adjusted PSYMB:" << std::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)
{
    std::ostringstream ost;
    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).
    auto 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) = ost.str().c_str();
}  //------------------------------------------------ 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,
                     std::vector<long>& descs, int late_check) :
    check_time(c_time),
    plot_ss_(pss),
    descriptors(descs)
{
    long dd, hh, mm, ss, qc;
    long skip, extract, ierr;

    // Initialize variables
    // e   itype = obs.messageType();
    isbtype_ = obs.messageSubtype();
    // const long satid_desc = 1007; //"satelliteIdentifier"
    const std::string satid_key = "satelliteIdentifier";
    ident_ = 0;
    sec2_conf_ = -1;
    late_ = 0;

    if (obs.hasSection2()) {
#if 0  // FAMI20171024 commented out to compile Metview
#ifdef MV_BUFRDC_TEST
      long *sec2 = (long *)obs.section2Ptr();
#endif

      if ( is_sat(isbtype_) )
         ident_ = obs.value(satid_key);
      else
         ident_ = obs.WmoIdentNumber();

      // Unpack needed values from section 2.
      // Day,hour,minute and sec
      long bword = 32;
      long 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;

      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  << std::endl;

	  if ( reb_time > check_time )
	    {
         late_ = 1;
	    }
	}
#endif
    }
}

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

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

    // Return value LATE
    if (late_)
        return LATE;

    // Evaluate symbol to be returned
    if (is_sat(isbtype_)) {
        // match subtype and ident against the satelite types
        // and set iflag to matching flag
        for (size_t 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 >= 50)
            iflag = 3;
        else if (qc >= 30)
            iflag = 4;
        else if (qc >= 0)
            iflag = 5;
    }

    return iflag;
}

//------------------------------------------------------------- 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;
    int tmp_val = 0;

    if (!plot_ss_ && sec2_conf_ >= 0)
        lowest = sec2_conf_;

    else if (descriptors.size() > 0) {
        int found = 0;
        for (size_t 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;
}

#ifdef MV_BUFRDC_TEST
//--------------------------------------------------------- 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) {
        std::cerr << "Number of bits to extract longer than computer word" << std::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
#endif

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

    CoverageService Datacoverage;

    Datacoverage.saveToPool(false);

    theApp.run();

    return 0;
}
