/*
  This file is part of CDO. CDO is a collection of Operators to manipulate and analyse Climate model Data.

  Author: Uwe Schulzweida

*/

#include <cfloat>
#include <cfenv>
#include <cassert>
#include <limits>

#include "compare.h"
#include "varray.h"
#include "cimdOmp.h"

//#pragma STDC FENV_ACCESS ON

const char *
fpe_errstr(int fpeRaised)
{
  const char *errstr = nullptr;

  // clang-format off
  if      (fpeRaised & FE_DIVBYZERO) errstr = "division by zero";
  else if (fpeRaised & FE_INEXACT)   errstr = "inexact result";
  else if (fpeRaised & FE_INVALID)   errstr = "invalid result";
  else if (fpeRaised & FE_OVERFLOW)  errstr = "overflow";
  else if (fpeRaised & FE_UNDERFLOW) errstr = "underflow";
  // clang-format on

  return errstr;
}

template <typename T>
inline T
min_value(T v1, T v2)
{
  return (v1 < v2) ? v1 : v2;
}

template <typename T>
inline T
max_value(T v1, T v2)
{
  return (v1 > v2) ? v1 : v2;
}

template <typename T>
MinMax
varray_min_max_mv(const size_t len, const T *array, const T missval)
{
  auto minmax_mv = [](const auto a, const auto mv_a, auto &vmin, auto &vmax, auto &nvals, auto is_EQ) {
    if (!is_EQ(a, mv_a))
      {
        vmin = min_value(vmin, a);
        vmax = max_value(vmax, a);
        nvals++;
      }
  };

  T vmin = std::numeric_limits<T>::max();
  T vmax = -std::numeric_limits<T>::max();

  size_t nvals = 0;
  if (std::isnan(missval))
    for (size_t i = 0; i < len; ++i) minmax_mv(array[i], missval, vmin, vmax, nvals, dbl_is_equal);
  else
    for (size_t i = 0; i < len; ++i) minmax_mv(array[i], missval, vmin, vmax, nvals, is_equal);

  return MinMax(vmin, vmax, nvals);
}

// Explicit instantiation
template MinMax varray_min_max_mv(const size_t len, const float *array, const float missval);
template MinMax varray_min_max_mv(const size_t len, const double *array, const double missval);

template <typename T>
MinMax
varray_min_max_mv(const size_t len, const Varray<T> &v, const T missval)
{
  return varray_min_max_mv(len, v.data(), missval);
}

// Explicit instantiation
template MinMax varray_min_max_mv(const size_t len, const Varray<float> &v, const float missval);
template MinMax varray_min_max_mv(const size_t len, const Varray<double> &v, const double missval);

template <typename T>
MinMaxSum
varray_min_max_sum(const size_t len, const Varray<T> &v, MinMaxSum mms)
{
  auto rmin = mms.min;
  auto rmax = mms.max;
  auto rsum = mms.sum;

#ifndef __ICC  // wrong result with icc19
#ifdef HAVE_OPENMP4
#pragma omp simd reduction(min : rmin) reduction(max : rmax) reduction(+ : rsum)
#endif
#endif
  for (size_t i = 0; i < len; ++i)
    {
      const double val = v[i];
      rmin = min_value(rmin, val);
      rmax = max_value(rmax, val);
      rsum += val;
    }

  return MinMaxSum(rmin, rmax, rsum, len);
}

// Explicit instantiation
template MinMaxSum varray_min_max_sum(const size_t len, const Varray<float> &v, MinMaxSum mms);
template MinMaxSum varray_min_max_sum(const size_t len, const Varray<double> &v, MinMaxSum mms);

