/***************************** 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 <sstream>
#include <cassert>

#include "HovArea.h"
#include "MvException.h"
//#include "StatsCompute.h"

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

bool HovAreaToolkit::ComputeSecondCoord()
{
    double x = 0., y = 0.;
    int i = 0;
    coord1_.clear();

    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(static_cast<int>(coord1_.size()));

    return true;
}

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

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

    bool interp = false;  // set bilinear interpolation

    StatsComputePtr comp = makeStatsCompute();
    assert(comp);
    if (!field.computeAlong(xint_, x1_, y1_, x2_, y2_, geoDir_, nrPoints_, gridNS_, interp, nullptr, comp)) {
        for (int i = 0; i < nrPoints_; i++) {
            xint_[i] = DBL_MAX;
        }
        throw MvException(moduleLabel_ + "Failed to compute statistics for area");
    }

    return true;
}

std::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 {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
    if (const char* ch = (const char*)in("AVERAGE_DIRECTION")) {
        geoDir_ = (strcmp(ch, "NORTH_SOUTH") == 0) ? HOV_NS : HOV_EW;
    }
    else {
        throw MvException(moduleLabel_ + "No AVERAGE_DIRECTION parameter specified");
    }

    // 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");

    stats_ = statsType(in);

    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 == nullptr)
        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 == nullptr) ? false : atoi(cp);

    // Retrieve stats value
    const char* statCh = (const char*)req("area_statistics");
    if (statCh == nullptr) {
        /// can be an old file!
        stats_ = MeanStats;
    }
    else {
        stats_ = statsType(statCh);
    }

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

    return true;
}

void HovAreaToolkit::NcWriteGlobalAttributesApp()
{
    // Add 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_);
    }

    netcdf_->addAttribute("area_statistics", statsName(stats_).c_str());
}

bool HovAreaToolkit::NcWriteSecondCoord()
{
    // Compute second coords (lat/lng)
    // Add second coordinate to the netCDF file
    std::vector<std::string> v_sdim(1, GetSecondCoordName());
    std::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& viewReq, MvRequest& dataReq)
{
    std::string msg;
    std::string label1 = "view";
    std::string label2 = "data";

    if (!MvRequest::paramEqualToVerb(viewReq, dataReq, "TYPE", label1, label2, msg)) {
        throw MvException(moduleLabel_ + msg);
    }

    for (auto name : {"AREA", "AREA_STATISTICS", "AVERAGE_DIRECTION", "SWAP_AXES", "RESOLUTION"}) {
        if (!MvRequest::paramsEqual(viewReq, dataReq, name, label1, label2, msg)) {
            throw MvException(moduleLabel_ + msg);
        }
    }

    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)
{
    std::cout << "Request IN:" << std::endl;
    in.print();

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

    std::cout << "Request OUT:" << std::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);
}
