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

// Methods for HovmoellerView class

#include "HovmoellerView.h"
#include <MvRequestUtil.hpp>
#include "ObjectList.h"
#include "PlotMod.h"
#include "Root.h"
#include "MvLog.h"

// Hovmoeller types
static const std::string HOVLINE = "LINE_HOVM";
static const std::string HOVAREA = "AREA_HOVM";
static const std::string HOVVERT = "VERTICAL_HOVM";

//--------------------------------------------------------
static HovmoellerViewFactory hovmoellerViewFactoryInstance;

PlotModView*
HovmoellerViewFactory::Build(Page& page,
                             const MvRequest& contextRequest,
                             const MvRequest& setupRequest)
{
    // Expand request
    MvRequest expReq = ObjectList::ExpandRequest(contextRequest, EXPAND_DEFAULTS);

    // Copy hidden parameters
    expReq.mars_merge(contextRequest);

    // Instantiate a Hovmoeller view
    return new HovmoellerView(page, expReq, setupRequest);
}

//--------------------------------------------------------
static HovmoellerViewM3Factory hovmoellerViewM3FactoryInstance;

PlotModView*
HovmoellerViewM3Factory::Build(Page& page,
                               const MvRequest& contextRequest,
                               const MvRequest& setupRequest)
{
    // Translate view request to Metview 4
    MvRequest viewM4Req = this->Translate(contextRequest);

    // Instantiate a Hovmoeller View
    return new HovmoellerView(page, viewM4Req, setupRequest);
}

MvRequest
HovmoellerViewM3Factory::Translate(const MvRequest& in)
{
    // Send a warning message
    MvLog().popup().warn() << "The Metview 3 HOVMOELLER VIEW icon is deprecated. An automatic translation to the Metview 4 HOVMOELLER VIEW 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.";

    // Expand request
    MvRequest req = ObjectList::ExpandRequest(in, EXPAND_DEFAULTS);

    // Copy Line parameter
    MvRequest viewReq("MHOVMOELLERVIEW");

    // Expand output request
    MvRequest out = ObjectList::ExpandRequest(viewReq, EXPAND_DEFAULTS);

    return out;
}

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

HovmoellerView::HovmoellerView(Page& owner,
                               const MvRequest& viewRequest,
                               const MvRequest& setupRequest) :
    CommonXSectView(owner, viewRequest, setupRequest),
    bDataUnit_(false),
    bUpdateDate_(true)
{
    SetVariables(viewRequest, true);
    ApplicationName(type_.c_str());
}

HovmoellerView::HovmoellerView(const HovmoellerView& old) :
    CommonXSectView(old)
{
    type_ = old.type_;
    dateMin_ = old.dateMin_;
    dateMax_ = old.dateMax_;
    bAutoDate_ = old.bAutoDate_;
    swapAxes_ = old.swapAxes_;
    bDataUnit_ = old.bDataUnit_;
    bUpdateDate_ = old.bUpdateDate_;
}

string HovmoellerView::Name()
{
    int id = Owner().Id();
    std::string name = (const char*)ObjectInfo::ObjectName(viewRequest_, "HovmoellerView", id);

    return name;
}

void HovmoellerView::DescribeYourself(ObjectInfo& description)
{
    // Convert my request to macro
    std::set<Cached> skipSet;
    description.ConvertRequestToMacro(appViewReq_, PUT_END, MacroName().c_str(), "mhovmoellerview", skipSet);
    description.PutNewLine(" ");
}

void HovmoellerView::SetVariables(const MvRequest& in, bool)
{
    // Set Hovmoeller type
    type_ = (const char*)in("TYPE");

    // Set geographical coordinates
    char param[6];
    if (in.countValues("LINE"))
        strcpy(param, "LINE");
    else
        strcpy(param, "AREA");

    latMin_ = in(param, 2);
    latMax_ = in(param, 0);
    lonMin_ = in(param, 1);
    lonMax_ = in(param, 3);

    // Check if coordinates follow Mars rules (n/w/s/e)
    bool swap = false;
    if (strcmp(param, "LINE") != 0) {
        if (lonMin_ > lonMax_) {
            double W = lonMin_;
            lonMin_ = lonMax_;
            lonMax_ = W;
            swap = true;
        }
        if (latMin_ > latMax_) {
            double W = latMin_;
            latMin_ = latMax_;
            latMax_ = W;
            swap = true;
        }
    }

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

    updateDates(in);

    // Set swapAxes flag
    swapAxes_ = ((const char*)in("SWAP_AXES") &&
                 strcmp(in("SWAP_AXES"), "YES") == 0)
                    ? true
                    : false;
}