template <typename T>
MinMaxSum
varray_min_max_sum_mv(const size_t len, const Varray<T> &v, const T missval, MinMaxSum mms)
{
  auto minmaxsum_mv = [](const auto val, const auto mv, auto &vmin, auto &vmax, auto &vsum, auto &nvals, auto is_EQ) {
    if (!is_EQ(val, mv))
      {
        vmin = min_value(vmin, val);
        vmax = max_value(vmax, val);
        vsum += val;
        nvals++;
      }
  };

  auto vmin = mms.min;
  auto vmax = mms.max;
  auto vsum = mms.sum;

  size_t nvals = 0;
  if (std::isnan(missval))
    {
#ifndef __ICC  // wrong result with icc19
#ifdef HAVE_OPENMP4
#pragma omp simd reduction(min : vmin) reduction(max : vmax) reduction(+ : vsum) reduction(+ : nvals)
#endif
#endif
      for (size_t i = 0; i < len; ++i) minmaxsum_mv((double)v[i], missval, vmin, vmax, vsum, nvals, dbl_is_equal);
    }
  else
    {
#ifndef __ICC  // wrong result with icc19
#ifdef HAVE_OPENMP4
#pragma omp simd reduction(min : vmin) reduction(max : vmax) reduction(+ : vsum) reduction(+ : nvals)
#endif
#endif
      for (size_t i = 0; i < len; ++i) minmaxsum_mv((double)v[i], missval, vmin, vmax, vsum, nvals, is_equal);
    }

  if (nvals == 0 && IS_EQUAL(vmin, std::numeric_limits<double>::max())) vmin = missval;
  if (nvals == 0 && IS_EQUAL(vmax, -std::numeric_limits<double>::max())) vmax = missval;

  return MinMaxSum(vmin, vmax, vsum, nvals);
}

// Explicit instantiation
template MinMaxSum varray_min_max_sum_mv(const size_t len, const Varray<float> &v, const float missval, MinMaxSum mms);
template MinMaxSum varray_min_max_sum_mv(const size_t len, const Varray<double> &v, const double missval, MinMaxSum mms);

template <typename T>
MinMaxMean
varray_min_max_mean(const size_t len, const Varray<T> &v)
{
  auto mms = varray_min_max_sum(len, v, MinMaxSum());
  const auto rmean = len != 0 ? mms.sum / static_cast<double>(len) : 0.0;
  return MinMaxMean(mms.min, mms.max, rmean, len);
}

// Explicit instantiation
template MinMaxMean varray_min_max_mean(const size_t len, const Varray<float> &v);
template MinMaxMean varray_min_max_mean(const size_t len, const Varray<double> &v);

template <typename T>
MinMaxMean
varray_min_max_mean_mv(const size_t len, const Varray<T> &v, const T missval)
{
  auto mms = varray_min_max_sum_mv(len, v, missval, MinMaxSum());
  const auto rmean = mms.n != 0 ? mms.sum / static_cast<double>(mms.n) : missval;
  return MinMaxMean(mms.min, mms.max, rmean, mms.n);
}

// Explicit instantiation
template MinMaxMean varray_min_max_mean_mv(const size_t len, const Varray<float> &v, const float missval);
template MinMaxMean varray_min_max_mean_mv(const size_t len, const Varray<double> &v, const double missval);

template <typename T>
MinMax
array_min_max_mask(const size_t len, const T *const array, const Varray<int> &mask)
{
  T rmin = std::numeric_limits<T>::max();
  T rmax = -std::numeric_limits<T>::max();

  if (!mask.empty())
    {
      for (size_t i = 0; i < len; ++i)
        {
          if (mask[i] == 0)
            {
              rmin = min_value(rmin, array[i]);
              rmax = max_value(rmax, array[i]);
            }
        }
    }
  else
    {
      for (size_t i = 0; i < len; ++i)
        {
          rmin = min_value(rmin, array[i]);
          rmax = max_value(rmax, array[i]);
        }
    }

  return MinMax(rmin, rmax);
}

// Explicit instantiation
template MinMax array_min_max_mask(const size_t len, const float *const array, const Varray<int> &mask);
template MinMax array_min_max_mask(const size_t len, const double *const array, const Varray<int> &mask);

void
array_add_array(const size_t len, double *array1, const double *array2)
{
#ifdef HAVE_OPENMP4
#pragma omp simd
#endif
  for (size_t i = 0; i < len; ++i) array1[i] += array2[i];
}

void
array_add_array_mv(const size_t len, double *array1, const double *array2, const double missval)
{
  if (std::isnan(missval))
    {
      for (size_t i = 0; i < len; i++)
        if (!DBL_IS_EQUAL(array2[i], missval)) array1[i] = DBL_IS_EQUAL(array1[i], missval) ? array2[i] : array1[i] + array2[i];
    }
  else
    {
      for (size_t i = 0; i < len; i++)
        if (IS_NOT_EQUAL(array2[i], missval)) array1[i] = IS_EQUAL(array1[i], missval) ? array2[i] : array1[i] + array2[i];
    }
}

