/*
  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 SPECSPACE_H
#define SPECSPACE_H

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

#include "cdo_options.h"
#include "transform.h"
#include "array.h"

class FC_Transformation
{
public:
  bool use_fftw = false;
  long nlon = 0;
  long nlat = 0;
  long ntr = 0;
  long nlev = 0;
  long ifax[10] = {};
  Varray<double> vtrig;

  FC_Transformation()
  {
    if (Options::Use_FFTW)
      {
#ifdef HAVE_LIBFFTW3
        if (Options::cdoVerbose) cdoPrint("Using fftw3 lib");
        use_fftw = true;
#else
        if (Options::cdoVerbose) cdoPrint("LIBFFTW3 support not compiled in!");
#endif
      }
  }

  void
  init(long _nlon, long _nlat, long _ntr, long _nlev = 0)
  {
    if (_nlon <= 0 || _nlat <= 0 || _ntr <= 0)
      {
        fprintf(stderr, "SP_Transformation.init(): parameter not initialized\n");
        return;
      }

    nlon = _nlon;
    nlat = _nlat;
    ntr = _ntr;
    nlev = _nlev;

    //if (nlev > 1) use_fftw = false;

    if (use_fftw == false)
      {
        vtrig.resize(nlon);
        const auto status = fft_set(vtrig.data(), ifax, nlon);
        if (status < 0) cdoAbort("FFT error!");
      }
  }
};

class SP_Transformation
{
public:
  FC_Transformation fcTrans;
  long nlat = 0;
  long ntr = 0;
  long poldim = 0;
  Varray<double> vpoli;
  Varray<double> vpold;
  Varray<double> vpol2;     // only for uv2dv
  Varray<double> vpol3;     // only for uv2dv
  Varray<double> vcoslat;   // only for scaluv with uv2dv
  Varray<double> vrcoslat;  // only for scaluv with uv2dv

  SP_Transformation() {}

  void
  init(long _nlon, long _nlat, long _ntr, int _flag, long _nlev = 0)
  {
    if (_nlon <= 0 || _nlat <= 0 || _ntr <= 0)
      {
        fprintf(stderr, "SP_Transformation.init(): parameter not initialized\n");
        return;
      }

    fcTrans.init(_nlon, _nlat, _ntr, _nlev);

    nlat = _nlat;
    ntr = _ntr;

    const long nsp = (ntr + 1) * (ntr + 2);
    poldim = nsp / 2 * nlat;

    varrayResize(vpoli, poldim);
    varrayResize(vpold, poldim);

    if (_flag) varrayResize(vpol2, poldim);
    if (_flag) varrayResize(vpol3, poldim);

    vcoslat.resize(nlat);
    vrcoslat.resize(nlat);

    if (_flag)
      after_legini_full(ntr, nlat, vpoli.data(), vpold.data(), nullptr, vpol2.data(), vpol3.data(), vcoslat.data());
    else
      after_legini(ntr, nlat, vpoli.data(), vpold.data(), vcoslat.data());

    for (long jgl = 0; jgl < nlat; ++jgl) vrcoslat[jgl] = 1.0 / vcoslat[jgl];
  }
};

class DV_Transformation
{
public:
  long ntr = 0;
  long fdim = 0;
  Varray<double> f1;
  Varray<double> f2;

  DV_Transformation() {}

  void
  init(long _ntr)
  {
    ntr = _ntr;

    const long dimsp = (ntr + 1) * (ntr + 2);
    fdim = dimsp / 2;

    f1.resize(fdim);
    f2.resize(fdim);

    geninx(ntr, f1.data(), f2.data());
  }
};

void dv2ps(const double *div, double *pot, long nlev, long ntr);

void trans_uv2dv(const SP_Transformation &spTrans, long nlev, int gridID1, double *gu, double *gv, int gridID2, double *sd,
                 double *svo);

void trans_dv2uv(const SP_Transformation &spTrans, const DV_Transformation &dvTrans, long nlev, int gridID1, double *sd,
                 double *svo, int gridID2, double *gu, double *gv);

void grid2spec(const SP_Transformation &spTrans, int gridIDin, double *arrayIn, int gridIDout, double *arrayOut);
void spec2grid(const SP_Transformation &spTrans, int gridIDin, double *arrayIn, int gridIDout, double *arrayOut);
void four2spec(const SP_Transformation &spTrans, int gridIDin, double *arrayIn, int gridIDout, double *arrayOut);
void spec2four(const SP_Transformation &spTrans, int gridIDin, double *arrayIn, int gridIDout, double *arrayOut);
void four2grid(const FC_Transformation &fcTrans, int gridIDin, double *arrayIn, int gridIDout, double *arrayOut);
void grid2four(const FC_Transformation &fcTrans, int gridIDin, double *arrayIn, int gridIDout, double *arrayOut);

void spec2spec(int gridIDin, double *arrayIn, int gridIDout, double *arrayOut);
void speccut(int gridIDin, double *arrayIn, double *arrayOut, int *waves);

void spcut(double *arrayIn, double *arrayOut, long ntr, const int *waves);

#endif