void HovmoellerView::updateDates(const MvRequest& in)
{
    // Set date limits
    bool bDateMin = false, bDateMax = false;
    if ((const char*)in("TIME_AXIS_MODE") && strcmp((const char*)in("TIME_AXIS_MODE"), "AUTOMATIC_FORWARDS") == 0) {
        bDateMin = bDateMax = true;
        dateMin_ = "2013-01-01";
        dateMax_ = "2013-01-01 00:01:00";
    }
    else if ((const char*)in("TIME_AXIS_MODE") && strcmp((const char*)in("TIME_AXIS_MODE"), "AUTOMATIC_BACKWARDS") == 0) {
        bDateMin = bDateMax = true;
        dateMax_ = "2013-01-01";
        dateMin_ = "2013-01-01 00:01:00";
    }
    else {
        if (strcmp((const char*)in("DATE_MIN"), "AUTOMATIC") == 0) {
            bDateMin = true;
            dateMin_ = "2013-01-01";
        }
        else {
            bDateMin = false;
            dateMin_ = (const char*)in("DATE_MIN");
        }

        if (strcmp((const char*)in("DATE_MAX"), "AUTOMATIC") == 0) {
            bDateMax = true;
            dateMax_ = "2013-01-01 00:01:00";
        }
        else {
            bDateMax = false;
            dateMax_ = (const char*)in("DATE_MAX");
        }
    }

    // Both date values must be automatic or non-automatic
    if (bDateMin && bDateMax)
        bAutoDate_ = true;
    else if (!bDateMin && !bDateMax)
        bAutoDate_ = false;
    else {
        MvLog().popup().err() << "Hovmoeller Error: both parameters DATE_MIN and DATE_MAX must be automatic or non-automatic.";
        bAutoDate_ = false;
    }
}

