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

#include <algorithm>
#include <iomanip>
#include <sstream>

#include "MvException.h"

#define HOV_SP 1013.25  // stardard pressure

void HovHeightToolkit::GenerateKey(std::string& str, MvRequest& rq)
{
    // The level value is ignored to build the key
    int par            = rq("PARAM");
    int level          = 0;
    const char* expver = rq("EXPVER");

    std::ostringstream oss;
    oss << std::setfill('0')
        << std::setw(1) << 'p'
        << std::setw(3) << par
        << std::setw(4) << level
        << std::setw(4) << (expver ? expver : "_")
        << std::ends;

    str = oss.str();

    return;
}

void HovHeightToolkit::UpdateLevelList(MvField& field, double ilevel)
{
    // Check if conversion from model level to pressure level is needed
    double level = (vertAxisType_ == HOV_PRESSURE && field.isModelLevel()) ? field.meanML_to_Pressure_bySP(double(HOV_SP * 100.), (int)ilevel) / 100. : ilevel;

    auto iter = std::find(coord1_.begin(), coord1_.end(), level);
    if (iter == coord1_.end())
        coord1_.push_back(level);
}

bool HovHeightToolkit::ComputeSecondCoord()
{
    // The variable coord1_ was already initialized during
    // the initialisation of the parameters

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

    return true;
}

bool HovHeightToolkit::ComputeValues(MvField& field, int index)
{
    MvFieldExpander x(field);
    xint_[index] = field.integrate(y1_, x1_, y2_, x2_);

    return true;
}

bool HovHeightToolkit::GenerateData(MvRequest& data)
{
    // Write initial info and variables to the netCDF
    if (!this->Initialiser())
        return false;

    // Allocate internal variables
    if (xint_)
        delete xint_;
    xint_ = new double[nrPoints_];

    // Sort data by level, expver and param
    MvFieldSet fs(data);
    MvFieldSetIterator iter(fs);
    iter.rewind();
    iter.sort("LEVELIST", '>');
    iter.sort("STEP");
    iter.sort("TIME");
    iter.sort("DATE");
    iter.sort("EXPVER");
    iter.sort("PARAM");

    // Generate data
    int currentGenerated = 0, lastNrGenerated = -1;
    int ind            = 0;
    std::string sfirst = "FIRSTFIELD";
    std::string timeKey, keystr;
    std::string lastTimeKey = sfirst;
    std::string lastKey     = sfirst;
    MvField field, lastField;
    ParamIterator paramIter;
    while ((field = iter())) {
        currentGenerated++;

        // Create keys
        MvRequest rq = field.getRequest();
        GenerateKey(keystr, rq);
        GenerateTimeKey(timeKey, rq);

        // Compute data
        if (!ComputeValues(field, ind++))
            return false;

        // Get all the computed level values for each time
        if (ind < nrPoints_) {
            // Extra check
            if (lastTimeKey != timeKey && lastTimeKey != sfirst)
                throw MvException("Hovmoeller-> Input data not consistent. Probably number of levels is not the same for all time stamps");

            lastTimeKey = timeKey;
            continue;
        }

        // Make sure the time stamps are the same
        if (lastTimeKey != timeKey)
            throw MvException("Hovmoeller-> Input data not consistent. Probably number of levels is not the same for all time stamps");

        // Save data in the ParamInfo structure
        ParamInfo* par = this->GetParamInfo(keystr);
        if (!par)
            return false;

        par->FillIndex(xint_, timeKey, nrPoints_);

        // Write out data, if it can be grouped together
        if (lastKey != keystr && lastKey != sfirst) {
            if (!NcWriteData(lastField, lastKey))
                return false;
            lastNrGenerated = currentGenerated;
        }

        lastKey     = keystr;
        lastField   = field;
        lastTimeKey = sfirst;
        ind         = 0;
    }

    // Write out the data in the netcdf file
    if (lastNrGenerated <= currentGenerated) {
        if (!NcWriteData(lastField, lastKey))
            return false;
    }
    netcdf_->close();

    return true;
}

