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

#include <iomanip>
#include <sstream>

#include "MvException.h"

#include <cassert>
#include <algorithm>
#include <cmath>
#include <numeric>

// convienience method, (vk/1999-08-05)
// checks if x is between r1 and r2
bool isWithinRange(double r1, double r2, double x)
{
    if (r1 <= x && x <= r2)
        return true;

    if (r2 <= x && x <= r1)
        return true;

    return false;
}

// Deletes all info about a parameter, including all space used
// for the level data.
ParamInfo::~ParamInfo()
{
    LevelIterator ii;

    for (ii = levels_.begin(); ii != levels_.end(); ii++) {
        if ((*ii).second)
            delete[](*ii).second->xvalues_;
    }
}

bool ParamInfo::IsSurface()
{
    return (LevelType() == "sfc" || NrLevels() < 1) ? true : false;
}

std::string ParamInfo::ExpVerTitle()
{
    if (expver_ == "_")  //-- missing ExpVer is stored as "_"
        return {""};
    else
        return std::string("Expver ") + expver_;
}

// Fill in data into a level in the LevelMap. Generate the level if needed,
// and delete any old values from the level.
void ParamInfo::Level(double* x, std::string lev, int n, bool scaleToPa)
{
    double flev = atof(lev.c_str());
    if (scaleToPa) {
        flev = ApplicationInfo::toPa(flev);
    }

    // Save data for one level
    auto ii = levels_.find(lev);
    if (ii == levels_.end()) {
        levels_[lev] = new LevelInfo(flev);
        ii = levels_.find(lev);
    }
    else {
        if ((*ii).second->xvalues_) {
            delete[](*ii).second->xvalues_;
            (*ii).second->xvalues_ = nullptr;
        }
        // TODO: check if it is needed to multiply by 100
        if ((*ii).second->yvalue_ >= XMISSING_VALUE)
            (*ii).second->yvalue_ = flev;
    }

    if ((*ii).second->xvalues_ == nullptr)
        (*ii).second->xvalues_ = new double[n];

    double* xx = (*ii).second->xvalues_;
    for (int i = 0; i < n; i++)
        xx[i] = x[i];
}

void ParamInfo::UpdateLevels(bool scaleToPa)
{
    LevelIterator ii;
    for (ii = levels_.begin(); ii != levels_.end(); ii++) {
        double flev = atof((*ii).first.c_str());
        (*ii).second->UpdateLevel((scaleToPa) ? ApplicationInfo::toPa(flev) : flev);
    }
}

void ParamInfo::AddLevel(std::string lev)
{
    if (levels_.find(lev) == levels_.end())
        levels_[lev] = new LevelInfo;
}

MvDate ParamInfo::ReferenceDate()
{
    double tt = date_ + time_ / 2400.;  // YYYYMMDD + HHMM
    return {tt};
}

MvDate ParamInfo::VerificationDate()
{
    double tt = date_ + time_ / 2400. + step_ / 24.;  // YYYYMMDD + HHMM + HH
    return {tt};
}

double* ParamInfo::getOneLevelValues(const std::string& clev)
{
    // Find the input level
    auto ll = levels_.find(clev);
    if (ll == levels_.end())
        return nullptr;

    return (*ll).second->XValues();
}

//=====================================================
//
// TargetLevelItem
//
//=====================================================

class TargetLevelItem
{
    friend class TargetLevelMaker;

public:
    TargetLevelItem() = default;
    void addValue(double val) { vals_.push_back(val); }
    void compute();
    bool isEmpty() const { return vals_.size() == 0; }

private:
    std::vector<double> vals_;
    double min_{std::numeric_limits<double>::max()};
    double max_{std::numeric_limits<double>::min()};
    double mean_{0.};
    double stdev_{0.};
};

void TargetLevelItem::compute()
{
    if (vals_.size() > 0) {
        min_ = *std::min_element(vals_.begin(), vals_.end());
        max_ = *std::max_element(vals_.begin(), vals_.end());
        mean_ = std::accumulate(vals_.begin(), vals_.end(), 0.0) / static_cast<double>(vals_.size());
        std::for_each(vals_.begin(), vals_.end(), [this](const double d) {
            stdev_ += std::pow(d - mean_, 2);
        });
        stdev_ = std::sqrt(stdev_ / static_cast<double>(vals_.size()));
    }
}

//=====================================================
//
// TargetLevelMaker
//
//=====================================================

class TargetLevelMaker
{
public:
    TargetLevelMaker(int levelNum) :
        levels_(levelNum) {}
    void add(double v, int level);
    void addDeltaFromMinMax(std::vector<double>& vals) const;
    void getMeans(std::vector<double>&);
    void getAdaptive(std::vector<double>&);
    double minVal() const { return minVal_; }
    double maxVal() const { return maxVal_; }

private:
    void compute();
    void addTargetVal(std::vector<double>& res, double startVal, double endVal, int num) const;

    bool computed_{false};
    std::vector<TargetLevelItem> levels_;
    double minVal_{std::numeric_limits<double>::max()};
    double maxVal_{std::numeric_limits<double>::min()};
};

void TargetLevelMaker::add(double v, int levelIdx)
{
    assert(!computed_);
    levels_[levelIdx].addValue(v);
    if (v > maxVal_) {
        maxVal_ = v;
    }
    if (v < minVal_) {
        minVal_ = v;
    }
}

void TargetLevelMaker::addTargetVal(std::vector<double>& res, double startVal, double endVal, int num) const
{
    double incr = (endVal - startVal) / (num + 1);
    for (int i = 0; i < num; i++) {
        res.push_back(startVal + i * incr);
    }
}

void TargetLevelMaker::addDeltaFromMinMax(std::vector<double>& vals) const
{
    if (!vals.empty()) {
        double deltaFirst = fabs(vals[0] - vals[1]);
        double deltaLast = fabs(vals[vals.size() - 1] - vals[vals.size() - 2]);
        if (vals[0] < vals[1]) {
            vals.insert(vals.begin(), minVal_ - deltaFirst);
            vals.push_back(maxVal_ + deltaLast);
        }
        else {
            vals.insert(vals.begin(), maxVal_ + deltaFirst);
            vals.push_back(minVal_ - deltaLast);
        }
    }
}

void TargetLevelMaker::compute()
{
    if (!computed_) {
        for (auto& v : levels_) {
            v.compute();
        }
        levels_.erase(std::remove_if(levels_.begin(), levels_.end(),
                                     [](auto v) { return v.isEmpty(); }),
                      levels_.end());
        computed_ = true;
    }
}