bool HovmoellerView::UpdateView()
{
    // Check if the View needs to be updated
    if (string(viewRequest_.getVerb()) == CARTESIANVIEW) {
        // The View has already been updated to CARTESIANVIEW,
        // but there is one case where the Date parameter needs to be
        // updated again: when parameters DATE_MIN and DATE_MAX in the
        // HovmoellerView were defined as AUTOMATIC and when the
        // CARTESIANVIEW was first created with no DataUnit attached
        // (i.e. an empty View was plotted - in this case MAGICS needs to
        // receive a default X/Y_DATE_MIN/MAX values). This code would not
        // be necessary if MAGICS was able to receive a AUTOMATIC value
        // without a DataUnit attached.
        if (bUpdateDate_) {
            if ((const char*)viewRequest_("X_AXIS_TYPE") &&
                (strcmp((const char*)viewRequest_("X_AXIS_TYPE"), "DATE") == 0)) {
                if (bAutoDate_) {
                    viewRequest_("X_AUTOMATIC") = "ON";
                    MvDate dmin = (const char*)viewRequest_("X_DATE_MIN");
                    MvDate dmax = (const char*)viewRequest_("X_DATE_MAX");
                    if (dmin > dmax)
                        viewRequest_("X_AUTOMATIC_REVERSE") = "ON";

                    viewRequest_.unsetParam("X_DATE_MIN");
                    viewRequest_.unsetParam("X_DATE_MAX");
                    bUpdateDate_ = false;
                }
            }
            else if ((const char*)viewRequest_("Y_AXIS_TYPE") &&
                     (strcmp((const char*)viewRequest_("Y_AXIS_TYPE"), "DATE") == 0)) {
                if (bAutoDate_) {
                    viewRequest_("Y_AUTOMATIC") = "ON";
                    MvDate dmin = (const char*)viewRequest_("Y_DATE_MIN");
                    MvDate dmax = (const char*)viewRequest_("Y_DATE_MAX");
                    if (dmin > dmax)
                        viewRequest_("Y_AUTOMATIC_REVERSE") = "ON";

                    viewRequest_.unsetParam("Y_DATE_MIN");
                    viewRequest_.unsetParam("Y_DATE_MAX");
                    bUpdateDate_ = false;
                }
            }
        }
        return true;
    }

    // This is called to handle the case when a data icon is dropped into a
    // view, which has initilally automatic dates set. Then the data is processed
    // by the Hovmoller module that sends back an updated view, which will overwrite
    // viewRequest_. However, its settings are not copied into member varables storing the
    // dates. So we have to do it here!
    updateDates(viewRequest_);

    // Translate specific parameters
    MvRequest cartView("CARTESIANVIEW");
    if (type_ == HOVLINE)
        UpdateViewLine(cartView);
    else if (type_ == HOVAREA)
        UpdateViewArea(cartView);
    else
        UpdateViewVert(cartView);

    // Copy PAGE and SUBPAGE definition
    metview::CopySomeParameters(viewRequest_, cartView, "PAGE");
    metview::CopySomeParameters(viewRequest_, cartView, "SUBPAGE");

    // Copy the original request without certain parameteres (to avoid duplication)
    metview::RemoveParameters(viewRequest_, "HORIZONTAL_AXIS");
    metview::RemoveParameters(viewRequest_, "VERTICAL_AXIS");
    metview::RemoveParameters(viewRequest_, "PAGE");
    metview::RemoveParameters(viewRequest_, "SUBPAGE");
    metview::RemoveParameters(viewRequest_, "_");
    cartView("_ORIGINAL_REQUEST") = viewRequest_;

    // Update request
    viewRequest_ = cartView;

    // Indicate that the plotting tree needs to be rebuilt
    Root::Instance().Refresh(false);

    return true;
}

void HovmoellerView::UpdateViewWithReq(MvRequest& view)
{
    this->ApplicationInfo(view);
    Owner().UpdateView(view);  // Test 20160927
}

void HovmoellerView::UpdateViewLine(MvRequest& cartView)
{
    // Translate Geographical and Time coordinates
    // IMPORTANT:
    // 1) The definition of the min/max/lat/lon coordinates must
    // match the organisation of the netCDF data matrix, which is:
    // | [LatNorth,LonWest] ... [LatNorth,LonEast] |
    // |          ...                              |
    // | [LatSouth,LonWest] ... [LatSouth,LonEast] |
    //
    // 2) Hovmoeller computes values from North to South (first value
    // in the netCDF matrix is [LatNorth,LonWest] and Magics plots them
    // from bottom to top by default.

    if (swapAxes_) {
        cartView("Y_AUTOMATIC") = "OFF";
        cartView("Y_AXIS_TYPE") = "GEOLINE";
        cartView("Y_MIN_LATITUDE") = latMin_;
        cartView("Y_MAX_LATITUDE") = latMax_;
        cartView("Y_MIN_LONGITUDE") = lonMax_;
        cartView("Y_MAX_LONGITUDE") = lonMin_;

        cartView("X_AXIS_TYPE") = "DATE";
        if (bAutoDate_ && bDataUnit_) {
            cartView("X_AUTOMATIC") = "ON";
            if (dateMin_ > dateMax_)
                cartView("X_AUTOMATIC_REVERSE") = "ON";

            bUpdateDate_ = false;
        }
        else {
            cartView("X_AUTOMATIC") = "OFF";
            cartView("X_DATE_MIN") = dateMin_.c_str();
            cartView("X_DATE_MAX") = dateMax_.c_str();
            if (!bAutoDate_)
                bUpdateDate_ = false;
        }

        // Copy axis definition
        cartView.setValue("HORIZONTAL_AXIS", viewRequest_.getSubrequest("TIME_AXIS"));
        cartView.setValue("VERTICAL_AXIS", viewRequest_.getSubrequest("GEO_AXIS"));
    }
    else {
        cartView("X_AUTOMATIC") = "OFF";
        cartView("X_AXIS_TYPE") = "GEOLINE";
        cartView("X_MIN_LATITUDE") = latMax_;
        cartView("X_MAX_LATITUDE") = latMin_;
        cartView("X_MIN_LONGITUDE") = lonMin_;
        cartView("X_MAX_LONGITUDE") = lonMax_;

        cartView("Y_AXIS_TYPE") = "DATE";
        if (bAutoDate_ && bDataUnit_) {
            cartView("Y_AUTOMATIC") = "ON";
            if (dateMin_ > dateMax_)
                cartView("Y_AUTOMATIC_REVERSE") = "ON";
            bUpdateDate_ = false;
        }
        else {
            cartView("Y_AUTOMATIC") = "OFF";
            cartView("Y_DATE_MIN") = dateMin_.c_str();
            cartView("Y_DATE_MAX") = dateMax_.c_str();
            if (!bAutoDate_)
                bUpdateDate_ = false;
        }

        // Copy axis definition
        cartView.setValue("HORIZONTAL_AXIS", viewRequest_.getSubrequest("GEO_AXIS"));
        cartView.setValue("VERTICAL_AXIS", viewRequest_.getSubrequest("TIME_AXIS"));
    }
    // cartView("X_AUTOMATIC") = "OFF";
}

