/***************************** 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 *************************************/

#pragma once

#include <memory>
#include <string>
#include <vector>

#include <mars.h>

namespace metview {

class SimpleField;
using SimpleFieldPtr = std::shared_ptr<SimpleField>;

class MarsComputeFlagDisabler
{
public:
    MarsComputeFlagDisabler() : flag_(mars.computeflg) {
        mars.computeflg = 0;
    }
    ~MarsComputeFlagDisabler() {mars.computeflg = flag_;}
protected:
    int flag_{0};
};

class VerticalInterpolation
{
public:
    enum TargetCoordMode {VectorTargetCoord, FieldsetTargetCoord};
    enum SurfaceValueMode {NoSurfaceValue, NumberSurfaceValue, FieldsetSurfaceValue};
    enum VerticalInterpolationMethod {LinearInterpolation, LogInterpolation};
    enum LevelType {NoLevelType, ModelLevel, PressureLevel, HeightLevelABS, HeightLevelAGR};

    VerticalInterpolation(fieldset* srcFs, fieldset* vcFs, fieldset* surfVcFs);
    void setTargetVc(fieldset *fs);
    void setTargetVc(const std::vector<double>&);
    void setSurfaceValues(fieldset* fs);
    void setSurfaceValues(double v);
    virtual fieldset* compute();

protected:
    void computeSurfaceVcMinMax(SimpleFieldPtr surfVcF, double &vmin, double &vmax);
    void computeVcMinMax(SimpleFieldPtr vcF, SimpleFieldPtr surfVcF, double &vmin, double &vmax);
    double scaleVc(double v);
    void scaleVc(SimpleFieldPtr f);
    bool findBracketingIdx(double vcVal, const std::vector<double>& vcMin, const std::vector<double>& vcMax,
                          int& idxFrom, int& idxTo);
    bool findBracketingIdx(double targetMin, double targetMax, const std::vector<double>& vcMin, const std::vector<double>& vcMax,
                                        int& idxFrom, int& idxTo);
    bool findBracketingIdx(double targetVc, const std::vector<double>& vc, int& idxFrom, int& idxTo);

    void interpolate(double targetVcVal, double vc1, double vc2,
                     double* f1, double* f2, field* fRes, size_t num);
    void interpolate(double targetVcVal, double *vcLower, double* vcUpper,
                     double* fLower, double* fUpper, field* fsRes, size_t num, int idx, int idxFrom, int idxTo, double vcMin);
    void 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);

    fieldset* fs_{nullptr};
    fieldset* vcFs_{nullptr};
    fieldset* surfVcFs_{nullptr};

    TargetCoordMode targetVcMode_{VectorTargetCoord};
    std::vector<double> targetVc_;
    fieldset* targetVcFs_{nullptr};
    LevelType targetLevType_{NoLevelType};
    std::string eccTargetLevType_;

    // vc scaling from the target vc units to the units of the vc fields
    bool vcScaleNeeded_{false};
    double vcFactor_{1.};

    SurfaceValueMode surfValMode_{NoSurfaceValue};
    double surfVal_{0};
    fieldset* surfValFs_{nullptr};

    bool srcAscending_{false};
    bool targetAscending_{false};
    VerticalInterpolationMethod interMethod_{LinearInterpolation};

};

class MlToPlInter : public VerticalInterpolation
{
public:
    MlToPlInter(fieldset* fsData, fieldset* lnspFs);
    fieldset* compute() override;

protected:
    fieldset* lnspFs_{nullptr};
};

class MlToHlInter : public VerticalInterpolation
{
public:
    MlToHlInter(fieldset* fsData, fieldset* fsZ, fieldset* fsZs, bool aboveSea,
                VerticalInterpolationMethod interpolationMethod);
};

class PlToPlInter : public VerticalInterpolation
{
public:
    PlToPlInter(fieldset* fsData, VerticalInterpolationMethod interpolationMethod);
    fieldset* compute() override;
};

fieldset* geopotentialOnMl(fieldset* tFs, fieldset* qFs, fieldset* lnspFs, fieldset* zsFs);
fieldset* pressureOnMl(fieldset* lnspFs, int lnspId, bool layer, const std::vector<int>& = {});
fieldset* pressureOnMl(fieldset* lnspFs, int lnspId, bool layer, fieldset* fs);
fieldset* verticalIntegralPl(fieldset* dataFs);
fieldset* verticalIntegralMl(fieldset* dataFs, fieldset* lnspFs, int lnspId, int topMl, int bottomMl);
fieldset* verticalIntegral(fieldset* dataFs, fieldset* lnspFs, int lnspId, int topMl, int bottomMl);

} // interpolation
