/***************************** 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 "Metview.h"
#include "MvException.h"

#include <cmath>
#include <iostream>


struct SpectraData
{
    SpectraData(int valNum) :
        valNum_(valNum)
    {
        if (valNum_ > 0)
            vals_ = new double[valNum_];
    }
    ~SpectraData()
    {
        if (vals_)
            delete[] vals_;
    }

    SpectraData(const SpectraData&) = delete;
    SpectraData& operator=(const SpectraData&) = delete;

    void setMetaData(MvField&);

    double* vals_{nullptr};
    int valNum_{0};
    int step_{0};
    std::string shortName_;
    std::string units_;
    std::string levelType_;
    std::string level_;
    std::string date_;
    std::string time_;
    std::string stepStr_;
};


class SpectraBase : public MvService
{
public:
    SpectraBase(const char* a) :
        MvService(a) {}
    virtual ~SpectraBase()
    {
        for (auto item : spec_) {
            delete item;
        }
    }
    void serve(MvRequest&, MvRequest&);

protected:
    void process(MvRequest&, MvRequest&);
    virtual void getInputParameters(MvRequest&) = 0;
    virtual void createOutputRequest(MvRequest&) = 0;
    virtual void buildAxis(MvRequest&, MvRequest&) = 0;
    void computeSpectra(MvFieldSet& fs, bool firstFieldOnly);

    bool dCycle_{false};
    bool plotDays_{false};
    int xMin_{1};
    int xMax_{1};
    int trunc_{213};  // highest wavenumber in plots of spectra
    std::vector<SpectraData*> spec_;
    double yMin_{0};
    double yMax_{-1E23};
    bool logXAxis_{false};
    bool logYAxis_{false};
    std::string userTitle_;
};

class SpectraGraph : public SpectraBase
{
public:
    SpectraGraph(const char* a) :
        SpectraBase(a) {}

protected:
    void getInputParameters(MvRequest&) override;
    void createOutputRequest(MvRequest&) override;
    void buildTitle(MvRequest&);
    void buildAxis(MvRequest&, MvRequest&) override;
    void compute();

    // these params are not used at all
    std::string tension_;  // interpolation for smoothing curves
    bool smooth_{false};   // allows the curves to be smoothed
};


void SpectraData::setMetaData(MvField& f)
{
    std::string t;
    shortName_ = f.getGribKeyValueString("shortName");
    units_ = f.getGribKeyValueString("units");
    level_ = f.getGribKeyValueString("level");
    levelType_ = f.getGribKeyValueString("typeOfLevel");
    stepStr_ = f.getGribKeyValueString("stepHumanReadable");
    if (stepStr_.empty()) {
        stepStr_ = f.getGribKeyValueString("step");
        stepStr_ += f.getGribKeyValueString("stepUnits");
    }
    date_ = f.getGribKeyValueString("dataDate");
    time_ = f.getGribKeyValueString("time");
}

void SpectraBase::serve(MvRequest& in, MvRequest& out)
{
    try {
        process(in, out);
    }
    catch (MvException& e) {
        setError(1, "Spectra-> %s", e.what());
    }
}

void SpectraBase::process(MvRequest& in, MvRequest& out)
{
    dCycle_ = ((const char*)in("DIURNAL_CYCLE") && strcmp(in("DIURNAL_CYCLE"), "YES") == 0) ? true : false;
    if (const char* cv = in("TRUNCATION")) {
        trunc_ = std::stoi(cv);
    }
    if (trunc_ < 1 || trunc_ > 50000) {
        throw MvException("Invalid truncation value=" + std::to_string(trunc_) + "!");
    }
    if (const char* cv = in("TITLE")) {
        userTitle_ = std::string(cv);
    }


    getInputParameters(in);

    MvRequest grib;
    in.getValue(grib, "DATA");
    MvFieldSet fs(grib);
    computeSpectra(fs, true);

    if (spec_.empty()) {
        throw MvException("No spherical harmonics field found in input!");
    }

    // Build the output request
    createOutputRequest(out);

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

void SpectraBase::computeSpectra(MvFieldSet& fs, bool firstFieldOnly)
{
    MvFieldSetIterator iter(fs);
    // iter.sort("STEP");
    while (MvField& f = iter()) {
        MvFieldExpandThenFree xpf(f);
        if (f.isSpectral()) {
            int spJ = static_cast<int>(f.getGribKeyValueLong("J"));
            int nMax = std::min(trunc_, spJ);
            xMax_ = std::max(xMax_, nMax);
            auto* spVal = new SpectraData(nMax + 1);
            for (int n = 0; n <= nMax; n++) {
                double v = 0.;
                for (int m = 0; m <= n; m++) {
                    int idx = 2 * (n + 1) + m * (2 * spJ + 1 - m) - 1;
                    v += f[idx] * f[idx] + f[idx - 1] * f[idx - 1];
                }
                spVal->vals_[n] = sqrt(v);
                if (yMax_ < spVal->vals_[n])
                    yMax_ = spVal->vals_[n];
            }
            spVal->setMetaData(f);
            spec_.emplace_back(spVal);
            if (firstFieldOnly) {
                return;
            }
        }
    }
}

void SpectraGraph::getInputParameters(MvRequest& in)
{
    xMax_ = trunc_;
    smooth_ = (strcmp(in("SMOOTHING"), "YES") == 0) ? true : false;
    tension_ = (const char*)in("TENSION");
    if (const char* ch = in("X_AXIS_TYPE")) {
        if (strcmp(ch, "LOGARITHMIC") == 0) {
            logXAxis_ = true;
        }
    }
    if (const char* ch = in("Y_AXIS_TYPE")) {
        if (strcmp(ch, "LOGARITHMIC") == 0) {
            logYAxis_ = true;
        }
    }
}

void SpectraGraph::createOutputRequest(MvRequest& out)
{
    out.setVerb("CARTESIANVIEW");
    out("X_AUTOMATIC") = "ON";
    out("Y_AUTOMATIC") = "ON";
    if (logXAxis_) {
        out("X_AXIS_TYPE") = "LOGARITHMIC";
    }
    if (logYAxis_) {
        out("Y_AXIS_TYPE") = "LOGARITHMIC";
    }

    MvRequest haxis;
    MvRequest vaxis;
    buildAxis(vaxis, haxis);
    out("HORIZONTAL_AXIS") = haxis;
    out("VERTICAL_AXIS") = vaxis;

    MvRequest title;
    buildTitle(title);

    // Build all curves
    for (auto& i : spec_) {
        MvRequest curve("INPUT_XY_POINTS");
        MvRequest mgraph("MGRAPH");
        mgraph("GRAPH_LINE_THICKNESS") = 2;
        for (int j = 1; j < i->valNum_; j++) {
            curve("INPUT_X_VALUES") += j;
            double v = i->vals_[j];
            if (logYAxis_ && v < 1E-30) {
                v = 1E-30;
            }
            curve("INPUT_Y_VALUES") += v;
        }
        out = out + curve + mgraph;
    }
    out = out + title;
}


void SpectraGraph::buildTitle(MvRequest& title)
{
    title = MvRequest("MTEXT");
    if (!userTitle_.empty()) {
        title("TEXT_LINE_1") = userTitle_.c_str();
    }
    else if (!spec_.empty()) {
        SpectraData* sp = spec_[0];
        std::string t = "Param: " + sp->shortName_ + " [" + sp->units_ + "] ";
        t += " Level: ";
        if (sp->levelType_ == "isobaricInhPa") {
            t += sp->level_ + "hPa";
        }
        else if (sp->levelType_ == "hybrid") {
            t += sp->level_ + "ML";
        }
        else if (sp->levelType_ == "hybrid") {
            t += "surface";
        }
        else {
            t += sp->level_ + " " + sp->levelType_;
        }

        t += " Date: " + sp->date_ + " " + sp->time_ + "UTC ";
        if (!sp->stepStr_.empty())
            t += "+" + sp->stepStr_;

        title("TEXT_LINE_1") = t.c_str();
    }

    title("TEXT_FONT_SIZE") = 0.4;
}

void SpectraGraph::buildAxis(MvRequest& vaxis, MvRequest& haxis)
{
    vaxis = MvRequest("MAXIS");
    vaxis("AXIS_ORIENTATION") = "VERTICAL";
    vaxis("AXIS_TICK_LABEL_ORIENTATION") = "HORIZONTAL";
    vaxis("AXIS_TITLE_TEXT") = "Amplitude";

    haxis = MvRequest("MAXIS");
    haxis("AXIS_ORIENTATION") = "HORIZONTAL";
    haxis("AXIS_TITLE_TEXT") = "Legendre polynomial order (n)";
}


int main(int argc, char** argv)
{
    MvApplication theApp(argc, argv);
    SpectraGraph specgraph("SPEC_GRAPH");
    theApp.run();
}