void HovmoellerView::UpdateViewArea(MvRequest& cartView)
{
    // Get geo axis
    MvRequest geoAxisReq = viewRequest_.getSubrequest("GEO_AXIS");
    if (!geoAxisReq)
        geoAxisReq.setVerb("MAXIS");

    // Translate Geographical and Time coordinates
    std::string dir = (const char*)viewRequest_("AVERAGE_DIRECTION");
    int hAxis = 0, vAxis = 0;  // 1 - time, 2 - latitude, 3 - longitude
    if (swapAxes_) {
        hAxis = (dir == "EAST_WEST") ? 2 : 1;
        vAxis = (dir == "EAST_WEST") ? 1 : 3;
    }
    else {
        hAxis = (dir == "EAST_WEST") ? 1 : 3;
        vAxis = (dir == "EAST_WEST") ? 2 : 1;
    }

    if (hAxis == 1)  // Horizontal time axis
    {
        cartView("X_AXIS_TYPE") = "DATE";
        if (bAutoDate_ && bDataUnit_) {
            cartView("X_AUTOMATIC") = "ON";
            if (dateMin_ > dateMax_)
                cartView("X_AUTOMATIC_REVERSE") = "ON";
            bUpdateDate_ = false;
        }
        else {
            cartView("X_AUTOMATIC") = "OFF";
            cartView("X_DATE_MIN") = dateMin_.c_str();
            cartView("X_DATE_MAX") = dateMax_.c_str();
            if (!bAutoDate_)
                bUpdateDate_ = false;
        }

        geoAxisReq("AXIS_ORIENTATION") = "VERTICAL";
        if (vAxis == 2)  // vertical latitude axis
        {
            geoAxisReq("AXIS_TICK_LABEL_TYPE") = "LATITUDE";
            cartView("Y_MIN") = latMin_;
            cartView("Y_MAX") = latMax_;
        }
        else  // vertical longitude axis
        {
            cartView("Y_MIN") = lonMin_;
            cartView("Y_MAX") = lonMax_;
            geoAxisReq("AXIS_TICK_LABEL_TYPE") = "LONGITUDE";
        }

        // Set axes definitions
        cartView.setValue("HORIZONTAL_AXIS", viewRequest_.getSubrequest("TIME_AXIS"));
        cartView.setValue("VERTICAL_AXIS", geoAxisReq);
    }
    else if (hAxis == 2)  // Horizontal latitude axis
    {
        cartView("X_AXIS_TYPE") = "REGULAR";
        cartView("X_MIN") = latMin_;
        cartView("X_MAX") = latMax_;
        geoAxisReq("AXIS_ORIENTATION") = "HORIZONTAL";
        geoAxisReq("AXIS_TICK_LABEL_TYPE") = "LATITUDE";

        cartView("Y_AXIS_TYPE") = "DATE";
        if (bAutoDate_ && bDataUnit_) {
            cartView("Y_AUTOMATIC") = "ON";
            if (dateMin_ > dateMax_)
                cartView("Y_AUTOMATIC_REVERSE") = "ON";
            bUpdateDate_ = false;
        }
        else {
            cartView("Y_AUTOMATIC") = "OFF";
            cartView("Y_DATE_MIN") = dateMin_.c_str();
            cartView("Y_DATE_MAX") = dateMax_.c_str();
            if (!bAutoDate_)
                bUpdateDate_ = false;
        }

        // Set axes definitions
        cartView.setValue("HORIZONTAL_AXIS", geoAxisReq);
        cartView.setValue("VERTICAL_AXIS", viewRequest_.getSubrequest("TIME_AXIS"));
    }
    else  // Horizontal longitude axis
    {
        cartView("X_AXIS_TYPE") = "REGULAR";
        cartView("X_MIN") = lonMin_;
        cartView("X_MAX") = lonMax_;
        geoAxisReq("AXIS_ORIENTATION") = "HORIZONTAL";
        geoAxisReq("AXIS_TICK_LABEL_TYPE") = "LONGITUDE";

        cartView("Y_AXIS_TYPE") = "DATE";
        if (bAutoDate_ && bDataUnit_) {
            cartView("Y_AUTOMATIC") = "ON";
            if (dateMin_ > dateMax_)
                cartView("Y_AUTOMATIC_REVERSE") = "ON";
            bUpdateDate_ = false;
        }
        else {
            cartView("Y_AUTOMATIC") = "OFF";
            cartView("Y_DATE_MIN") = dateMin_.c_str();
            cartView("Y_DATE_MAX") = dateMax_.c_str();
            if (!bAutoDate_)
                bUpdateDate_ = false;
        }

        // Set axes definitions
        cartView.setValue("HORIZONTAL_AXIS", geoAxisReq);
        cartView.setValue("VERTICAL_AXIS", viewRequest_.getSubrequest("TIME_AXIS"));
    }
}