void TargetLevelMaker::getMeans(std::vector<double>& res)
{
    compute();
    for (auto& v : levels_) {
        assert(!v.isEmpty());
        res.push_back(v.mean_);
    }
}

void TargetLevelMaker::getAdaptive(std::vector<double>& res)
{
    compute();

#if 0
    int i=0;
    for (auto &v: levels_) {
        double delta = (i==0)?0:(v.mean_-levels_[i-1].mean_);
        std::cout <<  v.vals_.size() << " " << v.mean_ << " " << v.min_ << " " << v.max_ <<
                      " " << v.max_ - v.min_ << " " << v.stdev_ << " "
                   << std::fabs(delta) << std::endl;
        i++;
    }
#endif

    // check if the level mean is representative, i.e. the spread is narrow enough (0,1)
    std::vector<int> levRep;
    for (std::size_t i = 0; i < levels_.size(); i++) {
        assert(!levels_[i].isEmpty());
        double delta = (i == 0) ? std::fabs(levels_[i].mean_ - levels_[i + 1].mean_) : std::fabs(levels_[i].mean_ - levels_[i - 1].mean_);
        levRep.push_back(levels_[i].stdev_ < delta);
    }

    bool ascending = levels_.front().mean_ < levels_.back().mean_;

    // find levels with large spread at the front
    int frontIdx = -1;
    int backIdx = -1;
    for (std::size_t i = 0; i < levels_.size(); i++) {
        if (levRep[i] == 1) {
            if (i > 0) {
                frontIdx = static_cast<int>(i);
            }
            break;
        }
    }

    // find levels with large spread at the back
    for (std::size_t i = levels_.size() - 1; i >= 0; i--) {
        if (levRep[i] == 1) {
            if (i < levels_.size() - 1) {
                backIdx = static_cast<int>(i);
            }
            break;
        }
    }

    // construct result
    res.clear();

    // intervals via the whole range
    if (frontIdx == -1 && backIdx == -1) {
        double startVal = (ascending) ? levels_.front().min_ : levels_.back().min_;
        double endVal = (ascending) ? levels_.back().max_ : levels_.front().max_;
        int num = std::max(static_cast<int>(levels_.size()) + 5, 10);
        addTargetVal(res, startVal, endVal, num);
    }
    else {
        // intervals at front
        if (frontIdx != -1) {
            double startVal = (ascending) ? levels_.front().min_ : levels_.front().max_;
            double endVal = levels_[frontIdx - 1].mean_;
            int num = std::max(static_cast<int>(frontIdx + 2), 10);
            addTargetVal(res, startVal, endVal, num);
        }

        // means in the middle
        std::size_t midStart = (frontIdx > -1) ? frontIdx : 0;
        std::size_t midEnd = (backIdx > -1) ? backIdx : levels_.size() - 1;
        for (std::size_t i = midStart; i <= midEnd; i++) {
            res.push_back(levels_[i].mean_);
        }

        // intervals at back
        if (backIdx != -1) {
            double startVal = levels_[backIdx + 1].mean_;
            double endVal = (ascending) ? levels_.back().max_ : levels_.back().min_;
            int num = std::max(static_cast<int>(levels_.size() - backIdx + 2), 10);
            addTargetVal(res, startVal, endVal, num);
        }
    }
}

//=====================================================
//
// ApplicationInfo
//
//=====================================================

void ApplicationInfo::determineLevelType(bool isML, bool isPL)
{
    // isML and isPL are both true if a fieldset contains LNSP and PL data. This is the
    // case when LNSP is supplied for to orography curve! In this case, treat it as PL with LNSP!
    if (haveGHBC()) {
        if (isML && !isPL) {
            levType_ = XS_ML_GHBC;
        }
        else {
            levType_ = XS_GHBC;
        }
    }
    else if (this->haveLNSP() && isML && !isPL) {
        levType_ = XS_ML_LNSP;
    }
    else if (isML && !isPL) {
        levType_ = XS_ML;
    }
    else if (isPL && (!isML || this->haveLNSP())) {
        levType_ = XS_PL;
    }
    else {
        levType_ = XS_ASIS;
    }

#if 0
    // isML and isPL are both true if a fieldset contains LNSP and PL data.
    // In this case, treat as PL with LNSP.
    if ((this->haveLNSP() || this->haveGHBC()) && !isPL) {
        if (levType_ != cML_UKMO_ND) {
            if (this->haveLNSP())
                levType_ = XS_ML_LNSP;
            else
                levType_ = XS_ML_GHBC;
        }
    }
    else {
        if (isML && !isPL)
            levType_ = XS_ML;
        else if (isPL)
            levType_ = XS_PL;
    }
#endif
    // When the target vertical coordinate system is pressure we need to scale the input
    // parameters (e.g. topLevel_) from hPa to Pa
    if (levType_ == XS_ML_LNSP || levType_ == XS_PL) {
        double factor = 100.;
        topLevel_ *= factor;
        bottomLevel_ *= factor;
        for (double& i : uiLSList_) {
            i *= factor;
        }
    }
}

// Check the level type info and update the levels accordingly
// This info is not known when the parameters and levels were added.
void ApplicationInfo::updateLevels(ParamMap& params)
{
    // Get level type flag
    bool needScaleToPa = (levType_ == XS_PL);

    // Update the levels
    ParamIterator ii;
    for (ii = params.begin(); ii != params.end(); ii++)
        (*ii).second->UpdateLevels(needScaleToPa);

    return;
}

void ApplicationInfo::Grid(double ns, double ew)
{
    // treatment of missing x/y increments (as in reduced Gaussian grids) -
    // prior to Metview 4.8.1, there was a bug here. gridWE() would give a very
    // tiny value when given a reduced Gaussian grid (based on misinterpreting
    // the result of ecCodes when reading a 'MISSING' GRIB key), but it would
    // not be cValueNotGiven. The intention was to set a 1x1 degree increment
    // in this case, but this was not triggered;
    // instead a very tiny rx meant that the computation would take a very long time.
    // However, the Cross Section application took the max value of dx and dy,
    // which was always dy, and so the result was always reasonable, even though
    // the intention there was also to use a 1x1 grid in the case of missing
    // increments in the GRIB header. We now explicitly give this behaviour in
    // order a) to work, and b) to be consitent with what Cross Section did before.
    // See also JIRA issue METV-1562.
    //-- has to be synchronised with averageAlong() (020731/vk)
    if (ew == cValueNotGiven)
        ew = ns;

    if (ns == cValueNotGiven) {
        ew = ns = 1.0;
    }

    gridNS_ = ns;
    gridEW_ = ew;
}

