/***************************** 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 <fstream>
#include <iostream>
#include <memory>
#include <limits>
#include <string>
#include <algorithm>

#include "fstream_mars_fix.h"

#include <cfloat>
#include <cmath>
#include <cassert>

#include "Metview.h"
#include "MvGrid.h"
#include "MvException.h"
#include "MvVariant.h"
#include "StatsCompute.h"

const bool cReleaseMemoryTrue = true;
const bool cReleaseMemoryFlag = cReleaseMemoryTrue;

const bool cExpandGridFalse = false;

const double cGridEpsilon = 5e-5;  //-- 0.05 millidegrees
const double cFullGlobeLimit = 359.9;

const double cCDR = atan(1.0) / 45.0;  //-- conversion factor Degrees->Radians
                                       //-- atan(1)/45 = (PI/4)/45 = PI/180

//
//  A single function to maintain:
//  -----------------------------
//   Function 'interpolateValue' is to replace 'interpolate'
//   in ./Macro/bufr.cc' (which has not been updated) and
//   'MvField::interpolateAt' (which has been updated several
//   times).  Rewritten 971212/vk.
//

//   Current implementation functions only with lat/long grids.
//   Allowed scanning modes:
//      - i direction increments from west to east only,
//      - j-direction increment may be either way

//   New general implementation in MvGrid.cc (lat-lon grids
//   still use this external function).           010821/vk

//   Moved inside MvLatLonGrid class in MvGrid.cc, 011025/vk

#if 0

double interpolateValue( field* Grib, double lat, double lon )
{
 //---
 //-- get GRIB field parameters
 //-

  std::cout << "INFO: calling obsolete function interpolateValue(), relinking needed!!!!" << std::endl;

   if( ( Grib->ksec2[0] != 0 ) &&       //-- Data Representation, GRIB Table 6
       ( Grib->ksec2[0] != 10 ) )       //-- for rotated: use rotated coords!
     {
       return DBL_MAX;
     }

   int ipoints = (int) Grib->ksec2[1];  //-- number of points along parallel
   int jpoints = (int) Grib->ksec2[2];  //-- number of points along meridian

   double lat1 = Grib->ksec2[3]/1000.;  //-- latitude of the first grid point
   double lon1 = Grib->ksec2[4]/1000.;  //-- longitude of the first grid point
   double lat9 = Grib->ksec2[6]/1000.;  //-- latitude of the last grid point
   double lon9 = Grib->ksec2[7]/1000.;  //-- longitude of the last grid point

 //---
 //-- Normalize longitudes to minimum positive range
 //-
   if( lon1 > lon9 )      //-- lon9 in western and >180, lon9 in eastern globe
      lon1 -= 360;

   while( lon1 < 0 )      //-- force longitudes >= 0
   {
      lon1 += 360.;
      lon9 += 360.;
   }
   while( lon < lon1 )
      lon += 360.;        //-- now point is either inside or above!

   if( lat1 > lat9 )      //-- prefer lat1=southern, lat9=northern
   {
      double tmp = lat1; lat1 = lat9; lat9 = tmp;
   }

 //---
 //-- Get and check increment values
 //-
   double ires  = Grib->ksec2[8]/1000.; //-- i direction increment
   double ires2 = (lon9-lon1)/(ipoints-1.0);
   if( fabs( ires-ires2 ) > cGridEpsilon )
     {
       std::cout << "use computed i increment: " << ires2 << ", not " << ires << std::endl;
       ires = ires2;
     }

   double jres  = Grib->ksec2[9]/1000.; //-- j direction increment
   double jres2 = (lat9-lat1)/(jpoints-1.0);
   if( fabs( jres-jres2 ) > cGridEpsilon )
     {
       std::cout << "use computed j increment: " << jres2 << ", not " << jres << std::endl;
       jres = jres2;
     }

   if( Grib->ksec2[5] == 0 )            //-- direction increments not given?
   {
      return DBL_MAX;                   //-- if ocean field then no interpolation!
   }

   int scanning_mode = (int) Grib->ksec2[10]; //-- see GRIB Table 8
   switch( scanning_mode )
   {
     case 0:              //-- 00000000: +i -j directions (north->south)
        jres = -jres;
        break;
     case 64:             //-- 01000000: +i +j directions (south->north)
        break;
     default:             //-- others not implemented!
        return DBL_MAX;
   }

 //---
 //-- Check if the point is outside grid
 //-
   char  globe = ipoints*ires > cFullGlobeLimit ? 'y' : 'n';

   if( globe == 'n' && ( lon < lon1 || lon > lon9 ) )
      return DBL_MAX;

   if( lat < lat1 || lat > lat9 )
      return DBL_MAX;

 //---
 //-- Let's find surrounding grid points
 //-
   int  ix1 = int((lon-lon1)/ires); //-- index for column on the east
   int  ix2 = ix1 + 1;              //-- index for column on the west

   if( ix2 > (ipoints-1) )
   {
      if( globe == 'y' )
         ix2 = 0;                   //-- wrap around globe
      else
         ix2 = ix1;                 //-- possible if lon==lon9 (east boundary)
   }

   //-- start either from north or from south depending on scanning mode --
   int  iy1 = int( ( lat - ( jres < 0 ? lat9 : lat1 ) ) / jres );
   int  iy2 = iy1 + 1;

   if( iy2 > (jpoints-1) )
      iy2 = iy1;                    //-- possible at "higher" boarder

 //---
 //-- Get and check grid point values
 //-
   double  grid_11 = Grib->rsec4[ipoints*iy1+ix1];
   double  grid_12 = Grib->rsec4[ipoints*iy1+ix2];
   double  grid_21 = Grib->rsec4[ipoints*iy2+ix1];
   double  grid_22 = Grib->rsec4[ipoints*iy2+ix2];

   if( grid_11 == mars.grib_missing_value ||
       grid_12 == mars.grib_missing_value ||
       grid_21 == mars.grib_missing_value ||
       grid_22 == mars.grib_missing_value    )
   {
      return DBL_MAX;
   }

 //---
 //-- Interpolate
 //-
   double  lon_ix1 = lon1 + ix1*ires;
   double  lat_iy1 = ( jres < 0 ? lat9 : lat1 ) + iy1*jres;

   double w1   = (lon - lon_ix1)/ires;
   double w2   = 1. - w1;
   //double val1 = w2*Grib->rsec4[ipoints*iy1+ix1] + w1*Grib->rsec4[ipoints*iy1+ix2];
   //double val2 = w2*Grib->rsec4[ipoints*iy2+ix1] + w1*Grib->rsec4[ipoints*iy2+ix2];
   double val1 = w2*grid_11 + w1*grid_12;
   double val2 = w2*grid_21 + w1*grid_22;

   w1 = ( lat - lat_iy1 )/jres;
   w2 = 1. - w1;
   return val1*w2 + val2*w1;
}
#endif

//==========================================================================

#if 0
static int inbox(double lon,double lat,double n,double w,double s,double e)
{
    if(lat > n || lat < s) return 0;

    while(e   > w + 360.0) e   -= 360.0; while(e   < w) e   += 360.0;
    while(lon > w + 360.0) lon -= 360.0; while(lon < w) lon += 360.0;

    if(lon > e || lon < w) return 0;

    return 1;
}
#endif

//==========================================================================

void MvFieldSet::_init()
{
    if (!Fs)
        Fs = new_fieldset(0);  // Make sure we have something
    Writable = false;

    Fields = new MvField*[Fs->count];

    for (int i = 0; i < Fs->count; i++) {
        Fields[i] = new MvField;
        Fields[i]->set_libmars_field(Fs->fields[i]);
    }
    Fs->refcnt++;
}

MvFieldSet::MvFieldSet(const char* file)
{
    Fs = read_fieldset(file, nullptr);
    _init();
}

MvFieldSet::MvFieldSet(const MvRequest& r)
{
    Fs = request_to_fieldset(r);
    _init();
}

MvFieldSet::MvFieldSet()
{
    Fs = new_fieldset(0);
    _init();
}

void MvFieldSet::_clean()
{
    int fs_count = Fs->count;
    Fs->refcnt--;
    if (Fs->refcnt == 0)
        free_fieldset(Fs);
    for (int i = 0; i < fs_count; ++i)
        delete Fields[i];
    delete[] Fields;
    Writable = false;
}

MvFieldSet::~MvFieldSet()
{
    _clean();
}

int MvFieldSet::countFields()
{
    return Fs->count;
}

// MvRequest MvFieldSet::getRequest( bool clone ) //original had an unused argument
MvRequest MvFieldSet::getRequest()
{
    return {fieldset_to_request(Fs), false};  //-- clone );
}

MvField& MvFieldSet::operator[](int n)
{
    // Force consruction of request, could be somewhere else ...

    Fields[n]->getRequest();

    return *(Fields[n]);
}

void MvFieldSet::_copy(const MvFieldSet& f)
{
    if (f.Writable)
        Fs = copy_fieldset(f.Fs, f.Fs->count, true);
    else
        Fs = f.Fs;
    save_fieldset(Fs);
}

MvFieldSet::MvFieldSet(const MvFieldSet& f)
{
    _copy(f);
    _init();
}

void MvFieldSet::operator=(const MvFieldSet& f)
{
    _clean();
    _copy(f);
    _init();
}

void MvFieldSet::operator+=(const MvField& f)
{
    //	MvFieldExpander expand(f);
    add_field(Fs, copy_field(f.libmars_field(), true));

    //-- add new field also to 'Fields'
    int cnt = Fs->count;
    auto** tmp = new MvField*[cnt];

    for (int i = 0; i < cnt - 1; i++)  //-- copy existing pointers
        tmp[i] = Fields[i];

    tmp[cnt - 1] = new MvField;
    tmp[cnt - 1]->set_libmars_field(Fs->fields[cnt - 1]);

    delete[] Fields;

    Fields = tmp;
}

//==========================================================================
//

MvFieldSetIterator::MvFieldSetIterator(MvFieldSet& fs)
{
    FieldSet = &fs;
    Count = fs.countFields();
    Current = 0;
    Order = new int[Count];

    for (int i = 0; i < Count; i++)
        Order[i] = i;
}

MvField& MvFieldSetIterator::operator()()
{
    static MvField EmptyField;  // Grib is NULL by default
    MvField* Match = nullptr;

    if (Current >= Count)
        return EmptyField;

    if (Filter) {
        do {
            MvField* f = &((*FieldSet)[Order[Current++]]);
            MvRequest r = f->getRequest();

            if (Filter(r))
                return *f;

        } while (Current < Count && Match == nullptr);

        return EmptyField;  // if there is no match
    }

    //    std::cout << "Iter: "  << Order[Current] << std::endl;
    return (*FieldSet)[Order[Current++]];
}


// The order of equivalent elements must be kept!
void MvFieldSetIterator::sort(const char* keyCh, char order)
{
    assert(keyCh);
    bool ascending = (order == '<');
    std::string key(keyCh);

    // the most typical keys
    static std::map<std::string, std::string> keys{{"EXPVER", "str"}, {"PARAM", "str"}, {"LEVELIST", "long"}, {"STEP", "long"}, {"TIME", "long"}, {"DATE", "date"}, {"NUMBER", "long"}, {"_DIAGITER", "str"}};

    // determine type. By default string value is used
    std::string t = "str";
    auto it = keys.find(key);
    if (it != keys.end()) {
        t = it->second;
    }

    assert(t == "str" || t == "long" || t == "date");

    long save = expand_flags(0);  // So MvDate work..
    std::vector<MvVariant> md(Count);
    std::vector<int> orderVec;
    for (int i = 0; i < Count; i++) {
        int idx = Order[i];
        orderVec.push_back(idx);
        MvField& f = (*FieldSet)[idx];
        MvRequest& r = f.getRequest();
        if (const char* v = get_value(r, keyCh, 0)) {
            if (t == "str") {
                md[idx] = MvVariant(std::string(v));
            }
            else if (t == "long") {
                md[idx] = MvVariant(atol(v));
            }
            else if (t == "date") {
                // date can be a climate date e.g. JAN-16. We convert it into a number for sorting
                int cld = MvDate::climDateNumber(v);
                if (cld != 0) {
                    md[idx] = MvVariant(cld);
                    // dates are treated as string otherwise
                }
                else {
                    md[idx] = MvVariant(std::string(v));
                }
            }
        }
        else {
            if (t == "str" || t == "date") {
                md[idx] = MvVariant("");
            }
            else if (t == "int") {
                md[idx] = MvVariant(std::numeric_limits<double>::min());
            }
        }
    }

    assert(md.size() == orderVec.size());
    assert(orderVec.size() == static_cast<size_t>(Count));

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

    for (std::size_t i = 0; i < orderVec.size(); i++) {
        Order[i] = orderVec[i];
    }
#if 0
    for (std::size_t i=0; i < orderVec.size(); i++) {
       std::cout  << i << " " << orderVec[i] << " " << md[orderVec[i]].toString() << std::endl;
    }
#endif

    expand_flags(save);
}

void MvFieldSetIterator::setFilter(const MvFilter& f)
{
    Filter = f;
}

MvFieldSetIterator::~MvFieldSetIterator()
{
    delete[] Order;
}

//==========================================================================
//
void MvField::ensurePackedMem()
{
    //-- old MARS (based on now obsolete GRIBEX) had three field states:
    //--	- packed_file:	field is on disk as a coded GRIB msg
    //--			 (requires tiny piece of memory and no processing)
    //--	- packed_mem:	field is in memory as a coded GRIB msg
    //--			 (requires some memory and a little processing to read it)
    //--	- expanded_mem:	field is in memory, decoded into obsolete GRIBEX API arrays
    //--			 This field state, with ecCodes, is now obsolete!
    //--
    //-- new MARS (based on ecCodes) uses only the two first field states!
    //--
    //-- This method ensures that the field is in memory (in packed_mem state)
    //-- and thus allows easy access to GRIB1 sections 0 and 1.

    if (mvgrid_->field_->shape == packed_file)
        set_field_state(mvgrid_->field_, packed_mem);
}

MvFieldState::MvFieldState(const MvField& f, field_state s, field_state state_to_revert_to, bool readonlyflag) :
    OldState(state_to_revert_to)
{
    readonly = readonlyflag;
    Field = &(MvField&)f;

    if (f.libmars_field()) {
        if (state_to_revert_to == unknown)  // by default, we will revert to the previous state
            OldState = f.libmars_field()->shape;
        else
            OldState = state_to_revert_to;  // otherwise, use the user-specified one

        set_field_state(f.libmars_field(), s);
    }
}

MvFieldState::~MvFieldState()
{
    // Restore field state
    if (Field->mvgrid_ && Field->mvgrid_->fieldPtr()) {
        auto* g = Field->mvgrid_->fieldPtr();
        assert(g);

        if (readonly) {
            // 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 (OldState == packed_mem) {
                release_mem(g->values);
                g->values = nullptr;
                g->value_count = 0;
                g->shape = packed_mem;
            }
        }
        else {
            set_field_state(g, OldState);
        }
    }
}

//==========================================================================

MvField::MvField()
{
    // Grib = 0;
    // Grd  = 0;
    mvgrid_ = 0;
}

MvField::~MvField()
{
    delete mvgrid_;  // Grd;
}

MvField::MvField(field* f)
{
    // Grib  = f;
    // Grd   = 0;
    mvgrid_ = MvGridFactory(f, cReleaseMemoryFlag, cExpandGridFalse);
}

MvField::MvField(const MvField& f)
{
    // Grib    = f.Grib;
    // Grd     = 0;
    Request = f.Request;
    mvgrid_ = MvGridFactory(f.libmars_field(), cReleaseMemoryFlag, cExpandGridFalse);
}

MvField& MvField::operator=(const MvField& f)
{
    if (&f == this) {
        libmars_field()->refcnt++;
    }
    else {
        if (mvgrid_)
            delete mvgrid_;

        mvgrid_ = MvGridFactory(f.libmars_field(), cReleaseMemoryFlag, cExpandGridFalse);
        Request = f.Request;
    }

    return *this;
}

void MvField::set_libmars_field(field* f)
{
    delete mvgrid_;
    mvgrid_ = MvGridFactory(f, cReleaseMemoryFlag, cExpandGridFalse);
}

MvRequest& MvField::getRequest()
{
    if (Request == nullptr) {
        const bool cThrowOnError = 1;
        std::string strGridType(mvgrid_->gridType());

        //-- field_to_request() does the expansion!!!
        //-- MvFieldLoader loader(*this); // Expand field

        Request = MvRequest(field_to_request(libmars_field()));

        long time = Request("TIME");
        long step = MvField::stepRangeDecoder(Request("STEP"));

        MvDate verif = (long)Request("DATE");
        verif += double((time / 100.0 + step) / 24.0);

        Request("VERIF") = verif;

        Request("DERIVED") = isDerived();  // we will need this so that we know whether to apply automatic scaling or not


        if (strGridType == cSatelliteImage) {
            Request("REPRES") = "SV";
            Request("_IMAGE_MAP_COLUMNS") = mvgrid_->getLong("numberOfPointsAlongXAxis", cThrowOnError);
            Request("_IMAGE_MAP_ROWS") = mvgrid_->getLong("numberOfPointsAlongYAxis", cThrowOnError);
            Request("_IMAGE_MAP_SUB_SAT_LATITUDE") = mvgrid_->getLong("latitudeOfSubSatellitePoint", cThrowOnError);
            Request("_IMAGE_MAP_SUB_SAT_LONGITUDE") = mvgrid_->getLong("longitudeOfSubSatellitePoint", cThrowOnError);
            Request("_IMAGE_MAP_X_EARTH_DIAMETER") = mvgrid_->getLong("dx", cThrowOnError);
            Request("_IMAGE_MAP_Y_EARTH_DIAMETER") = mvgrid_->getLong("dy", cThrowOnError);
            Request("_IMAGE_MAP_SUB_SAT_X") = mvgrid_->getLong("XpInGridLengths", cThrowOnError);
            Request("_IMAGE_MAP_SUB_SAT_Y") = mvgrid_->getLong("YpInGridLengths", cThrowOnError);
            Request("_IMAGE_MAP_GRID_ORIENTATION") = mvgrid_->getLong("orientationOfTheGrid", cThrowOnError);
            Request("_IMAGE_MAP_CAMERA_ALTITUDE") = mvgrid_->getLong("NrInRadiusOfEarth", cThrowOnError);
            Request("_IMAGE_MAP_INITIAL_COLUMN") = mvgrid_->getLong("xCoordinateOfOriginOfSectorImage", cThrowOnError);
            Request("_IMAGE_MAP_INITIAL_ROW") = mvgrid_->getLong("yCoordinateOfOriginOfSectorImage", cThrowOnError);
        }
    }

    return Request;
}

MvField::operator void*()
{
    return libmars_field();
}
int MvField::operator!()
{
    return libmars_field() == nullptr;  // NULL;
}


// double MvField::modelLevelToPressureexp(double sp, int ml)
double MvField::meanML_to_Pressure_bySP(double sp, int ml)
{
    //-- if GRIB header contains vertical coordinate
    //-- coefficient only for a single level then
    //-- method ML_to_Pressure_bySP() will be called

    double p = 0;
    if (mvgrid_->vertCoordCoefPairCount() > 1) {
        double C11, C12, C21, C22;

        mvgrid_->vertCoordCoefs(C11, C21, ml - 1);
        mvgrid_->vertCoordCoefs(C12, C22, ml);

        p = meanPressureLevel(C11, C12, C21, C22, sp);
    }
    else {
        p = ML_to_Pressure_bySP(sp, ml);
    }

    return p;

#if 0
   MvFieldExpander expand( *this ); //-- It is cheaper now, because we are
	                            //-- not packing anymore

   fortfloat* vert  = &Grib->rsec2[10];
   int        nb    = (int)Grib->ksec2[11]/2-1;
   double     plev  = 0;

   if( nb > 0 )
     {
       plev = meanPressureLevel( vert[ml-1],  vert[ml],
                                 vert[ml+nb], vert[ml+nb+1], sp );
     }
   else
     {
       plev = ML_to_Pressure_bySP( sp, ml );
     }

   return plev;
#endif
}

// double MvField::modelLevelToPressure(double lnsp, int ml)
double MvField::meanML_to_Pressure_byLNSP(double lnsp, int ml)
{
    return meanML_to_Pressure_bySP(exp(lnsp), ml);
}

// double MvField::modelLevelToPressure2(double lnsp, int ml)
double MvField::ML_to_Pressure_byLNSP(double lnsp, int ml_in)
{
    return ML_to_Pressure_bySP(exp(lnsp), ml_in);
}

double MvField::ML_to_Pressure_bySP(double sp, int ml_in)
{
    //-- this one works also for fields that have been encoded
    //-- with only one pair of coefficients in GRIB section 2
    //--   (in which case input parameter ml_in is dummy!)

    double C1, C2;
    mvgrid_->vertCoordCoefs(C1, C2, ml_in);
    return pressureLevel(C1, C2, sp);

#if 0
   assert(Grib->rsec2);

   fortfloat* vert = &Grib->rsec2[10];          //-- start of coefficient pairs
   int        nb   = (int) Grib->ksec2[11]/2-1; //-- nr of coefficient pairs
   int        ml   = nb == 0 ? 0 : ml_in;       //-- if only one level coefficients

   return pressureLevel( vert[ml], vert[ml+1+nb], sp );
#endif
}

bool MvField::vertCoordCoefs(int level, double& C1, double& C2)
{
    if (!isModelLevel()) {
        std::cerr << "MvField::vertCoordCoefs: not a model level field" << std::endl;
        return false;
    }

    return mvgrid_->vertCoordCoefs(C1, C2, level);

#if 0
   assert(Grib->rsec2);

   fortfloat* vert = &Grib->rsec2[10];          //-- start of coefficient pairs
   int        nb   = vertCoordCoefPairCount();
   if( level > nb )
     {
       std::cerr << "MvField::vertCoordCoefs: not enough coefficients in GRIB header" << std::endl;
       return false;
     }

   int ml = level;
   C1 = vert[ml];
   C2 = vert[ml+nb];

   return true;
#endif
}

bool MvField::vertCoordCoefs(double& C1, double& C2)
{
    if (!isModelLevel()) {
        std::cerr << "MvField::vertCoordCoefs: not a model level field" << std::endl;
        return false;
    }

    int lev = mvgrid_->vertCoordCoefPairCount() == 1 ? 0 : (int)level();

    return mvgrid_->vertCoordCoefs(C1, C2, lev);

#if 0
   assert(Grib->rsec2);

   fortfloat* vert = &Grib->rsec2[10];          //-- start of coefficient pairs
   int        nb   = vertCoordCoefPairCount();
   int        ml   = nb == 1 ? 0 : (int)level();//-- if only one level coefficients

   C1 = vert[ml];
   C2 = vert[ml+nb];

   return true;
#endif
}

int MvField::vertCoordCoefPairCount()
{
    if (!isModelLevel())
        return 0;

    return mvgrid_->vertCoordCoefPairCount();

    // assert(Grib->ksec2);
    //-- C array index 11 = Fortran array index 12 (Emoslib API)
    // return (int)Grib->ksec2[11]/2;
}

// Perform computations along a direction (ew or ns) within an area
bool MvField::computeAlong(double* val,
                           double X1, double Y1,
                           double X2, double Y2,
                           int equat, int npoints,
                           double res, bool cUseNearestPoint,
                           double* coords,
                           metview::StatsComputePtr comp)
{
    if (!comp) {
        return false;
    }

    mvgrid_->init();

    if (!mvgrid_->hasLocationInfo()) {
        marslog(LOG_EROR, "computeAlong(): unimplemented or spectral data - unable to extract location data");
        return false;
    }

    double x, y, rx, ry;
    double cang, myValue;
    int i = 0, j = 0;

    // Set resolution. If it is not given, try to compute it.
    if (res == 0.) {
        rx = gridWE();
        ry = gridNS();

        // 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 Xsect using averageAlong (020731/vk)
        if (rx == cValueNotGiven)
            rx = ry;

        if (ry == cValueNotGiven) {
            rx = ry = 1.0;
            marslog(LOG_WARN, "computeAlong(): grid interval set to 1.0/1.0");
        }
    }
    else
        rx = ry = res;

    if (ry < 0)
        ry = -ry;  //-- internal grid scan mode S->N

    double last_X =
        X2 > X1 + cFullGlobeLimit ? X2 - rx : X2;  //-- do not duplicate edges if global

    if (equat == 1)  //-- E->W
    {
        for (i = 0, y = Y1; y >= Y2;) {
            if (i == npoints) {
                marslog(LOG_EROR, "computeAlong(): too small output array!");
                return false;
            }


            val[i] = DBL_MAX;
            comp->reset();

            for (j = 0, x = X1; x <= last_X;) {
                if (cUseNearestPoint) {
                    MvGridPoint myPoint = mvgrid_->nearestGridpoint(y, x, false);
                    myValue = myPoint.value_;
                }
                else
                    myValue = mvgrid_->interpolatePoint(y, x);

                if ((myValue != DBL_MAX) && !(MISSING_VALUE(myValue))) {
                    comp->add(myValue);
                }

                j++;
                x = X1 + (j * rx);  // do not add within the loop becuase of cumulative errors
            }

            val[i] = comp->compute(false);

            if (coords)  // save geographical coordinates
                coords[i] = y;

            i++;
            y = Y1 - (i * ry);  // do not add within the loop becuase of cumulative errors
        }
    }

    else  //-- N->S
    {
        for (x = X1, i = 0; x <= X2;) {
            if (i == npoints) {
                marslog(LOG_EROR, "computeAlong(): too small output array!");
                return false;
            }

            comp->reset();

            for (j = 0, y = Y1; y >= Y2;) {
                cang = cos(cCDR * y);
                if (cUseNearestPoint) {
                    MvGridPoint myPoint = mvgrid_->nearestGridpoint(y, x, false);
                    myValue = myPoint.value_;
                }
                else {
                    myValue = mvgrid_->interpolatePoint(y, x);
                }
                if ((myValue != DBL_MAX) && !(MISSING_VALUE(myValue))) {
                    comp->add(myValue, cang);
                }

                j++;
                y = Y1 - (j * ry);  // do not add within the loop becuase of cumulative errors
            }

            val[i] = comp->compute(true);
            if (coords)  // save geographical coordinates
                coords[i] = x;

            i++;
            x = X1 + (i * rx);  // do not add within the loop becuase of cumulative errors
        }
    }
    return true;
}

bool MvField::averageAlong(double* val,
                           double X1, double Y1,
                           double X2, double Y2,
                           int equat, int npoints,
                           double res, bool cUseNearestPoint,
                           double* coords)
{
    auto comp = metview::StatsComputePtr(new metview::MeanStatsCompute());
    return computeAlong(val, X1, Y1, X2, Y2, equat, npoints, res, cUseNearestPoint, coords, comp);
}

//-------
//------ averageAt() works fine when area coordinates fit
//----- with the underlying lat-lon grid points; integrate()
//---- works fine also when area and grid points mismatch!
//---
//-- averageAt() is not capable to handle grids other than
//- non-rotated lat-lon; integrate() is much more versatile

double MvField::averageAt(double X1, double Y1, double X2, double Y2)
{
#if 1
    return integrate(Y1, X1, Y2, X2);
#else
    MvFieldExpander expand(*this);  //-- Expand field
    if (!isLatLon()) {
        marslog(LOG_EROR, "old MvField::averageAt() works only woth lat-lon data");
        return -9999 - level();  // DBL_MAX; //-- MagicsEngine starts looping with DBL_MAX!
    }
    //-- Where is this point in matrix ?
    int nhcells = (int)Grib->ksec2[1];
    int nvcells = (int)Grib->ksec2[2];

    int ix, iy;            // cell index that contains point
    double x1, y1, y1min;  // coordinates of cell that contains point
    double rx, ry;         // cell resolution
    double x, y, xx;

    x1 = Grib->ksec2[4] / 1000.;
    y1 = Grib->ksec2[3] / 1000.;
    rx = gridWE();  //- Grib->ksec2[8]/1000.;
    ry = gridNS();  //- Grib->ksec2[9]/1000.;
    if (ry < 0) {
        marslog(LOG_EROR, "old MvField::averageAt() cannot handle data scanning South->North");
        return -9999 - level();  // DBL_MAX; //-- MagicsEngine starts looping with DBL_MAX!
    }
    y1min = y1 - (nvcells - 1) * ry;

    int globe = FALSE;
    if (nhcells * rx > cFullGlobeLimit)
        globe = TRUE;

    int i, j, samples = 0;
    double cang,
        weight = 0.,
        val = 0.;

    for (y = Y1, i = 0; y >= Y2;)  //-- Scan East->West
    {
        if (y > y1 || y <= y1min)
            continue;

        iy = int((y1 - y) / ry);
        cang = cos(cCDR * y);
        for (x = X1, j = 0; x <= X2;) {
            xx = x;

            if (globe)  //-- Transform longitude from -180<->180 to 0<->360
            {
                if (xx < 0.)
                    xx = 360. + xx;
                else if (xx < 180. && x1 > 180.)
                    xx += 360.;
            }

            if (xx < x1)
                continue;

            if (xx > (nhcells - 1) * rx + x1 && !globe)
                continue;

            ix = int((xx - x1) / rx);
#if 1
            //-- this block for debugging...
            int myInd = nhcells * iy + ix;
            double myVal = Grib->rsec4[myInd];
            val += cang * myVal;
#else
            val += cang * Grib->rsec4[nhcells * iy + ix];
#endif
            weight += cang;
            samples++;

            j++;
            x = X1 + (j * rx);  // do not add within the loop becuase of cumulative errors
        }

        i++;
        y = Y1 - (i * ry);  // do not add within the loop becuase of cumulative errors
    }
    if (samples)
        val /= weight;
    else
        val = DBL_MAX;
    // std::cout << samples << '\t' << val << std::endl;
    return val;
#endif
}

double MvField::interpolateAt(double x_lon, double y_lat)
{
    mvgrid_->init();

    return mvgrid_->interpolatePoint(y_lat, x_lon);
}

bool MvField::getMatrixNN(double x_lon, double y_lat, MvMatrix& mat)
{
    mvgrid_->init();

    return mvgrid_->getMatrixNN(y_lat, x_lon, mat);
}

double MvField::nearestGridpoint(double x_lon, double y_lat, bool nearestValid)
{
    mvgrid_->init();

    MvGridPoint gp = mvgrid_->nearestGridpoint(y_lat, x_lon, nearestValid);

    return gp.value_;
}

MvGridPoint MvField::nearestGridpointInfo(double x_lon, double y_lat, bool nearestValid)
{
    mvgrid_->init();

    MvGridPoint gp = mvgrid_->nearestGridpoint(y_lat, x_lon, nearestValid);

    return gp;
}


#if 0
// Where is this point in matrix ?

    int nhcells = (int) Grib->ksec2[1];
    int nvcells = (int) Grib->ksec2[2];

    int	ix, iy, ix1, iy1;	// cell index that contains point
    double	x1, y1;	// coordinates of cell that contains point
    double	rx, ry;	// cell resolution

    double w1, w2;
    x1 = Grib->ksec2[4]/1000.;
    y1 = Grib->ksec2[3]/1000.;

    rx = gridWE();   //- Grib->ksec2[8]/1000.;
    ry = gridNS();   //- Grib->ksec2[9]/1000.;

    int globe = FALSE;
    if (nhcells*rx > cFullGlobeLimit)
      globe = TRUE;

// Transform longitude from -180<->180 to 0<->360

    /* Bug fix for:
    if (x < 0.) x = 360. + x;
    else if (x<180. && x1>180.) x += 360.;
    */
    if (globe )
    {
        if( x < 0.) x = 360. + x;
        else if (x<180. && x1>180.) x += 360.;
    }