auto count_mv = [](const auto val, const auto mv, auto &num, auto is_EQ) {
  if (is_EQ(val, mv)) num++;
};

template <typename T>
size_t
array_num_mv(const size_t len, const T *array, const T missval)
{
  size_t nmiss = 0;

  if (std::isnan(missval))
    {
      for (size_t i = 0; i < len; ++i) count_mv(array[i], missval, nmiss, dbl_is_equal);
    }
  else
    {
      for (size_t i = 0; i < len; ++i) count_mv(array[i], missval, nmiss, is_equal);
    }

  return nmiss;
}

// Explicit instantiation
template size_t array_num_mv(const size_t len, const float *array, const float missval);
template size_t array_num_mv(const size_t len, const double *array, const double missval);

template <typename T>
size_t
varray_num_mv(const size_t len, const Varray<T> &v, const T missval)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());

  size_t nmiss = 0;

  if (std::isnan(missval))
    {
      for (size_t i = 0; i < len; ++i) count_mv(v[i], missval, nmiss, dbl_is_equal);
    }
  else
    {
      for (size_t i = 0; i < len; ++i) count_mv(v[i], missval, nmiss, is_equal);
    }

  return nmiss;
}

// Explicit instantiation
template size_t varray_num_mv(const size_t len, const Varray<float> &v, const float missval);
template size_t varray_num_mv(const size_t len, const Varray<double> &v, const double missval);

template <typename T>
MinMax
varray_min_max(const size_t len, const T *array)
{
  T vmin = std::numeric_limits<T>::max();
  T vmax = -std::numeric_limits<T>::max();

#ifndef __ICC  // wrong result with icc19
#ifdef HAVE_OPENMP4
#pragma omp parallel for simd if (len > 999999) default(shared) schedule(static) reduction(min : vmin) reduction(max : vmax)
#endif
#endif
  for (size_t i = 0; i < len; ++i)
    {
      vmin = min_value(vmin, array[i]);
      vmax = max_value(vmax, array[i]);
    }

  return MinMax(vmin, vmax);
}

// Explicit instantiation
template MinMax varray_min_max(const size_t len, const float *array);
template MinMax varray_min_max(const size_t len, const double *array);

template <typename T>
MinMax
varray_min_max(const size_t len, const Varray<T> &v)
{
  return varray_min_max(len, v.data());
}

// Explicit instantiation
template MinMax varray_min_max(const size_t len, const Varray<float> &v);
template MinMax varray_min_max(const size_t len, const Varray<double> &v);

template <typename T>
MinMax
varray_min_max(const Varray<T> &v)
{
  T vmin = std::numeric_limits<T>::max();
  T vmax = -std::numeric_limits<T>::max();

  const auto len = v.size();
#ifndef __ICC  // wrong result with icc19
#ifdef HAVE_OPENMP4
#pragma omp parallel for simd if (len > 999999) default(shared) schedule(static) reduction(min : vmin) reduction(max : vmax)
#endif
#endif
  for (size_t i = 0; i < len; ++i)
    {
      vmin = min_value(vmin, v[i]);
      vmax = max_value(vmax, v[i]);
    }

  return MinMax(vmin, vmax);
}

// Explicit instantiation
template MinMax varray_min_max(const Varray<float> &v);
template MinMax varray_min_max(const Varray<double> &v);

template <typename T>
T
varray_min(const size_t len, const Varray<T> &v)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());

  auto vmin = v[0];

  for (size_t i = 0; i < len; ++i)
    vmin = min_value(vmin, v[i]);

  return vmin;
}

// Explicit instantiation
template float varray_min(const size_t len, const Varray<float> &v);
template double varray_min(const size_t len, const Varray<double> &v);

template <typename T>
T
varray_max(const size_t len, const Varray<T> &v)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());

  auto vmax = v[0];

  for (size_t i = 0; i < len; ++i)
    vmax = max_value(vmax, v[i]);

  return vmax;
}