// Compute geographical points from delta intervals
void ApplicationInfo::computeLine(int npoints)
{
    // Compute delta intervals
    nrPoints_ = npoints;  // initial value
    double dx = double(x2_ - x1_) / double(nrPoints_ - 1);
    double dy = double(y2_ - y1_) / double(nrPoints_ - 1);

    // Initialize vectors
    lon_.clear();
    lat_.clear();
    lon_.reserve(nrPoints_);
    lat_.reserve(nrPoints_);
    lon_.push_back(x1_);
    lat_.push_back(y1_);

    // Compute initial coordinates via pole
    if (viaPole()) {
        double lat_k_prev = y1_;  //-- xsect line over a pole
        bool overThePole = false;
        double original_dy = dy;

        for (int k = 1; k < nrPoints_; k++) {
            double lat_k = lat_k_prev + dy;
            if ((lat_k < -90.0 || lat_k > 90.0) && !overThePole)  // have we just gone over the pole?
            {
                lat_k_prev += dy;  //-- compensate for mirroring
                dy = -dy;          //-- start mirroring backwards
                if (lat_k < -90.0)
                    lat_k = -180.0 - lat_k;  //-- fix for South Pole
                else
                    lat_k = 180.0 - lat_k;  //-- fix for North Pole

                if (dx == 0) {
                    double dtemp = x1_ + 180.0;  //-- straight over the pole
                    if (dtemp > 360.0)           //-- ensure inside normal range
                        dtemp = dtemp - 360.0;

                    lon_.push_back(dtemp);
                }

                overThePole = true;
            }
            else {
                lon_.push_back(lon_[k - 1] + dx);
            }

            lat_.push_back(lat_k);
            lat_k_prev += dy;
        }

        // the above code computes the real co-ordinates of the points, but Magics will not be happy
        // with them (e.g. 0, -1, -2, ..., -89, -90, -89, ..., -2, -1, 0) so we will create a list of
        // latitudes that go in one direction only and will be passed to Magics for plotting.
        // Also, Magics seems to be happier if the longitude does not change, so we just plot
        // with an array of constant longitudes
        latForPlotting_.clear();
        latForPlotting_.reserve(nrPoints_);
        lonForPlotting_.clear();
        lonForPlotting_.reserve(nrPoints_);
        for (int k = 0; k < nrPoints_; k++) {
            latForPlotting_.push_back(y1_ + (original_dy * k));
            lonForPlotting_.push_back(x1_);
        }
        haveLatsForPlotting(true);
        haveLonsForPlotting(true);
    }
    // Compute coordinates
    else {
        for (int k = 1; k < nrPoints_; k++)  //-- no pole
        {
            lon_.push_back(lon_[k - 1] + dx);
            lat_.push_back(lat_[k - 1] + dy);
        }
    }

    return;
}

// Compute geographical points according to the Horizontal Point Mode parameter
void ApplicationInfo::computeLine(int& npoints, MvField* field)
{
    // Compute initial geographical points
    this->computeLine(npoints);

    // Use the default geographical points computed by function computeLine
    if (hor_point_ == "INTERPOLATE")
        return;

    // Update lat/long values
    // Given the default lat/lon positions (computed by function computeLine)
    // select only the nearest points to the original grid points
    MvLocation loc(lat_[0], lon_[0]);
    MvLocation loc2 = field->nearestGridPointLocation(loc);
    double distmin = loc.distanceInMeters(loc2);
    std::vector<double> lat2;
    std::vector<double> lon2;
    double distc = 0.;  // current distance in meters
    int index = 0;      // index of the closest location
    for (int k = 1; k < nrPoints_; k++) {
        MvLocation loc(lat_[k], lon_[k]);
        MvLocation loc1 = field->nearestGridPointLocation(loc);
        distc = loc.distanceInMeters(loc1);
        if (loc1.longitude() != loc2.longitude() ||
            loc1.latitude() != loc2.latitude()) {
            lat2.push_back(lat_[index]);
            lon2.push_back(lon_[index]);
            loc2 = loc1;
            index = k;
            distmin = distc;
        }
        else {
            // Save the smallest distance
            if (distc < distmin) {
                distmin = distc;
                index = k;
            }
        }
    }

    // Process the last point
    lat2.push_back(lat_[index]);
    lon2.push_back(lon_[index]);

    // Update coordinates and total number of points
    nrPoints_ = npoints = static_cast<int>(lat2.size());
    lon_.clear();
    lat_.clear();
    lon_ = lon2;
    lat_ = lat2;
}

// Generate data values.
// Only ever called if we use pressure levels or UKMO model levels
void ApplicationInfo::InterpolateVerticala(std::vector<double>& cp, ParamInfo* param, std::vector<double>& yvals)
{
    if (!interpolate_) {
        this->getLevelValues(param, cp);
        return;
    }

    int i = 0;
    double lpa = 0., pk = 0.;
    LevelInfo *firstlev = nullptr, *nextlev = nullptr;
    int nrLevels = this->outputLevels();
    cp.clear();
    cp.reserve(static_cast<std::size_t>(nrLevels) * static_cast<std::size_t>(nrPoints_));
    for (int k = 0; k < nrLevels; k++) {
        // Invert Model level data, except for UKMO
        // kmodel = (levType_ == cML_UKMO_ND) ? k : (nrLevels)-k-1;

        // pk = GetInterpolatedYValue(k);
        pk = yvals[k];

        if (findPressure(param->Levels(), pk, firstlev, nextlev)) {
            // TODO: clarify the reason of this check
            if (levType_)
                lpa = (pk - firstlev->YValue()) / (nextlev->YValue() - firstlev->YValue());
            else
                // if ( logax_ )
                //    lpa = ( pk - firstlev->YValue()) / (nextlev->YValue() - firstlev->YValue());
                // else
                lpa = log(pk / firstlev->YValue()) / log(nextlev->YValue() / firstlev->YValue());

            double* xvals = firstlev->XValues();
            double* xnext = nextlev->XValues();

            for (i = 0; i < nrPoints_; i++) {
                if (xvals[i] < XMISSING_VALUE && xnext[i] < XMISSING_VALUE) {
                    cp.push_back(xvals[i] + (xnext[i] - xvals[i]) * lpa);  // replace k with kmodel
                }
                else {
                    cp.push_back(XMISSING_VALUE);  // replace k with kmodel
                }
            }
        }
        else
            for (i = 0; i < nrPoints_; i++)
                cp.push_back(XMISSING_VALUE);
    }
}