// Check if the point is inside grid

    if (x < x1)                          return DBL_MAX;
    if (x > (nhcells-1)*rx+x1 && !globe) return DBL_MAX;
    if (y > y1 || y < y1-(nvcells-1)*ry) return DBL_MAX;

// Let's find which cell contains the point

    ix = int((x-x1)/rx);

    double xx = x+rx;
    // Arne's bugfix
    //if (xx > (nhcells-1)*rx+x1)
    if (xx >= nhcells*rx+x1)
        if (globe) ix1 = 0;
        else
        ix1 = ix;
    else
    ix1 = int((xx-x1)/rx);

    iy = int((y1-y)/ry);

    double yy = y-ry;
    if (yy < y1-(nvcells-1)*ry) iy1 = iy;
    else
    iy1 = int((y1-yy)/ry);

    x1 += ix*rx;
    y1 -= iy*ry;

    w1 = (x - x1)/rx;
    w2 = 1. - w1;

    int		ix_found;
    ix_found = (w1 < w2) ? ix : ix1;

    w1 = (y1 - y)/ry;
    w2 = 1. - w1;

    int		iy_found;
    iy_found =  (w1 < w2) ? iy : iy1;

    return Grib->rsec4[nhcells*iy_found+ix_found];

}
#endif


double MvField::cornerGridpoint(double x, double y, int c)
{
    //	Find nearest  grid point

    //	MvFieldExpander expand(*this); // Expand field

    // Where is this point in matrix ?

    int nhcells = mvgrid_->getLong("numberOfPointsAlongAParallel");  // (int) Grib->ksec2[1];
    int nvcells = mvgrid_->getLong("numberOfPointsAlongAMeridian");  // (int) Grib->ksec2[2];

    int ix = 0, iy = 0, ix1 = 0, iy1 = 0;  // cell index that contains point
    double x1, y1;                         // coordinates of cell that contains point
    double rx, ry;                         // cell resolution
    double val1, val2;

    double w1 = 0.5, w2 = 0.5;
    x1 = mvgrid_->getDouble("longitudeOfFirstGridPointInDegrees");  // Grib->ksec2[4]/1000.;
    y1 = mvgrid_->getDouble("latitudeOfFirstGridPointInDegrees");   // Grib->ksec2[3]/1000.;

    rx = gridWE();  //- Grib->ksec2[8]/1000.;
    ry = gridNS();  //- Grib->ksec2[9]/1000.;

    int globe = FALSE;
    if (nhcells * rx > cFullGlobeLimit)
        globe = TRUE;

    // Transform longitude from -180<->180 to 0<->360

    if (x < 0.)
        x = 360. + x;
    else if (x < 180. && x1 > 180.)
        x += 360.;

    // Check if the point is inside grid

    if (x < x1)
        return DBL_MAX;
    if (x > (nhcells - 1) * rx + x1 && !globe)
        return DBL_MAX;
    if (y > y1 || y < y1 - (nvcells - 1) * ry)
        return DBL_MAX;

    // Let's find which cell contains the point

    ix = int((x - x1) / rx);

    double xx = x + rx;
    if (xx > (nhcells - 1) * rx + x1)
        if (globe)
            ix1 = 0;
        else
            ix1 = ix;
    else
        ix1 = int((xx - x1) / rx);

    iy = int((y1 - y) / ry);

    double yy = y - ry;
    if (yy < y1 - (nvcells - 1) * ry)
        iy1 = iy;
    else
        iy1 = int((y1 - yy) / ry);

    //    x1 += ix * rx;
    //    y1 -= iy * ry;

    int ix_found = 0, iy_found = 0;
    if (c == 0) {
        ix_found = ix;
        iy_found = iy;
        val1 = (*mvgrid_)[nhcells * iy_found + ix_found];
    }
    else if (c == 1) {
        ix_found = ix;
        iy_found = iy1;
        val1 = (*mvgrid_)[nhcells * iy_found + ix_found];
    }
    else if (c == 2) {
        ix_found = ix1;
        iy_found = iy1;
        val1 = (*mvgrid_)[nhcells * iy_found + ix_found];
    }
    else if (c == 3) {
        ix_found = ix1;
        iy_found = iy;
        val1 = (*mvgrid_)[nhcells * iy_found + ix_found];
    }
    else {
        val1 = w2 * (*mvgrid_)[nhcells * iy + ix] + w1 * (*mvgrid_)[nhcells * iy + ix1];

        val2 = w2 * (*mvgrid_)[nhcells * (iy1) + ix] + w1 * (*mvgrid_)[nhcells * (iy1) + ix1];
        val1 = w1 * val1 + w2 * val2;
    }

    return val1;
}


