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

  Copyright (C) 2003-2020 Uwe Schulzweida, <uwe.schulzweida AT mpimet.mpg.de>
  See COPYING file for copying and redistribution conditions.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 2 of the License.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
*/
#ifndef ARRAY_H
#define ARRAY_H

#include <iostream>  // cerr
#include <vector>
#include <cstddef>
#include <cfloat>
#include "compare.h"


struct MinMax
{
  double min = DBL_MAX;
  double max = -DBL_MAX;
  size_t n = 0;
  MinMax() {};
  MinMax(double rmin, double rmax, size_t rn) : min(rmin), max(rmax), n(rn) {};
  MinMax(double rmin, double rmax) : min(rmin), max(rmax), n(0) {};
};

struct MinMaxSum : MinMax
{
  double sum = 0;
  MinMaxSum() {};
  MinMaxSum(double rmin, double rmax, double rsum, size_t rn) : sum(rsum) { min = rmin; max = rmax; n = rn; };
  MinMaxSum(double rmin, double rmax, double rsum) : sum(rsum) { min = rmin; max = rmax; n = 0; };
};

struct MinMaxMean : MinMax
{
  double mean = 0;
  MinMaxMean() {};
  MinMaxMean(double rmin, double rmax, double rmean, size_t rn) : mean(rmean) { min = rmin; max = rmax; n = rn; };
  MinMaxMean(double rmin, double rmax, double rmean) : mean(rmean) { min = rmin; max = rmax; n = 0; };
};

template <typename T>
using Varray = std::vector<T>;

template <typename T>
using Varray2D = std::vector<std::vector<T>>;

template <typename T>
using Varray3D = std::vector<std::vector<std::vector<T>>>;

template <typename T>
using Varray4D = std::vector<std::vector<std::vector<std::vector<T>>>>;

#define VARRAY_2D(T, P2D, N, M) \
  Varray2D<T> P2D(N);           \
  if ((N))                      \
    for (size_t i = 0; i < (size_t)(N); ++i) P2D[i].resize(M);

#define IX2D(y, x, nx) ((y) * (nx) + (x))

template <typename T>
inline void
varrayFree(Varray<T> &v)
{
  v.clear();
  v.shrink_to_fit();
}

#define varrayResize(p, s) varray_resize((p), (s), __FILE__, __LINE__)
#define varrayResizeInit(p, s, v) varray_resize((p), (s), (v), __FILE__, __LINE__)

template <typename T>
inline void
varray_resize(Varray<T> &v, size_t count, const char *file, int line)
{
  try
    {
      v.resize(count);
    }
  catch (const std::exception &e)
    {
      std::cerr << "Exception caught when trying to allocate " << count << " vector elements: "
                << e.what() << " in " << file << ":" << line << '\n';
      throw;
    }
}

template <typename T>
inline void
varray_resize(Varray<T> &v, size_t count, T value, const char *file, int line)
{
  try
    {
      v.resize(count, value);
    }
  catch (const std::exception &e)
    {
      std::cerr << "Exception caught when trying to allocate " << count << " vector elements: "
                << e.what() << " in " << file << ":" << line << '\n';
      throw;
    }
}

/*
template <class T, size_t ROW, size_t COL>
using Matrix = std::array<std::array<T, COL>, ROW>;
*/

// clang-format off
#define MADDMN(x, y) (DBL_IS_EQUAL((x), missval1) || DBL_IS_EQUAL((y), missval2) ? missval1 : (x) + (y))
#define MSUBMN(x, y) (DBL_IS_EQUAL((x), missval1) || DBL_IS_EQUAL((y), missval2) ? missval1 : (x) - (y))
#define MMULMN(x, y) (DBL_IS_EQUAL((x), 0.) || DBL_IS_EQUAL((y), 0.) \
                      ? 0  : DBL_IS_EQUAL((x), missval1) || DBL_IS_EQUAL((y), missval2) ? missval1 : (x) * (y))
#define MDIVMN(x, y) (DBL_IS_EQUAL((x), missval1) || DBL_IS_EQUAL((y), missval2) || DBL_IS_EQUAL((y), 0.) ? missval1 : (x) / (y))
#define MPOWMN(x, y) (DBL_IS_EQUAL((x), missval1) || DBL_IS_EQUAL((y), missval2) ? missval1 : std::pow((x), (y)))
#define MSQRTMN(x) (DBL_IS_EQUAL((x), missval1) || (x) < 0 ? missval1 : std::sqrt(x))

#define ADD(x, y) ((x) + (y))
#define SUB(x, y) ((x) - (y))
#define MUL(x, y) ((x) * (y))
#define DIV(x, y) (IS_EQUAL((y), 0.) ? missval1 : (x) / (y))
#define POW(x, y) std::pow((x), (y))
#define SQRT(x) std::sqrt(x)

#define ADDM(x, y) (IS_EQUAL((x), missval1) || IS_EQUAL((y), missval2) ? missval1 : (x) + (y))
#define SUBM(x, y) (IS_EQUAL((x), missval1) || IS_EQUAL((y), missval2) ? missval1 : (x) - (y))
#define MULM(x, y)                            \
  (IS_EQUAL((x), 0.) || IS_EQUAL((y), 0.) ? 0 \
                                          : IS_EQUAL((x), missval1) || IS_EQUAL((y), missval2) ? missval1 : (x) * (y))