// Generate data values.
// Only ever called if levType_ == XS_ML_LNSP ie if we use non-UKMO model levels
void ApplicationInfo::InterpolateVerticalb(MvField& field, std::vector<double>& cp, ParamInfo* param, double* splin, std::vector<double>& yvals)
{
    if (!interpolate_) {
        this->getLevelValues(param, cp);
        return;
    }

    // Convert surface pressure
    std::vector<double> sp;
    sp.reserve(nrPoints_);
    for (int i = 0; i < nrPoints_; i++)
        sp.push_back(exp(splin[i]));

    // Allocate memory for the output data
    int nrLevels = outputLevels();
    cp.clear();
    cp.reserve(static_cast<std::size_t>(nrLevels) * static_cast<std::size_t>(nrPoints_));

    // Main loop
    double lpa = 0.;
    LevelInfo *firstlev = nullptr, *nextlev = nullptr;
    double firstval = 0., nextval = 0.;
    for (int k = 0; k < nrLevels; k++) {
        double pk = yvals[k];

        // Invert Model level data
        for (int i = 0; i < nrPoints_; i++) {
            if (sp[i] >= XMISSING_VALUE) {
                cp.push_back(XMISSING_VALUE);
                continue;
            }

            if (this->findModelPressure(param->Levels(), pk, sp[i], field, firstlev, nextlev, firstval, nextval)) {
                if (firstlev->XValues()[i] < XMISSING_VALUE && nextlev->XValues()[i] < XMISSING_VALUE) {
                    lpa = log(pk / firstval) / log(nextval / firstval);
                    cp.push_back(firstlev->XValues()[i] + (nextlev->XValues()[i] - firstlev->XValues()[i]) * lpa);
                }
                else
                    cp.push_back(XMISSING_VALUE);
            }
            // Create more 'valid' points near the orography to avoid
            // having empty areas in the plot
            else {
                bool useValue = false;
                if (firstval < THRESHOLD_INTERPOLATION)                 // upper atmosphere?
                    useValue = fabs(pk - firstval) < (firstval * 0.5);  // close to 'real' level?
                else
                    useValue = fabs(pk - firstval) < THRESHOLD_INTERPOLATION;

                if (useValue)
                    cp.push_back(firstlev->XValues()[i]);
                else
                    cp.push_back(XMISSING_VALUE);
            }
        }
    }

    return;
}

// Generate data values.
// Only ever called if levType_ == XS_ML_GHBC || XS_GHBC
void ApplicationInfo::InterpolateVerticalGHBC(std::vector<double>& cp, ParamInfo* param, ParamInfo* paramGHBC, std::vector<double>& yvals)
{
    if (!interpolate_) {
        // we completely ignore yvals!
        outputLevels(param->NrLevels());
        getLevelValues(param, cp);
        return;
    }

    double lpa = 0., vcVal1 = 0., vcVal2 = 0.;
    LevelInfo *parLev1 = nullptr, *parLev2 = nullptr;
    int nrLevels = outputLevels();
    cp.clear();
    cp.reserve(static_cast<std::size_t>(nrLevels) * static_cast<std::size_t>(nrPoints_));
    assert(nrLevels >= 2);
#if 0
    bool doLog = false;
#endif

    // loop for the target levels
    for (int k = 0; k < nrLevels; k++) {
        double level = yvals[k];  // target vertical coordinate level
#if 0
        doLog = (level < 600);
        if (doLog) std::cout << level << std::endl;
#endif
        // loop for the xs line points
        for (int i = 0; i < nrPoints_; i++) {
#if 0
            if (doLog) std::cout << " (" << i << ")" << std::endl;
#endif
            bool needExtrapolation = false;
            // find the bounding input vc levels for the target level
            if (findModelPressureGHBC(param->Levels(), paramGHBC->Levels(), level, i,
                                      parLev1, parLev2, vcVal1, vcVal2, needExtrapolation)) {
#if 0
                if (doLog && needExtrapolation) {
                    std::cout << "   " <<  parLev1->XValues()[i] << " " << parLev2->XValues()[i] << " " << vcVal1 << " " << vcVal2 << std::endl;
                }
#endif
                if (parLev1->XValues()[i] < XMISSING_VALUE && parLev2->XValues()[i] < XMISSING_VALUE) {
                    lpa = (level - vcVal1) / (vcVal2 - vcVal1);
                    double rv = parLev1->XValues()[i] + (parLev2->XValues()[i] - parLev1->XValues()[i]) * lpa;
                    if (needExtrapolation && extrapolateGHBCMode_ == LinearExtrapolation && extrapolateGHBCFixedSign_) {
                        assert(extrapolateGHBCMode_ == LinearExtrapolation);
                        if (parLev1->XValues()[i] * rv <= 0) {
                            rv = 0.;
                        }
                    }
                    cp.push_back(rv);
                }
                else {
                    cp.push_back(XMISSING_VALUE);
                }
            }
            else {
#if 0
                if (doLog) {
                    std::cout << "   not found " <<  std::endl;
                }
#endif
                cp.push_back(XMISSING_VALUE);
            }
        }
    }
}

bool ApplicationInfo::findPressure(LevelMap& lmap, double value, LevelInfo*& l1, LevelInfo*& l2)
{
    auto ii = lmap.begin();
    LevelInfo *tmp1 = (*ii).second, *tmp2 = nullptr;
    ii++;

    for (; ii != lmap.end(); ii++) {
        tmp2 = (*ii).second;
        if (isWithinRange(tmp1->YValue(), tmp2->YValue(), value)) {
            l1 = tmp1;
            l2 = tmp2;
            return true;
        }

        tmp1 = tmp2;
    }

    return false;
}