// Explicit instantiation
template float varray_max(const size_t len, const Varray<float> &v);
template double varray_max(const size_t len, const Varray<double> &v);

template <typename T>
T
varray_range(const size_t len, const Varray<T> &v)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());

  auto vmin = v[0];
  auto vmax = v[0];

  for (size_t i = 0; i < len; ++i)
    {
      vmin = min_value(vmin, v[i]);
      vmax = max_value(vmax, v[i]);
    }

  return (vmax - vmin);
}

// Explicit instantiation
template float varray_range(const size_t len, const Varray<float> &v);
template double varray_range(const size_t len, const Varray<double> &v);

template <typename T>
T
varray_min_mv(const size_t len, const Varray<T> &v, const T missval)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());

  T vmin = std::numeric_limits<T>::max();

  for (size_t i = 0; i < len; ++i)
    if (!DBL_IS_EQUAL(v[i], missval))
      vmin = min_value(vmin, v[i]);

  if (IS_EQUAL(vmin, std::numeric_limits<T>::max())) vmin = missval;

  return vmin;
}

// Explicit instantiation
template float varray_min_mv(const size_t len, const Varray<float> &v, const float missval);
template double varray_min_mv(const size_t len, const Varray<double> &v, const double missval);

template <typename T>
T
varray_max_mv(const size_t len, const Varray<T> &v, const T missval)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());

  T vmax = -std::numeric_limits<T>::max();

  for (size_t i = 0; i < len; ++i)
    if (!DBL_IS_EQUAL(v[i], missval))
      vmax = max_value(vmax, v[i]);

  if (IS_EQUAL(vmax, -std::numeric_limits<T>::max())) vmax = missval;

  return vmax;
}

// Explicit instantiation
template float varray_max_mv(const size_t len, const Varray<float> &v, const float missval);
template double varray_max_mv(const size_t len, const Varray<double> &v, const double missval);

template <typename T>
T
varray_range_mv(const size_t len, const Varray<T> &v, const T missval)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());

  T vmin = std::numeric_limits<T>::max();
  T vmax = -std::numeric_limits<T>::max();

  for (size_t i = 0; i < len; ++i)
    if (!DBL_IS_EQUAL(v[i], missval))
      {
        vmin = min_value(vmin, v[i]);
        vmax = max_value(vmax, v[i]);
      }

  return (IS_EQUAL(vmin, std::numeric_limits<T>::max()) && IS_EQUAL(vmax, -std::numeric_limits<T>::max())) ? missval : vmax - vmin;
}

// Explicit instantiation
template float varray_range_mv(const size_t len, const Varray<float> &v, const float missval);
template double varray_range_mv(const size_t len, const Varray<double> &v, const double missval);

template <typename T>
double
varray_sum(const size_t len, const Varray<T> &v)
{
  // assert(len > 0); // failed in remapcon
  assert(v.size() > 0);
  assert(len <= v.size());

  double sum = 0.0;
  for (size_t i = 0; i < len; ++i) sum += v[i];

  return sum;
}

// Explicit instantiation
template double varray_sum(const size_t len, const Varray<float> &v);
template double varray_sum(const size_t len, const Varray<double> &v);

template <typename T>
double
varray_sum_mv(const size_t len, const Varray<T> &v, const T missval)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());

  double sum = 0.0;
  size_t nvals = 0;

  if (std::isnan(missval))
    {
      for (size_t i = 0; i < len; ++i)
        if (!DBL_IS_EQUAL(v[i], missval))
          {
            sum += v[i];
            nvals++;
          }
    }
  else
    {
      for (size_t i = 0; i < len; ++i)
        if (IS_NOT_EQUAL(v[i], missval))
          {
            sum += v[i];
            nvals++;
          }
    }

  if (!nvals) sum = missval;

  return sum;
}

// Explicit instantiation
template double varray_sum_mv(const size_t len, const Varray<float> &v, const float missval);
template double varray_sum_mv(const size_t len, const Varray<double> &v, const double missval);

template <typename T>
double
varray_mean(const size_t len, const Varray<T> &v)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());

  const auto sum = varray_sum(len, v);

  return sum / len;
}

