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

/********************************************************************************
 Important information:

 1. The Bottom/Top Levels will be computed automatically from the data, if
    they are not given as a input parameters.

 2. Relationship among fields and interpolation:

                  ML    ML&LNSP   PL    PL&LNSP
         INTY    ok      ok       ok     ok1
         INTN    ok     error     ok     ok1

   INTY/N = interpolation yes/no
   ok1    = LNSP is ignored

 3. If fieldset contains ML&LNSP, a conversion to PL will be computed automatically
 ********************************************************************************/

#include "Average.h"

// Application mode
enum
{
    AVERAGE_NS,
    AVERAGE_EW
};

// Function different: returns true if d1 and d2 are larger than e apart
inline static bool different(double d1, double d2, double e)
{
    return (fabs(d1 - d2) > e);
}

Average::Average() :
    Xsect("MXAVERAGE")
{
    type_ = "MXAVERAGE";
    view_ = "MXAVERAGEVIEW";
}

Average::Average(const char* kw) :
    Xsect(kw)
{
    type_ = kw;
    view_ = "MXAVERAGEVIEW";
}

bool Average::getCoordinates(MvRequest& in, ApplicationInfo& appInfo)
{
    // Get input values
    double Y1 = in("AREA", 0);
    double X1 = in("AREA", 1);
    double Y2 = in("AREA", 2);
    double X2 = in("AREA", 3);

    // Check coordinates
    if (!checkCoordinates(X1, Y1, X2, Y2))
        return false;

    // Save coordinates
    appInfo.setAreaLine(X1, X2, Y1, Y2);

    return true;
}

bool Average::getAppParameters(MvRequest& in, ApplicationInfo& appInfo)
{
    // Retrieve geographical coordinates
    if (!this->getCoordinates(in, appInfo))
        return false;

    inputMode_ = (strcmp(in("DIRECTION"), "NS") == 0 || strcmp(in("DIRECTION"), "NORTH SOUTH") == 0) ? AVERAGE_NS : AVERAGE_EW;

    return true;
}

bool Average::consistencyCheck(MvRequest& req1, MvRequest& req2)
{
    // Check coordinates
    if ((double)req1("AREA", 0) != (double)req2("AREA", 0) ||
        (double)req1("AREA", 1) != (double)req2("AREA", 1) ||
        (double)req1("AREA", 2) != (double)req2("AREA", 2) ||
        (double)req1("AREA", 3) != (double)req2("AREA", 3)) {
        setError(1, "Average-> AREA coordinates are not consistent");
        return false;
    }

    // Check direction
    int im1 = (strcmp(req1("DIRECTION"), "NS") == 0 || strcmp(req1("DIRECTION"), "NORTH SOUTH") == 0) ? AVERAGE_NS : AVERAGE_EW;
    int im2 = (strcmp(req2("DIRECTION"), "NS") == 0 || strcmp(req2("DIRECTION"), "NORTH SOUTH") == 0) ? AVERAGE_NS : AVERAGE_EW;
    if (im1 != im2) {
        setError(1, "Average-> DIRECTION parameter is not consistent");
        return false;
    }

    return true;
}

// Generate data for average aplication.
bool Average::generateData(ApplicationInfo& appInfo, ParamMap& params,
                           MvNetCDF& cdf, MvField& field, const string& key)
{
    // Get parameter info
    ParamInfo* par = appInfo.getParamInfo(params, key);
    if (!par)
        return false;

    // SHOULD GENERATE DATA FOR LNSP????
    if (appInfo.isLNSP(par->Parameter()))
        return true;

    // Generate the netCDF parameter variable
    string geoVar = (inputMode_ == AVERAGE_NS) ? XS_VARLON : XS_VARLAT;
    setGeoIndexVar(geoVar);
    return contourValues(appInfo, params, cdf, field, key);
}

// Compute number of points
int Average::computeGeographicalPoints(ApplicationInfo& appInfo, MvField*)
{
    double x1, x2, y1, y2;
    appInfo.getAreaLine(x1, x2, y1, y2);
    int numberNS = (int)(fabs((appInfo.Y1() - appInfo.Y2()) / appInfo.GridNS()) + 0.0000001) + 1;  // avoid precision errors

    int numberEW = (int)(fabs((appInfo.X2() - appInfo.X1()) / appInfo.GridEW()) + 0.0000001) + 1;  // avoid precision errors

    int np = (inputMode_ == AVERAGE_NS) ? numberEW : numberNS;
    if (np <= 0) {
        setError(1, "Average-> Failed to compute number of grid points. Check data internal representation");
        return 0;
    }

    // Compute the coordinates
    appInfo.computeLine(np);

    return np;
}

bool Average::fillValues(ApplicationInfo& appInfo, MvField& field, double* vals)
{
    // Get initial info
    MvFieldExpander x(field);
    int i;
    int npoint = appInfo.NrPoints();
    double X1, X2, Y1, Y2;
    appInfo.getAreaLine(X1, X2, Y1, Y2);

    // Average over area
    bool ok = field.averageAlong(vals, X1, Y1, X2, Y2, inputMode_, npoint);

    // AverageAlong() does not know array size. If it fails then
    // return array remains empty. Thus fill with missing values here!
    if (!ok) {
        for (i = 0; i < npoint; i++)
            vals[i] = XMISSING_VALUE;

        return false;
    }

    // Check valid values
    for (i = 0; i < npoint; i++) {
        if (vals[i] >= DBL_MAX)
            vals[i] = XMISSING_VALUE;
    }

    return true;
}