bool ApplicationInfo::findModelPressure(LevelMap& lmap, double value, double splin,
                                        MvField& field, LevelInfo*& l1, LevelInfo*& l2,
                                        double& val1, double& val2)
{
    auto ii = lmap.begin();
    double tmpval1 = 0., tmpval2 = 0.;
    LevelInfo *tmpl1 = nullptr, *tmpl2 = nullptr;

    tmpl1 = (*ii).second;
    ii++;
    tmpval1 = field.meanML_to_Pressure_bySP(splin, (int)(tmpl1->YValue()));

    // desired pressure above (in the atmosphere) the top (first) level in the data?
    if (value < tmpval1) {
        val1 = tmpval1;
        l1 = tmpl1;
        return false;
    }


    for (; ii != lmap.end(); ii++) {
        tmpl2 = (*ii).second;
        tmpval2 = field.meanML_to_Pressure_bySP(splin, (int)(tmpl2->YValue()));

        if (isWithinRange(tmpval1, tmpval2, value)) {
            l1 = tmpl1;
            l2 = tmpl2;
            val1 = tmpval1;
            val2 = tmpval2;
            return true;
        }

        tmpl1 = tmpl2;
        tmpval1 = tmpval2;
    }

    l1 = tmpl1;
    return false;
}

bool ApplicationInfo::findModelPressureGHBC(LevelMap& parLevels, LevelMap& vcLevels,
                                            double level, int xsPointIdx,
                                            LevelInfo*& parLev1, LevelInfo*& parLev2,
                                            double& vcVal1, double& vcVal2, bool& needExtrapolation)
{
    auto itParLev = parLevels.begin();
    auto itVcLev = vcLevels.begin();

    auto actParLev1 = (*itParLev).second;
    auto actVcLev1 = (*itVcLev).second;
    vcVal1 = actVcLev1->XValues()[xsPointIdx];
    itParLev++;
    itVcLev++;
    auto actParLev2 = (*itParLev).second;
    auto actVcLev2 = (*itVcLev).second;
    vcVal2 = actVcLev2->XValues()[xsPointIdx];
    bool vcAscending = vcVal1 < vcVal2;

    needExtrapolation = false;

    // this will allow extrapolation at top/bottom
    if (extrapolateGHBC_) {
        if (!vcAscending) {
            assert(vcVal2 < vcVal1);
            if (level > vcVal1) {
                needExtrapolation = true;
                if (extrapolateGHBCMode_ == ConstantExtrapolation) {
                    parLev1 = actParLev1;
                    parLev2 = parLev1;
                }
                else {
                    parLev1 = actParLev1;
                    parLev2 = actParLev2;
                }
                return true;
            }
        }
        else {
            assert(vcVal1 < vcVal2);
            if (level < vcVal1) {
                needExtrapolation = true;
                if (extrapolateGHBCMode_ == ConstantExtrapolation) {
                    parLev1 = actParLev1;
                    parLev2 = parLev1;
                }
                else {
                    parLev1 = actParLev1;
                    parLev2 = actParLev2;
                }
                return true;
            }
        }
    }

    while (itVcLev != vcLevels.end()) {
        if (isWithinRange(vcVal1, vcVal2, level)) {
            parLev1 = actParLev1;
            parLev2 = actParLev2;
            return true;
        }

        itParLev++;
        itVcLev++;

        if (itVcLev != vcLevels.end()) {
            actParLev1 = actParLev2;
            vcVal1 = vcVal2;
            actParLev2 = (*itParLev).second;
            actVcLev2 = (*itVcLev).second;
            vcVal2 = actVcLev2->XValues()[xsPointIdx];
        }
    }

    // this will allow extrapolation at top/bottom
    if (extrapolateGHBC_) {
        if (!vcAscending) {
            assert(vcVal2 < vcVal1);
            if (level < vcVal2) {
                needExtrapolation = true;
                if (extrapolateGHBCMode_ == ConstantExtrapolation) {
                    parLev2 = actParLev2;
                    parLev1 = parLev2;
                }
                else {
                    parLev1 = actParLev1;
                    parLev2 = actParLev2;
                }
                return true;
            }
        }
        else {
            assert(vcVal1 < vcVal2);
            if (level > vcVal2) {
                needExtrapolation = true;
                if (extrapolateGHBCMode_ == ConstantExtrapolation) {
                    parLev2 = actParLev2;
                    parLev1 = parLev2;
                }
                else {
                    parLev1 = actParLev1;
                    parLev2 = actParLev2;
                }
                return true;
            }
        }
    }

    return false;
}

// the method from MV3 to scale vertical velocity
void ApplicationInfo::scaleVerticalVelocity_mv3(ParamInfo* par)
{
    LevelMap lmap = par->Levels();
    auto ii = lmap.begin();

    // Compute distance
    double dellat = ABS(y2_ - y1_), dellon = ABS(x2_ - x1_);
    double latm = (y1_ + y2_) / 2.;
    double dellona = dellon * cos(cCDR * latm);
    double dist = 110442.3 * sqrt(dellat * dellat + dellona * dellona);

    double hscale = 21. / dist;
    double vscale = 12. / ((bottomLevel_ - topLevel_));

    if (levType_ == XS_ML)
        vscale = 12. / (100 * 1000);

    double factor = -vscale / hscale;
    double* values = nullptr;
    for (; ii != lmap.end(); ii++) {
        values = (*ii).second->XValues();

        for (int i = 0; i < nrPoints_; i++) {
            if (values[i] < XMISSING_VALUE)
                values[i] *= factor;
            else
                values[i] = XMISSING_VALUE;
        }
    }
}

