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

 Copyright 2022 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 "GribVertical.h"

#include <cmath>
#include <cassert>
#include <algorithm>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <memory>
#include <numeric>
#include <tuple>

#include "MvException.h"
#include "MvLog.h"
#include "MvSci.h"

namespace metview
{

class SimpleFieldMetaData
{
    friend class SimpleField;

public:
    SimpleFieldMetaData() = default;
    int level() const { return level_; }
    int paramId() const { return paramId_; }
    int levelInPa() const;
    bool isSpectral() const { return spectral_; }
    bool isPressureLevel() const { return levelType_ == IsobaricInHPaLevel || levelType_ == IsobaricInPaLevel; }
    bool isModelLevel() const { return levelType_ == ModelLevel; }

protected:
    void setSpectral(const std::string& gridType);
    void setLevelType(const std::string& typeOfLevel);

    enum LevelType
    {
        NoLevelType,
        IsobaricInPaLevel,
        IsobaricInHPaLevel,
        ModelLevel,
        OtherLevelType
    };
    int level_{-1};
    int paramId_{-1};
    bool spectral_{false};
    LevelType levelType_{NoLevelType};
};

using SimpleFieldMetaDataPtr = std::shared_ptr<SimpleFieldMetaData>;

// Lightweight wrapper around the libMars field structure. Readonly. Should only
// be used locally inside computation functions!
class SimpleField : public std::enable_shared_from_this<SimpleField>
{
    friend class SimpleFieldset;

public:
    static SimpleFieldPtr make(field* f = nullptr);
    ~SimpleField();
    SimpleField(const SimpleField&) = delete;
    SimpleField& operator=(const SimpleField&) = delete;

    field* rawField() const { return field_; }
    double* values() const { return field_->values; }
    std::size_t valueNum() const { return field_ ? (field_->value_count) : 0; }
    bool hasValidValues() const;
    bool isExpanded() const { return field_ ? (field_->shape == expand_mem) : false; }
    void expandMemory();
    void releaseMemory();
    bool isEmpty() const { return field_ == nullptr; }

    field* cloneRaw(bool copyValues);

    using DoubleFuncPtr1 = double (*)(double);
    using DoubleFuncPtr2 = std::function<double(double)>;

    // chaning values
    void transformValues(DoubleFuncPtr1);
    void transformValues(DoubleFuncPtr2);
    void subtractValues(SimpleFieldPtr other);
    void setValues(double val);
    static void setAllValuesToMissing(field* f);
    static void setValues(field* f, double val);
    static void scaleValues(field* f, double val);
    void copyValuesTo(field* f);

    // min + max
    void computeMinMax(double& spMin, double& spMax);
    void computeDiffMinMax(SimpleFieldPtr other, double& spMin, double& spMax);

    // pressure + ml
    void halfPres(double* sp, double* mlPres, std::size_t num, int ml);
    void meanMlPressureFromSp(double* sp, double* mlPres, std::size_t num, int ml);
    double meanMlPressureFromSp(double sp, int ml);
    void mlPressureLayerFromSp(double* sp, double* mlPres, std::size_t num, int ml);
    void addMlPressureIntegralDeltaFromSp(SimpleFieldPtr sp, double* res);
    int totalMlLevelNum() { return pvPairCount() - 1; }

    // metadat access
    int level()
    {
        loadMetaData();
        return md_->level();
    }
    int levelInPa()
    {
        loadMetaData();
        return md_->levelInPa();
    }
    int paramId()
    {
        loadMetaData();
        return md_->paramId();
    }
    bool isPressureLevel()
    {
        loadMetaData();
        return md_->isPressureLevel();
    }
    bool isModelLevel()
    {
        loadMetaData();
        return md_->isModelLevel();
    }
    bool isSpectral()
    {
        loadMetaData();
        return md_->isSpectral();
    }

    SimpleFieldMetaDataPtr metaData()
    {
        loadMetaData();
        return md_;
    }

protected:
    SimpleField(field* f = nullptr);
    void loadPv();
    void loadMetaData();
    int pvPairCount();
    bool mlCoeffs(double& c1, double& c2, int ml);
    static void computeMinMax(field* fs, double& minv, double& maxv);
    static void computeDiffMinMax(field* fs1, field* fs2, double& minv, double& maxv);
    long getLongValue(const std::string& key);
    void getStringValue(const std::string& key, std::string& val);

    field* field_{nullptr};
    std::vector<double> pv_;
    SimpleFieldMetaDataPtr md_{nullptr};
};

class SimpleFieldset
{
public:
    SimpleFieldset(fieldset* fs);
    SimpleFieldset(const SimpleFieldset&) = delete;
    SimpleFieldset& operator=(const SimpleFieldset&) = delete;

    void sort(const std::string& key, bool ascending);
    void sortLevelsByPa(bool ascending);
    SimpleFieldPtr operator[](int i);
    std::size_t size() const { return fields_.size(); }
    bool isEmpty() const { return size() == 0; }

protected:
    fieldset* fs_{nullptr};
    std::vector<SimpleFieldPtr> fields_;
    std::vector<int> order_;
};

class SimpleFieldExpander
{
public:
    virtual ~SimpleFieldExpander() = default;
    SimpleFieldPtr field() const { return field_; }

protected:
    SimpleFieldExpander(SimpleFieldPtr f) :
        field_{f} {}

    SimpleFieldPtr field_{nullptr};
    field_state oriState_{unknown};
};

class SimpleFieldDataExpander : public SimpleFieldExpander
{
public:
    SimpleFieldDataExpander(SimpleFieldPtr);
    ~SimpleFieldDataExpander();
};

class SimpleFieldMetaDataExpander : public SimpleFieldExpander
{
public:
    SimpleFieldMetaDataExpander(SimpleFieldPtr);
    ~SimpleFieldMetaDataExpander();

private:
    bool revert_{false};
};

class FieldExpanderOrArray
{
public:
    FieldExpanderOrArray(SimpleFieldPtr);
    FieldExpanderOrArray(std::size_t num, double val);
    ~FieldExpanderOrArray();
    FieldExpanderOrArray(const FieldExpanderOrArray&) = delete;
    FieldExpanderOrArray& operator=(const FieldExpanderOrArray&) = delete;

