/***************************** 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 "HovArea.h"
#include "MvException.h"

HovAreaToolkit::HovAreaToolkit() :
    geoDir_(HOV_EW)
{
}

const char* HovAreaToolkit::GetSecondCoordName()
{
    if (geoDir_ == HOV_NS)
        return HOV_VARLON.c_str();
    else
        return HOV_VARLAT.c_str();
}

bool HovAreaToolkit::ComputeSecondCoord()
{
    double x, y;
    int i;

    if (geoDir_ == HOV_EW)  //average E->W
    {
        for (i = 0, y = y1_; y >= y2_;) {
            // do not add within the loop becuase of cumulative errors
            coord1_.push_back(y);
            i++;
            y = y1_ - (i * gridNS_);
        }
    }
    else  //average N->S
    {
        for (i = 0, x = x1_; x <= x2_;) {
            // do not add within the loop becuase of cumulative errors
            coord1_.push_back(x);
            i++;
            x = x1_ + (i * gridEW_);
        }
    }

    // Initialise number of points
    this->NPoints(coord1_.size());

    return true;
}

bool HovAreaToolkit::ComputeValues(MvField& field, int)
{
    MvFieldExpander x(field);

    // Average over area
    // 1) AverageAlong() does not know array size. If it fails then returned array
    //    remains empty. Thus, fill with missing values here.
    // 2) AverageAlong() assumes that the AREA definition follows the MARS convention
    //    N-S/W-E. Therefore, if average direction is EW then the computed values follow
    //    the order N-S, e.g. xint_[0] = average(N), ..., xint_[nrPoints_] = average(S)
    //    and gcoord1_[0] = N], ..., gcoord1_[nrPoints_] = S.
    // 3) gridNS_ or gridEW_ have the same value

    bool interp = false;  //set bilinear interpolation
    if (!field.averageAlong(xint_, x1_, y1_, x2_, y2_, geoDir_, nrPoints_, gridNS_, interp)) {
        for (int i = 0; i < nrPoints_; i++)
            xint_[i] = DBL_MAX;

        throw MvException("Hovmoeller-> Failed to compute area average");
    }

    return true;
}

string HovAreaToolkit::GetTitle(ParamInfo* par)
{
    char title[256];
    if (geoDir_ == HOV_EW) {
        sprintf(title, "Hovmoeller of %s %s %s (%.1f%s-%.1f%s)",
                par->ParamLongName().c_str(), par->LevelTitle().c_str(), par->ExpVerTitle().c_str(),
                (x1_ < 0) ? -x1_ : x1_, (x1_ < 0) ? "W" : "E",
                (x2_ < 0) ? -x2_ : x2_, (x2_ < 0) ? "W" : "E");
    }
    else {
        sprintf(title, "Hovmoeller of %s %s %s (%.1f%s-%.1f%s)",
                par->ParamLongName().c_str(), par->LevelTitle().c_str(), par->ExpVerTitle().c_str(),
                (y1_ < 0) ? -y1_ : y1_, (y1_ < 0) ? "S" : "N",
                (y2_ < 0) ? -y2_ : y2_, (y2_ < 0) ? "S" : "N");
    }

    return string(title);
}

bool HovAreaToolkit::GetInputInfo(MvRequest& in)
{
    // Get area
    y1_ = in("AREA", 0);
    x1_ = in("AREA", 1);
    y2_ = in("AREA", 2);
    x2_ = in("AREA", 3);

    // Make sure coordinates follow Mars rules (n/w/s/e)
    //   bool swap = false;
    if (x1_ > x2_) {
        double W = x1_;
        x1_      = x2_;
        x2_      = W;
        //      swap = true;
    }
    if (y2_ > y1_) {
        double W = y1_;
        y1_      = y2_;
        y2_      = W;
        //      swap = true;
    }

    // Send a warning message
    //   if ( swap )
    //      setError(0,"Input geographical coordinates do not follow MARS rules (n/w/s/e). Values have been swapped.");

    // Get direction
    geoDir_ = ((const char*)in("AVERAGE_DIRECTION") && strcmp(in("AVERAGE_DIRECTION"), "NORTH_SOUTH") == 0) ? HOV_NS : HOV_EW;

    // Get swap axes flag
    swapAxes_ = ((const char*)in("SWAP_AXES") && strcmp(in("SWAP_AXES"), "YES") == 0) ? true : false;

    // Set axes orientation
    if ((swapAxes_ && geoDir_ == HOV_EW) || (!swapAxes_ && geoDir_ == HOV_NS))
        verticalAxis_ = HOV_TIME;
    else
        verticalAxis_ = HOV_GEO;

    // Initialize x/y resolution
    gridNS_ = in("RESOLUTION");
    gridEW_ = in("RESOLUTION");

    return true;
}

bool HovAreaToolkit::GetInputInfo(MvNetCDF* netcdf)
{
    // Retrieve attributes values
    MvRequest req = netcdf->getRequest();

    // Retrieve Direction value
    const char* cp = (const char*)req("average_direction");
    if (cp == NULL)
        throw MvException("Hovmoeller-> Netcdf file: AVERAGE_DIRECTION attribute not found");

    geoDir_ = (strcmp(cp, "NORTH_SOUTH") == 0) ? HOV_NS : HOV_EW;

    // Retrieve swap axes flag, if exists
    cp        = (const char*)req("swap_axes");
    swapAxes_ = (cp == NULL) ? false : atoi(cp);

    // Get common information from the netCDF file
    // This command needs the geoDir_ info
    if (!GetInputCommonInfo(netcdf))
        return false;

    return true;
}

void HovAreaToolkit::NcWriteGlobalAttributesApp()
{
    // Add average direction
    const char* adir = (geoDir_ == HOV_NS) ? "NORTH_SOUTH" : "EAST_WEST";
    netcdf_->addAttribute("average_direction", adir);

    // Add swap axes flag
    if (swapAxes_)
        netcdf_->addAttribute("swap_axes", swapAxes_);
}

bool HovAreaToolkit::NcWriteSecondCoord()
{
    // Compute second coords (lat/lng)
    // Add second coordinate to the netCDF file
    vector<string> v_sdim(1, GetSecondCoordName());
    vector<long> v_ndim(1, nrPoints_);
    MvNcVar* ncx = netcdf_->addVariable(v_sdim[0], ncDouble, v_ndim, v_sdim);
    ncx->put(coord1_, (long)nrPoints_);
    if (verticalAxis_ == HOV_TIME) {
        ncx->addAttribute("long_name", "longitude");
        ncx->addAttribute("units", "degrees_east");
    }
    else {
        ncx->addAttribute("long_name", "latitude");
        ncx->addAttribute("units", "degrees_north");  //or degrees_south?
    }

    return true;
}

bool HovAreaToolkit::consistencyCheck(MvRequest& req1, MvRequest& req2)
{
    // Check input TYPE
    if ((const char*)req1("TYPE") != (const char*)req2.getVerb()) {
        ostringstream oss;
        oss << "Hovmoeller-> TYPE parameter has two different values: "
            << (const char*)req1("TYPE")
            << " and "
            << (const char*)req2.getVerb();
        throw MvException(oss.str());
    }

    // 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))
        throw MvException("Hovmoeller-> AREA coordinates are not consistent");

    // Check DIRECTION
    if ((const char*)req1("AVERAGE_DIRECTION") != (const char*)req2("AVERAGE_DIRECTION")) {
        ostringstream oss;
        oss << "Hovmoeller-> AVERAGE_DIRECTION parameter is not consistent: "
            << (const char*)req1("AVERAGE_DIRECTION")
            << " and "
            << (const char*)req2("AVERAGE_DIRECTION");
        throw MvException(oss.str());
    }

    // Check SWAP AXES
    if ((const char*)req1("SWAP_AXES") != (const char*)req2("SWAP_AXES")) {
        ostringstream oss;
        oss << "Hovmoeller-> SWAP_AXES parameter is not consistent: "
            << (const char*)req1("SWAP_AXES")
            << " and "
            << (const char*)req2("SWAP_AXES");
        throw MvException(oss.str());
    }

    // Check RESOLUTION
    if ((const char*)req1("RESOLUTION") != (const char*)req2("RESOLUTION")) {
        ostringstream oss;
        oss << "Hovmoeller-> RESOLUTION parameter is not consistent: "
            << (const char*)req1("RESOLUTION")
            << " and "
            << (const char*)req2("RESOLUTION");
        throw MvException(oss.str());
    }

    return true;
}

MvRequest HovAreaToolkit::CreateViewRequest()
{
    MvRequest viewReq("MHOVMOELLERVIEW");

    viewReq("TYPE") = this->ApplicationType();
    viewReq.addValue("AREA", y1_);
    viewReq.addValue("AREA", x1_);
    viewReq.addValue("AREA", y2_);
    viewReq.addValue("AREA", x2_);

    viewReq("AVERAGE_DIRECTION") = (geoDir_ == HOV_NS) ? "NORTH_SOUTH" : "EAST_WEST";

    viewReq("SWAP_AXES") = swapAxes_ ? "YES" : "NO";
    if ((const char*)origReq_("RESOLUTION"))
        viewReq("RESOLUTION") = (const char*)origReq_("RESOLUTION");

    viewReq("_CLASS")        = "MHOVMOELLERVIEW";
    viewReq("_DEFAULT")      = 1;
    viewReq("_DATAATTACHED") = "YES";

    return viewReq;
}

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

void AreaHovmoeller::serve(MvRequest& in, MvRequest& out)
{
    cout << "Request IN:" << endl;
    in.print();

    // Compute Hovmoeller diagram
    origReq_ = in;
    if (!Compute(in, out)) {
        setError(1, "Hovmoeller-> Failed to generate Hovmoeller Areagrep");
        return;
    }

    cout << "Request OUT:" << endl;
    out.print();
}

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

// Translate Metview 3 AreaHovmoeller Data to Metview 4 definition. Call Metview 4
// server to process the job.
void AreaHovmoellerM3::serve(MvRequest& in, MvRequest& out)
{
    // Send a general warning message
    setError(0, "The Metview 3 Area-Hovmoeller DATA icon is deprecated. An automatic translation to the correspondent Metview 4 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 Area-Hovmoeller DATA icon is deprecated. Parameters TIME_AXIS_DIRECTION, TIME_AXIS and GEO_AXIS will not be translated internally.");
    MvRequest req = in;
    req.unsetParam("TIME_AXIS_DIRECTION");
    req.unsetParam("TIME_AXIS");
    req.unsetParam("GEO_AXIS");

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

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