#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "cdo_options.h"
#include "cdo_omp.h"
#include <cdi.h>

#if __has_include(<fftw3.h>)
#include <mutex>
std::mutex fftw3Mutex;
#endif

#ifdef HAVE_LIBFFTW3
#include "cdo_fftw3.h"

void
fourier2grid(int gridID1, Varray<double> const &array1, Varray<double> &array2)
{
  auto nlon = gridInqXsize(gridID1);
  auto nlat = gridInqYsize(gridID1);

  std::vector<FFTW3RCMemory> fmPool(Threading::ompNumMaxThreads);

#ifdef _OPENMP
#pragma omp parallel for default(shared) schedule(static)
#endif
  for (size_t ilat = 0; ilat < nlat; ++ilat)
  {
    auto tid = (Threading::ompNumMaxThreads == 1) ? 0 : cdo_omp_get_thread_num();
    auto &fm = fmPool[tid];
    if (fm.empty()) fm.init(FFTW_BACKWARD, nlon);

    for (size_t ifc = 0; ifc < (nlon / 2 + 1); ++ifc)
    {
      auto index = 2 * (ilat * nlon + ifc);
      fm.complex[ifc][0] = array1[index];
      fm.complex[ifc][1] = array1[index + 1];
    }

    fftw_execute(fm.plan);

    for (size_t ilon = 0; ilon < nlon; ++ilon) { array2[ilat * nlon + ilon] = fm.real[ilon]; }
  }

  if (Options::cdoVerbose) fftw_print_plan(fmPool[0].plan);
}

void
grid2fourier(int gridID1, Varray<double> const &array1, int gridID2, Varray<double> &array2)
{
  (void) gridID2;
  auto nlon = gridInqXsize(gridID1);
  auto nlat = gridInqYsize(gridID1);

  double norm = 1.0 / nlon;

  std::vector<FFTW3RCMemory> fmPool(Threading::ompNumMaxThreads);

#ifdef _OPENMP
#pragma omp parallel for default(shared) schedule(static)
#endif
  for (size_t ilat = 0; ilat < nlat; ++ilat)
  {
    auto tid = (Threading::ompNumMaxThreads == 1) ? 0 : cdo_omp_get_thread_num();
    auto &fm = fmPool[tid];
    if (fm.empty()) fm.init(FFTW_FORWARD, nlon);

    for (size_t ilon = 0; ilon < nlon; ++ilon) { fm.real[ilon] = array1[ilat * nlon + ilon]; }

    fftw_execute(fm.plan);

    for (size_t ifc = 0; ifc < (nlon / 2 + 1); ++ifc)
    {
      auto index = 2 * (ilat * nlon + ifc);
      array2[index] = norm * fm.complex[ifc][0];
      array2[index + 1] = norm * fm.complex[ifc][1];
    }
    for (size_t ifc = (nlon / 2 + 1); ifc < nlon; ++ifc)
    {
      auto index = 2 * (ilat * nlon + ifc);
      array2[index] = 0.0;
      array2[index + 1] = 0.0;
    }
  }

  if (Options::cdoVerbose) fftw_print_plan(fmPool[0].plan);
}

#else

#include "cdo_output.h"
#include "varray.h"
void
fourier2grid(int gridID1, Varray<double> const &array1, Varray<double> &array2)
{
  (void) gridID1;
  (void) array1;
  (void) array2;
  cdo_abort("FFTW3 support not compiled in!");
}

void
grid2fourier(int gridID1, Varray<double> const &array1, int gridID2, Varray<double> &array2)
{
  (void) gridID1;
  (void) gridID2;
  (void) array1;
  (void) array2;
  cdo_abort("FFTW3 support not compiled in!");
}

#endif