// compute vertical velocity from omega using the hydrostatic equation
void ApplicationInfo::computeVerticalVelocity(ParamInfo* parW, ParamInfo* parT, ParamInfo* parLnsp, MvField& field, double factor)
{
    LevelMap lmap_w = parW->Levels();
    auto ww = lmap_w.begin();
    double* wValues = nullptr;
    double* splin = nullptr;

    const double Rd = 287.058;  // Gas constant
    const double g = 9.81;      // gravitational acceleration
    const double t0 = 273.16;   // reference temperature

    // we have the values on the cross section line but have not
    // performed the vertical interpolation yet

    assert(levType_ == XS_ML_LNSP || levType_ == XS_PL || levType_ == XS_ML_GHBC);
    if (levType_ != XS_ML_LNSP && levType_ != XS_PL && levType_ != XS_ML_GHBC) {
        throw MvException("XSection-> Cannot compute vertical velocity! Level type is not supported!");
    }

    // for model levels with lnsp we need to compute the pressure on the current level along the
    // cross section line
    if (levType_ == XS_ML_LNSP || levType_ == XS_ML_GHBC) {
        // Get LNSP values
        if (!parLnsp)
            throw MvException("XSection-> Cannot compute vertical velocity! LNSP field is not found!");

        splin = parLnsp->getOneLevelValues("1");
        if (!splin)
            throw MvException("XSection-> Cannot compute vertical velocity! LNSP field is empty!");
    }

    if (parT) {
        LevelMap lmap_t = parT->Levels();
        auto tt = lmap_t.begin();
        double* tValues = nullptr;

        for (; ww != lmap_w.end() && tt != lmap_t.end(); ww++, tt++) {
            wValues = (*ww).second->XValues();
            tValues = (*tt).second->XValues();

            // the pressure values along the line
            std::vector<double> p(nrPoints_, XMISSING_VALUE);
            double level = (*ww).second->YValue();
            getPressureOnLevel(splin, field, p, level);

            for (int i = 0; i < nrPoints_; i++) {
                if (wValues[i] < XMISSING_VALUE && tValues[i] < XMISSING_VALUE)
                    wValues[i] = -factor * wValues[i] / (g * p[i] / (Rd * tValues[i]));
                else
                    wValues[i] = XMISSING_VALUE;
            }
        }
    }
    else {
        for (; ww != lmap_w.end(); ww++) {
            wValues = (*ww).second->XValues();

            // the pressure values along the line
            std::vector<double> p(nrPoints_, XMISSING_VALUE);
            double level = (*ww).second->YValue();
            getPressureOnLevel(splin, field, p, level);

            for (int i = 0; i < nrPoints_; i++) {
                if (wValues[i] < XMISSING_VALUE)
                    wValues[i] = -factor * wValues[i] / (g * p[i] / (Rd * t0));
                else
                    wValues[i] = XMISSING_VALUE;
            }
        }
    }
}

// scale vertical velocity with the given factor
void ApplicationInfo::scaleVerticalVelocity(ParamInfo* par, double factor)
{
    LevelMap lmap = par->Levels();
    auto ii = lmap.begin();

    double* values = nullptr;
    for (; ii != lmap.end(); ii++) {
        values = (*ii).second->XValues();
        for (int i = 0; i < nrPoints_; i++) {
            if (values[i] < XMISSING_VALUE)
                values[i] *= factor;
            else
                values[i] = XMISSING_VALUE;
        }
    }
}

void ApplicationInfo::getPressureOnLevel(double* splin, MvField& field, std::vector<double>& p, double level)
{
    if (levType_ == XS_ML_LNSP || levType_ == XS_ML_GHBC) {
        assert(splin);
        for (int i = 0; i < nrPoints_; i++) {
            if (splin[i] < XMISSING_VALUE) {
                p[i] = field.meanML_to_Pressure_byLNSP(splin[i], static_cast<int>(level));
            }
            else {
                p[i] = XMISSING_VALUE;
            }
        }
    }
    else if (levType_ == XS_PL) {
        // we need Pa units
        double p_val = level;
        for (int i = 0; i < nrPoints_; i++) {
            p[i] = p_val;
        }
    }
    else {
        for (int i = 0; i < nrPoints_; i++) {
            p[i] = XMISSING_VALUE;
        }
    }
}

void ApplicationInfo::setMinMaxLevels(double P1, double P2, int nlevel)
{
    bottomLevel_ = P1;
    topLevel_ = P2;

    nInputLevels_ = nlevel;
}

void ApplicationInfo::getMinMaxLevels(double& P1, double& P2, int& nlevel)
{
    P1 = bottomLevel_;
    P2 = topLevel_;
    nlevel = nInputLevels_;
}

#if 0
void ApplicationInfo::setYValues(MvNcVar *yvar, ParamInfo *par)
{
   int nrY = NrYValues(par);
   double *yvalues = new double[nrY];

   // Retrieve y values
   if ( interpolate_ )
   {
      if ( levType_ == XS_ML )
      {
         yvalues[0] = PresTop_;   //ncx2->setCurrent(ntime_);
   //ncx2->put(x_values,1,nrPoints);

         yvalues[1] = PresBot_;
      }
      else if ( levType_ == cML_UKMO_ND )
      {
         yvalues[1] = PresTop_;
         yvalues[0] = PresBot_;
      }
      else
      {
         yvalues[0] = PresTop_/100;
         yvalues[1] = PresBot_/100;
      }
   }
   else
   {
      LevelMap lmap = par->Levels();
      int i = 0;   // the index of the first axis value to set
      int inc = 1; // the amount to increment the axis value index by

      if ( levType_ == cML_UKMO_ND )  // UK Met Office model levels are 'upside-down'
      {
         i = nrY - 1;
         inc = -1;
      }   //ncx2->setCurrent(ntime_);
   //ncx2->put(x_values,1,nrPoints);


      for ( LevelIterator ii = lmap.begin(); ii != lmap.end(); ii++, i+=inc )
      {
         yvalues[i] = (*ii).second->YValue();
         if ( levType_ == XS_PL )
            yvalues[i] /= 100;
      }
   }

   // Insert Y values as an attribute of the input variable
   yvar->addAttribute("_Y_VALUES",nrY,yvalues);
   //ncx2->setCurrent(ntime_);
   //ncx2->put(x_values,1,nrPoints);

   delete [] yvalues;
}
#endif

bool ApplicationInfo::computeLevelMatrixValues(ParamMap& params, const std::string& key,
                                               ParamInfo* resPar,
                                               std::vector<double>& y_values,
                                               std::vector<double>& cp, MvField& field)
{
    // Get parameter info
    auto ii = params.find(key);
    ParamInfo* par = (*ii).second;

    // Compute values
    double* splin = nullptr;
    if (levType_ == XS_ML_LNSP) {
        // Get LNSP values
        ParamInfo* parLnsp = this->getParamInfo(params, LNSP_FIELD, key);
        if (!parLnsp)
            return false;

        splin = parLnsp->getOneLevelValues("1");
        if (!splin)  // LNSP not found
            throw MvException("XSection-> LNSP field not found");

        // Compute level values
        this->computeLevelInfoUI(par, y_values, splin, field);

        // Interpolate matrix
        this->InterpolateVerticalb(field, cp, resPar, splin, y_values);
    }
    else if (levType_ == XS_ML_GHBC || levType_ == XS_GHBC)  // handle height-based coordinates
    {
        // Get extra values
        ParamInfo* parGHBC = getParamInfo(params, paramGHBC_, key);
        if (!parGHBC)
            return false;

        // Compute level values
        computeLevelInfoGHBC(parGHBC, y_values);

        // Interpolate matrix
        InterpolateVerticalGHBC(cp, resPar, parGHBC, y_values);
    }
    else {
        // Compute level values
        this->computeLevelInfoUI(par, y_values, splin, field);

        // Interpolate matrix
        this->InterpolateVerticala(cp, resPar, y_values);
    }

    return true;
}