// Explicit instantiation
template double varray_mean(const size_t len, const Varray<float> &v);
template double varray_mean(const size_t len, const Varray<double> &v);

template <typename T>
double
varray_mean_mv(const size_t len, const Varray<T> &v, const T missval)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());

  double sum = 0.0, sumw = 0.0;

  for (size_t i = 0; i < len; ++i)
    if (!DBL_IS_EQUAL(v[i], missval))
      {
        sum += v[i];
        sumw += 1;
      }

  double missval1 = missval, missval2 = missval;
  return DIVMN(sum, sumw);
}

// Explicit instantiation
template double varray_mean_mv(const size_t len, const Varray<float> &v, const float missval);
template double varray_mean_mv(const size_t len, const Varray<double> &v, const double missval);

template <typename T>
double
varray_weighted_mean(const size_t len, const Varray<T> &v, const Varray<double> &w, const T missval)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());
  assert(len <= w.size());

  double sum = 0.0, sumw = 0.0;

  for (size_t i = 0; i < len; ++i)
    {
      sum += w[i] * v[i];
      sumw += w[i];
    }

  return IS_EQUAL(sumw, 0.) ? missval : sum / sumw;
}

// Explicit instantiation
template double varray_weighted_mean(const size_t len, const Varray<float> &v, const Varray<double> &w, const float missval);
template double varray_weighted_mean(const size_t len, const Varray<double> &v, const Varray<double> &w, const double missval);

template <typename T>
double
varray_weighted_mean_mv(const size_t len, const Varray<T> &v, const Varray<double> &w, const T missval)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());
  assert(len <= w.size());

  const double missval1 = missval, missval2 = missval;
  double sum = 0.0, sumw = 0.0;

  for (size_t i = 0; i < len; ++i)
    if (!DBL_IS_EQUAL(v[i], missval1) && !DBL_IS_EQUAL(w[i], missval1))
      {
        sum += w[i] * v[i];
        sumw += w[i];
      }

  return DIVMN(sum, sumw);
}

// Explicit instantiation
template double varray_weighted_mean_mv(const size_t len, const Varray<float> &v, const Varray<double> &w, const float missval);
template double varray_weighted_mean_mv(const size_t len, const Varray<double> &v, const Varray<double> &w, const double missval);

template <typename T>
double
varray_avg_mv(const size_t len, const Varray<T> &v, const T missval)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());

  const double missval1 = missval, missval2 = missval;
  double sum = 0.0, sumw = 0.0;

  for (size_t i = 0; i < len; ++i)
    {
      sum = ADDMN(sum, v[i]);
      sumw += 1;
    }

  return DIVMN(sum, sumw);
}

// Explicit instantiation
template double varray_avg_mv(const size_t len, const Varray<float> &v, const float missval);
template double varray_avg_mv(const size_t len, const Varray<double> &v, const double missval);

template <typename T>
double
varray_weighted_avg_mv(const size_t len, const Varray<T> &v, const Varray<double> &w, const T missval)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());
  assert(len <= w.size());

  const double missval1 = missval, missval2 = missval;
  double sum = 0.0, sumw = 0.0;

  for (size_t i = 0; i < len; ++i)
    if (!DBL_IS_EQUAL(w[i], missval))
      {
        sum = ADDMN(sum, MULMN(w[i], v[i]));
        sumw = ADDMN(sumw, w[i]);
      }

  return DIVMN(sum, sumw);
}

// Explicit instantiation
template double varray_weighted_avg_mv(const size_t len, const Varray<float> &v, const Varray<double> &w, const float missval);
template double varray_weighted_avg_mv(const size_t len, const Varray<double> &v, const Varray<double> &w, const double missval);

template <typename T>
static void
varrayPrevarsum0(size_t len, const Varray<T> &v, double &rsum, double &rsumw)
{
  rsum = 0.0;
  for (size_t i = 0; i < len; i++)
    {
      rsum += v[i];
    }
  rsumw = len;
}

template <typename T>
static void
varrayPrevarsum0MV(size_t len, const Varray<T> &v, double missval, double &rsum, double &rsumw)
{
  rsum = rsumw = 0.0;
  for (size_t i = 0; i < len; i++)
    if (!DBL_IS_EQUAL(v[i], missval))
      {
        rsum += v[i];
        rsumw += 1.0;
      }
}