std::string HovHeightToolkit::GetTitle(ParamInfo* par)
{
    char title[256];
    sprintf(title, "Hovmoeller of %s %s (%.1f%s/%.1f%s/%.1f%s/%.1f%s)",
            par->ParamLongName().c_str(), par->ExpVerTitle().c_str(),
            (y1_ < 0) ? -y1_ : y1_, (y1_ < 0) ? "S" : "N",
            (x1_ < 0) ? -x1_ : x1_, (x1_ < 0) ? "W" : "E",
            (y2_ < 0) ? -y2_ : y2_, (y2_ < 0) ? "S" : "N",
            (x2_ < 0) ? -x2_ : x2_, (x2_ < 0) ? "W" : "E");

    return std::string(title);
}

bool HovHeightToolkit::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 )
    //      marslog(LOG_WARN,"WARNING: Input geographical coordinates do not follow MARS rules (n/w/s/e). Values have been swapped.");

    // Get vertical axis type
    vertAxisType_ = ((const char*)in("VERTICAL_LEVEL_TYPE") && strcmp(in("VERTICAL_LEVEL_TYPE"), "PRESSURE") == 0) ? HOV_PRESSURE : HOV_ASINDATA;

    // Set axis orientation
    verticalAxis_ = HOV_GEO;

    // Initialize x/y resolution  ??? Do we need this ???
    gridEW_ = gridNS_ = 1;  //in("RESOLUTION");

    return true;
}

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

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

    // Retrieve vertical type axes flag
    const char* cp = (const char*)req("vertical_type");
    if (cp == nullptr)
        throw MvException("Hovmoeller-> Netcdf input file error: vertical_type attribute not found");

    vertAxisType_ = (std::string(cp) == "PRESSURE") ? HOV_PRESSURE : HOV_ASINDATA;

    // Set axis orientation
    verticalAxis_ = HOV_GEO;

    return true;
}

void HovHeightToolkit::NcWriteGlobalAttributesApp()
{
    // Add average direction
    const char* adir = (vertAxisType_ == HOV_PRESSURE) ? "PRESSURE" : "AS_IN_DATA";
    netcdf_->addAttribute("vertical_type", adir);
}

bool HovHeightToolkit::NcWriteSecondCoord()
{
    // 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->addAttribute("long_name", "Atmospheric Levels");
    ncx->addAttribute("units", "hPa");
    ncx->put(coord1_, (long)nrPoints_);

    return true;
}

bool HovHeightToolkit::consistencyCheck(MvRequest& req1, MvRequest& req2)
{
    // Check input TYPE
    if ((const char*)req1("TYPE") != (const char*)req2.getVerb()) {
        std::ostringstream oss;
        oss << "Hovmoeller-> TYPE parameter is not consistent: "
            << (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 VERTICAL_LEVEL_TYPE
    if ((const char*)req1("VERTICAL_LEVEL_TYPE") != (const char*)req2("VERTICAL_LEVEL_TYPE")) {
        std::ostringstream oss;
        oss << "Hovmoeller-> VERTICAL_LEVEL_TYPE parameter is not consistent: "
            << (const char*)req1("VERTICAL_LEVEL_TYPE")
            << " and "
            << (const char*)req2("VERTICAL_LEVEL_TYPE");
        throw MvException(oss.str());
    }

    return true;
}

MvRequest HovHeightToolkit::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("VERTICAL_LEVEL_TYPE") = (vertAxisType_ == HOV_PRESSURE) ? "PRESSURE" : "AS_IN_DATA";

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

    return viewReq;
}

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

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

    // Compute Hovmoeller diagram
    origReq_ = in;
    if (!Compute(in, out))
        setError(1, "HeightHovmoeller-> Failed to generate Hovmoeller Vertical");

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

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

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

    // Parameter HEIGHT_AXIS_TYPE is replaced by VERTICAL_LEVEL_TYPE in Metview 4
    if ((const char*)req("HEIGHT_AXIS_TYPE")) {
        req("VERTICAL_LEVEL_TYPE") = req("HEIGHT_AXIS_TYPE");
        req.unsetParam("HEIGHT_AXIS_TYPE");
    }

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

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