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

#include <iostream>

#include "BufrThermo.h"
#include "GribThermo.h"

// Thermo data types
const static int N_DATATYPE                   = 3;
const static std::string DATATYPE[N_DATATYPE] = {"GRIB_THERMO", "BUFR_THERMO", "ODB_THERMO"};

Thermo::Thermo(const char* kw) :
    MvService(kw),
    actionMode_("prepare"),
    procType_(THERMO_DATA_MODULE)
{
}

void Thermo::serve(MvRequest& in, MvRequest& out)
{
    // Error messages either via MvException or setError will be handled
    // by the caller (MvService::serve)

    std::cout << "request IN" << std::endl;
    in.print();

    // Get Input parameters
    origReq_ = in;
    if (!this->getInputParameters(in))
        return;

    // It is a netCDF data and it has been already processed.
    // Build the output request
    if (dataRequest_.getVerb() == std::string("NETCDF"))
        this->createProcessedOutputRequest(out);
    else {
        // Create a temporary netCDF file name
        ncFileName_ = marstmp();

        // Process data
        if (!processData())
            return;

        // Build the output request
        if (!this->createOutputRequest(out))
            return;
    }

    // Add hidden values to the output request
    // out1("_VERB") = "NETCDF_MATRIX";
    if ((const char*)in("_NAME"))
        out("_NAME") = in("_NAME");

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

    return;
}

// Retrieve parameters from the input request and make a consistency check.
// There are 4 types of input request:
// 1. DATA_DROPPED: Grib dropped into a View
// 2. DATA_MODULE_DROPPED: Data module dropped into a View
// 3. DATA_MODULE: Process a data module
// 4. netCDF dropped in a View
//
// Actions for each 4 types:
// 1. Use the original View.
// 2. Use the original View and check if the input ThermoData parameters are
//    consistent with the View.
// 3. Build a new View.
// 4. Use the original View and check if the netCDF parameters are consistent
//    with the View.
//
bool Thermo::getInputParameters(MvRequest& in)
{
    // Retrieve data request
    if (!in.countValues("DATA")) {
        setError(1, "Thermo-> No data files specified!");
        return false;
    }

    in.getValue(dataRequest_, "DATA");

    // Get and save action mode. Default value is "prepare"
    actionMode_ = (const char*)in("_ACTION") ? (const char*)in("_ACTION") : "prepare";

    // Retrieve original View if exists, e.g. if a data or a
    // data module was dropped into a view
    MvRequest viewRequest;
    if ((const char*)in("_CONTEXT"))
        viewRequest = in("_CONTEXT");

    // Check if a data module was dropped into the view
    const char* verb   = (const char*)in("_VERB");
    bool moduleDropped = this->isDataModule(verb);

    // Check the type of input request to be processed
    MvRequest commonRequest;
    if (viewRequest)
        procType_ = moduleDropped ? THERMO_DATA_MODULE_DROPPED : THERMO_DATA_DROPPED;
    else
        procType_ = THERMO_DATA_MODULE;

    // Type of input request is a DATA_DROPPED into a View
    // Get information from the View request
    if (procType_ == THERMO_DATA_DROPPED) {
        // Retrieve 'original' request from the View
        commonRequest = this->getAppView(viewRequest);

        // Retrieve parameters
        if (!getAppParameters(commonRequest))
            return false;
    }
    // Process a data module
    // Get information from the module icon parameters
    else if (procType_ == THERMO_DATA_MODULE) {
        // Retrieve parameters
        if (!this->getAppParameters(in))
            return false;
    }
    // Process a data module dropped into a view
    // Get information from the module and view. Check if they are compatible.
    else if (procType_ == THERMO_DATA_MODULE_DROPPED) {
        // Retrieve 'original' request from the View
        commonRequest = this->getAppView(viewRequest);

        // Retrieve 'original' request from the data module
        MvRequest modRequest = dataRequest_.getSubrequest("_ORIGINAL_REQUEST");
        if (!modRequest)
            modRequest = in;

        // Retrieve application specific parametes
        if (!this->getAppParameters(modRequest))
            return false;

        // Consistency check, only for non-default View request
        if (!((const char*)commonRequest("_DEFAULT") && (int)commonRequest("_DEFAULT") == 1))
            if (!this->consistencyCheck(commonRequest, modRequest))
                return false;
    }

    return true;
}

bool Thermo::isVisualise()
{
    // If the output is not 'visualisation' related: actions
    // "not visualise" or "prepare" (in certain cases), then
    // send back the "netCDF" request
    if (actionMode_ != "visualise" &&
        !(actionMode_ == "prepare" && procType_ == THERMO_DATA_MODULE_DROPPED) &&
        !(actionMode_ == "prepare" && procType_ == THERMO_DATA_DROPPED))
        return false;

    return true;
}

bool Thermo::isDataModule(const char* verb)
{
    if (!verb)
        return false;

    for (const auto& i : DATATYPE) {
        if (verb == i)
            return true;
    }

    return false;
}

MvRequest Thermo::getAppView(MvRequest& viewRequest)
{
    // Check if the original view request has been already processed,
    // e.g. it has been already replaced by the CartesianView
    if ((const char*)viewRequest("_ORIGINAL_REQUEST"))
        return viewRequest("_ORIGINAL_REQUEST");
    else
        return viewRequest;
}

void Thermo::createProcessedOutputRequest(MvRequest& out)
{
    // Create NetCDF output request
    MvRequest data    = origReq_.getSubrequest("DATA");
    MvRequest out1    = data.getSubrequest("_VISUALISE");
    MvRequest viewReq = data.getSubrequest("_CARTESIANVIEW");
    data.unsetParam("_VISUALISE");         // to avoid duplication of info
    data.unsetParam("_CARTESIANVIEW");     // to avoid duplication of info
    data.unsetParam("_ORIGINAL_REQUEST");  // to avoid duplication of info
    out1("NETCDF_DATA") = data;

    // Final output request
    // If an icon was dropped into a view, uPlot will ignore it.
    // Mode-specific options
    out = viewReq + out1;
}

MvRequest Thermo::buildMacroConverterRequest()
{
    const char* caux = origReq_.getVerb();
    MvRequest oreq(caux);
    oreq("_MACRO_DECODE_TAG") = 1;  // extra info for the Macro Converter

    oreq("_CLASS") = (const char*)origReq_("_CLASS") ? (const char*)origReq_("_CLASS") : caux;

    oreq("_VERB") = (const char*)origReq_("_VERB") ? (const char*)origReq_("_VERB") : caux;

    // Check if the input data is a MARS retrieval (RETRIEVE)
    // or a file (GRIB/BUFR)
    if (strcmp((const char*)oreq("_CLASS"), "RETRIEVE") == 0 ||
        strcmp((const char*)oreq("_CLASS"), "GRIB") == 0 ||
        strcmp((const char*)oreq("_CLASS"), "BUFR") == 0) {
        MvRequest retReq = origReq_("DATA");
        if ((const char*)retReq("_NAME"))
            oreq("_NAME") = (const char*)retReq("_NAME");
    }
    else if ((const char*)origReq_("_NAME"))
        oreq("_NAME") = (const char*)origReq_("_NAME");

    return oreq;
}

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

int main(int argc, char** argv)
{
    MvApplication theApp(argc, argv);

    GribThermo gribThermo;
    BufrThermo bufrThermo;

    // The applications don't try to read or write from pool, this
    // should not be done with the new PlotMod.
    gribThermo.saveToPool(false);
    bufrThermo.saveToPool(false);

    theApp.run();
}