template <typename T>
static void
varrayPrevarsum(size_t len, const Varray<T> &v, double &rsum, double &rsumw, double &rsumq, double &rsumwq)
{
  rsum = rsumq = 0.0;
  for (size_t i = 0; i < len; i++)
    {
      const double vd = v[i];
      rsum += vd;
      rsumq += vd * vd;
    }
  rsumw = len;
  rsumwq = len;
}

template <typename T>
static void
varrayPrevarsumMV(size_t len, const Varray<T> &v, T missval, double &rsum, double &rsumw, double &rsumq, double &rsumwq)
{
  rsum = rsumq = rsumw = rsumwq = 0.0;
  for (size_t i = 0; i < len; i++)
    if (!DBL_IS_EQUAL(v[i], missval))
      {
        const double vd = v[i];
        rsum += vd;
        rsumq += vd * vd;
        rsumw += 1.0;
        rsumwq += 1.0;
      }
}

template <typename T>
double
varray_var(size_t len, const Varray<T> &v, size_t nmiss, T missval)
{
  double rsum = 0.0, rsumw = 0.0, rsumq = 0.0, rsumwq = 0.0;
  if (nmiss > 0)
    varrayPrevarsumMV(len, v, missval, rsum, rsumw, rsumq, rsumwq);
  else
    varrayPrevarsum(len, v, rsum, rsumw, rsumq, rsumwq);

  auto rvar = IS_NOT_EQUAL(rsumw, 0.0) ? (rsumq * rsumw - rsum * rsum) / (rsumw * rsumw) : missval;
  if (rvar < 0.0 && rvar > -1.e-5) rvar = 0.0;

  return rvar;
}

// Explicit instantiation
template double varray_var(const size_t len, const Varray<float> &v, size_t nmiss, const float missval);
template double varray_var(const size_t len, const Varray<double> &v, size_t nmiss, const double missval);

template <typename T>
double
varray_var_1(size_t len, const Varray<T> &v, size_t nmiss, T missval)
{
  double rsum = 0.0, rsumw = 0.0, rsumq = 0.0, rsumwq = 0.0;
  if (nmiss > 0)
    varrayPrevarsumMV(len, v, missval, rsum, rsumw, rsumq, rsumwq);
  else
    varrayPrevarsum(len, v, rsum, rsumw, rsumq, rsumwq);

  auto rvar = (rsumw * rsumw > rsumwq) ? (rsumq * rsumw - rsum * rsum) / (rsumw * rsumw - rsumwq) : missval;
  if (rvar < 0.0 && rvar > -1.e-5) rvar = 0.0;

  return rvar;
}

// Explicit instantiation
template double varray_var_1(const size_t len, const Varray<float> &v, size_t nmiss, const float missval);
template double varray_var_1(const size_t len, const Varray<double> &v, size_t nmiss, const double missval);

template <typename T>
static void
varrayWeightedPrevarsum(size_t len, const Varray<T> &v, const Varray<double> &w, double &rsum, double &rsumw, double &rsumq,
                        double &rsumwq)
{
  rsum = rsumq = rsumw = rsumwq = 0.0;
  for (size_t i = 0; i < len; i++)
    {
      const double vd = v[i];
      rsum += w[i] * vd;
      rsumq += w[i] * vd * vd;
      rsumw += w[i];
      rsumwq += w[i] * w[i];
    }
}

template <typename T>
static void
varrayWeightedPrevarsumMV(size_t len, const Varray<T> &v, const Varray<double> &w, double missval, double &rsum, double &rsumw,
                          double &rsumq, double &rsumwq)
{
  rsum = rsumq = rsumw = rsumwq = 0.0;
  for (size_t i = 0; i < len; i++)
    if (!DBL_IS_EQUAL(v[i], missval) && !DBL_IS_EQUAL(w[i], missval))
      {
        const double vd = v[i];
        rsum += w[i] * vd;
        rsumq += w[i] * vd * vd;
        rsumw += w[i];
        rsumwq += w[i] * w[i];
      }
}

