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

#include <cassert>
#include <cmath>
#include <sstream>

#include "MvException.h"

const char* HovLineToolkit::GetSecondCoordName()
{
    // By default, it is longitude, unless values x1_ and x2_
    // are the same. In this case, it is latitude.
    // In the NetCDF Visualiser request, parameter NETCDF_X_VARIABLE
    // can not have all the same values (Magics restriction).
    if (std::fabs(x2_ - x1_) > 0.00001) {
        glConvention_ = "lonlat";
        return HOV_VARLON.c_str();
    }
    else {
        glConvention_ = "latlon";
        return HOV_VARLAT.c_str();
    }
}

const char* HovLineToolkit::GetSecondAuxiliaryCoordName()
{
    // By default, it is latitude, unless values x1_ and x2_
    // are the same. In this case, it is longitude..
    // In the NetCDF Visualiser request, parameter NETCDF_X_VARIABLE
    // can not have all the same values (Magics restriction).
    if (std::fabs(x2_ - x1_) > 0.00001)
        return HOV_VARLAT.c_str();
    else
        return HOV_VARLON.c_str();
}

bool HovLineToolkit::ComputeSecondCoord()
{
    // Compute number of points
    if (!this->EvaluateNPoints())
        return false;

    // Initialize variables
    coord1_.clear();
    coord2_.clear();
    coord1_.reserve(nrPoints_);
    coord2_.reserve(nrPoints_);
    double dx = double(x2_ - x1_) / double(nrPoints_ - 1);
    double dy = double(y2_ - y1_) / double(nrPoints_ - 1);

    // Compute values
    for (int k = 0; k < nrPoints_; k++) {
        coord1_.push_back(x1_ + (k * dx));
        coord2_.push_back(y1_ + (k * dy));
    }

    return true;
}

bool HovLineToolkit::ComputeValues(MvField& field, int)
{
    // Initialize variables
    MvFieldExpander x(field);

    // Interpolatate along a line
    for (int k = 0; k < nrPoints_; k++)
        xint_[k] = field.interpolateAt(coord1_[k], coord2_[k]);

    return true;
}

std::string HovLineToolkit::GetTitle(ParamInfo* par)
{
    std::string title = "Hovmoeller of " + par->ParamLongName() + " " + par->LevelTitle() + " " + par->ExpVerTitle();

    return title;
}

bool HovLineToolkit::EvaluateNPoints()
{
    if (y1_ > 90. || y1_ < -90. || y2_ > 90. || y2_ < -90.)
        throw MvException(moduleLabel_ + "Invalid input latitude coordinates");

    double dellat = std::fabs(y2_ - y1_);
    double dellon = std::fabs(x2_ - x1_);
    int np = int(sqrt(dellon * dellon + dellat * dellat) / MAX(gridNS_, gridEW_));
    if (np <= 0) {
        throw MvException(moduleLabel_ +
                          "Computed number of points along the hovmoeller line is invalid: " +
                          std::to_string(np) + ". Check the input data grid geometry.");
    }

    nrPoints_ = MAX(np, 64);  // 64->arbitrary minimum number of points

    return true;
}

bool HovLineToolkit::GetInputInfo(MvRequest& in)
{
    // Get line
    y1_ = in("LINE", 0);
    x1_ = in("LINE", 1);
    y2_ = in("LINE", 2);
    x2_ = in("LINE", 3);

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

    // Set axes orientation
    verticalAxis_ = swapAxes_ ? HOV_GEO : HOV_TIME;

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

    return true;
}

bool HovLineToolkit::GetInputInfo(MvNetCDF* netcdf)
{
    // Get common information from the netCDF file
    if (!GetInputCommonInfo(netcdf))
        return false;

    // Retrieve attributes values
    MvRequest req = netcdf->getRequest();

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

    return true;
}

void HovLineToolkit::NcWriteGlobalAttributesApp()
{
    // Add swap axes flag
    if (swapAxes_)
        netcdf_->addAttribute("swap_axes", swapAxes_);
}

bool HovLineToolkit::NcWriteSecondCoord()
{
    // Add longitude coordinate to the netCDF file
    std::vector<std::string> v_sdim(1, HOV_VARLON);
    std::vector<long> v_ndim(1, nrPoints_);
    MvNcVar* ncx = netcdf_->addVariable(v_sdim[0], ncDouble, v_ndim, v_sdim);
    ncx->addAttribute("long_name", "longitude");
    ncx->addAttribute("units", "degrees_east");
    ncx->put(coord1_, (long)nrPoints_);

    // Add latitude coordinate to the netCDF file
    v_sdim[0] = HOV_VARLAT;
    ncx = netcdf_->addVariable(v_sdim[0], ncDouble, v_ndim, v_sdim);
    ncx->addAttribute("long_name", "latitude");
    ncx->addAttribute("units", "degrees_north");  // or degrees_south?
    ncx->put(coord2_, (long)nrPoints_);

    return true;
}

bool HovLineToolkit::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 : {"LINE", "SWAP_AXES", "RESOLUTION"}) {
        if (!MvRequest::paramsEqual(viewReq, dataReq, name, label1, label2, msg)) {
            throw MvException(moduleLabel_ + msg);
        }
    }
    return true;
}

MvRequest HovLineToolkit::CreateViewRequest()
{
    MvRequest viewReq("MHOVMOELLERVIEW");
    viewReq("TYPE") = this->ApplicationType();
    viewReq.addValue("LINE", y1_);
    viewReq.addValue("LINE", x1_);
    viewReq.addValue("LINE", y2_);
    viewReq.addValue("LINE", x2_);

    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 LineHovmoeller::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 Line");

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

    return;
}

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


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

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

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