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

#include <assert.h>

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

string ParamInfo::ExpVerTitle()
{
    if (expver_ == "_")  //-- missing ExpVer is stored as "_"
        return string("");
    else
        return 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, string lev, int n, bool modlev)
{
    double flev = atof(lev.c_str());

    // Save data for one level
    LevelIterator ii = levels_.find(lev);
    if (ii == levels_.end()) {
        levels_[lev] = new LevelInfo(flev, modlev);
        ii           = levels_.find(lev);
    }
    else {
        if ((*ii).second->xvalues_) {
            delete[](*ii).second->xvalues_;
            (*ii).second->xvalues_ = 0;
        }
        if ((*ii).second->yvalue_ >= XMISSING_VALUE)
            (*ii).second->yvalue_ = modlev ? flev : flev * 100;
    }

    if ((*ii).second->xvalues_ == 0)
        (*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 modlev)
{
    LevelIterator ii;
    for (ii = levels_.begin(); ii != levels_.end(); ii++) {
        double flev = atof((*ii).first.c_str());
        (*ii).second->UpdateLevel(flev, modlev);
    }
}

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

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

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

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

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

/////////////////// ApplicationInfo /////////////////////

// Constructor
ApplicationInfo::ApplicationInfo() :
    x1_(0),
    x2_(0),
    y1_(0),
    y2_(0),
    gridNS_(0),
    gridEW_(0),
    topLevel_(0),
    bottomLevel_(0),
    nrPoints_(0),
    nInputLevels_(0),
    nOutputLevels_(0),
    levType_(XS_PL),
    viaPole_(false),
    haveLatsForPlotting_(false),
    haveLonsForPlotting_(false),
    haveLNSP_(false),
    haveGHBC_(false),
    paramGHBC_(-991),
    hor_point_("INTERPOLATE"),
    interpolate_(false),
    uiLSType_("FROM_DATA"),
    uiLSCount_(100),
    uiVLinearScaling_(true)
{
}

int ApplicationInfo::levelType(bool isML, bool isPL)
{
    // 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  // it must be PL
            levType_ = XS_PL;
    }

    return levType_;
}