template <typename T>
double
varray_weighted_var(size_t len, const Varray<T> &v, const Varray<double> &w, size_t nmiss, T missval)
{
  double rsum = 0.0, rsumw = 0.0, rsumq = 0.0, rsumwq = 0.0;
  if (nmiss > 0)
    varrayWeightedPrevarsumMV(len, v, w, missval, rsum, rsumw, rsumq, rsumwq);
  else
    varrayWeightedPrevarsum(len, v, w, rsum, rsumw, rsumq, rsumwq);

  auto rvar = IS_NOT_EQUAL(rsumw, 0) ? (rsumq * rsumw - rsum * rsum) / (rsumw * rsumw) : missval;
  if (rvar < 0.0 && rvar > -1.e-5) rvar = 0.0;

  return rvar;
}

// Explicit instantiation
template double varray_weighted_var(const size_t len, const Varray<float> &v, const Varray<double> &w, size_t nmiss,
                                    const float missval);
template double varray_weighted_var(const size_t len, const Varray<double> &v, const Varray<double> &w, size_t nmiss,
                                    const double missval);

template <typename T>
double
varray_weighted_var_1(size_t len, const Varray<T> &v, const Varray<double> &w, size_t nmiss, T missval)
{
  double rsum = 0.0, rsumw = 0.0, rsumq = 0.0, rsumwq = 0.0;
  if (nmiss > 0)
    varrayWeightedPrevarsumMV(len, v, w, missval, rsum, rsumw, rsumq, rsumwq);
  else
    varrayWeightedPrevarsum(len, v, w, rsum, rsumw, rsumq, rsumwq);

  auto rvar = (rsumw * rsumw > rsumwq) ? (rsumq * rsumw - rsum * rsum) / (rsumw * rsumw - rsumwq) : missval;
  if (rvar < 0.0 && rvar > -1.e-5) rvar = 0.0;

  return rvar;
}

// Explicit instantiation
template double varray_weighted_var_1(const size_t len, const Varray<float> &v, const Varray<double> &w, size_t nmiss,
                                      const float missval);
template double varray_weighted_var_1(const size_t len, const Varray<double> &v, const Varray<double> &w, size_t nmiss,
                                      const double missval);

template <typename T>
static void
varrayPrekurtsum(size_t len, const Varray<T> &v, const double mean, double &rsum3w, double &rsum4w, double &rsum2diff,
                 double &rsum4diff)
{
  rsum2diff = rsum4diff = 0.0;
  for (size_t i = 0; i < len; i++)
    {
      const double vdiff = v[i] - mean;
      rsum2diff += vdiff * vdiff;
      rsum4diff += vdiff * vdiff * vdiff * vdiff;
    }
  rsum3w = len;
  rsum4w = len;
}

template <typename T>
static void
varrayPrekurtsumMV(size_t len, const Varray<T> &v, T missval, const double mean, double &rsum3w, double &rsum4w, double &rsum2diff,
                   double &rsum4diff)
{
  rsum3w = rsum4w = rsum2diff = rsum4diff = 0.0;
  for (size_t i = 0; i < len; i++)
    if (!DBL_IS_EQUAL(v[i], missval))
      {
        const double vdiff = v[i] - mean;
        rsum2diff += vdiff * vdiff;
        rsum4diff += vdiff * vdiff * vdiff * vdiff;
        rsum3w += 1;
        rsum4w += 1;
      }
}

template <typename T>
double
varray_kurt(size_t len, const Varray<T> &v, size_t nmiss, T missval)
{
  double rsum3w;  // 3rd moment variables
  double rsum4w;  // 4th moment variables
  double rsum2diff, rsum4diff;
  double rsum, rsumw;

  if (nmiss > 0)
    {
      varrayPrevarsum0MV(len, v, missval, rsum, rsumw);
      varrayPrekurtsumMV(len, v, missval, (rsum / rsumw), rsum3w, rsum4w, rsum2diff, rsum4diff);
    }
  else
    {
      varrayPrevarsum0(len, v, rsum, rsumw);
      varrayPrekurtsum(len, v, (rsum / rsumw), rsum3w, rsum4w, rsum2diff, rsum4diff);
    }

  if (IS_EQUAL(rsum3w, 0.0) || IS_EQUAL(rsum2diff, 0.0)) return missval;

  auto rkurt = ((rsum4diff / rsum3w) / std::pow(rsum2diff / rsum3w, 2)) - 3.0;
  if (rkurt < 0.0 && rkurt > -1.e-5) rkurt = 0.0;

  return rkurt;
}