bool ApplicationInfo::computeLevelMatrixValues(ParamMap& params, const std::string& key,
                                               std::vector<double>& y_values,
                                               std::vector<double>& cp,
                                               MvField& field)
{
    // Get parameter info
    auto ii = params.find(key);
    ParamInfo* par = (*ii).second;

    return computeLevelMatrixValues(params, key, par, y_values, cp, field);

#if 0
    // Compute values
    double* splin = 0;
    if (levType_ == XS_ML_LNSP) {
        // Get LNSP values
        ParamInfo* parLnsp = this->getParamInfo(params, LNSP_FIELD, key);
        if (!parLnsp)
            return false;

        splin = parLnsp->getOneLevelValues("1");
        if (!splin)  // LNSP not found
            throw MvException("XSection-> LNSP field not found");

        // Compute level values
        this->computeLevelInfoUI(par, y_values, splin, field);

        // Interpolate matrix
        this->InterpolateVerticalb(field, cp, par, splin, y_values);
    }
    else if (levType_ == XS_ML_GHBC)  // handle height-based coordinates
    {
        // Get extra values
        ParamInfo* parGHBC = this->getParamInfo(params, paramGHBC_, key);
        if (!parGHBC)
            return false;

        // Compute level values
        this->computeLevelInfoGHBC(parGHBC, y_values);

        // Interpolate matrix
        this->InterpolateVerticalGHBC(cp, par, parGHBC, y_values);
    }
    else {
        // Compute level values
        this->computeLevelInfoUI(par, y_values, splin, field);

        // Interpolate matrix
        this->InterpolateVerticala(cp, par, y_values);
    }

    return true;
#endif
}

void ApplicationInfo::computeLevelInfoUI(ParamInfo* par, std::vector<double>& vals, double* splin, MvField& field)
{
    // Compute the levels according to the parameters given in the user interface
    if (uiLSType_ == "FROM_DATA") {
        if (splin == nullptr)
            this->computeLevelInfo(par, vals);
        else
            this->computeLevelInfoFromData(par, vals, splin, field, false);
    }
    else if (uiLSType_ == "ADAPTIVE") {
        if (splin == nullptr)
            this->computeLevelInfo(par, vals);
        else
            this->computeLevelInfoFromData(par, vals, splin, field, true);
    }
    else if (uiLSType_ == "COUNT") {
        LevelMap lmap = par->Levels();
        nInputLevels_ = lmap.size();
        this->computeLevelInfoWithCount(vals);
    }
    else {
        assert(uiLSType_ == "LEVEL_LIST");
        vals = this->levelList();
        nOutputLevels_ = vals.size();
        nInputLevels_ = vals.size();
    }
}

int ApplicationInfo::computeLevelInfo(ParamInfo* par, std::vector<double>& vals)
{
    // Get levels
    LevelMap lmap = par->Levels();
    int nrLevels = lmap.size();
    vals.clear();
    vals.reserve(nrLevels);
    for (auto& jj : lmap)
        vals.push_back(jj.second->YValue());

    nInputLevels_ = nOutputLevels_ = nrLevels;

    return nrLevels;
}

int ApplicationInfo::computeLevelInfoFromData(ParamInfo* par, std::vector<double>& vals, double* splin, MvField& field, bool useAdaptive)
{
    // Convert ML&LNSP to PL using the following algorithm:
    // a) for each ML value (1,2,...N) get all the correspondent values
    //    and compute the average
    // b) compute extra levels by adding an offset (10 hPa) until reaches the highest
    //    pressure value (bottomLevel_), which was computed previously.
    if (levType_ != XS_ML_LNSP) {
        return this->computeLevelInfo(par, vals);
    }

    // Part A: compute "average" levels
    LevelMap lmap = par->Levels();
    int nrLevels = lmap.size();
    vals.clear();

    TargetLevelMaker target(nrLevels);
    int levelIdx = 0;

    for (auto& jj : lmap) {
        LevelInfo* lInfo = jj.second;
        for (int i = 0; i < nrPoints_; i++) {
            if (splin[i] < XMISSING_VALUE) {
                target.add(field.meanML_to_Pressure_byLNSP(splin[i], (int)(lInfo->YValue())), levelIdx);
            }
        }
        levelIdx++;
    }

    if (!useAdaptive) {
        target.getMeans(vals);
    }
    else {
        target.getAdaptive(vals);
    }

    // Part B: add extra levels, bottomLevel is in hPa
    double level1 = vals[nrLevels - 1] + OFFSET_LEVELINFO;
    while (level1 <= bottomLevel_) {
        vals.push_back(level1);
        level1 += OFFSET_LEVELINFO;
    }

    // Assume that the visualisation will be in the Log space too
    uiVLinearScaling_ = false;

    nInputLevels_ = nrLevels;
    nOutputLevels_ = vals.size();

    return nOutputLevels_;
}

int ApplicationInfo::computeLevelInfoWithCount(std::vector<double>& vals)
{
    // Initialize array
    vals.clear();
    vals.reserve(uiLSCount_);

    // Compute equally separated levels
    if (uiVLinearScaling_) {
        double zdp = (bottomLevel_ - topLevel_) / double(uiLSCount_ - 1);
        for (int i = 0; i < uiLSCount_ - 1; i++) {
            vals.push_back(topLevel_ + i * zdp);
        }
        vals.push_back(bottomLevel_);
    }
    // Compute levels using a log scaling
    else {
        double logBottomLevel = log(bottomLevel_);
        double logTopLevel = log(topLevel_);
        double zdp = (logBottomLevel - logTopLevel) / double(uiLSCount_ - 1);
        for (int i = 0; i < uiLSCount_ - 1; i++) {
            vals.push_back(exp(logTopLevel + i * zdp));
        }
        vals.push_back(bottomLevel_);
    }

    nOutputLevels_ = uiLSCount_;

    return nOutputLevels_;
}


void ApplicationInfo::computeLevelInfoGHBC(ParamInfo* par, std::vector<double>& vals)
{
    // Compute the levels according to the parameters given in the user interface
    if (uiLSType_ == "FROM_DATA") {
        this->computeLevelInfoGHBCFromData(par, vals, false);
    }
    else if (uiLSType_ == "ADAPTIVE") {
        this->computeLevelInfoGHBCFromData(par, vals, true);
    }
    else if (uiLSType_ == "COUNT") {
        LevelMap lmap = par->Levels();
        nInputLevels_ = lmap.size();
        this->computeLevelInfoWithCount(vals);
    }
    else {
        assert(uiLSType_ == "LEVEL_LIST");
        vals = this->levelList();
        nOutputLevels_ = vals.size();
        nInputLevels_ = vals.size();
    }
}

