/***************************** 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 <functional>
#include <memory>
#include <string>
#include <vector>

#include <mars.h>

namespace metview
{

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

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


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

protected:
    int flag_{0};
};


class TargetVc;

class VerticalInterpolation
{
    friend class Surface;
    friend class Vc;
    friend class TargetVc;
    friend class TargetVcValue;
    friend class TargetVcValueAboveSurf;
    friend class TargetVcFieldAboveSurf;
    friend class TargetVcField;

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);
    virtual ~VerticalInterpolation()=default;
    void setTargetVc(fieldset* fs);
    void setTargetVc(const std::vector<double>&);
    void setSurfaceValues(fieldset* fs);
    void setSurfaceValues(double v);
    virtual fieldset* compute();

protected:
    double toRawVc(double v);
    void toRawVc(SimpleFieldPtr f);
    double fromRawVc(double v);
    void fromRawVc(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, bool firstIdx, bool lastIdx, double vcMin);
    void interpolate(double* targetVcVal, double* vc1, double* vc2,
                     double* f1, double* f2, field* fRes, size_t num, int idx, bool firstIdx, bool lastIdx,
                     double vcMin, double targetVcMin, double targetVcMax);


    void interpolate(TargetVc* targetVcObj, double* vc1, double* vc2,
                     double* f1, double* f2, field* fRes, size_t num, int idx, int idxFrom, int idxTo, double vcMin);


    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_;
    std::string eccPosTargetLevType_;
    std::string eccNegTargetLevType_;

    // vc transform between raw and target vc units (e.g. geopotential and height)
    bool vcTransformNeeded_{false};
    // raw to target
    DoubleFuncPtr1 vcDirectMethod_{nullptr};
    // target to raw
    DoubleFuncPtr1 vcInverseMethod_{nullptr};

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

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

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

protected:
    fieldset* lnspFs_{nullptr};
};

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

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

class PlToHlInter : public GenLevelToHlInter
{
public:
    PlToHlInter(fieldset* fsData, fieldset* fsZ, fieldset* fsZs, bool aboveSea,
                VerticalInterpolationMethod interpolationMethod,  bool geometricheight);
};

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);

}  // namespace metview