double MvField::computeInArea(double north, double west, double south, double east, bool weighted, StatsComputePtr comp)
{
    assert(comp);
    MvGeoBox geoArea(north, west, south, east);

    mvgrid_->init();

    if (!mvgrid_->hasLocationInfo()) {
        marslog(LOG_EROR, "integrate(): unimplemented or spectral data - unable to extract location data");
        return DBL_MAX;
    }

    // The init function is not expanding the field.
    // So, we must expand it here before accessing its values
    MvFieldExpander x(*this);

    comp->reset();
    comp->setInvalidValue(mars.grib_missing_value);

    for (int j = 0; j < mvgrid_->length(); ++j) {
        if (geoArea.isInside(mvgrid_->lat_y(), mvgrid_->lon_x())) {
            double v = mvgrid_->value();  //-- for debugging

            if (!MISSING_VALUE(v))  // only consider the non-missing values
            {
                if (weighted) {
                    double w = mvgrid_->weight();  //-- for debugging
                    comp->add(v, w);
                }
                else {
                    comp->add(v);
                }
            }
        }
        mvgrid_->advance();
    }
    return comp->compute(weighted);
}

double MvField::integrate(double north, double west, double south, double east)
{
    auto comp = StatsComputePtr(new MeanStatsCompute());
    return computeInArea(north, west, south, east, true, comp);
}