#define DIVM(x, y) (IS_EQUAL((x), missval1) || IS_EQUAL((y), missval2) || IS_EQUAL((y), 0.) ? missval1 : (x) / (y))
#define POWM(x, y) (IS_EQUAL((x), missval1) || IS_EQUAL((y), missval2) ? missval1 : std::pow((x), (y)))
#define SQRTM(x) (IS_EQUAL((x), missval1) || (x) < 0 ? missval1 : std::sqrt(x))

#define ADDMN(x, y) FADDMN(x, y, missval1, missval2)
#define SUBMN(x, y) FSUBMN(x, y, missval1, missval2)
#define MULMN(x, y) FMULMN(x, y, missval1, missval2)
#define DIVMN(x, y) FDIVMN(x, y, missval1, missval2)
#define POWMN(x, y) FPOWMN(x, y, missval1, missval2)
#define SQRTMN(x) FSQRTMN(x, missval1)

static inline double FADDMN(const double x, const double y, const double missval1, const double missval2)
{
  return MADDMN(x, y);
}
static inline double FSUBMN(const double x, const double y, const double missval1, const double missval2)
{
  return MSUBMN(x, y);
}
static inline double FMULMN(const double x, const double y, const double missval1, const double missval2)
{
  return MMULMN(x, y);
}
static inline double FDIVMN(const double x, const double y, const double missval1, const double missval2)
{
  return MDIVMN(x, y);
}
static inline double FPOWMN(const double x, const double y, const double missval1, const double missval2)
{
  return MPOWMN(x, y);
}
static inline double FSQRTMN(const double x, const double missval1)
{
  return MSQRTMN(x);
}
// clang-format on

const char *fpe_errstr(int fpeRaised);

template <typename T>
MinMaxSum varrayMinMaxSum(size_t len, const Varray<T> &array, MinMaxSum mms);

template <typename T>
MinMaxSum varrayMinMaxSumMV(size_t len, const Varray<T> &array, double missval, MinMaxSum mms);

MinMaxMean varrayMinMaxMean(size_t len, const Varray<double> &array);
MinMaxMean varrayMinMaxMeanMV(size_t len, const Varray<double> &array, double missval);

MinMax arrayMinMaxMask(size_t len, const double *array, const Varray<int> &mask);

void arrayAddArray(size_t len, double *array1, const double *array2);
void arrayAddArrayMV(size_t len, double *array1, const double *array2, double missval);

template <typename T>
void
varrayFill(const size_t len, T *v, const T value)
{
  for (size_t i = 0; i < len; ++i) v[i] = value;
}

template <typename T>
void
varrayFill(Varray<T> &v, const T value)
{
  varrayFill(v.size(), v.data(), value);
}

template <typename T>
void
varrayFill(const size_t len, Varray<T> &v, const T value)
{
  varrayFill(len, v.data(), value);
}

template <typename T>
void
arrayCopy(const size_t len, const T *array1, T *array2)
{
  for (size_t i = 0; i < len; ++i) array2[i] = array1[i];
}

template <typename T>
void
varrayCopy(const size_t len, const T &array1, T &array2)
{
  for (size_t i = 0; i < len; ++i) array2[i] = array1[i];
}

size_t arrayNumMV(size_t len, const double *array, double missval);

template <typename T>
size_t varrayNumMV(size_t len, const Varray<T> &array, T missval);

MinMax varrayMinMax(size_t len, const Varray<double> &v);
MinMax varrayMinMax(size_t len, const double *array);

template <typename T>
MinMax varrayMinMax(const Varray<T> &v);

MinMax varrayMinMaxMV(size_t len, const Varray<double> &array, double missval);
MinMax varrayMinMaxMV(size_t len, const double *array, double missval);

double varrayMin(size_t len, const Varray<double> &v);
double varrayMax(size_t len, const Varray<double> &v);
double varrayRange(size_t len, const Varray<double> &v);
double varrayMinMV(size_t len, const Varray<double> &v, double missval);
double varrayMaxMV(size_t len, const Varray<double> &v, double missval);
double varrayRangeMV(size_t len, const Varray<double> &v, double missval);

double varraySum(size_t len, const Varray<double> &v);
double varraySumMV(size_t len, const Varray<double> &v, double missval);

double varrayMean(size_t len, const Varray<double> &v);
double varrayMeanMV(size_t len, const Varray<double> &v, double missval);
double varrayWeightedMean(size_t len, const Varray<double> &v, const Varray<double> &w, double missval);
double varrayWeightedMeanMV(size_t len, const Varray<double> &v, const Varray<double> &w, double missval);

double varrayAvgMV(size_t len, const Varray<double> &v, double missval);
double varrayWeightedAvgMV(size_t len, const Varray<double> &v, const Varray<double> &w, double missval);

double varrayVar(size_t len, const Varray<double> &v, size_t nmiss, double missval);
double varrayVar1(size_t len, const Varray<double> &v, size_t nmiss, double missval);

double varrayWeightedVar(size_t len, const Varray<double> &v, const Varray<double> &w, size_t nmiss, double missval);
double varrayWeightedVar1(size_t len, const Varray<double> &v, const Varray<double> &w, size_t nmiss, double missval);

double varrayKurt(size_t len, const Varray<double> &v, size_t nmiss, double missval);
double varraySkew(size_t len, const Varray<double> &v, size_t nmiss, double missval);

#endif  //  ARRAY_H