MvRequest Average::createOutputRequest(ApplicationInfo& appInfo, ParamInfo* parInfo)
{
    // Create netCDF data request
    MvRequest xs("NETCDF");
    xs("PATH")      = ncName_.c_str();
    xs("TEMPORARY") = 1;

    std::string dims = XS_VARTIME + ":0";

    // Create NetCDF output request
    string varname = getNetcdfVarname(parInfo->ParamName()).c_str();
    MvRequest out1("NETCDF_XY_MATRIX");
    out1("NETCDF_DATA")              = xs;
    out1("NETCDF_MISSING_ATTRIBUTE") = "_FillValue";
    out1("NETCDF_VALUE_VARIABLE")    = varname.c_str();
    out1("NETCDF_Y_VARIABLE")        = getNetcdfLevelVarname(varname).c_str();
    out1("NETCDF_X_VARIABLE")        = (inputMode_ == AVERAGE_NS) ? "lon" : "lat";
    out1("NETCDF_DIMENSION_SETTING_METHOD") = "index";
    out1("NETCDF_DIMENSION_SETTING") = dims.c_str();

    // Add information to help the Macro Converter to translate
    // the output request to a Macro code
    out1("_ORIGINAL_REQUEST") = buildMacroConverterRequest();

    // Create View request using some parameters from the xaverage data request
    MvRequest viewReq("MXAVERAGEVIEW");
    if ((const char*)origReq_("AREA")) {
        viewReq.addValue("AREA", (double)origReq_("AREA", 0));
        viewReq.addValue("AREA", (double)origReq_("AREA", 1));
        viewReq.addValue("AREA", (double)origReq_("AREA", 2));
        viewReq.addValue("AREA", (double)origReq_("AREA", 3));
    }
    if ((const char*)origReq_("DIRECTION"))
        viewReq("DIRECTION") = origReq_("DIRECTION");

    viewReq("_CLASS")        = "MXAVERAGEVIEW";
    viewReq("_DEFAULT")      = true;
    viewReq("_DATAATTACHED") = "YES";
    if (appInfo.levelType() != cML_UKMO_ND &&
        appInfo.levelType() != XS_ML_GHBC)  // UK MetOffice model levels and GHBC fields are 'upside-down'
        viewReq("_Y_AUTOMATIC_REVERSE") = "on";

    // If action is not visualisation related then return the netcdf data.
    // Also, add the visualisation and original requests as hidden parameters.
    // These may be used later to help the visualisation of the netcdf data.
    if (!isVisualise(appInfo)) {
        xs("_VIEW")         = view_.c_str();
        xs("_VISUALISE")    = out1;
        xs("_VIEW_REQUEST") = viewReq;
        return xs;
    }

    // Final output request
    MvRequest out = viewReq + out1;
    return out;
}

bool Average::fieldConsistencyCheck(MvField& field, double north, double south, double east, double west)
{
    const double epsilon = 0.0015;  // difference allowed in field coordinates
    if (different(field.north(), north, epsilon) ||
        different(field.south(), south, epsilon) ||
        different(field.east(), east, epsilon) ||
        different(field.west(), west, epsilon)) {
        setError(1, "Average-> fields must all have the same dimensions.");
        return false;
    }

    return true;
}

string Average::titleVariable(ApplicationInfo& appInfo, ParamInfo* par)
{
    char titlestr[150];

    // Generate title
    if (inputMode_ == AVERAGE_EW) {
        double x1 = appInfo.X1();
        double x2 = appInfo.X2();
        sprintf(titlestr, "Average of %s %d %02d step %d %s (%.1f%s-%.1f%s)",
                par->ParamLongName().c_str(), par->Date(), par->Time(), par->Step(),
                par->ExpVerTitle().c_str(),
                (x1 < 0) ? -x1 : x1, (x1 < 0) ? "W" : "E",
                (x2 < 0) ? -x2 : x2, (x2 < 0) ? "W" : "E");
    }
    else  //AVERAGE_NS
    {
        double y1 = appInfo.Y1();
        double y2 = appInfo.Y2();
        sprintf(titlestr, "Average of %s %d %02d step %d %s (%.1f%s-%.1f%s)",
                par->ParamLongName().c_str(), par->Date(), par->Time(), par->Step(),
                par->ExpVerTitle().c_str(),
                (y1 < 0) ? -y1 : y1, (y1 < 0) ? "S" : "N",
                (y2 < 0) ? -y2 : y2, (y2 < 0) ? "S" : "N");
    }

    return string(titlestr);
}

//-------------------------------------------------------------------------

// Translate Metview 3 Average Data to Metview 4 definition. Call Metview 4
// server to process the job.
void AverageM3::serve(MvRequest& in, MvRequest& out)
{
    // Send a general warning message
    setError(0, "The Metview 3 AVERAGE DATA icon is deprecated. An automatic translation to the Metview 4 AVERAGE DATA icon will be performed internally, but may not work for all cases. It is recommended to manually replace the old icons with their new equivalents.");

    // There are input parameters that are no longer available in Metview 4.
    // Remove them and send a warning message.
    setError(0, "The Metview 3 AVERAGE DATA icon is deprecated. Parameters PRESSURE_LEVEL_AXIS, INTERPOLATE_VALUES, BOTTOM_PRESSURE and TOP_PRESSURE will not be translated internally.");
    MvRequest req = in;
    req.unsetParam("PRESSURE_LEVEL_AXIS");
    req.unsetParam("INTERPOLATE_VALUES");
    req.unsetParam("BOTTOM_PRESSURE");
    req.unsetParam("TOP_PRESSURE");

    // Keep the remaining parameters and update the VERB
    req.setVerb("MXAVERAGE");

    // Call the Xsection server to process the job
    Xsect::serve(req, out);
}