//-- should be checked and tested!!! vk/981106...

double MvField::stdev(double n, double w, double s, double e)
{
    MvGeoBox geoArea(n, w, s, e);

    mvgrid_->init();

    if (!mvgrid_->hasLocationInfo()) {
        marslog(LOG_EROR, "stdev(): unimplemented or spectral data - unable to extract location data");
        return DBL_MAX;
    }

    double val = 0;
    double wght = 0;
    double sum = 0;
    double sum2 = 0;
    int pcnt = 0;

    for (int j = 0; j < mvgrid_->length(); ++j) {
        if (geoArea.isInside(mvgrid_->lat_y(), mvgrid_->lon_x())) {
            double w = mvgrid_->weight();  //-- for debugging
            double v = mvgrid_->value();   //-- for debugging

            wght += w;
            sum += w * v;
            sum2 += w * v * v;
            ++pcnt;
        }

        mvgrid_->advance();
    }

    if (wght)
        val = sqrt(sum2 / wght - (sum / wght) * (sum / wght));

    return val;
}

//-- should be checked and tested!!! vk/981106...

double MvField::covar(MvField& other, double n, double w, double s, double e)
{
    MvGeoBox geoArea(n, w, s, e);

    mvgrid_->init();

    if (!mvgrid_->hasLocationInfo()) {
        marslog(LOG_EROR, "covar(): unimplemented or spectral data - unable to extract location data");
        return DBL_MAX;
    }

    //-- cReleaseMemoryFalse or cReleaseMemoryTrue???
    std::unique_ptr<MvGridBase> grd2(MvGridFactory(other.libmars_field(), cReleaseMemoryTrue));

    if (!mvgrid_->isEqual(grd2.get())) {
        marslog(LOG_EROR, "Cannot compute covariance between different grids!");
        return DBL_MAX;
    }

    double val = 0;
    double wght = 0;
    double sumx = 0;
    double sumy = 0;
    double sumxy = 0;
    int pcnt = 0;

    for (int j = 0; j < mvgrid_->length(); ++j) {
        if (geoArea.isInside(mvgrid_->lat_y(), mvgrid_->lon_x())) {
            double w1 = mvgrid_->weight();  //-- for debugging
            double v1 = mvgrid_->value();   //-- for debugging
            double w2 = grd2->weight();     //-- for debugging
            double v2 = grd2->value();      //-- for debugging

            wght += w1;
            sumx += w1 * v1;
            sumy += w2 * v2;
            sumxy += w1 * v1 * v2;
            ++pcnt;
        }

        mvgrid_->advance();
        grd2->advance();
    }

    if (wght)
        val = sumxy / wght - (sumx / wght) * (sumy / wght);

    return val;
}