void HovmoellerView::UpdateViewVert(MvRequest& cartView)
{
    // Vertical scaling mode
    const char* vs = "REGULAR";
    if ((const char*)viewRequest_("VERTICAL_SCALING") && strcmp((const char*)viewRequest_("VERTICAL_SCALING"), "LOG") == 0)
        vs = "LOGARITHMIC";

    // Translate Vertical and Time coordinates
    cartView("X_AXIS_TYPE") = "DATE";
    if (bAutoDate_ && bDataUnit_) {
        cartView("X_AUTOMATIC") = "ON";
        if (dateMin_ > dateMax_)
            cartView("X_AUTOMATIC_REVERSE") = "ON";
        bUpdateDate_ = false;
    }
    else {
        cartView("X_AUTOMATIC") = "OFF";
        cartView("X_DATE_MIN") = dateMin_.c_str();
        cartView("X_DATE_MAX") = dateMax_.c_str();
        if (!bAutoDate_)
            bUpdateDate_ = false;
    }

    cartView("Y_AUTOMATIC") = "OFF";
    cartView("Y_AXIS_TYPE") = vs;
    cartView("Y_MIN") = yMin_;
    cartView("Y_MAX") = yMax_;

    // Copy axis definition
    cartView.setValue("HORIZONTAL_AXIS", viewRequest_.getSubrequest("TIME_AXIS"));
    cartView.setValue("VERTICAL_AXIS", viewRequest_.getSubrequest("VERTICAL_AXIS"));
}

void HovmoellerView::ApplicationInfo(const MvRequest& in)
{
    // Check if there is a Data Unit attached
    MvRequest req = in;
    while (req) {
        if (ObjectList::IsDataUnit(req.getVerb())) {
            bDataUnit_ = true;
            return;
        }
        req.advance();
    }

    bDataUnit_ = false;
    return;
}