void ApplicationInfo::computeLevelInfoGHBCFromData(ParamInfo* par, std::vector<double>& vals, bool useAdaptive)
{
    LevelMap lmap = par->Levels();
    int nrLevels = lmap.size();
    vals.clear();
    TargetLevelMaker target(nrLevels);

    int levelIdx = 0;
    for (auto& jj : lmap) {
        LevelInfo* lInfo = jj.second;
        for (int i = 0; i < nrPoints_; i++) {
            double xval = lInfo->XValues()[i];
            if (xval < XMISSING_VALUE) {
                target.add(xval, levelIdx);
            }
        }
        levelIdx++;
    }

    // Compute the average vertical coordinate value for each input GHBC level
    // along the cross section line. The result is one coordinate value per level.
    if (!useAdaptive) {
        target.getMeans(vals);
        // The output levels are determined with an adaptive technique
    }
    else {
        target.getAdaptive(vals);
    }

    nrLevels = vals.size();
    nInputLevels_ = nOutputLevels_ = nrLevels;

    // for extrapolation add min and max to levels
    // NOTE: for bounded vertical coordinates  (eg. pressure > 0)
    // we have to be sure the added levels are valid! The problem is
    // that we do not know anything about the nature of the vertical
    // coordinate (since it is "generic").
    if (extrapolateGHBC_ && nrLevels > 1) {
        target.addDeltaFromMinMax(vals);
        nOutputLevels_ = vals.size();
    }
}

void ApplicationInfo::getLevelValues(ParamInfo* par, std::vector<double>& cp)
{
    LevelMap lmap = par->Levels();
    LevelIterator ii;

#if 1
    cp.clear();
    cp.reserve(lmap.size() * nrPoints_);
    for (ii = lmap.begin(); ii != lmap.end(); ii++)
#else
    int nrY = NrYValues(par);
    int k = nrY - 1;  // the index of the first level to set
    int kinc = -1;    // the amount to increment the level index by

    if (levType_ == cML_UKMO_ND)  // UK Met Office model levels are 'upside-down'
    {
        k = 0;
        kinc = 1;
    }

    for (ii = lmap.begin(); ii != lmap.end(); ii++, k += kinc)
#endif

    {
        LevelInfo* lInfo = (*ii).second;
        for (int i = 0; i < nrPoints_; i++)
            cp.push_back(lInfo->XValues()[i]);
    }
}

#if 0
double ApplicationInfo::GetInterpolatedYValue(int index)
{
   double pk;

   // Interpolation
   if ( logax_ )
   {
      double deltalog = (log10(PresBot_) - log10(PresTop_))/(nrLevels_ - 1);
      pk = exp((log10(PresTop_) + (double)index*deltalog)*log(10.));
   }
   else
   {
      double zdp = ( PresBot_ - PresTop_)/ ( nrLevels_ - 1 );
      pk = (levType_ == cML_UKMO_ND) ? PresTop_ + index : PresTop_+ index * zdp;
   }

   return pk;
}
#endif

void ApplicationInfo::setAreaLine(double L1, double L2, double R1, double R2)
{
    // Initialize area or line coordinates
    x1_ = L1;
    x2_ = L2;
    y1_ = R1;
    y2_ = R2;

    // Check values
    if (y1_ > 90. || y1_ < -90. || y2_ > 90. || y2_ < -90.)
        viaPole_ = true;

    return;
}

void ApplicationInfo::getAreaLine(double& L1, double& L2, double& R1, double& R2)
{
    L1 = x1_;
    L2 = x2_;
    R1 = y1_;
    R2 = y2_;
}

void ApplicationInfo::getAreaLine(double* area)
{
    area[0] = y1_;
    area[1] = x1_;
    area[2] = y2_;
    area[3] = x2_;
}

double ApplicationInfo::Ancos()
{
    double angle = atan2(y2_ - y1_, x2_ - x1_);
    return cos(angle);
}

double ApplicationInfo::Ansin()
{
    double angle = atan2(y2_ - y1_, x2_ - x1_);
    return sin(angle);
}

void ApplicationInfo::setVerticalInterpolationFlag()
{
    if (levType_ == XS_ML_LNSP || levType_ == XS_ML_GHBC || levType_ == XS_GHBC ||
        uiLSType_ == "COUNT" || uiLSType_ == "LEVEL_LIST") {
        interpolate_ = true;
    }
    else {
        interpolate_ = false;
    }
}

void ApplicationInfo::setVerticalInterpolationFlag(bool flag)
{
    interpolate_ = flag;
}

ParamInfo* ApplicationInfo::getParamInfo(ParamMap& params, const std::string& key)
{
    // Check if field is valid
    auto ii = params.find(key);
    if (ii == params.end()) {
        std::ostringstream error;
        error << "XSection getParamInfo-> Could not find the requested field: " << key;
        throw MvException(error.str());
    }

    // Get parameter info
    return (*ii).second;
}

ParamInfo* ApplicationInfo::getParamInfo(ParamMap& params, int key, const std::string& keyBase)
{
    // Build the new key based on keyBase
    std::ostringstream os;
    os << std::setfill('0')
       << "p"
       << std::setw(6) << key
       << keyBase.substr(7);

    std::string newKey = os.str();

    // Get paraminfo
    ParamInfo* par = nullptr;
    try {
        par = this->getParamInfo(params, newKey);
        if (par)
            return par;
    }
    catch (MvException& e) {
        // Special case: if paramInfo not found and key is "extra" (LNSP or GHBC)
        // then try to find any other "extra" paramInfo (do not use the keyBase info)
        if (this->isLNSP(key) || this->isGHBC(key)) {
            for (auto& param : params) {
                par = param.second;
                if (par->Parameter() == key)
                    return par;
            }
        }

        std::ostringstream error;
        error << "XSection getParamInfo-> Could not find the requested field: " << key;
        throw MvException(error.str());
    }

    return par;
}

void ApplicationInfo::levelList(std::vector<double>& list)
{
    uiLSList_ = list;
    topLevel_ = list[0];
    bottomLevel_ = list[list.size() - 1];
}