#if 0

class MvFieldCreator : public MvObjectCreator {
	MvObject *createObject(MvRequest& r)  { MvFieldSet *fs = new MvFieldSet(r); r.next(); return fs; }
	MvFieldCreator() : MvObjectCreator("GRIB") {};
};

static MvFieldCreator GRIBCreator;

#endif


bool MvField::isLatLon()
{
    // gribsec2_ll *s2 = (gribsec2_ll*) &Grib->ksec2[0];
    // return (s2->data_rep == GRIB_LAT_LONG);
    // return mvgrid_->getLong("dataRepresentationType") == GRIB_LAT_LONG;
    return mvgrid_->gridType() == cLatLonGrid;
}

int MvField::numberOfLat()
{
    // gribsec2_ll *s2 = (gribsec2_ll*) &Grib->ksec2[0];
    // return s2->points_meridian;
    return mvgrid_->getLong("numberOfPointsAlongAMeridian");
}

int MvField::numberOfLon()
{
    // gribsec2_ll *s2 = (gribsec2_ll*) &Grib->ksec2[0];
    // return s2->points_parallel;
    return mvgrid_->getLong("numberOfPointsAlongAParallel");
}

double MvField::north()
{
    // gribsec2_ll *s2 = (gribsec2_ll*) &Grib->ksec2[0];
    // return (double)s2->limit_north/(double)GRIB_FACTOR;
    return mvgrid_->getDouble("latitudeOfFirstGridPointInDegrees");
}

