/***************************** LICENSE START ***********************************

 Copyright 2020 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 "ThermoView.h"
#include "MvRequestUtil.hpp"
#include "ObjectList.h"
#include "PlotMod.h"
#include "PmContext.h"
#include "Root.h"
#include "MvLog.h"

// Thermo types
static const std::string THERMOTEPHI = "TEPHIGRAM";
static const std::string THERMOSKEWT = "SKEW_T";
static const std::string THERMOEMAGRAM = "EMAGRAM";

// Thermo data types
std::map<std::string, std::string> create_dataTypeMap()
{
    std::map<std::string, std::string> m;
    m["GRIB"] = "GRIB_THERMO";
    m["BUFR"] = "BUFR_THERMO";
    m["ODB"] = "ODB_THERMO";
    return m;
}

static std::map<std::string, std::string> DATA_TYPE = create_dataTypeMap();

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

static ThermoViewFactory thermoViewFactoryInstance;

PlotModView*
ThermoViewFactory::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 Thermo view
    return new ThermoView(page, expReq, setupRequest);
}

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

ThermoView::ThermoView(Page& owner,
                       const MvRequest& viewRequest,
                       const MvRequest& setupRequest) :
    CommonXSectView(owner, viewRequest, setupRequest)
{
    SetVariables(viewRequest, true);
}

ThermoView::ThermoView(const ThermoView& old) :
    CommonXSectView(old)
{
    type_ = old.type_;
}

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

    return name;
}

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

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

bool ThermoView::UpdateView()
{
    // It has already been updated
    if (string(viewRequest_.getVerb()) == CARTESIANVIEW)
        return true;

    // Create a Cartesian View request related to Tephigram
    // Magics requires that the value for parameter map_projection be
    // in lower case
    MvRequest cartView("CARTESIANVIEW");
    std::string sdata = type_;
    std::transform(sdata.begin(), sdata.end(), sdata.begin(), ::tolower);
    cartView("MAP_PROJECTION") = sdata.c_str();

    // Translate axes min/max values
    // Check where the values should be taken from
    if ((const char*)viewRequest_("_DEFAULT") &&
        (int)viewRequest_("_DEFAULT") == 1 &&
        (const char*)viewRequest_("_DATAATTACHED") &&
        strcmp((const char*)viewRequest_("_DATAATTACHED"), "YES") == 0) {
        cartView("X_AUTOMATIC") = "on";
        cartView("Y_AUTOMATIC") = "on";
    }
    else {
        cartView("X_AUTOMATIC") = "off";
        cartView("Y_AUTOMATIC") = "off";

        // Get min/max axes coordinates
        cartView("X_MIN") = viewRequest_("MINIMUM_TEMPERATURE");
        cartView("X_MAX") = viewRequest_("MAXIMUM_TEMPERATURE");
        cartView("Y_MIN") = viewRequest_("BOTTOM_PRESSURE");
        cartView("Y_MAX") = viewRequest_("TOP_PRESSURE");
    }

    // Copy ThermoGrid definition
    MvRequest reqGrid = viewRequest_.getSubrequest("THERMO_GRID");
    if (reqGrid)
        cartView("THERMO_GRID") = reqGrid;

    // Remove some unnecessary Page and SubPage parameters and
    // copy the remaining ones to the new View
    if ((const char*)viewRequest_("PAGE_FRAME") &&
        strcmp((const char*)viewRequest_("PAGE_FRAME"), "OFF") == 0)
        metview::RemoveParameters(viewRequest_, "PAGE_FRAME");

    if ((const char*)viewRequest_("PAGE_ID_LINE") &&
        strcmp((const char*)viewRequest_("PAGE_ID_LINE"), "OFF") == 0)
        metview::RemoveParameters(viewRequest_, "PAGE_ID_LINE");

    if ((const char*)viewRequest_("SUBPAGE_FRAME") &&
        strcmp((const char*)viewRequest_("SUBPAGE_FRAME"), "OFF") == 0)
        metview::RemoveParameters(viewRequest_, "SUBPAGE_FRAME");

    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;
}

bool ThermoView::UpdateViewWithReq(MvRequest& viewRequest)
{
    // Get the original ThermoView request in case it has already
    // been translated to a CartesianView request
    MvRequest tview;
    std::string tverb;
    if (!ObjectList::IsThermoView(viewRequest_.getVerb())) {
        // Retrieve the original ThermoView request
        tview = viewRequest_("_ORIGINAL_REQUEST");
    }
    else
        tview = viewRequest_;

    // Changing to another View is not allowed
    if (!ObjectList::IsThermoView(viewRequest.getVerb())) {
        // Changing View is disabled at the moment
        MvLog().popup().err() << "Changing View (" << tview.getVerb() << " to " << viewRequest.getVerb() << ") is currently disabled";
        return false;
    }

    // If the request came from an Application then ignore the new
    // View definition and use the current View to draw the new plot
    MvRequest contextReq = viewRequest.getSubrequest("_CONTEXT");
    if (contextReq)
        return true;

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

    // Update the current view
    type_ = (const char*)req("TYPE");

    // Update request
    // Owner().UpdateView(req);  // it will be updated later (CommonXSectView)

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

    // Redraw this page
    // Owner().RedrawIfWindow();
    // Owner().NotifyObservers();

    // Owner().InitZoomStacks();

    return true;
}

void ThermoView::ApplicationInfo(const MvRequest& req)
{
    // Get the VIEW request
    MvRequest viewReq;
    if (ObjectList::IsView(req.getVerb()))
        viewReq = req;
    else {
        // Try to find a hidden VIEW in the request
        viewReq = req.getSubrequest("_VIEW_REQUEST");
        if (!viewReq) {
            // Nothing to be done
            return;
        }
    }

    return;
}

void ThermoView::Drop(PmContext& context)
{
    // Before processing the drop (parent's function), check for icon drops
    // specific to this class:
    // a) dataUnit: initializes the type of the ThermoData module to be called
    // b) thermogrid: update the View
    // c) thermoview: update the View
    MvRequest dropRequest = context.InRequest();
    bool first = true;
    while (dropRequest) {
        // Data unit
        std::string verb = dropRequest.getVerb();
        if (first && ObjectList::IsDataUnit(verb.c_str()) == true) {
            MvRequest req = dropRequest.justOneRequest();

            if (!this->SetThermoType(req))
                return;  // suitable thermo type not found

            first = false;
        }
        // Thermo grid
        else if (ObjectList::IsThermoGrid(verb.c_str()) == true) {
            updateThermoGrid(dropRequest.justOneRequest());
        }
        else if (ObjectList::IsThermoView(verb.c_str())) {
            // First: check and update the internal ThermoView structure
            // Second: ask CommonXSectView::Drop to update the Page
            MvRequest req = dropRequest.justOneRequest();
            if (!this->UpdateViewWithReq(req))
                return;
        }

        dropRequest.advance();
    }

    // Call the parent to perform the Drop request
    CommonXSectView::Drop(context);

    return;
}

void ThermoView::updateThermoGrid(const MvRequest& req)
{
    thermoGridReq_ = req;
    if (!isThermoGridForeground()) {
        viewRequest_("THERMO_GRID") = req;
    }
    else {
        MvRequest gr("MTHERMOGRID");
        gr("THERMO_ISOTHERM_GRID") = "OFF";
        gr("THERMO_ISOBAR_GRID") = "OFF";
        gr("THERMO_DRY_ADIABATIC_GRID") = "OFF";
        gr("THERMO_SATURATED_ADIABATIC_GRID") = "OFF";
        gr("THERMO_MIXING_RATIO_GRID") = "OFF";
        viewRequest_("THERMO_GRID") = gr;
    }
}

bool ThermoView::SetThermoType(MvRequest& req)
{
    std::string thermoType;
    std::string verb = req.getVerb();
    std::map<std::string, std::string>::iterator it;
    it = DATA_TYPE.find(verb.c_str());
    if (it != DATA_TYPE.end())
        thermoType = it->second;
    else if (verb == "NETCDF") {
        if ((const char*)req("_VERB"))
            thermoType = (const char*)req("_VERB");
        else if ((const char*)req("_CLASS"))
            thermoType = (const char*)req("_CLASS");
        else {
            // Thermo type not found
            MvLog().popup().err() << "Suitable thermo type not found: " << verb;
            return false;
        }
    }
    else if (verb.find("NETCDF") != std::string::npos) {
        // The application has been already called to process the data.
        // These are a set of requests containing the NETCDF_XY_*;
        // therefore it will be processed withoug calling again the
        // application. There is no need to set the ApplicationName.
        return true;
    }
    else if (verb.find("INPUT") != std::string::npos) {
        // The application has been already called to process the data.
        // These are a set of requests containing the _XY_*;
        // therefore it will be processed withoug calling again the
        // application. There is no need to set the ApplicationName.
        return true;
    }
    else {
        // Thermo type not found
        MvLog().popup().err() << "Suitable thermo type not found:" << verb;
        return false;
    }

    ApplicationName(thermoType);
    return true;
}

bool ThermoView::ConsistencyCheckWithStr(MvRequest& req1, MvRequest& req2, std::string& param)
{
    // Build a list of parameters to be checked
    std::vector<std::string> vparam;
    vparam.emplace_back("POINT_SELECTION");

    if ((const char*)req1("COORDINATES"))
        vparam.emplace_back("COORDINATES");
    if ((const char*)req1("THRESHOLD"))
        vparam.emplace_back("THRESHOLD");
    if ((const char*)req1("AREA_AVERAGE"))
        vparam.emplace_back("AREA_AVERAGE");
    if ((const char*)req1("STATION"))
        vparam.emplace_back("STATION");
    if ((const char*)req1("WMO_NAME"))
        vparam.emplace_back("WMO_NAME");
    if ((const char*)req1("WMO_IDENT"))
        vparam.emplace_back("WMO_IDENT");
    if ((const char*)req1("WIGOS_SERIES"))
        vparam.emplace_back("WIGOS_SERIES");
    if ((const char*)req1("WIGOS_ISSUER"))
        vparam.emplace_back("WIGOS_ISSUER");
    if ((const char*)req1("WIGOS_ISSUE_NUMBER"))
        vparam.emplace_back("WIGOS_ISSUE_NUMBER");
    if ((const char*)req1("WIGOS_LOCAL_NAME"))
        vparam.emplace_back("WIGOS_LOCAL_NAME");
    if ((const char*)req1("POINT_EXTRACTION"))
        vparam.emplace_back("POINT_EXTRACTION");

    vparam.emplace_back("DEW_POINT_FORMULATION");
    vparam.emplace_back("TEMPERATURE_PARAM");
    vparam.emplace_back("SPECIFIC_HUMIDITY_PARAM");
    vparam.emplace_back("LNSP_PARAM");
    vparam.emplace_back("U_WIND_PARAM");
    vparam.emplace_back("V_WIND_PARAM");

    // Check one by one because we want to know which one is different
    return req1.checkParameters(req2, vparam, param);
}

void ThermoView::DrawForeground()
{
    if (isThermoGridForeground()) {
        GraphicsEngine& ge = Owner().GetGraphicsEngine();
        ge.Draw(thermoGridReq_, true);
    }
}

bool ThermoView::isThermoGridForeground() const
{
    if (thermoGridReq_) {
        if (const char* ch = thermoGridReq_("THERMO_GRID_LAYER_MODE")) {
            return (strcmp(ch, "FOREGROUND") == 0);
        }
    }
    return false;
}