// Explicit instantiation
template double varray_kurt(const size_t len, const Varray<float> &v, size_t nmiss, const float missval);
template double varray_kurt(const size_t len, const Varray<double> &v, size_t nmiss, const double missval);

template <typename T>
static void
varrayPreskewsum(size_t len, const Varray<T> &v, const double mean, double &rsum3w, double &rsum4w, double &rsum3diff,
                 double &rsum2diff)
{
  rsum2diff = 0.0;
  rsum3diff = 0.0;
  for (size_t i = 0; i < len; i++)
    {
      const double vdiff = v[i] - mean;
      rsum3diff += vdiff * vdiff * vdiff;
      rsum2diff += vdiff * vdiff;
    }
  rsum3w = len;
  rsum4w = len;
}

template <typename T>
static void
varrayPreskewsumMV(size_t len, const Varray<T> &v, T missval, const double mean, double &rsum3w, double &rsum4w, double &rsum3diff,
                   double &rsum2diff)
{
  rsum3w = rsum4w = rsum3diff = rsum2diff = 0.0;
  for (size_t i = 0; i < len; i++)
    if (!DBL_IS_EQUAL(v[i], missval))
      {
        const double vdiff = v[i] - mean;
        rsum3diff += vdiff * vdiff * vdiff;
        rsum2diff += vdiff * vdiff;
        rsum3w += 1;
        rsum4w += 1;
      }
}

template <typename T>
double
varray_skew(size_t len, const Varray<T> &v, size_t nmiss, T missval)
{
  double rsum3w;  // 3rd moment variables
  double rsum4w;  // 4th moment variables
  double rsum3diff, rsum2diff;
  double rsum, rsumw;

  if (nmiss > 0)
    {
      varrayPrevarsum0MV(len, v, missval, rsum, rsumw);
      varrayPreskewsumMV(len, v, missval, (rsum / rsumw), rsum3w, rsum4w, rsum3diff, rsum2diff);
    }
  else
    {
      varrayPrevarsum0(len, v, rsum, rsumw);
      varrayPreskewsum(len, v, (rsum / rsumw), rsum3w, rsum4w, rsum3diff, rsum2diff);
    }

  if (IS_EQUAL(rsum3w, 0.0) || IS_EQUAL(rsum3w, 1.0) || IS_EQUAL(rsum2diff, 0.0)) return missval;

  auto rskew = (rsum3diff / rsum3w) / std::pow((rsum2diff) / (rsum3w - 1.0), 1.5);
  if (rskew < 0.0 && rskew > -1.e-5) rskew = 0.0;

  return rskew;
}

// Explicit instantiation
template double varray_skew(const size_t len, const Varray<float> &v, size_t nmiss, const float missval);
template double varray_skew(const size_t len, const Varray<double> &v, size_t nmiss, const double missval);

#include <algorithm>

template <typename T>
static double
get_nth_element(T *array, size_t length, size_t n)
{
  std::nth_element(array, array + n, array + length);
  return array[n];
}

template <typename T>
double
varray_median(const size_t len, const Varray<T> &v, size_t nmiss, const T missval)
{
  assert(len > 0);
  assert(v.size() > 0);
  assert(len <= v.size());

  double median = missval;

  if (nmiss == 0)
    {
      Varray<T> v2 = v;
      if (len % 2 == 0)
        {
          const auto k = len / 2;
          const auto vk1 = get_nth_element(v2.data(), len, k - 1);
          const auto vk2 = get_nth_element(v2.data(), len, k);
          median = (vk1 + vk2) * 0.5;
        }
      else
        {
          const auto k = (len + 1) / 2;
          median = get_nth_element(v2.data(), len, k - 1);
        }
    }

  return median;
}

// Explicit instantiation
template double varray_median(const size_t len, const Varray<float> &v, size_t nmiss, const float missval);
template double varray_median(const size_t len, const Varray<double> &v, size_t nmiss, const double missval);