double MvField::south()
{
    // gribsec2_ll *s2 = (gribsec2_ll*) &Grib->ksec2[0];
    // return (double)s2->limit_south/(double)GRIB_FACTOR;
    return mvgrid_->getDouble("latitudeOfLastGridPointInDegrees");
}

double MvField::east()
{
    // gribsec2_ll *s2 = (gribsec2_ll*) &Grib->ksec2[0];
    // return (double)s2->limit_east/(double)GRIB_FACTOR;
    return mvgrid_->getDouble("longitudeOfLastGridPointInDegrees");
}

double MvField::west()
{
    // gribsec2_ll *s2 = (gribsec2_ll*) &Grib->ksec2[0];
    // return (double)s2->limit_west/(double)GRIB_FACTOR;
    return mvgrid_->getDouble("longitudeOfFirstGridPointInDegrees");
}


double MvField::gridNS()
{
    // TODO: implement this method in MvGrid since it has to be grid specific!!!
    double suppliedDy = mvgrid_->getDouble("jDirectionIncrementInDegrees");
    if (fabs(suppliedDy) > 180.) {
        suppliedDy = cValueNotGiven;
    }

    bool scanNeg = (mvgrid_->getLong("jScansPositively") != 0);
    double northVal = north();
    double southVal = south();
    long numLat = numberOfLat();
    double computed = cValueNotGiven;
    if (northVal != cValueNotGiven && southVal != cValueNotGiven &&
        numLat != GRIB_MISSING_LONG) {
        computed = (northVal - southVal) / (numLat - 1);
    }

    if (suppliedDy != cValueNotGiven && computed != cValueNotGiven) {
        // This is the case when GRIB header precision is too small
        if (fabs(computed - suppliedDy) > cGridEpsilon) {
            return computed;
        }
        else {
            if (scanNeg) {
                suppliedDy = -suppliedDy;
            }
            return suppliedDy;
        }
    }
    else if (suppliedDy != cValueNotGiven) {
        if (scanNeg) {
            suppliedDy = -suppliedDy;
        }
        return suppliedDy;
    }
    else if (computed != cValueNotGiven) {
        return computed;
    }

    return cValueNotGiven;
}

double MvField::gridWE()
{
    // TODO: implement this method in MvGrid since it has to be grid specific!!!
    double suppliedDx = mvgrid_->getDouble("iDirectionIncrementInDegrees");
    if (fabs(suppliedDx) > 360.) {
        suppliedDx = cValueNotGiven;
    }

    bool scanPos = (mvgrid_->getLong("iScansNegatively") != 0);
    double eastVal = east();
    double westVal = west();
    long numLon = numberOfLon();
    double computed = cValueNotGiven;
    if (eastVal != cValueNotGiven && westVal != cValueNotGiven &&
        numLon != GRIB_MISSING_LONG) {
        computed = (eastVal - westVal) / (numLon - 1);
    }

    if (suppliedDx != cValueNotGiven && computed != cValueNotGiven) {
        // This is the case when GRIB header precision is too small
        if (fabs(computed - suppliedDx) > cGridEpsilon) {
            return computed;
        }
        else {
            if (scanPos) {
                suppliedDx = -suppliedDx;
            }
            return suppliedDx;
        }
    }
    else if (suppliedDx != cValueNotGiven) {
        if (scanPos) {
            suppliedDx = -suppliedDx;
        }
        return suppliedDx;
    }
    else if (computed != cValueNotGiven) {
        return computed;
    }

    return cValueNotGiven;
}

bool MvField::isDerived()
{
    // gribsec1 *s1 = (gribsec1*)&Grib->ksec1[0];
    // return s1->generation == mars.computeflg;
    return mvgrid_->getLong("generatingProcessIdentifier") == mars.computeflg;
}