    double* values() const { return (xp_) ? xp_->field()->values() : vals_; }
    std::size_t num() const { return (xp_) ? xp_->field()->valueNum() : num_; }

private:
    SimpleFieldDataExpander* xp_{nullptr};
    double* vals_{nullptr};
    std::size_t num_{0};
};

//========================================
//
// SimpleFieldMetaData
//
//========================================

void SimpleFieldMetaData::setSpectral(const std::string& gridType)
{
    spectral_ = (gridType == "sh");
}

void SimpleFieldMetaData::setLevelType(const std::string& typeOfLevel)
{
    if (typeOfLevel == "isobaricInPa") {
        levelType_ = IsobaricInPaLevel;
    }
    else if (typeOfLevel == "isobaricInhPa") {
        levelType_ = IsobaricInHPaLevel;
    }
    else if (typeOfLevel == "hybrid") {
        levelType_ = ModelLevel;
    }
    else if (!typeOfLevel.empty()) {
        levelType_ = OtherLevelType;
    }
}

int SimpleFieldMetaData::levelInPa() const
{
    if (levelType_ == IsobaricInPaLevel) {
        return level_;
    }
    else if (levelType_ == IsobaricInHPaLevel) {
        return level_ * 100;
    }
    return -1;
}

//========================================
//
// SimpleField
//
//========================================

SimpleFieldPtr SimpleField::make(field* f)
{
    return SimpleFieldPtr(new SimpleField(f));
}

SimpleField::SimpleField(field* f) :
    field_(f)
{
}

SimpleField::~SimpleField()
{
    releaseMemory();
}

void SimpleField::expandMemory()
{
    if (field_) {
        set_field_state(field_, expand_mem);
    }
}

void SimpleField::releaseMemory()
{
    if (field_) {
        set_field_state(field_, packed_file);
    }
}

field* SimpleField::cloneRaw(bool copyValues)
{
    // make sure the raw field is expanded
    field_state ori = field_->shape;
    int oriRefCnt = (field_->r) ? field_->r->refcnt : -1000;
    set_field_state(field_, expand_mem);

    // create new field
    field* r = copy_field(field_, true);
    set_field_state(r, expand_mem);
    r->bitmap = copyValues ? field_->bitmap : false;

    if (!copyValues) {
        setValues(r, 0.);
    }

    // decouple the request from the source
    if (r->r && field_->r && r->r == field_->r) {
        r->r = new_field_request(field_->r->r);
    }

    if (field_->r) {
        field_->r->refcnt = oriRefCnt;
    }

    // reset original state
    set_field_state(field_, ori);
    return r;
}

int SimpleField::pvPairCount()
{
    loadPv();
    return pv_.size() / 2;
}

void SimpleField::loadPv()
{
    if (pv_.empty()) {
        SimpleFieldMetaDataExpander mdx(shared_from_this());
        assert(field_->shape == packed_mem || field_->shape == expand_mem);
        size_t pvNum = 0;
        grib_get_size(field_->handle, "pv", &pvNum);
        if (pvNum > 0) {
            double* vals = new double[pvNum];
            int err = grib_get_double_array(field_->handle, "pv", vals, &pvNum);
            if (err == 0) {
                pv_.reserve(pvNum);
                pv_.assign(vals, vals + pvNum);
            }
            delete[] vals;
        }
    }
}

bool SimpleField::mlCoeffs(double& c1, double& c2, int lev)
{
    c1 = c2 = 0.;
    loadPv();
    if (lev >= 0 && lev < pvPairCount()) {
        c1 = pv_[lev];
        c2 = pv_[lev + pvPairCount()];
        return true;
    }
    return false;
}

void SimpleField::halfPres(double* sp, double* mlPres, std::size_t num, int ml)
{
    double A, B;
    mlCoeffs(A, B, ml - 1);
    for (std::size_t i = 0; i < num; i++) {
        if (!MISSING_VALUE(sp[i])) {
            mlPres[i] = A + B * sp[i];
        }
        else {
            mlPres[i] = mars.grib_missing_value;
        }
    }
}

double SimpleField::meanMlPressureFromSp(double sp, int ml)
{
    double pVec;
    meanMlPressureFromSp(&sp, &pVec, 1, ml);
    return pVec;
}

void SimpleField::meanMlPressureFromSp(double* sp, double* mlPres, std::size_t num, int ml)
{
    assert(ml >= 1);

    if (pvPairCount() > 1) {
        double C11, C12, C21, C22;
        mlCoeffs(C11, C21, ml - 1);
        mlCoeffs(C12, C22, ml);
        auto A = C11 + C12;
        auto B = C21 + C22;
        for (std::size_t i = 0; i < num; i++) {
            if (!MISSING_VALUE(sp[i])) {
                mlPres[i] = (A + B * sp[i]) / 2.0;
            }
            else {
                mlPres[i] = mars.grib_missing_value;
            }
        }
    }
    else {
        double C12, C22;
        mlCoeffs(C12, C22, ml);
        for (std::size_t i = 0; i < num; i++) {
            if (!MISSING_VALUE(sp[i])) {
                mlPres[i] = C12 + C22 * sp[i];
            }
            else {
                mlPres[i] = mars.grib_missing_value;
            }
        }
    }
}

void SimpleField::mlPressureLayerFromSp(double* sp, double* mlPres, std::size_t num, int ml)
{
    assert(ml >= 1);

    if (pvPairCount() > 1) {
        double C11, C12, C21, C22;
        mlCoeffs(C11, C21, ml - 1);
        mlCoeffs(C12, C22, ml);
        auto A = C12 - C11;
        auto B = C22 - C21;
        for (std::size_t i = 0; i < num; i++) {
            if (!MISSING_VALUE(sp[i])) {
                mlPres[i] = A + B * sp[i];
            }
            else {
                mlPres[i] = mars.grib_missing_value;
            }
        }
    }
    else {
        for (std::size_t i = 0; i < num; i++) {
            mlPres[i] = mars.grib_missing_value;
        }
    }
}

void SimpleField::addMlPressureIntegralDeltaFromSp(SimpleFieldPtr sp, double* res)
{
    assert(sp);
    auto num = valueNum();
    if (sp->pvPairCount() > 1) {
        auto lev = level();
        assert(lev >= 1);
        double C11, C12, C21, C22;
        sp->mlCoeffs(C11, C21, lev - 1);
        sp->mlCoeffs(C12, C22, lev);
        auto A = C12 - C11;
        auto B = C22 - C21;
        double* spVals = sp->values();
        double* vals = values();
        for (std::size_t i = 0; i < num; i++) {
            if (!MISSING_VALUE(spVals[i]) && !MISSING_VALUE(res[i]) && !MISSING_VALUE(vals[i])) {
                res[i] += (A + B * spVals[i]) * vals[i];
            }
            else {
                res[i] = mars.grib_missing_value;
            }
        }
    }
    else {
        for (std::size_t i = 0; i < num; i++) {
            res[i] = mars.grib_missing_value;
        }
    }
}

void SimpleField::transformValues(DoubleFuncPtr1 func)
{
    assert(isExpanded());
    for (std::size_t i = 0; i < field_->value_count; i++) {
        if (!MISSING_VALUE(field_->values[i])) {
            field_->values[i] = func(field_->values[i]);
        }
    }
}

void SimpleField::transformValues(DoubleFuncPtr2 func)
{
    assert(isExpanded());
    for (std::size_t i = 0; i < field_->value_count; i++) {
        if (!MISSING_VALUE(field_->values[i])) {
            field_->values[i] = func(field_->values[i]);
        }
    }
}

void SimpleField::subtractValues(SimpleFieldPtr other)
{
    assert(isExpanded());
    auto of = other->rawField();
    for (std::size_t i = 0; i < field_->value_count; i++) {
        if (!MISSING_VALUE(field_->values[i]) && !MISSING_VALUE(of->values[i])) {
            field_->values[i] -= of->values[i];
        }
    }
}

bool SimpleField::hasValidValues() const
{
    assert(isExpanded());
    for (std::size_t i = 0; i < field_->value_count; i++) {
        if (!MISSING_VALUE(field_->values[i])) {
            return true;
        }
    }
    return false;
}

void SimpleField::setAllValuesToMissing(field* f)
{
    if (f) {
        f->bitmap = true;
        for (std::size_t i = 0; i < f->value_count; i++) {
            f->values[i] = mars.grib_missing_value;
        }
    }
}

void SimpleField::setValues(double val)
{
    assert(isExpanded());
    setValues(field_, val);
}

void SimpleField::setValues(field* f, double val)
{
    if (f) {
        for (std::size_t i = 0; i < f->value_count; i++) {
            if (!MISSING_VALUE(f->values[i])) {
                f->values[i] = val;
            }
        }
    }
}

void SimpleField::scaleValues(field* f, double val)
{
    if (f) {
        for (std::size_t i = 0; i < f->value_count; i++) {
            if (!MISSING_VALUE(f->values[i])) {
                f->values[i] *= val;
            }
        }
    }
}

void SimpleField::copyValuesTo(field* f)
{
    assert(isExpanded());
    assert(f);
    assert(field_->value_count == f->value_count);
    for (std::size_t i = 0; i < field_->value_count; i++) {
        f->values[i] = field_->values[i];
    }
}

void SimpleField::computeMinMax(double& spMin, double& spMax)
{
    assert(isExpanded());
    computeMinMax(field_, spMin, spMax);
}

void SimpleField::computeMinMax(field* fs, double& minv, double& maxv)
{
    assert(fs);
    minv = std::numeric_limits<double>::max();
    maxv = std::numeric_limits<double>::min();
    for (size_t i = 0; i < fs->value_count; i++) {
        if (!MISSING_VALUE(fs->values[i])) {
            if (fs->values[i] < minv) {
                minv = fs->values[i];
            }
            if (fs->values[i] > maxv) {
                maxv = fs->values[i];
            }
        }
    }
}

void SimpleField::computeDiffMinMax(SimpleFieldPtr other, double& spMin, double& spMax)
{
    assert(isExpanded());
    computeDiffMinMax(field_, other->rawField(), spMin, spMax);
}

void SimpleField::computeDiffMinMax(field* fs1, field* fs2, double& minv, double& maxv)
{
    assert(fs1);
    assert(fs2);
    if (fs1->value_count != fs2->value_count) {
        throw MvException("computeDiffMinMax: diffrent count in fs1 and fs2!" +
                          std::to_string(fs1->value_count) + " != " + std::to_string(fs2->value_count));
    }
    minv = std::numeric_limits<double>::max();
    maxv = std::numeric_limits<double>::min();
    for (size_t i = 0; i < fs1->value_count; i++) {
        if (!MISSING_VALUE(fs1->values[i]) && !MISSING_VALUE(fs2->values[i])) {
            auto d = fs1->values[i] - fs2->values[i];
            if (d < minv) {
                minv = d;
            }
            if (d > maxv) {
                maxv = d;
            }
        }
    }
}

long SimpleField::getLongValue(const std::string& key)
{
    long val = std::numeric_limits<long>::max();
    ;
    if (field_) {
        SimpleFieldMetaDataExpander mdx(shared_from_this());
        assert(field_->shape == packed_mem || field_->shape == expand_mem);
        grib_get_long(field_->handle, key.c_str(), &val);
    }
    return val;
}

void SimpleField::getStringValue(const std::string& key, std::string& val)
{
    val = "NA";
    if (field_) {
        SimpleFieldMetaDataExpander mdx(shared_from_this());
        assert(field_->shape == packed_mem || field_->shape == expand_mem);
        const size_t cMaxBuf = 99;
        char strbuf[cMaxBuf + 1];
        size_t slen = cMaxBuf;
        if (!grib_get_string(field_->handle, key.c_str(), strbuf, &slen)) {
            val = std::string(strbuf);
        }
    }
}

void SimpleField::loadMetaData()
{
    if (!md_) {
        md_ = SimpleFieldMetaDataPtr(new SimpleFieldMetaData);
        if (field_) {
            SimpleFieldMetaDataExpander mdx(shared_from_this());
            md_->level_ = getLongValue("level");
            md_->paramId_ = getLongValue("paramId");
            std::string v;
            getStringValue("typeOfLevel", v);
            md_->setLevelType(v);
            getStringValue("gridType", v);
            md_->setSpectral(v);
        }
    }
}

//========================================
//
// SimpleFieldset
//
//========================================

SimpleFieldset::SimpleFieldset(fieldset* fs) :
    fs_(fs)
{
    if (fs_) {
        for (int i = 0; i < fs_->count; i++) {
            fields_.push_back(SimpleField::make(fs->fields[i]));
            order_.push_back(i);
        }
    }
}

SimpleFieldPtr SimpleFieldset::operator[](int i)
{
    return fields_[order_[i]];
}

void SimpleFieldset::sort(const std::string& key, bool ascending)
{
    std::iota(order_.begin(), order_.end(), 0);
    std::vector<int> md;
    for (std::size_t i = 0; i < fields_.size(); i++) {
        if (key == "level") {
            md.push_back(fields_[i]->metaData()->level());
        }
        else if (key == "levelInPa") {
            md.push_back(fields_[i]->metaData()->levelInPa());
        }
        else {
            md.push_back(fields_[i]->getLongValue(key));
        }
    }

    std::stable_sort(order_.begin(), order_.end(),
                     [&md, ascending](size_t i1, size_t i2) { return (ascending) ? (md[i1] < md[i2]) : (md[i1] > md[i2]); });
}

void SimpleFieldset::sortLevelsByPa(bool ascending)
{
    std::iota(order_.begin(), order_.end(), 0);
    std::vector<int> md;
    for (std::size_t i = 0; i < fields_.size(); i++) {
        md.push_back(fields_[i]->metaData()->levelInPa());
    }

    std::stable_sort(order_.begin(), order_.end(),
                     [&md, ascending](size_t i1, size_t i2) { return (ascending) ? (md[i1] < md[i2]) : (md[i1] > md[i2]); });
}

//========================================
//
// SimpleFieldDataExpander
//
//========================================

SimpleFieldDataExpander::SimpleFieldDataExpander(SimpleFieldPtr f) :
    SimpleFieldExpander(f)
{
    if (field_) {
        auto* g = field_->rawField();
        if (g) {
            oriState_ = g->shape;
            set_field_state(g, expand_mem);
            assert(g->shape == packed_mem || g->shape == expand_mem);
        }
    }
}

SimpleFieldDataExpander::~SimpleFieldDataExpander()
{
    if (field_) {
        auto* g = field_->rawField();
        if (g) {
            // if we revert back to packed_mem (from expand_mem) set_field_state()
            // will always write the values back to the grib_handle. This is unnecessary
            // in read-only mode, so we apply the code below to set the state in the
            // most efficient way. Ideally set_field_state() should offer an option for
            // it so could avoid using this low level code here!
            if (oriState_ == packed_mem) {
                release_mem(g->values);
                g->values = NULL;
                g->value_count = 0;
                g->shape = packed_mem;
            }
            else {
                set_field_state(g, oriState_);
            }
        }
    }
}

//========================================
//
// SimpleFieldMetaDataExpander
//
//========================================

SimpleFieldMetaDataExpander::SimpleFieldMetaDataExpander(SimpleFieldPtr f) :
    SimpleFieldExpander(f)
{
    if (field_) {
        auto* g = field_->rawField();
        if (g) {
            if (g->shape != packed_mem && g->shape != expand_mem) {
                oriState_ = field_->rawField()->shape;
                set_field_state(g, packed_mem);
                revert_ = true;
            }
            assert(g->shape == packed_mem || g->shape == expand_mem);
        }
    }
}

SimpleFieldMetaDataExpander::~SimpleFieldMetaDataExpander()
{
    if (revert_ && field_) {
        auto* g = field_->rawField();
        if (g) {
            set_field_state(g, oriState_);
        }
    }
}

//========================================
//
// FieldOrArray
//
//========================================

FieldExpanderOrArray::FieldExpanderOrArray(SimpleFieldPtr fld)
{
    xp_ = new SimpleFieldDataExpander(fld);
}

FieldExpanderOrArray::FieldExpanderOrArray(std::size_t num, double val) :
    num_(num)
{
    vals_ = new double[num_];
    for (std::size_t k = 0; k < num_; k++) {
        vals_[k] = val;
    }
}

FieldExpanderOrArray::~FieldExpanderOrArray()
{
    if (xp_) {
        delete xp_;
    }
    if (vals_) {
        delete[] vals_;
    }
}

//===============================
//
// VerticalInterpolation
//
//===============================

VerticalInterpolation::VerticalInterpolation(fieldset* srcFs, fieldset* vcFs, fieldset* surfVcFs) :
    fs_(srcFs), vcFs_(vcFs), surfVcFs_(surfVcFs)
{
}

void VerticalInterpolation::setTargetVc(fieldset* fs)
{
    targetVcMode_ = FieldsetTargetCoord;
    targetVcFs_ = fs;
}

void VerticalInterpolation::setTargetVc(const std::vector<double>& v)
{
    targetVcMode_ = VectorTargetCoord;
    targetVc_ = v;
}

void VerticalInterpolation::setSurfaceValues(fieldset* fs)
{
    surfValMode_ = FieldsetSurfaceValue;
    surfValFs_ = fs;
}

void VerticalInterpolation::setSurfaceValues(double v)
{
    surfValMode_ = NumberSurfaceValue;
    surfVal_ = v;
}

void VerticalInterpolation::computeSurfaceVcMinMax(SimpleFieldPtr f, double& vmin, double& vmax)
{
    if (targetLevType_ == HeightLevelAGR) {
        vmin = 0.;
        vmax = 0.;
    }
    else {
        assert(f);
        assert(f->isExpanded());
        f->computeMinMax(vmin, vmax);
    }
}

void VerticalInterpolation::computeVcMinMax(SimpleFieldPtr vcF, SimpleFieldPtr surfVcF, double& vmin, double& vmax)
{
    assert(vcF);
    assert(vcF->isExpanded());
    if (targetLevType_ == HeightLevelAGR) {
        assert(surfVcF);
        assert(surfVcF->isExpanded());
        vcF->computeDiffMinMax(surfVcF, vmin, vmax);
    }
    else {
        vcF->computeMinMax(vmin, vmax);
    }
}

double VerticalInterpolation::scaleVc(double v)
{
    if (vcScaleNeeded_) {
        if (!MISSING_VALUE(v)) {
            return v * vcFactor_;
        }
    }
    return v;
}

void VerticalInterpolation::scaleVc(SimpleFieldPtr f)
{
    assert(f);
    assert(!f->isEmpty());
    if (vcScaleNeeded_) {
        auto p = [this](double v) -> double { return v * vcFactor_; };
        f->transformValues(p);
        //        auto v = f->values();
        //        for(std::size_t k=0; k < f->valueNum(); k++) {
        //            if (!MISSING_VALUE(v[k])) {
        //                v[k] *= vcFactor_;
        //            }
        //        }
    }
}

bool VerticalInterpolation::findBracketingIdx(double vcVal, const std::vector<double>& vcMin, const std::vector<double>& vcMax,
                                              int& idxFrom, int& idxTo)
{
    if (targetAscending_) {
        if (vcMin.front() <= vcVal && vcMax.back() >= vcVal) {
            for (std::size_t j = 0; j < vcMax.size(); j++) {
                if (vcMax[j] < vcVal) {
                    idxFrom = j;
                }
            }
            for (std::size_t j = 0; j < vcMin.size(); j++) {
                if (vcMin[j] > vcVal) {
                    idxTo = j;
                    break;
                }
            }
            return true;
        }
    }
    else {
        if (vcMin.back() <= vcVal && vcMax.front() >= vcVal) {
            for (std::size_t j = 0; j < vcMin.size(); j++) {
                if (vcMin[j] > vcVal) {
                    idxFrom = j;
                }
            }
            for (std::size_t j = 0; j < vcMax.size(); j++) {
                if (vcMax[j] < vcVal) {
                    idxTo = j;
                    break;
                }
            }
            return true;
        }
    }
    return false;
}

bool VerticalInterpolation::findBracketingIdx(double targetMin, double targetMax, const std::vector<double>& vcMin, const std::vector<double>& vcMax,
                                              int& idxFrom, int& idxTo)
{
    if (vcMin.front() <= targetMax && vcMax.back() >= targetMin) {
        for (std::size_t j = 0; j < vcMax.size(); j++) {
            if (vcMax[j] < targetMin) {
                idxFrom = j;
            }
        }
        for (std::size_t j = 0; j < vcMin.size(); j++) {
            if (vcMin[j] > targetMax) {
                idxTo = j;
                break;
            }
        }
        return true;
    }
    return false;
}

// fixed coordinate levels
bool VerticalInterpolation::findBracketingIdx(double targetVc, const std::vector<double>& vc, int& idxFrom, int& idxTo)
{
    double eps = 1e-5;
    if (!targetAscending_) {
        if (vc.front() >= targetVc && vc.back() <= targetVc) {
            for (std::size_t j = 0; j < vc.size(); j++) {
                if (std::fabs(vc[j] - targetVc) < eps) {
                    idxFrom = j;
                    idxTo = j;
                    return true;
                }
            }
            for (std::size_t j = 0; j < vc.size() - 1; j++) {
                if (vc[j] > targetVc && vc[j + 1] < targetVc) {
                    idxFrom = j;
                    idxTo = j + 1;
                    return true;
                }
            }
        }
    }
    return false;
}

// fixed bracketing coordinate level values
void VerticalInterpolation::interpolate(double targetVcVal, double vc1, double vc2,
                                        double* f1, double* f2, field* fRes, size_t num)
{
    auto method = interMethod_;

    // we cannot use log interpolation near 0 or negative vc values
    if (method == LogInterpolation && (targetVcVal < 1E-5 || vc1 < 1E-5 || vc2 < 1E-5)) {
        method = LinearInterpolation;
    }

    double dTarget = targetVcVal - vc1;
    double dVc = (vc2 - vc1);
    double dLogTarget = std::log(targetVcVal / vc1);
    double dLogVc = std::log(vc2 / vc1);

    // vc1 <= targetVcVal && targetVcVal < vc2
    for (std::size_t k = 0; k < num; k++) {
        if (!MISSING_VALUE(f1[k]) && !MISSING_VALUE(f2[k])) {
            if (method == LinearInterpolation) {
                fRes->values[k] = f1[k] + dTarget * (f2[k] - f1[k]) / dVc;
            }
            else {
                fRes->values[k] = f1[k] + dLogTarget * (f2[k] - f1[k]) / dLogVc;
            }
        }
        else {
            fRes->values[k] = mars.grib_missing_value;
            fRes->bitmap = 1;
        }
    }
}

void VerticalInterpolation::interpolate(double targetVcVal, double* vc1, double* vc2,
                                        double* f1, double* f2, field* fRes, size_t num, int idx, int idxFrom, int idxTo, double vcMin)
{
    auto method = interMethod_;

    // we cannot use log interpolation near 0 or negative vc values
    if (method == LogInterpolation && (targetVcVal < 1E-5 || vcMin < 1E-5)) {
        method = LinearInterpolation;
    }

    for (std::size_t k = 0; k < num; k++) {
        if (!MISSING_VALUE(vc1[k]) && !MISSING_VALUE(vc2[k]) &&
            vc1[k] <= targetVcVal && targetVcVal < vc2[k]) {
            if (!MISSING_VALUE(f1[k]) && !MISSING_VALUE(f2[k])) {
                if (method == LinearInterpolation) {
                    fRes->values[k] = f1[k] + (targetVcVal - vc1[k]) * (f2[k] - f1[k]) / (vc2[k] - vc1[k]);
                }
                else {
                    fRes->values[k] = f1[k] +
                                      std::log(targetVcVal / vc1[k]) * (f2[k] - f1[k]) / std::log(vc2[k] / vc1[k]);
                }
            }
            else {
                fRes->values[k] = mars.grib_missing_value;
                fRes->bitmap = 1;
            }
        }
        else if ((targetAscending_ && ((idx == idxFrom && targetVcVal < vc1[k]) || (idx == idxTo - 1 && targetVcVal > vc2[k]))) ||
                 (!targetAscending_ && ((idx == idxFrom && targetVcVal > vc2[k]) || (idx == idxTo - 1 && targetVcVal < vc1[k])))) {
            fRes->values[k] = mars.grib_missing_value;
            fRes->bitmap = 1;
        }
    }
}

void VerticalInterpolation::interpolate(double* targetVcVal, double* vc1, double* vc2,
                                        double* f1, double* f2, field* fRes, size_t num, int idx, int idxFrom, int idxTo,
                                        double vcMin, double targetVcMin, double targetVcMax)
{
    auto method = interMethod_;

    // we cannot use log interpolation near 0 or negative vc values
    if (method == LogInterpolation && (targetVcMin < 1E-5 || vcMin < 1E-5)) {
        method = LinearInterpolation;
    }

#if 0
    int pos=697 - 1 ;
    std::cout << "idx=" << idx << " target=" << targetVcVal[pos] << " vc1=" << vc1[pos] << " vc2=" << vc2[pos] << std::endl;
#endif

    for (std::size_t k = 0; k < num; k++) {
        if (!MISSING_VALUE(vc1[k]) && !MISSING_VALUE(vc2[k] && !MISSING_VALUE(targetVcVal[k])) &&
            vc1[k] <= targetVcVal[k] && targetVcVal[k] < vc2[k]) {
            if (!MISSING_VALUE(f1[k]) && !MISSING_VALUE(f2[k])) {
                if (method == LinearInterpolation) {
                    fRes->values[k] = f1[k] + (targetVcVal[k] - vc1[k]) * (f2[k] - f1[k]) / (vc2[k] - vc1[k]);
                }
                else {
                    fRes->values[k] = f1[k] +
                                      std::log(targetVcVal[k] / vc1[k]) * (f2[k] - f1[k]) / std::log(vc2[k] / vc1[k]);
                }
            }
            else {
                fRes->values[k] = mars.grib_missing_value;
                fRes->bitmap = 1;
            }
        }
        else if ((targetAscending_ && ((idx == idxFrom && targetVcMax < vc1[k]) || (idx == idxTo - 1 && targetVcMin > vc2[k]))) ||
                 (!targetAscending_ && ((idx == idxFrom && targetVcMin > vc2[k]) || (idx == idxTo - 1 && targetVcMax < vc1[k])))) {
            fRes->values[k] = mars.grib_missing_value;
            fRes->bitmap = 1;
        }
    }
}


fieldset* VerticalInterpolation::compute()
{
    if (fs_->count < 2) {
        throw MvException("At least input levels are required!");
    }

    if (fs_->count != vcFs_->count) {
        throw MvException("Different number of input fields in fs and vcFs! " +
                          std::to_string(vcFs_->count) +
                          " (fs) != " + std::to_string(vcFs_->count) + " (vcFs)");
    }

    SimpleFieldset fs(fs_);
    SimpleFieldset vcFs(vcFs_);

    // Sort by levels from bottom to top
    fs.sort("level", srcAscending_);
    vcFs.sort("level", srcAscending_);

    // compute min and max vc on all the levels ...
    size_t num = 0;
    std::vector<double> vcMax, vcMin;
    std::vector<int> vcLev;
    int surfIdxOffset = 0;

    // get field storing surface level coordinate values. We will keep it expanded throughout the
    // whole computation
    SimpleFieldPtr surfVcF;
    if (targetLevType_ == HeightLevelAGR || surfValMode_ != NoSurfaceValue) {
        surfVcF = SimpleField::make(surfVcFs_->fields[0]);
        surfVcF->expandMemory();
    }

    // ... but first handle the surface
    if (surfValMode_ != NoSurfaceValue) {
        vcLev.push_back(0);  // a made up level
        double vmin, vmax;
        assert(surfVcF);
        computeSurfaceVcMinMax(surfVcF, vmin, vmax);
        vcMin.push_back(vmin);
        vcMax.push_back(vmax);
        surfIdxOffset = 1;
    }

    // ... then the other levels
    for (std::size_t i = 0; i < vcFs.size(); i++) {
        SimpleFieldDataExpander fx(vcFs[i]);
        double vmin, vmax;
        computeVcMinMax(vcFs[i], surfVcF, vmin, vmax);
        vcMin.push_back(vmin);
        vcMax.push_back(vmax);
        vcLev.push_back(vcFs[i]->metaData()->level());

        // determine the number of points in a field
        if (num == 0) {
            num = vcFs[i]->valueNum();
        }
    }

    assert(vcMin.size() == vcMax.size());
    assert(vcLev.size() == vcMin.size());

    // paramId of the input data. Will be determined later if needed.
    long paramId = -1;

    // define the resulting fieldset
    fieldset* result = new_fieldset(targetVc_.size());

    // SimpleField surfValF;
    SimpleFieldPtr surfValF;
    SimpleFieldset targetVcFs(targetVcFs_);
    int targetVcNum = (targetVcMode_ == VectorTargetCoord) ? static_cast<int>(targetVc_.size()) : targetVcFs.size();

    // perform the interpolation for all the target vc levels
    for (int i = 0; i < targetVcNum; i++) {
        // create a field for the result
        field* fRes = fs[0]->cloneRaw(false);
        assert(fRes->shape = expand_mem);

        // find bracketing levels
        int idxFrom = 0;
        int idxTo = static_cast<int>(vcMin.size()) - 1;
        bool hasBracket = false;
        double targetVcMin, targetVcMax;
        if (targetVcMode_ == VectorTargetCoord) {
            // the target vc is always in the right units. However the vc fields might need
            // scaling. We apply an inverse scaling to the target vc so that it should be in the same
            // units as the vc fields. This is the most effective way to carry out the computations
            double targetVcVal = scaleVc(targetVc_[i]);
            hasBracket = findBracketingIdx(targetVcVal, vcMin, vcMax, idxFrom, idxTo) && idxFrom < idxTo;
        }
        else {
            assert(!targetVcFs.isEmpty());
            targetVcFs[i]->expandMemory();
            scaleVc(targetVcFs[i]);
            targetVcFs[i]->computeMinMax(targetVcMin, targetVcMax);
            hasBracket = findBracketingIdx(targetVcMin, targetVcMax, vcMin, vcMax, idxFrom, idxTo) && idxFrom < idxTo;
        }

        if (!hasBracket) {
            SimpleField::setAllValuesToMissing(fRes);
        }
        else {
            // Iterate through level pairs
            // Only keep two data/vc levels expanded at the same time.

            // Expand start level
            std::map<int, FieldExpanderOrArray> expandValMap, expandVcMap;

            // The start level is the surface
            if (surfIdxOffset == 1 && idxFrom == 0) {
                assert(surfValMode_ != NoSurfaceValue);
                // surf data
                if (surfValMode_ == FieldsetSurfaceValue) {
                    if (!surfValF) {
                        surfValF = SimpleField::make(surfValFs_->fields[0]);
                    }
                    expandValMap.emplace(idxFrom, surfValF);
                }
                else if (surfValMode_ == NumberSurfaceValue) {
                    expandValMap.emplace(std::piecewise_construct,
                                         std::forward_as_tuple(idxFrom),
                                         std::forward_as_tuple(num, surfVal_));
                }

                if (targetLevType_ == HeightLevelAGR) {
                    expandVcMap.emplace(std::piecewise_construct,
                                        std::forward_as_tuple(idxFrom),
                                        std::forward_as_tuple(num, 0.));
                }
                else {
                    // surfVcF is already expanded!
                    expandVcMap.emplace(idxFrom, surfVcF);
                }
                // The start level is not the surface
            }
            else {
                expandValMap.emplace(idxFrom, fs[idxFrom - surfIdxOffset]);
                expandVcMap.emplace(idxFrom, vcFs[idxFrom - surfIdxOffset]);

                // temporary modification, will not be written back to the GRIB data
                if (targetLevType_ == HeightLevelAGR) {
                    vcFs[idxFrom - surfIdxOffset]->subtractValues(surfVcF);
                }
            }

            for (int j = idxFrom; j <= idxTo - 1; j++) {
                // unexpand the data for the previous level
                if (j > idxFrom) {
                    auto it = expandValMap.find(j - 1);
                    if (it != expandValMap.end()) {
                        expandValMap.erase(it);
                    }
                    it = expandVcMap.find(j - 1);
                    if (it != expandVcMap.end()) {
                        expandVcMap.erase(it);
                    }
                }

                // expand the next level
                int nextIdx = j + 1;
                int nextFsIdx = nextIdx - surfIdxOffset;
                expandValMap.emplace(nextIdx, fs[nextFsIdx]);
                expandVcMap.emplace(nextIdx, vcFs[nextFsIdx]);

                if (targetLevType_ == HeightLevelAGR) {
                    vcFs[nextFsIdx]->subtractValues(surfVcF);
                }

                // 1=smaller vc 2=larger vc
                int idx1 = (targetAscending_) ? j : j + 1;
                int idx2 = (targetAscending_) ? j + 1 : j;

                assert(expandValMap.size() == 2);
                assert(expandVcMap.size() == 2);
                auto f1 = expandValMap.at(idx1).values();
                auto f2 = expandValMap.at(idx2).values();
                auto vc1 = expandVcMap.at(idx1).values();
                auto vc2 = expandVcMap.at(idx2).values();

                for (int k : {idx1, idx2}) {
                    if (expandValMap.at(k).num() != num) {
                        throw MvException("Level=" + std::to_string(vcLev[k]) +
                                          "in data has different number of points than first data field!");
                    }
                    if (expandVcMap.at(k).num() != num) {
                        throw MvException("Level=" + std::to_string(vcLev[k]) +
                                          "in vertical coordinate has different number of points than first data field!");
                    }
                }

                // perform the interpolation
                if (targetVcMode_ == VectorTargetCoord) {
                    double targetVcVal = scaleVc(targetVc_[i]);
                    interpolate(targetVcVal,
                                vc1, vc2, f1, f2,
                                fRes, num, j, idxFrom, idxTo, vcMin[idx1]);
                }
                else {
                    interpolate(targetVcFs[i]->values(),
                                vc1, vc2, f1, f2,
                                fRes, num, j, idxFrom, idxTo, vcMin[idx1], targetVcMin, targetVcMax);
                }
            }
        }

        if (targetVcMode_ == FieldsetTargetCoord) {
            targetVcFs[i]->releaseMemory();
        }

        // if the target typeOfLevel is "heightAboveGround" for certain levels and
        // certain paremeters ecCodes silently changes the shortName!!!
        // E.g. if we set the level as 10m for param "ws" it becomes "10si"!!!
        // Here we try to avoid it by adding a small delta to the level!!!
        if (targetVcMode_ == VectorTargetCoord) {
            assert(fRes->shape == expand_mem);
            long tv = static_cast<long>(targetVc_[i]);
            size_t len = eccTargetLevType_.size();
            grib_set_string(fRes->handle, "typeOfLevel", eccTargetLevType_.c_str(), &len);

            bool adjustLevelInOutput = false;
            if (eccTargetLevType_ == "heightAboveGround") {
                if (paramId == -1) {
                    grib_get_long(fRes->handle, "paramId", &paramId);
                }
                if (paramId == 131 || paramId == 132 || paramId == 10) {
                    adjustLevelInOutput = true;
                }
            }
            if (adjustLevelInOutput && (tv == 10 || tv == 100 || tv == 200)) {
                grib_set_double(fRes->handle, "level", tv + 0.00001);
            }
            else {
                grib_set_long(fRes->handle, "level", tv);
            }
        }

        set_field(result, fRes, i);
        save_fieldset(result);
    }

    if (surfVcF) {
        surfVcF->releaseMemory();
    }

    return result;
}

//===============================
//
// MlToPlInter
//
//===============================

MlToPlInter::MlToPlInter(fieldset* dataFs, fieldset* lnspFs) :
    VerticalInterpolation(dataFs, nullptr, nullptr),
    lnspFs_(lnspFs)
{
    srcAscending_ = false;
    targetAscending_ = false;
}

fieldset* MlToPlInter::compute()
{
    if (fs_->count < 2) {
        throw MvException("At least two model level fields are required!");
    }

    if (lnspFs_->count < 1) {
        throw MvException("Lnsp field is empty!");
    }

    if (*std::min_element(targetVc_.begin(), targetVc_.end()) <= 0.) {
        throw MvException("Target pressure values must always be positive!");
    }

    // Sort by model levels in descending order (bottom -> top).
    SimpleFieldset fs(fs_);
    fs.sort("level", false);

    // compute surface pressure (Pa). We store the values in the lnsp values pointer and
    // make sure this is not written back to the file.
    assert(lnspFs_->count >= 1);
    SimpleFieldPtr sp = SimpleField::make(lnspFs_->fields[0]);
    sp->expandMemory();
    if (!sp->hasValidValues()) {
        throw MvException("No valid values found in lnsp!");
    }
    sp->transformValues(std::exp);
    std::size_t num = sp->valueNum();
    assert(num > 0);

    // compute min and max of surface pressure (Pa)
    double spMin, spMax;
    sp->computeMinMax(spMin, spMax);

    // compute min and max pressure on all the model levels. We use the PV array stored in
    // the first field to do so.
    std::vector<double> mlPMax, mlPMin;
    std::vector<int> mlVec;
    for (std::size_t i = 0; i < fs.size(); i++) {
        int ml = fs[i]->metaData()->level();
        mlVec.push_back(ml);
        mlPMin.push_back(fs[0]->meanMlPressureFromSp(spMin, ml));
        mlPMax.push_back(fs[0]->meanMlPressureFromSp(spMax, ml));
    }

    assert(mlPMin.size() == mlPMax.size());

    // define the resulting fieldset
    fieldset* result = new_fieldset(targetVc_.size());

    // allocate and initialise data for the computations
    double* pLower = new double[num];
    double* pUpper = new double[num];

    // perform the interpolation for all the target pressure levels
    for (std::size_t i = 0; i < targetVc_.size(); i++) {
        auto pres = targetVc_[i] * 100;

        // create resulting field
        field* fRes = fs[0]->cloneRaw(false);

        if (fRes->value_count != num) {
            throw MvException("Model level fields have different number of points than lnsp!");
        }

        // find bracketing model levels. Both mlPMax and mlPMin are supposed to be sorted
        // in descending order
        int idxFrom = 0;
        int idxTo = static_cast<int>(mlPMin.size()) - 1;

        if (!findBracketingIdx(pres, mlPMin, mlPMax, idxFrom, idxTo) || idxFrom >= idxTo) {
            SimpleField::setAllValuesToMissing(fRes);
        }
        else {
            // iterate through level pairs

            // Only keep two levels expanded at the same time. Expand start level.
            std::map<int, SimpleFieldDataExpander> expandMap;
            expandMap.emplace(idxFrom, fs[idxFrom]);

            // compute pressure on start level
            fs[0]->meanMlPressureFromSp(sp->values(), pLower, num, mlVec[idxFrom]);

            for (int j = idxFrom; j <= idxTo - 1; j++) {
                // expand the necessary fields
                if (j > idxFrom) {
                    auto it = expandMap.find(j - 1);
                    if (it != expandMap.end()) {
                        expandMap.erase(it);
                    }
                }
                expandMap.emplace(j + 1, fs[j + 1]);
                assert(expandMap.find(j) != expandMap.end());
                assert(expandMap.size() == 2);

                auto fLower = fs[j];
                auto fUpper = fs[j + 1];
                if (fLower->valueNum() != num) {
                    throw MvException("Model level=" + std::to_string(mlVec[j]) + " has different number of points than lnsp!");
                }
                if (fUpper->valueNum() != num) {
                    throw MvException("Model level=" + std::to_string(mlVec[j + 1]) + " has different number of points than lnsp!");
                }

                // compute pressure on ML and make sure pLower stores the pressure on the
                // lower level, while pUpper does so on on the upper level
                if (j > idxFrom) {
                    std::swap(pLower, pUpper);
                }
                fs[0]->meanMlPressureFromSp(sp->values(), pUpper, num, mlVec[j + 1]);

                interpolate(pres,
                            pUpper, pLower, fUpper->values(), fLower->values(),
                            fRes, num, j, idxFrom, idxTo, mlPMin[j + 1]);
            }
        }

        // pres is now in Pa
        if (pres >= 100.) {
            std::string levType = "isobaricInhPa";
            size_t len = levType.size();
            grib_set_string(fRes->handle, "typeOfLevel", levType.c_str(), &len);
            grib_set_long(fRes->handle, "level", static_cast<long>(pres) / 100.);
        }
        else {
            std::string levType = "isobaricInPa";
            size_t len = levType.size();
            grib_set_string(fRes->handle, "typeOfLevel", levType.c_str(), &len);
            grib_set_long(fRes->handle, "level", static_cast<long>(pres));
        }

        set_field(result, fRes, i);
        save_fieldset(result);
    }

    delete[] pLower;
    delete[] pUpper;
    sp->releaseMemory();
    return result;
}

//===============================
//
// MlToHlInter
//
//===============================

MlToHlInter::MlToHlInter(fieldset* dataFs, fieldset* zFs, fieldset* zsFs,
                         bool aboveSea, VerticalInterpolationMethod interpolationMethod) :
    VerticalInterpolation(dataFs, zFs, zsFs)
{
    vcScaleNeeded_ = true;
    // go from metres to gpm
    vcFactor_ = MvSci::cEarthG;

    srcAscending_ = false;
    targetAscending_ = true;
    if (aboveSea) {
        targetLevType_ = HeightLevelABS;
        eccTargetLevType_ = "heightAboveSea";
    }
    else {
        targetLevType_ = HeightLevelAGR;
        eccTargetLevType_ = "heightAboveGround";
    }
    interMethod_ = interpolationMethod;
}

fieldset* geopotentialOnMl(fieldset* tFs, fieldset* qFs, fieldset* lnspFs, fieldset* zsFs)
{
    if (tFs->count < 2) {
        throw MvException("At least two model level fields are required!");
    }

    if (lnspFs->count < 1) {
        throw MvException("lnsp cannot be empty!");
    }

    if (zsFs->count < 1) {
        throw MvException("zs cannot be empty!");
    }

    if (tFs->count != qFs->count) {
        throw MvException("t and q must contain the same number of levels! " +
                          std::to_string(tFs->count) + " != " + std::to_string(qFs->count));
    }

    const double cRD = 287.06;
    const double cLog2 = std::log(2.);

    SimpleFieldset t(tFs);
    SimpleFieldset q(qFs);

    // Sort by model levels in descending order (bottom -> top).
    t.sort("level", false);
    q.sort("level", false);

    // check levels
    for (std::size_t i = 0; i < t.size(); i++) {
        if (t[i]->level() != q[i]->level()) {
            throw MvException("t and q must contain the same model levels!");
        }
        if (i > 0) {
            if (t[i]->level() != t[i - 1]->level() - 1) {
                throw MvException("There can be no gaps in the model level range! Level=" +
                                  std::to_string(t[i - 1]->level() - 1) + " is missing!");
            }
        }
    }

    // get the bottom and top model levels
    int bottomLevel = t[0]->level();
    if (bottomLevel != t[0]->totalMlLevelNum()) {
        throw MvException("Bottom level should be " + std::to_string(t[0]->totalMlLevelNum()) +
                          " instead of " + std::to_string(bottomLevel));
    }

    // compute surface pressure (Pa). We store the values in the lnsp values pointer and
    // make sure this is not written back to the file.
    assert(lnspFs->count >= 1);
    SimpleFieldPtr sp = SimpleField::make(lnspFs->fields[0]);
    sp->expandMemory();
    if (!sp->hasValidValues()) {
        throw MvException("No valid values found in lnsp!");
    }
    sp->transformValues(std::exp);
    std::size_t num = sp->valueNum();
    assert(num > 0);

    // We use the zs field to store the half level z values. We must not write
    // the values back to file!
    assert(zsFs->count >= 1);
    SimpleFieldPtr zHalf = SimpleField::make(zsFs->fields[0]);
    zHalf->expandMemory();
    if (!zHalf->hasValidValues()) {
        throw MvException("No valid values found in zs!");
    }

    std::size_t levNum = t.size();

    // define the resulting fieldset
    fieldset* result = new_fieldset(t.size());

    // work arrays
    double* pHalfBelow = new double[num];
    double* pHalf = new double[num];

    // initialise presssure on half level below
    t[0]->halfPres(sp->values(), pHalfBelow, num, bottomLevel + 1);

    for (std::size_t i = 0; i < t.size(); i++) {
        int lev = t[i]->level();
        assert(lev > 0);

        SimpleFieldDataExpander fxT(t[i]);
        SimpleFieldDataExpander fxQ(q[i]);

        // create resulting field
        field* fRes = t[0]->cloneRaw(false);

        // compute pressure on half level
        t[0]->halfPres(sp->values(), pHalf, num, lev);

        // z_f is the geopotential of this full level
        // integrate from previous (lower) half-level z_h to the full level
        double dLogP, alpha;
        double* tVal = t[i]->values();
        double* qVal = q[i]->values();
        double* zHalfVal = zHalf->values();
        double tv;
        for (std::size_t k = 0; k < num; k++) {
            if (!MISSING_VALUE(pHalfBelow[k]) && !MISSING_VALUE(zHalfVal[k]) && !MISSING_VALUE(tVal[k]) && !MISSING_VALUE(qVal[k])) {
                tv = tVal[k] * (1. + 0.609133 * qVal[k]) * cRD;
                if (lev == 1) {
                    dLogP = std::log(pHalfBelow[k] / 0.1);
                    alpha = cLog2;
                }
                else {
                    dLogP = std::log(pHalfBelow[k] / pHalf[k]);
                    alpha = 1. - dLogP * pHalf[k] / (pHalfBelow[k] - pHalf[k]);
                }

                // zFull
                fRes->values[k] = zHalfVal[k] + tv * alpha;
#if 0
                if (k==0) {
                    std::cout << "lev=" << lev << " pHalfBelow=" << pHalfBelow[k] << " pHalf=" << pHalf[k] <<
                          " dLogP=" << dLogP << " alpha=" << alpha << " tv=" << tv <<
                          " zHalf=" << zHalf->values()[k] << " zFull=" << fRes->values[k] << std::endl;
                }
#endif
                // zHalf
                if (i != levNum - 1) {
                    zHalfVal[k] += (tv * dLogP);
                }
            }
            else {
                fRes->values[k] = mars.grib_missing_value;
                fRes->bitmap = 1;
                pHalfBelow[k] = mars.grib_missing_value;
            }
        }

        std::swap(pHalf, pHalfBelow);

        // set metadata
        grib_set_long(fRes->handle, "level", lev);
        grib_set_long(fRes->handle, "paramId", 129);
        grib_set_long(fRes->handle, "generatingProcessIdentifier", 128);

        set_field(result, fRes, t.size() - i - 1);
        save_fieldset(result);
    }

    delete[] pHalf;
    delete[] pHalfBelow;
    sp->releaseMemory();
    zHalf->releaseMemory();
    return result;
}

//===============================
//
// PlToPlInter
//
//===============================

PlToPlInter::PlToPlInter(fieldset* dataFs, VerticalInterpolationMethod interpolationMethod) :
    VerticalInterpolation(dataFs, nullptr, nullptr)
{
    srcAscending_ = false;
    targetAscending_ = false;
    interMethod_ = interpolationMethod;
}

fieldset* PlToPlInter::compute()
{
    if (fs_->count < 2) {
        throw MvException("At least two input pressure level fields are required!");
    }

    if (*std::min_element(targetVc_.begin(), targetVc_.end()) <= 0.) {
        throw MvException("Target pressure values must always be positive!");
    }

    // Sort by pressure levels (using Pa) in descending order (bottom -> top).
    SimpleFieldset fs(fs_);
    fs.sort("levelInPa", false);

    // get pressure in Pa on all the levels
    std::vector<double> plVec;
    for (std::size_t i = 0; i < fs.size(); i++) {
        if (!fs[i]->isPressureLevel()) {
            throw MvException("Input data can only contain pressure level fields!");
        }
        int p = fs[i]->metaData()->levelInPa();
        plVec.push_back(p);
    }

    //    MvLog().info() << MV_FN_INFO << " plVec=" << plVec;

    std::size_t num = 0;

    // define the resulting fieldset
    fieldset* result = new_fieldset(targetVc_.size());

    // perform the interpolation for all the target pressure levels
    for (std::size_t i = 0; i < targetVc_.size(); i++) {
        auto pres = targetVc_[i] * 100;
        // create resulting field
        field* fRes = fs[0]->cloneRaw(false);

        // determine number of points
        if (num == 0) {
            num = fRes->value_count;
            if (num == 0) {
                throw MvException("No values found in input data!");
            }
        }

        // find bracketing pressure levels. Both mlPMax and mlPMin are supposed to be sorted
        // in descending order
        int idxFrom = 0;
        int idxTo = static_cast<int>(plVec.size()) - 1;

        if (!findBracketingIdx(pres, plVec, idxFrom, idxTo) || idxFrom > idxTo) {
            // MvLog().info() << " idxFrom=" << idxFrom << " idxTo=" << idxTo;
            SimpleField::setAllValuesToMissing(fRes);
        }
        else {
            // the target level is in the input data
            if (idxFrom == idxTo) {
                SimpleFieldDataExpander fx(fs[idxFrom]);
                fs[idxFrom]->copyValuesTo(fRes);
            }
            else {
                assert(idxFrom + 1 == idxTo);
                SimpleFieldDataExpander fx(fs[idxFrom]);
                SimpleFieldDataExpander fx1(fs[idxTo]);

                auto idxLower = idxFrom;
                auto idxUpper = idxTo;
                auto fLower = fs[idxLower];
                auto fUpper = fs[idxUpper];
                auto pLower = plVec[idxLower];
                auto pUpper = plVec[idxUpper];
                if (fLower->valueNum() != num) {
                    throw MvException("Pressure level=" + std::to_string(plVec[idxLower]) + "Pa has unexpected different number of points! " +
                                      std::to_string(fLower->valueNum()) + "!=" + std::to_string(num));
                }
                if (fUpper->valueNum() != num) {
                    throw MvException("Pressure level=" + std::to_string(plVec[idxUpper]) + "Pa has unexpected different number of points! " +
                                      std::to_string(fUpper->valueNum()) + "!=" + std::to_string(num));
                }

                interpolate(pres,
                            pUpper, pLower, fUpper->values(), fLower->values(),
                            fRes, num);
            }
        }

        // pres is now in Pa
        if (pres >= 100.) {
            std::string levType = "isobaricInhPa";
            size_t len = levType.size();
            grib_set_string(fRes->handle, "typeOfLevel", levType.c_str(), &len);
            grib_set_long(fRes->handle, "level", static_cast<long>(pres) / 100.);
        }
        else {
            std::string levType = "isobaricInPa";
            size_t len = levType.size();
            grib_set_string(fRes->handle, "typeOfLevel", levType.c_str(), &len);
            grib_set_long(fRes->handle, "level", static_cast<long>(pres));
        }

        MarsComputeFlagDisabler mflagDisabler;
        set_field(result, fRes, i);
        save_fieldset(result);
    }

    return result;
}

//===============================
//
// Standalone functions
//
//===============================

fieldset* pressureOnMl(fieldset* lnspFs, int lnspId, bool layer, const std::vector<int>& levels)
{
    if (lnspFs->count < 1) {
        throw MvException("No fields found in lnsp fieldset!");
    }

    SimpleFieldset lnsp(lnspFs);
    assert(lnspFs->count >= 1);

    // define the resulting fieldset
    fieldset* result = new_fieldset(0);

    for (std::size_t i = 0; i < lnsp.size(); i++) {
        // compute surface pressure (Pa). We store the values in the lnsp values pointer and
        // make sure this is not written back to the file.
        auto sp = lnsp[i];
        SimpleFieldDataExpander fx(sp);

        if (sp->paramId() != lnspId) {
            throw MvException("paramId in lnsp field does not match expected value! " +
                              std::to_string(sp->paramId()) + " != " + std::to_string(lnspId));
        }

        sp->transformValues(std::exp);
        std::size_t num = sp->valueNum();
        assert(num > 0);

        // determine target levels
        std::vector<int> targetLevels = levels;
        if (levels.empty()) {
            targetLevels.resize(sp->totalMlLevelNum());
            std::iota(targetLevels.begin(), targetLevels.end(), 1);
        }

        for (std::size_t k = 0; k < targetLevels.size(); k++) {
            int ml = targetLevels[k];
            if (ml < 1 || ml > sp->totalMlLevelNum()) {
                throw MvException("Invalid target model level=" + std::to_string(ml) + " specified!");
            }

            // create resulting field
            field* fRes = sp->cloneRaw(false);
            // mean pressure on model level
            if (!layer) {
                sp->meanMlPressureFromSp(sp->values(), fRes->values, num, ml);
                // size of the pressure layer (=thickness) around the model level
            }
            else {
                sp->mlPressureLayerFromSp(sp->values(), fRes->values, num, ml);
            }

            // set metadata
            grib_set_long(fRes->handle, "level", ml);
            grib_set_long(fRes->handle, "paramId", 54);
            grib_set_long(fRes->handle, "generatingProcessIdentifier", 128);

            add_field(result, fRes);
            save_fieldset(result);
        }
    }

    return result;
}

fieldset* pressureOnMl(fieldset* lnspFs, int lnspId, bool layer, fieldset* fs)
{
    SimpleFieldset levFs(fs);
    std::vector<int> levels;

    // check levels
    for (std::size_t i = 0; i < levFs.size(); i++) {
        levels.push_back(levFs[i]->level());
    }
    return pressureOnMl(lnspFs, lnspId, layer, levels);
}

fieldset* verticalIntegralPl(fieldset* dataFs)
{
    if (dataFs->count < 2) {
        throw MvException("At least two pressure level fields are required!");
    }

    SimpleFieldset fs(dataFs);

    // get pressure levels values in Pa
    fs.sortLevelsByPa(false);

    // get pressure levels values in Pa
    std::vector<long> presVec;
    for (std::size_t i = 0; i < fs.size(); i++) {
        auto md = fs[i]->metaData();
        if (!md->isSpectral()) {
            presVec.emplace_back(md->levelInPa());
        }
        else {
            throw MvException("Spherical harmonics fields are not supported! Level=" + std::to_string(md->level()));
        }
    }

    assert(fs.size() == presVec.size());

    // check pressure level validity
    if (presVec.size() < 2) {
        throw MvException("Pressure level computations need at least two non-spectral fields! " +
                          std::to_string(presVec.size()) + " fields found!");
    }

    for (size_t i = 1; i < presVec.size(); i++) {
        if (presVec[i] == presVec[i - 1]) {
            throw MvException("Pressure level " + std::to_string(presVec[i]) +
                              " Pa appears multiple times. Pressure levels must be unique in input data!");
        }
    }

    // create resulting field
    field* fRes = fs[0]->cloneRaw(false);

    // init zero values
    SimpleField::setValues(fRes, 0.);

    std::size_t num = fRes->value_count;

    for (size_t i = 0; i < fs.size(); i++) {
        SimpleFieldDataExpander fx(fs[i]);

        auto md = fs[i]->metaData();
        if (!md->isPressureLevel()) {
            throw MvException("For pressure level integral all fields must be defined on pressure levels!");
        }

        if (fs[i]->valueNum() != num) {
            throw MvException("Field on level=" + std::to_string(md->level()) + " has different number of points than expected! " +
                              std::to_string(fs[i]->valueNum()) + " != " + std::to_string(num));
        }

        assert(fs[i]->valueNum() == num);

        double dp = 0.;
        if (i == 0) {
            dp = (presVec[0] - presVec[1]) / 2;
        }
        else if (i + 1 == presVec.size()) {
            dp = (presVec[i - 1] - presVec[i]) / 2;
        }
        else {
            dp = (presVec[i - 1] - presVec[i + 1]) / 2.;
        }

        if (dp <= 0.) {
            throw MvException("Invalid pressure layer thickness=" + std::to_string(dp) + " Pa found!");
        }

        assert(dp > 0.);
        double* vals = fs[i]->values();
        for (std::size_t k = 0; k < num; k++) {
            if (!MISSING_VALUE(fRes->values[k])) {
                if (!MISSING_VALUE(vals[k])) {
                    fRes->values[k] += vals[k] * dp;
                }
                else {
                    fRes->values[k] = mars.grib_missing_value;
                    fRes->bitmap = 1;
                }
            }
        }
    }

    SimpleField::scaleValues(fRes, 1. / MvSci::cEarthG);

    fieldset* result = new_fieldset(0);
    add_field(result, fRes);
    save_fieldset(result);

    return result;
}

fieldset* verticalIntegralMl(fieldset* dataFs, fieldset* lnspFs, int lnspId, int topMl, int bottomMl)
{
    SimpleFieldset fs(dataFs);
    SimpleFieldPtr sp = nullptr;
    if (!lnspFs) {
        for (std::size_t i = 0; i < fs.size(); i++) {
            if (fs[i]->paramId() == lnspId) {
                sp = fs[i];
                assert(sp->rawField()->shape == packed_file);
                if (dataFs->count - 1 < 2) {
                    throw MvException("At least one model level field is required on top of LNSP!");
                }
                break;
            }
        }
    }
    else {
        if (dataFs->count < 1) {
            throw MvException("At least one model level field is required!");
        }
        if (lnspFs->count < 1) {
            throw MvException("LNSF fieldset seems to be empty!");
        }
        sp = SimpleField::make(lnspFs->fields[0]);
        assert(sp->rawField()->shape = packed_file);
    }

    if (!sp) {
        throw MvException("No LNSP field found!");
    }

    assert(sp->rawField()->shape != expand_mem);

    if (sp->paramId() != lnspId) {
        throw MvException("LNSP field has a diffrent paramId than expected! " +
                          std::to_string(sp->paramId()) + " != " + std::to_string(lnspId));
    }

    if (sp->isSpectral()) {
        throw MvException("LNSP field cannot be spectral!");
    }

    assert(sp->rawField()->shape = packed_file);

    // compute surface pressure (Pa). We store the values in the lnsp values pointer and
    // make sure this is not written back to the file.
    sp->expandMemory();
    assert(sp->rawField()->shape = expand_mem);
    if (!sp->hasValidValues()) {
        throw MvException("No valid values found in LNSP!");
    }
    sp->transformValues(std::exp);
    std::size_t num = sp->valueNum();
    assert(num > 0);
    assert(sp->rawField()->shape = expand_mem);

    bool limitedRange = false;
    if (topMl >= 0 && bottomMl >= 0) {
        if (sp->totalMlLevelNum() < topMl) {
            throw MvException("Top level=" + std::to_string(topMl) + " cannot be larger than " +
                              std::to_string(sp->totalMlLevelNum()) + " for the input data!");
        }
        if (sp->totalMlLevelNum() < bottomMl) {
            throw MvException("Bottom level=" + std::to_string(bottomMl) + " cannot be larger than " +
                              std::to_string(sp->totalMlLevelNum()) + " for the input data!");
        }
        if (topMl < bottomMl) {
            std::swap(topMl, bottomMl);
        }
        limitedRange = true;
    }

    assert(sp->rawField()->shape = expand_mem);

    // create resulting field from the first non-lnsp field
    field* fRes = nullptr;
    for (size_t i = 0; i < fs.size(); i++) {
        if (fs[i] != sp) {
            fRes = fs[i]->cloneRaw(false);
            assert(sp->rawField()->shape = expand_mem);
            break;
        }
    }

    if (!fRes) {
        throw MvException("Could not create resulting field!");
    }

    // init result to zero values
    SimpleField::setValues(fRes, 0.);

    for (size_t i = 0; i < fs.size(); i++) {
        if (fs[i] != sp &&
            (!limitedRange || (fs[i]->level() >= bottomMl && fs[i]->level() <= topMl))) {
            assert(fs[i]->rawField()->shape = packed_file);
            SimpleFieldDataExpander fx(fs[i]);
            assert(fs[i]->rawField()->shape = expand_mem);

            if (fs[i]->isSpectral()) {
                throw MvException("Data field (level=" + std::to_string(fs[i]->level()) + ") cannot be spectral!");
            }
            if (!fs[i]->isModelLevel()) {
                throw MvException("Data field (level=" + std::to_string(fs[i]->level()) + ") must be defined on a model levels!");
            }
            if (fs[i]->valueNum() != num) {
                throw MvException("Data field (level=" + std::to_string(fs[i]->level()) + ") has different number of points than expected! " +
                                  std::to_string(fs[i]->valueNum()) + " != " + std::to_string(num));
            }
            fs[i]->addMlPressureIntegralDeltaFromSp(sp, fRes->values);
            assert(fs[i]->rawField()->shape = expand_mem);
        }
        if (fs[i] != sp) {
            assert(fs[i]->rawField()->shape = packed_file);
        }
    }

    SimpleField::scaleValues(fRes, 1 / MvSci::cEarthG);

    sp->releaseMemory();
    assert(sp->rawField()->shape = packed_file);

    fieldset* result = new_fieldset(0);
    add_field(result, fRes);
    save_fieldset(result);

    return result;
}

fieldset* verticalIntegral(fieldset* dataFs, fieldset* lnspFs, int lnspId, int topMl, int bottomMl)
{
    // if no lnsp is provided we need to figure out if we have pressure levels
    if (!lnspFs) {
        SimpleFieldset f(dataFs);
        if (f[0]->isPressureLevel()) {
            return verticalIntegralPl(dataFs);
        }
    }
    return verticalIntegralMl(dataFs, lnspFs, lnspId, topMl, bottomMl);
}

}  // namespace metview