// 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 modlev = this->levelTypeFlag();

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

    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);
    vector<double> lat2;
    vector<double> lon2;
    double distc;   // 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 = 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(vector<double>& cp, ParamInfo* param, vector<double>& yvals)
{
    if (!interpolate_) {
        this->getLevelValues(param, cp);
        return;
    }

    int i;
    double lpa, pk;
    LevelInfo *firstlev, *nextlev;
    int nrLevels = this->outputLevels();
    cp.clear();
    cp.reserve(nrLevels * 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)) {
            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, vector<double>& cp, ParamInfo* param, double* splin, vector<double>& yvals)
{
    if (!interpolate_) {
        this->getLevelValues(param, cp);
        return;
    }

    // Convert surface pressure
    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(nrLevels * nrPoints_);

    // Main loop
    double lpa;
    LevelInfo *firstlev, *nextlev;
    double firstval, nextval;
    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
void ApplicationInfo::InterpolateVerticalGHBC(vector<double>& cp, ParamInfo* param, ParamInfo* paramGHBC, vector<double>& yvals)
{
    if (!interpolate_) {
        // we completely ignore yvals!
        outputLevels(param->NrLevels());
        getLevelValues(param, cp);
        return;
    }

    double lpa, vcVal1, vcVal2;
    LevelInfo *parLev1, *parLev2;
    int nrLevels     = outputLevels();
    cp.clear();
    cp.reserve(nrLevels * nrPoints_);
    assert(nrLevels >= 2);

    //loop for the target levels
    for (int k = 0; k < nrLevels; k++) {
        double level = yvals[k];  // target vertical coordinate level

        //loop for the xs line points
        for (int i = 0; i < nrPoints_; i++) {
            //find the bounding input vc levels for the target level
            if (findModelPressureGHBC(param->Levels(), paramGHBC->Levels(), level, i,
                                      parLev1, parLev2, vcVal1, vcVal2)) {
                if (parLev1->XValues()[i] < XMISSING_VALUE &&
                    parLev2->XValues()[i] < XMISSING_VALUE) {
                        lpa = (level - vcVal1) / (vcVal2 - vcVal1);
                        cp.push_back(parLev1->XValues()[i] + (parLev2->XValues()[i] - parLev1->XValues()[i]) * lpa);
                } else {
                    cp.push_back(XMISSING_VALUE);
                }
            } else {
                cp.push_back(XMISSING_VALUE);
            }
       }
    }

    return;
}

bool ApplicationInfo::findPressure(LevelMap& lmap, double value, LevelInfo*& l1, LevelInfo*& l2)
{
    LevelIterator ii = lmap.begin();
    LevelInfo *tmp1  = (*ii).second, *tmp2;
    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)
{
    LevelIterator ii = lmap.begin();
    double tmpval1, tmpval2;
    LevelInfo *tmpl1, *tmpl2;

    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)
{
    LevelIterator itParLev = parLevels.begin();
    LevelIterator 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;

    //this will allow extrapolation at top/bottom
    if (extrapolateGHBC_) {
        if (!vcAscending) {
            assert(vcVal2 < vcVal1);
            if (level > vcVal1) {
                parLev1   = actParLev1;
                parLev2   = actParLev2;
                return true;
            }
        } else {
            assert(vcVal1 < vcVal2);
            if (level < vcVal1) {
                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;
            actVcLev1 = actVcLev2;
            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) {
                parLev1   = actParLev1;
                parLev2   = actParLev2;
                return true;
            }
        } else {
            assert(vcVal1 < vcVal2);
            if (level > vcVal2) {
                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();
    LevelIterator 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;
    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();
    LevelIterator 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();
        LevelIterator tt = lmap_t.begin();
        double *tValues;

        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();
    LevelIterator ii = lmap.begin();

    double* values;
    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) {
        assert(splin);
        for (int i = 0; i < nrPoints_; i++) {
            if (splin[i] < XMISSING_VALUE) {
                p[i] = field.meanML_to_Pressure_byLNSP(splin[i], 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 string& key,
                                               ParamInfo* resPar,
                                               vector<double>& y_values,
                                               vector<double>& cp, MvField& field)
{
    // Get parameter info
    ParamIterator ii = params.find(key);
    ParamInfo* par   = (*ii).second;

    // 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, resPar, splin, y_values);
    }
    else if (levType_ == XS_ML_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 string& key,
                                               vector<double>& y_values,
                                               vector<double>& cp,
                                               MvField& field)
{  
    // Get parameter info
    ParamIterator 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
}

int ApplicationInfo::computeLevelInfoUI(ParamInfo* par, 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)
            return this->computeLevelInfo(par, vals);
        else
            return this->computeLevelInfo(par, vals, splin, field);
    }
    else if (uiLSType_ == "COUNT") {
        LevelMap lmap = par->Levels();
        nInputLevels_ = lmap.size();
        return this->computeLevelInfo(vals);
    }
    else {
        assert(uiLSType_ == "LEVEL_LIST");
        vals           = this->levelList();
        nOutputLevels_ = vals.size();
        nInputLevels_  = vals.size();
        return nOutputLevels_;
    }
}

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

    nInputLevels_ = nOutputLevels_ = nrLevels;

    return nrLevels;
}

int ApplicationInfo::computeLevelInfo(ParamInfo* par, vector<double>& vals, double* splin, MvField& field)
{
    // 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();
    vals.reserve(nrLevels);  // initial allocation, it will increase in part B
    for (LevelIterator jj = lmap.begin(); jj != lmap.end(); jj++) {
        LevelInfo* lInfo = (*jj).second;
        double dlev      = 0.;
        int count        = 0;
        for (int i = 0; i < nrPoints_; i++) {
            if (splin[i] < XMISSING_VALUE) {
                dlev += field.meanML_to_Pressure_byLNSP(splin[i], (int)(lInfo->YValue()));
                count++;
            }
        }

        double mean = count ? dlev / count : 0.;
        vals.push_back(mean);
    }

    // Part B: add extra levels
    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::computeLevelInfo(vector<double>& vals)
{
    // Initialize array
    vals.clear();
    vals.reserve(uiLSCount_);

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

    nOutputLevels_ = uiLSCount_;

    return nOutputLevels_;
}

void ApplicationInfo::computeLevelInfoGHBC(ParamInfo* par, vector<double>& vals)
{
    //Compute the average vertical coordinate value for each input GHBC level
    //along the cross section line. The result is one coordinate value per level.
    LevelMap lmap = par->Levels();
    int nrLevels  = lmap.size();
    vals.clear();
    double minVal = 1E20;
    double maxVal = -1E20;
    double xval;
    for (LevelIterator jj = lmap.begin(); jj != lmap.end(); jj++) {
        LevelInfo* lInfo = (*jj).second;
        double dlev      = 0.;
        int count        = 0;
        for (int i = 0; i < nrPoints_; i++) {
            xval = lInfo->XValues()[i];
            if (xval < XMISSING_VALUE) {
                dlev += xval;
                count++;
                if (xval < minVal)
                    minVal = xval;
                if (xval > maxVal)
                    maxVal = xval;
            }
        }

        double mean = (count)?(dlev/count):0.;
        vals.push_back(mean);
    }

    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) {
        double deltaFirst = fabs(vals[0]-vals[1]);
        double deltaLast = fabs(vals[nrLevels-1]-vals[nrLevels-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);
        }
        nOutputLevels_+=2;
    }
}

void ApplicationInfo::getLevelValues(ParamInfo* par, 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);
}

bool ApplicationInfo::levelTypeFlag()
{
    if (levType_ == XS_PL)
        return false;
    else
        return true;
}

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

    return;
}

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

ParamInfo* ApplicationInfo::getParamInfo(ParamMap& params, const string& key)
{
    // Check if field is valid
    ParamIterator 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 string& keyBase)
{
    // Build the new key based on keyBase
    ostringstream os;
    os << setfill('0')
       << "p"
       << setw(6) << key
       << keyBase.substr(7);

    string newKey = os.str();

    // Get paraminfo
    ParamInfo* par;
    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 (ParamIterator ii = params.begin(); ii != params.end(); ii++) {
                par = (*ii).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(vector<double>& list)
{
    uiLSList_    = list;
    topLevel_    = list[0];
    bottomLevel_ = list[list.size() - 1];
}