double MvField::parameter()
{
    // if( Grib->buffer )
    //	return (unsigned char)*(Grib->buffer+(8+9-1)); //-- 9th octet in sec1

    // gribsec1 *s1 = (gribsec1*)&Grib->ksec1[0];
    // return s1->parameter;
#if 1
    // return mvgrid_->getDouble("mars.param"); //- does not work in GRIB 2 ???
    std::string p = mvgrid_->getString("mars.param");
    double d = atof(p.c_str());
    return d;
#else
    if (mvgrid_->getLong("editionNumber") > 1)
        return mvgrid_->getDouble("parameterNumber");  //-- GRIB2 only
    else
        return mvgrid_->getLong("indicatorOfParameter");  //-- grib1 only
#endif
}

std::string MvField::parameterName()
{
    return mvgrid_->getString("mars.param");
}

int MvField::table()
{
    // if( Grib->buffer )
    //	return (unsigned char)*(Grib->buffer+(8+4-1)); //-- 4th octet in sec1

    // gribsec1 *s1 = (gribsec1*)&Grib->ksec1[0];
    // return s1->version;

    long ed = mvgrid_->getLong("editionNumber");
    if (ed > 1) {
        marslog(LOG_INFO, "MvField::table(): No table 2 version in GRIB 2!");
        return 0;
    }
    else
        return mvgrid_->getLong("gribTablesVersionNo");
}

int MvField::centre()
{
    // if( Grib->buffer )
    //	return (unsigned char)*(Grib->buffer+(8+5-1)); //-- 5th octet in sec1


    // gribsec1 *s1 = (gribsec1*)&Grib->ksec1[0];
    // return s1->center;
    return mvgrid_->getLong("centre");
}

int MvField::generatingProcess()
{
    // if( Grib->buffer )
    //	return (unsigned char)*(Grib->buffer+(8+6-1)); //-- 6th octet in sec1


    // gribsec1 *s1 = (gribsec1*)&Grib->ksec1[0];
    // return s1->generation;
    return mvgrid_->getLong("generatingProcessIdentifier");
}

double MvField::level()
{
    double lev = mvgrid_->getDouble("level");
    if (levelType() == GRIB_ABOVE_1HPA)
        return lev / 100.0;
    else
        return lev;

#if 0
	gribsec1 *s1 = (gribsec1*)&Grib->ksec1[0];
	if(s1->level_type == GRIB_ABOVE_1HPA)
		return  s1->top_level/100.0;
	else
		return s1->top_level;
#endif
}

double MvField::level_L2()
{
    double lev = mvgrid_->getDouble("bottomLevel");
    if (levelType() == GRIB_ABOVE_1HPA)
        return lev / 100.0;
    else
        return lev;

#if 0
	gribsec1 *s1 = (gribsec1*)&Grib->ksec1[0];
	if(s1->level_type == GRIB_ABOVE_1HPA)
		return  s1->bottom_level/100.0;
	else
		return s1->bottom_level;
#endif
}

std::string MvField::levelTypeString()
{
    std::string levTyp = mvgrid_->getString("mars.levtype");
    return levTyp;
}

int MvField::levelType()
{
    // gribsec1 *s1 = (gribsec1*)&Grib->ksec1[0];
    // return s1->level_type;

    int lev = mvgrid_->getLong("levelType");
    return lev;
}

int MvField::dataRepres()
{
    // return (int)Grib->ksec2[0]; //-- fortran index (1)
    // return mvgrid_->getLong("dataRepresentationType");


    // maybe not the very best way to do it, but we have quite a few
    // functions which expect an integer from this function, so we
    // take our tried and tested mvgrid_->gridType() and convert it
    // into a number. The problem is that GRIB 2 does not seem to
    // be able to give dataRepresentationType as a number.

    std::string gridType = mvgrid_->gridType();

    if (gridType == cLatLonGrid)
        return GRIB_LAT_LONG;
    else if (gridType == cLatLonRotatedGrid)
        return GRIB_ROTATED_LAT_LONG;
    else if (gridType == cGaussianGrid)
        return GRIB_GAUSSIAN;
    else if (gridType == cPolarStereoGrid)
        return 5;
    else if (gridType == cLambertGrid)
        return 3;
    else if (gridType == cMercatorGrid)
        return 1;
    else if (gridType == cSatelliteImage)
        return GRIB_SPACE_VIEW;
    else if (gridType == cLatLonReducedGrid)
        return GRIB_LAT_LONG;
    else if (gridType == cGaussianReducedGrid)
        return GRIB_GAUSSIAN;
    else
        return 100;  // nonsense value
}

bool MvField::isModelLevel()
{
    // gribsec1 *s1 = (gribsec1*)&Grib->ksec1[0];
    // return s1->level_type == GRIB_MODEL_LEVEL;

    // return levelType() == GRIB_MODEL_LEVEL || levelType() == cML_UKMO_ND;

    std::string levTyp = mvgrid_->getString("mars.levtype");
    if (levTyp == "ml" || levTyp == cML_UKMO_ND_STR) {
        return true;
    }
    else  // some GRIB files don't have mars.levtype defined the way we'd like
    {
        levTyp = mvgrid_->getString("levelType");
        return (levTyp == "ml" || levTyp == cML_UKMO_ND_STR);
    }
}

bool MvField::isPressureLevel()
{
    // gribsec1 *s1 = (gribsec1*)&Grib->ksec1[0];

    // return  levelType() == GRIB_PRESSURE_LEVEL ||
    //	levelType() == GRIB_ABOVE_1HPA;

    std::string levTyp = mvgrid_->getString("mars.levtype");
    return levTyp == "pl";
}

bool MvField::isDepth()
{
    // gribsec1 *s1 = (gribsec1*)&Grib->ksec1[0];
    return levelType() == GRIB_DEPTH_BELOW_SEA_LEVEL;
}

long MvField::pressureLevelInPa()
{
    if (isPressureLevel()) {
        if (mvgrid_->getString("typeOfLevel") == "isobaricInhPa") {
            return mvgrid_->getLong("level") * 100;
        }
        else if (mvgrid_->getString("typeOfLevel") == "isobaricInPa") {
            return mvgrid_->getLong("level");
        }
    }
    return -1;
}


double MvField::yyyymmddFoh()
{
#if 0
	gribsec1 *s1 = (gribsec1*)&Grib->ksec1[0];
	if ( s1 )
	  {
	    double year = (long)((s1->century-1) * 100 + s1->year);
	    double d1   = year*10000. + (double)s1->month*100. + (double)s1->day;
	    double d2 = (( (double)s1->hour*60. + (double)s1->minute ) * 60. ) / 86400.;

	    return (d1 + d2);   //-- using obsolete GRIBEX API (needs struct expansion)
	  }
	else if( Grib->buffer )
	  {
	    //-- set octet ptr 'sec1' in such a way that following indices
	    //-- refer to GRIB section 1 octets, i.e. skip section 0 except the last octet
	    unsigned char* sec1 = (unsigned char*)(Grib->buffer + 7);

	    double year = (long)((sec1[25]-1) * 100 + sec1[13]);
	    double d1   = year*10000. + (double)sec1[14]*100. + (double)sec1[15];
	    double d2   = (( (double)sec1[16]*60. + (double)sec1[17] ) * 60. ) / 86400.;

	    return (d1 + d2);   //-- using original GRIB octets (works without expansion)
	  }

	return 0;               //-- failed to extract date
#endif
    double dat = mvgrid_->getDouble("dataDate");
    double tim = mvgrid_->getDouble("time");

    // nontested_eccodes_port("MvField::yyyymmddFoh()");
    return dat + tim / 2400.0;  //--HHMM
}


double MvField::validityDateTime()
{
    return mvgrid_->validityDateTime();
}

double MvField::stepFoh()
{
#if 0
	double d3 = 0.0;
	gribsec1 *s1 = (gribsec1*)&Grib->ksec1[0];
	if ( s1 )                //-- using obsolete GRIBEX API (needs expanding)
	  {
	    fortint p1 = s1->p1;
	    if ( p1 > 0 )
	      {
		fortint timeUnit = s1->time_unit;
		if ( timeUnit == 0 ) // minutes
			d3 = (double)p1 / (24.*60.);
		else if ( timeUnit == 1 ) // hours
			d3 = (double)p1 / 24.;
		else if ( timeUnit == 2 ) // days
			d3 = (double)p1;
		else
			marslog(LOG_EROR,"Time unit code not implemented yet.  General date value used");
	      }
	  }
	else if( Grib->buffer )  //-- use original GRIB octets (works without expansion)
	  {
	    //-- set octet ptr 'sec1' in such a way that following indices
	    //-- refer to GRIB section 1 octets, i.e. skip section 0 except the last octet
	    unsigned char* sec1 = (unsigned char*)(Grib->buffer + 7);

	    double p1 = (double)sec1[19];
	    if( sec1[21] == 10 )              //-- p1 may occupy both octets p1 & p2 ?
	      p1 = 256.*p1 + (double)sec1[20];

	    if( p1 > 0 )
	      {
		if( sec1[18] == 0 ) // minutes
		  d3 = p1 / (24.*60.);
		else if( sec1[18] == 1 ) // hours
		  d3 = p1 / 24.;
		else if( sec1[18] == 2 ) // days
		  d3 = p1;
		else
		  marslog(LOG_EROR,"Time unit code not implemented yet.  General date value used");
	      }
	  }

	return d3;
#endif

    double d3 = mvgrid_->getDouble("mars.step", false, true);  // throw, quiet
    if (d3 == DBL_MAX) {
        d3 = mvgrid_->getDouble("step", false, true);
        if (d3 == DBL_MAX) {
            d3 = mvgrid_->getDouble("endStep", false, true);
            if (d3 == DBL_MAX)
                d3 = 0;  // can't find a step key, so step=0
        }
    }


    // https://apps.ecmwf.int/codes/grib/format/grib2/ctables/4/4 for the complete list
    // note that 1 is hours, which is what we assume by default
    long stepUnit = mvgrid_->getLong("indicatorOfUnitOfTimeRange");
    if (stepUnit < 0)  // means that the key could not be found, therefore assume 'hours'
        stepUnit = 1;

    if (stepUnit == 0)  // minutes
        d3 /= 60.0;

    return d3 / 24.0;
}


// stepRangeDecoder
// -- Returns the step number from a step string.
// -- e.g. '72' -> 72;
// -- e.g. '96-120' -> 120
// -- See the header file for more information.
// -- Note that this code is based on code in ecCodes
// -- (grib_accessor_class_g1step_range.c) which does the same thing, i.e., with
// -- this data, grib_get_long() would return 120.

int MvField::stepRangeDecoder(const char* stepString)
{
    char buff[100];
    long start_step = 0, step = 0;
    char* p = buff;

    if (stepString != nullptr) {
        strncpy(buff, stepString, sizeof(buff) - 1);

        start_step = strtol(stepString, &p, 10);  // convert to int, 'p' points to first non-numeric char

        if (*p == 0)  // step was just a number, so use that
            step = start_step;
        else  // step was a range, so use the number after the non-numeric char
            step = strtol(++p, nullptr, 10);

        return step;
    }

    else {
        return 0;  // no step string
    }
}

//________________________________________________________________
//                                      Emoslib GRIB Table 2 stuff
//  Emoslib GRIB Table 2 versions are
//  used to get parameter MARS name.
//

Cached MvField::marsName()
{
    std::string p = mvgrid_->getString("shortName");
    Cached c(p.c_str());

    if (isDerived())        //-- generally ok?
        return lowcase(c);  //-- ok for Vis5D!
    else
        return upcase(c);
}


/* OBSOLETE: REMOVE LATER
//________________________________________________________________
//                                       Magics GRIB Table 2 stuff
//  Magics Extended GRIB Table 2 versions
//  are used to get vector pair info i.e.
//  recognise wind and other vector fields.
//

Cached MvField::vectorPair()
{
    char buf[ 512 ];

    std::ifstream istrm( (const char*)MagicsTable2FileName(), std::ios::in );
    if( ! istrm )
    {
        marslog(LOG_INFO, "Magics Extended GRIB Table 2 Version %d open error", table() );
        return Cached( "no_pair" );
    }

    char tableVers[ 5 ];
    ostrstream tvstr( tableVers, sizeof( tableVers ) );
    tvstr << "."
          << table()                 //-- required for new (030612) MARS param syntax
          << std::ends;

    istrm.getline( buf, sizeof( buf ) );
    int  current = atoi( buf );
        int  param   = (int)parameter();

    while( ! istrm.eof() && istrm.good() && current < param )
    {
        istrm.getline( buf, sizeof( buf ) );
        current = atoi( buf );
    }

    if( current == param )
    {
        //-- there are max 12 entries on a line, separated by '&'s
        //-- if 9th entry > 0
        //--    then entry 10 is the param number for the pair

        char* start = buf;
        for( int i=1; i<9; ++i )     //-- locate 9th entry
        {
            start = strstr( start, "&" );
            if( start )
                ++start;             //-- i:th entry exists
            else
                break;               //-- less than 9 entries
        }
        if( start )                  //-- did the 9th entry exist?
        {
            if( atol( start ) > 0 )  //-- entry value > 0 => has a pair
            {
                start = strstr( start, "&" );
                if( start )          //-- does the 10th entry exist?
                {
                    ++start;
                    while( *start == ' ' )
                        ++start;           //-- remove leading spaces

                    char* amp = strstr( start, "&" );
                    if( amp )
                    {
                        *amp-- = '\0';
                        while( *amp == ' ' )
                            *amp-- = '\0';  //-- remove trailing spaces
                    }

                    return Cached( start ) + Cached( tableVers ); //-- OK, return pair!
                }
            }
        }

        return Cached( "no_pair" );         //-- scalar, has no pair!
    }

    marslog(LOG_INFO , "Param %d missing from Magics Extended GRIB Table 2 Vers %d"
          , param , table() );

    return Cached( "no_pair" );
}
*/


MvLocation MvField::nearestGridPointLocation(const MvLocation& l)
{
    MvLocation nearestLoc;
    mvgrid_->init();

    if (mvgrid_->hasLocationInfo()) {
        MvGridPoint nearestPoint = mvgrid_->nearestGridpoint(l.latitude(), l.longitude(), false);
        nearestLoc = nearestPoint.loc_;
    }
    else {
        marslog(LOG_EROR, "nearestGridPointLocation(): unimplemented or spectral data - unable to extract location data");
    }

    return nearestLoc;
}

int MvField::find(double min, double max, std::vector<double>& lat, std::vector<double>& lon)
{
    if (!mvgrid_->hasLocationInfo()) {
        marslog(LOG_EROR, "MvField::Find(). unimplemented or spectral data - unable to extract location data");
        return 0;
    }

    int count = 0;
    MvFieldExpander x(*this);
    mvgrid_->init();
    for (int j = 0; j < mvgrid_->length(); ++j) {
        if (!mvgrid_->hasValue())  // missing value
            continue;

        if (mvgrid_->value() >= min && mvgrid_->value() <= max) {
            lat.push_back(mvgrid_->lat_y());
            lon.push_back(mvgrid_->lon_x());
            count++;
        }

        mvgrid_->advance();
    }

    return count;
}

bool MvField::isSpectral()
{
    return getGribKeyValueString("gridType") == "sh";
}
