MODULE fourier
    USE prec_const, ONLY: xp, c_xp_c, c_xp_r, imagu, mpi_xp_c
    use, intrinsic :: iso_c_binding
    implicit none

    ! INCLUDE 'fftw3.f03'
    INCLUDE 'fftw3-mpi.f03'

    PRIVATE

    !! Module parameter
    ! 2D fft routines or 1D methods (for ExBshear simulations, 1D is required)
    ! The 2D fft is using fftw mpi optimization
    ! The 1D is not using mpi and does a data transfer with redundant computations
    !   (each process computes the convolution)
    LOGICAL, PUBLIC, PROTECTED :: FFT2D = .TRUE.

    !! Module accessible routines
    PUBLIC :: init_grid_distr_and_plans, poisson_bracket_and_sum, finalize_plans, apply_inv_ExB_NL_factor

    !! Module variables
    !! 2D fft specific variables (C interface)
    type(C_PTR)                 :: cdatar_f, cdatar_g, cdatar_c
    type(C_PTR)                 :: cdatac_f, cdatac_g, cdatac_c
    type(C_PTR) ,        PUBLIC :: planf, planb
    integer(C_INTPTR_T), PUBLIC :: alloc_local_1, alloc_local_2
    integer(C_INTPTR_T)         :: NX_, NY_, NY_halved, local_nky_, local_nx_ 
    real   (c_xp_r), pointer, PUBLIC :: real_data_f(:,:), real_data_g(:,:), bracket_sum_r(:,:)
    complex(c_xp_c), pointer, PUBLIC :: cmpx_data_f(:,:), cmpx_data_g(:,:), bracket_sum_c(:,:)
    REAL(xp),                 PUBLIC :: inv_Nx_, inv_Ny_
    !! 1D fft specific variables (full fortran interface)
    ! Single DFT plans
    type(C_PTR), PUBLIC :: plan_ky2y_c2r ! transform from ( x,ky) to ( x, y) (complex to real)
    type(C_PTR), PUBLIC :: plan_y2ky_r2c ! transform from ( x, y) to ( x,ky) (real to complex)
    type(C_PTR), PUBLIC :: plan_kx2x_c2c ! transform from (kx,ky) to ( x,ky) (complex to complex)
    type(C_PTR), PUBLIC :: plan_x2kx_c2c ! transform from ( x,ky) to (kx,ky) (complex to complex)
!     COMPLEX(xp), DIMENSION(:), ALLOCATABLE :: f_kx_l,f_x_l,f_ky_g ! aux array for plans
!     REAL(xp),    DIMENSION(:), ALLOCATABLE :: f_y_g  
    complex(c_xp_c), ALLOCATABLE, PUBLIC :: f_kx_l(:),f_x_l(:),f_ky_g(:)
    real   (c_xp_r), ALLOCATABLE, PUBLIC :: f_y_g(:)
    ! Many DFT plans
    type(C_PTR), PUBLIC :: plan_many_ky2y_c2r ! transform from ( x,ky) to ( x, y) (complex to real)
    type(C_PTR), PUBLIC :: plan_many_y2ky_r2c ! transform from ( x, y) to ( x,ky) (real to complex)
    type(C_PTR), PUBLIC :: plan_many_kx2x_c2c ! transform from (kx,ky) to ( x,ky) (complex to complex)
    type(C_PTR), PUBLIC :: plan_many_x2kx_c2c ! transform from ( x,ky) to (kx,ky) (complex to complex)
    COMPLEX(xp), DIMENSION(:,:), ALLOCATABLE :: f_kxky_l ! working arrays
    COMPLEX(xp), DIMENSION(:,:), ALLOCATABLE :: f_xky_l  ! temp. 1D ifft algo stage (local mpi)
    REAL(xp),    DIMENSION(:,:), ALLOCATABLE :: f_yx_g   ! poisson bracket sum in real space
    COMPLEX(xp), DIMENSION(:,:), ALLOCATABLE :: f_kyx_g  ! final poisson bracket in complex space

    ! to chose between a plan many or 1D plan loops
    LOGICAL :: PLAN_MANY = .FALSE.
    CONTAINS
    !******************************************************************************!
    !------------- Initialize the grid distribution and plans -------------
    ! If we use the 2D fftw routines, the fftw library decides the best data distribution
    SUBROUTINE init_grid_distr_and_plans(Nx,Ny,communicator,&
            local_nx_ptr,local_nx_ptr_offset,local_nky_ptr,local_nky_ptr_offset)
        USE basic, ONLY: speak
        IMPLICIT NONE
        INTEGER, INTENT(IN)  :: Nx,Ny, communicator
        INTEGER(C_INTPTR_T), INTENT(OUT) :: local_nx_ptr,local_nx_ptr_offset
        INTEGER(C_INTPTR_T), INTENT(OUT) :: local_nky_ptr,local_nky_ptr_offset
        NX_        = Nx; NY_ = Ny
        inv_Nx_    = 1._xp/REAL(NX_,xp)
        NY_halved  = NY_/2 + 1
        ! Not clear which one should be the normalization factor
        inv_Ny_    = 1._xp/REAL(NY_,xp)     
        ! Call FFTW 2D mpi routines to distribute the data and init 2D MPI FFTW plans
        CALL fft2D_distr_and_plans(communicator,&
                local_nx_ptr,local_nx_ptr_offset,local_nky_ptr,local_nky_ptr_offset)
        local_nx_  = local_nx_ptr  ! store number of local x  in the module
        local_nky_ = local_nky_ptr ! store number of local ky in the module
        ! Init 1D MPI FFTW plans for ExB rate correction factor
        CALL fft1D_plans
        ! store data distr. in the module for the poisson_bracket function
    END SUBROUTINE init_grid_distr_and_plans

    !------------- 2D fft initialization and mpi distribution
    SUBROUTINE fft2D_distr_and_plans(communicator,&
                local_nx_ptr,local_nx_ptr_offset,local_nky_ptr,local_nky_ptr_offset)
        IMPLICIT NONE
        INTEGER, INTENT(IN)  :: communicator
        ! Distribution in the real space along x
        INTEGER(C_INTPTR_T), INTENT(OUT) :: local_nx_ptr, local_nx_ptr_offset
        ! Distribution in the fourier space along ky
        INTEGER(C_INTPTR_T), INTENT(OUT) :: local_nky_ptr,local_nky_ptr_offset
        !! Complex arrays F, G
        ! Compute the room to allocate
#ifdef SINGLE_PRECISION
        alloc_local_1 = fftwf_mpi_local_size_2d(NY_halved, NX_,communicator,&
                local_nky_ptr, local_nky_ptr_offset)
#else
        alloc_local_1 = fftw_mpi_local_size_2d(NY_halved, NX_,communicator,&
                local_nky_ptr, local_nky_ptr_offset)
#endif
    ! Initalize pointers to this room
#ifdef SINGLE_PRECISION
        cdatac_f = fftwf_alloc_complex(alloc_local_1)
        cdatac_g = fftwf_alloc_complex(alloc_local_1)
        cdatac_c = fftwf_alloc_complex(alloc_local_1)
#else
        cdatac_f = fftw_alloc_complex(alloc_local_1)
        cdatac_g = fftw_alloc_complex(alloc_local_1)
        cdatac_c = fftw_alloc_complex(alloc_local_1)
#endif
        ! Initalize the arrays with the rooms pointed
        call c_f_pointer(cdatac_f,   cmpx_data_f, [NX_ ,local_nky_ptr])
        call c_f_pointer(cdatac_g,   cmpx_data_g, [NX_ ,local_nky_ptr])
        call c_f_pointer(cdatac_c, bracket_sum_c, [NX_ ,local_nky_ptr])
        !! Real arrays iFFT(F), iFFT(G)
        ! Compute the room to allocate
        alloc_local_2 = fftw_mpi_local_size_2d(NX_,NY_halved,communicator,&
                local_nx_ptr, local_nx_ptr_offset)
        ! Initalize pointers to this room
#ifdef SINGLE_PRECISION
        cdatar_f = fftwf_alloc_real(2*alloc_local_2)
        cdatar_g = fftwf_alloc_real(2*alloc_local_2)
        cdatar_c = fftwf_alloc_real(2*alloc_local_2)
#else
        cdatar_f = fftw_alloc_real(2*alloc_local_2)
        cdatar_g = fftw_alloc_real(2*alloc_local_2)
        cdatar_c = fftw_alloc_real(2*alloc_local_2)
#endif
        ! Initalize the arrays with the rooms pointed
        call c_f_pointer(cdatar_f,   real_data_f, [2*(NY_/2 + 1),local_nx_ptr])
        call c_f_pointer(cdatar_g,   real_data_g, [2*(NY_/2 + 1),local_nx_ptr])
        call c_f_pointer(cdatar_c, bracket_sum_r, [2*(NY_/2 + 1),local_nx_ptr])
        ! Plan Creation (out-of-place forward and backward FFT)
#ifdef SINGLE_PRECISION
        planf = fftwf_mpi_plan_dft_r2c_2D(NX_,NY_,real_data_f,cmpx_data_f,communicator, &
                                            ior(FFTW_MEASURE, FFTW_MPI_TRANSPOSED_OUT))
        planb = fftwf_mpi_plan_dft_c2r_2D(NX_,NY_,cmpx_data_f,real_data_f,communicator, &
                                            ior(FFTW_MEASURE, FFTW_MPI_TRANSPOSED_IN))
#else
        planf = fftw_mpi_plan_dft_r2c_2D(NX_,NY_,real_data_f,cmpx_data_f,communicator, &
                                            ior(FFTW_MEASURE, FFTW_MPI_TRANSPOSED_OUT))
        planb = fftw_mpi_plan_dft_c2r_2D(NX_,NY_,cmpx_data_f,real_data_f,communicator, &
                                            ior(FFTW_MEASURE, FFTW_MPI_TRANSPOSED_IN))
#endif
        if ((.not. c_associated(planf)) .OR. (.not. c_associated(planb))) then
            ERROR STOP '>> ERROR << 2D MPI plan creation error!!'
        end if
    END SUBROUTINE fft2D_distr_and_plans
    !******************************************************************************!

    !******************************************************************************!
    !------------- 1D initialization with balanced data distribution
    SUBROUTINE fft1D_plans
        USE utility,  ONLY: decomp1D
        IMPLICIT NONE
        ! local var
        integer(C_INTPTR_T) :: rank     ! rank of each 1D fourier transforms
        integer(C_INTPTR_T) :: n        ! size of the data to fft
        integer(C_INTPTR_T) :: howmany  ! howmany 1D fourier transforms
        integer(C_INTPTR_T) :: inembed, onembed
        integer(C_INTPTR_T) :: istride, ostride
        integer(C_INTPTR_T) :: idist, odist
        !! Plans of the 4 single 1D transforms
        !----------- 1: FFTx and inv through local ky data
        !(kx,ky) <-> (x,ky), C <-> C, transforms
        ! in & out
        ALLOCATE( f_kx_l(NX_))
        ALLOCATE(  f_x_l(NX_))
#ifdef SINGLE_PRECISION
        CALL sfftw_plan_dft_1d(plan_kx2x_c2c,NX_,f_kx_l,f_x_l,FFTW_BACKWARD,FFTW_PATIENT)
        CALL sfftw_plan_dft_1d(plan_x2kx_c2c,NX_,f_x_l,f_kx_l,FFTW_FORWARD, FFTW_PATIENT)
#else
        CALL dfftw_plan_dft_1d(plan_kx2x_c2c,NX_,f_kx_l,f_x_l,FFTW_BACKWARD,FFTW_PATIENT)
        CALL dfftw_plan_dft_1d(plan_x2kx_c2c,NX_,f_x_l,f_kx_l,FFTW_FORWARD, FFTW_PATIENT)
#endif
        !----------- 2: FFTy and inv through global ky data
        !(y,x) <-> (ky,x), R <-> C, transforms (bplan_y in GENE)
        ! in & out
        ALLOCATE( f_y_g(NY_))
        ALLOCATE(f_ky_g(NY_/2+1))
#ifdef SINGLE_PRECISION
        CALL sfftw_plan_dft_r2c_1d(plan_y2ky_r2c,NY_,f_y_g,f_ky_g,FFTW_PATIENT)            
        CALL sfftw_plan_dft_c2r_1d(plan_ky2y_c2r,NY_,f_ky_g,f_y_g,FFTW_PATIENT)            
#else
        CALL dfftw_plan_dft_r2c_1d(plan_y2ky_r2c,NY_,f_y_g,f_ky_g,FFTW_PATIENT)
        CALL dfftw_plan_dft_c2r_1d(plan_ky2y_c2r,NY_,f_ky_g,f_y_g,FFTW_PATIENT)
#endif
        !! Plans of the 4 1D many transforms required
        !----------- 1: FFTx and inv through local ky data
        !1.1 (kx,ky) -> (x,ky), C -> C, transforms
        ! in:
        ALLOCATE( f_kxky_l(NX_,local_nky_))
        ! out:
        ALLOCATE(  f_xky_l(NX_,local_nky_))
        ! transform parameters
        rank    = 1              ! 1D transform
        n       = NX_            ! all kx modes
        howmany = local_nky_     ! all local ky
        inembed = NX_            ! all data must be transformed
        onembed = NX_
        idist   = NX_            ! distance between data to transforms (x columns)
        odist   = NX_
        istride = 1              ! contiguous data
        ostride = 1
#ifdef SINGLE_PRECISION
        CALL sfftw_plan_many_dft(plan_many_kx2x_c2c, rank, n, howmany,&
                                 f_kxky_l, inembed, istride, idist,&
                                  f_xky_l, onembed, ostride, odist,& 
                                 FFTW_BACKWARD, FFTW_PATIENT)                
#else
        CALL dfftw_plan_many_dft(plan_many_kx2x_c2c, rank, n, howmany,&
                                 f_kxky_l, inembed, istride, idist,&
                                  f_xky_l, onembed, ostride, odist,& 
                                 FFTW_BACKWARD, FFTW_PATIENT)    
#endif
        ! 1.2 (x,ky) -> (kx,ky), C -> C, transforms
        ! in:  f_xky_l
        ! out: f_kxky_l
        ! transform parameters
        rank    = 1              ! 1D transform
        n       = NX_            ! all kx modes
        howmany = local_nky_     ! all local ky
        inembed = NX_            ! all data must be transformed
        onembed = NX_
        idist   = NX_            ! distance between data to transforms (x columns)
        odist   = NX_
        istride = 1              ! contiguous data
        ostride = 1
#ifdef SINGLE_PRECISION
        CALL sfftw_plan_many_dft(plan_many_x2kx_c2c, rank, n, howmany,&
                                 f_xky_l, inembed, istride, idist,&
                                f_kxky_l, onembed, ostride, odist,& 
                                FFTW_FORWARD, FFTW_PATIENT)                
#else
        CALL dfftw_plan_many_dft(plan_many_x2kx_c2c, rank, n, howmany,&
                                 f_xky_l, inembed, istride, idist,&
                                f_kxky_l, onembed, ostride, odist,& 
                                FFTW_FORWARD, FFTW_PATIENT)    
#endif

        !----------- 2: FFTy and inv through global ky data
        !  (can be improved by inputting directly the padded array of size 2(Ny/2+1))
        ! 2.1 (y,x) -> (ky,x), R -> C, transforms (bplan_y in GENE)
        ! in:
        ALLOCATE(f_yx_g(NY_,local_nx_))
        ! out:
        ALLOCATE(f_kyx_g(NY_/2+1,local_nx_))
        ! transform parameters
        rank    = 1              ! 1D transform
        n       = NY_            ! all y
        howmany = local_nx_      ! all x
        inembed = NY_            ! all y must be used
        istride = 1              ! contiguous data
        idist   = NY_            ! distance between two slice to transforms (y row)
        onembed = NY_/2+1
        ostride = 1
        odist   = NY_/2+1
#ifdef SINGLE_PRECISION
        CALL sfftw_plan_many_dft_r2c(plan_many_y2ky_r2c, rank, n, howmany,&
                             f_yx_g, inembed, istride, idist,&
                            f_kyx_g, onembed, ostride, odist,& 
                                    FFTW_PATIENT)                
#else
        CALL dfftw_plan_many_dft_r2c(plan_many_y2ky_r2c, rank, n, howmany,&
                             f_yx_g, inembed, istride, idist,&
                            f_kyx_g, onembed, ostride, odist,& 
                                    FFTW_PATIENT)    
#endif
        !-----------
        ! 2.2 (x,ky) -> (x,y), C -> R, transforms (fplan_y in GENE)
        ! in:  f_kyx_g
        ! out: f_yx_g
        ! transform parameters
        rank    = 1               ! 1D transform
        n       = NY_             ! all ky
        howmany = local_nx_       ! all x
        inembed = NY_/2+1         ! all ky must be used
        istride = 1               ! contiguous data
        idist   = NY_/2+1         ! distance between two slice to transforms (y row)
        onembed = NY_             ! to all y
        ostride = 1
        odist   = NY_
#ifdef SINGLE_PRECISION
        CALL sfftw_plan_many_dft_c2r(plan_many_ky2y_c2r, rank, n, howmany,&
                            f_kyx_g, inembed, istride, idist,&
                             f_yx_g, onembed, ostride, odist,& 
                                    FFTW_PATIENT)                
#else
        CALL dfftw_plan_many_dft_c2r(plan_many_ky2y_c2r, rank, n, howmany,&
                            f_kyx_g, inembed, istride, idist,&
                             f_yx_g, onembed, ostride, odist,& 
                                    FFTW_PATIENT)    
#endif
    if ((.not. c_associated(plan_kx2x_c2c)) .OR. &
        (.not. c_associated(plan_x2kx_c2c)) .OR. &
        (.not. c_associated(plan_ky2y_c2r)) .OR. &
        (.not. c_associated(plan_ky2y_c2r)) .OR. &
        (.not. c_associated(plan_many_x2kx_c2c)) .OR. &
        (.not. c_associated(plan_many_kx2x_c2c)) .OR. &
        (.not. c_associated(plan_many_ky2y_c2r)) .OR. &
        (.not. c_associated(plan_many_y2ky_r2c)) ) then
        ERROR STOP '>> ERROR << 1D plan creation error!!'
    end if

END SUBROUTINE fft1D_plans
    !******************************************************************************!

    !******************************************************************************!
    !!! Compute the poisson bracket to real space and sum it to the bracket_sum_r
    !   module variable (convolution theorem)
    SUBROUTINE poisson_bracket_and_sum( ky_array, kx_array, inv_Ny, inv_Nx, AA_y, AA_x,&
                                        local_nky, total_nkx, F_, G_,&
                                        ExB_NL_CORRECTION,ExB_NL_factor,sum_real_)
        IMPLICIT NONE
        INTEGER,                                  INTENT(IN) :: local_nky,total_nkx
        REAL(xp),                                 INTENT(IN) :: inv_Nx, inv_Ny
        REAL(xp), DIMENSION(local_nky),           INTENT(IN) :: ky_array, AA_y
        REAL(xp), DIMENSION(total_nkx),           INTENT(IN) :: AA_x
        REAL(xp), DIMENSION(local_nky,total_nkx), INTENT(IN) :: kx_array
        COMPLEX(c_xp_c), DIMENSION(local_nky,total_nkx), &
                                                  INTENT(IN) :: F_, G_
        COMPLEX(xp), DIMENSION(total_nkx,local_nky), &
                                                  INTENT(IN) :: ExB_NL_factor
        LOGICAL, INTENT(IN) :: ExB_NL_CORRECTION
        real(c_xp_r), pointer,                 INTENT(INOUT) :: sum_real_(:,:)
        ! local variables
        INTEGER :: ikx,iky
        COMPLEX(xp), DIMENSION(total_nkx,local_nky) :: ikxF, ikyG, ikyF, ikxG
        REAL(xp):: kxs, ky
        
        ! Build the fields to convolve
        ! Store df/dx, dg/dy and df/dy, dg/dx
        DO iky = 1,local_nky
                DO ikx = 1,total_nkx
                        ky  = ky_array(iky)
                        kxs = kx_array(iky,ikx)
                        ikxF(ikx,iky) = imagu*kxs*F_(iky,ikx)
                        ikyG(ikx,iky) = imagu*ky *G_(iky,ikx)
                        ikyF(ikx,iky) = imagu*ky *F_(iky,ikx)
                        ikxG(ikx,iky) = imagu*kxs*G_(iky,ikx)
                ENDDO
        ENDDO
        IF(ExB_NL_CORRECTION) THEN 
            ! Apply the ExB shear correction factor exp(ixkySJdT)
            CALL apply_ExB_NL_factor(ikxF,ExB_NL_factor)
            CALL apply_ExB_NL_factor(ikyG,ExB_NL_factor)
            CALL apply_ExB_NL_factor(ikyF,ExB_NL_factor)
            CALL apply_ExB_NL_factor(ikxG,ExB_NL_factor)
        ENDIF
        ! Anti Aliasing
        DO iky = 1,local_nky
                DO ikx = 1,total_nkx
                        ikxF(ikx,iky) = ikxF(ikx,iky)*AA_y(iky)*AA_x(ikx)
                        ikyG(ikx,iky) = ikyG(ikx,iky)*AA_y(iky)*AA_x(ikx)
                        ikyF(ikx,iky) = ikyF(ikx,iky)*AA_y(iky)*AA_x(ikx)
                        ikxG(ikx,iky) = ikxG(ikx,iky)*AA_y(iky)*AA_x(ikx)
                ENDDO
        ENDDO
        !-------------------- First term df/dx x dg/dy --------------------
#ifdef SINGLE_PRECISION
        call fftwf_mpi_execute_dft_c2r(planb, ikxF, real_data_f)
        call fftwf_mpi_execute_dft_c2r(planb, ikyG, real_data_g)
#else
        call fftw_mpi_execute_dft_c2r(planb, ikxF, real_data_f)
        call fftw_mpi_execute_dft_c2r(planb, ikyG, real_data_g)
#endif
        sum_real_ = sum_real_ + real_data_f*real_data_g*inv_Ny*inv_Nx
        !--------------------------------------------------------------------

        !-------------------- Second term -df/dy x dg/dx --------------------
#ifdef SINGLE_PRECISION
        call fftwf_mpi_execute_dft_c2r(planb, ikyF, real_data_f)
        call fftwf_mpi_execute_dft_c2r(planb, ikxG, real_data_g)
#else
        call fftw_mpi_execute_dft_c2r(planb, ikyF, real_data_f)
        call fftw_mpi_execute_dft_c2r(planb, ikxG, real_data_g)
#endif
        sum_real_ = sum_real_ - real_data_f*real_data_g*inv_Ny*inv_Nx
    END SUBROUTINE poisson_bracket_and_sum
    !******************************************************************************!
    !******************************************************************************!

    !******************************************************************************!
    ! Apply the exp(-i*x*ky*S*J*dt) factor to the Poisson bracket fields (ikxF, ikyG, ...) 
    ! (see Mcmillan et al. 2019)
    SUBROUTINE apply_ExB_NL_factor(fkxky,ExB_NL_factor)
        IMPLICIT NONE
        COMPLEX(xp), DIMENSION(NX_,local_nky_), INTENT(INOUT)  :: fkxky
        COMPLEX(xp), DIMENSION(NX_,local_nky_), INTENT(IN)     :: ExB_NL_factor
        ! local variables
        COMPLEX(xp), DIMENSION(NX_,local_nky_) :: tmp_kxky, tmp_xky
        integer(C_INTPTR_T) :: ix,ikx,iky
        ! Fill the buffer
        DO iky = 1,local_nky_
                DO ikx = 1,NX_
                        tmp_kxky(ikx,iky) = fkxky(ikx,iky)
                ENDDO
        ENDDO
        ! go to x,ky
        CALL iFFT_kxky_to_xky(tmp_kxky,tmp_xky)
        ! multiply result by NL factor
        DO iky = 1,local_nky_
            DO ix = 1,NX_
                tmp_xky(ix,iky) = inv_Nx_*tmp_xky(ix,iky)*ExB_NL_factor(ix,iky)
            ENDDO
        ENDDO
        ! back to kx,ky
        CALL FFT_xky_to_kxky(tmp_xky,tmp_kxky)
        ! fill output array
        DO iky = 1,local_nky_
                DO ikx = 1,NX_
                        fkxky(ikx,iky) = tmp_kxky(ikx,iky)
                ENDDO
        ENDDO
    END SUBROUTINE apply_ExB_NL_factor

    ! High level FFT routines required
    ! 1D inverse Fourier transform from (kx,ky) to (x,ky), complex to complex
    SUBROUTINE iFFT_kxky_to_xky(in_kxky,out_xky)
        IMPLICIT NONE
        ! arguments
        COMPLEX(xp), DIMENSION(NX_,local_nky_), INTENT(IN)  :: in_kxky
        COMPLEX(xp), DIMENSION(NX_,local_nky_), INTENT(OUT) :: out_xky
        ! local variables
        ! COMPLEX(xp), DIMENSION(NX_) :: in_kx, out_x
        INTEGER :: iky
#ifdef SINGLE_PRECISION
        !plan many 1D transforms
        CALL sfftw_execute_dft(plan_many_kx2x_c2c, in_kxky, out_xky)
#else 
        IF (PLAN_MANY) THEN
        ! IF (.TRUE.) THEN                
                !plan many 1D transforms
                ! CALL dfftw_execute_dft(plan_many_kx2x_c2c, in_kxky, out_xky)
                f_kxky_l = in_kxky
                CALL dfftw_execute_dft(plan_many_kx2x_c2c, f_kxky_l, f_xky_l)
                out_xky  = f_xky_l
        ELSE
                !or loop over 1D transforms (less efficient)
                DO iky = 1,local_nky_
                        ! in_kx = in_kxky(:,iky)
                        ! CALL dfftw_execute_dft(plan_kx2x_c2c,in_kx,out_x)
                        ! out_xky(:,iky) = out_x
                        f_kx_l = in_kxky(:,iky)
                        CALL dfftw_execute_dft(plan_kx2x_c2c,f_kx_l,f_x_l)
                        out_xky(:,iky) = f_x_l
                ENDDO
        ENDIF
#endif
    END SUBROUTINE iFFT_kxky_to_xky
    ! 1D Fourier transform from kx,ky) to (kx,ky), complex to complex
    SUBROUTINE FFT_xky_to_kxky(in_xky,out_kxky)
        IMPLICIT NONE
        ! arguments
        COMPLEX(xp), DIMENSION(NX_,local_nky_), INTENT(IN)  :: in_xky
        COMPLEX(xp), DIMENSION(NX_,local_nky_), INTENT(OUT) :: out_kxky
        ! local variables
        ! COMPLEX(xp), DIMENSION(NX_) :: in_x, out_kx
        INTEGER :: iky
#ifdef SINGLE_PRECISION
        CALL sfftw_execute_dft(plan_many_x2kx_c2c, in_xky, out_kxky)
#else 
        IF (PLAN_MANY) THEN
        ! IF (.TRUE.) THEN                
                !plan many 1D transforms
                ! CALL dfftw_execute_dft(plan_many_x2kx_c2c, in_xky, out_kxky)
                f_xky_l  = in_xky
                CALL dfftw_execute_dft(plan_many_x2kx_c2c, f_xky_l, f_kxky_l)
                out_kxky = f_kxky_l
        ELSE
                !or loop over 1D transforms (less efficient)
                DO iky = 1,local_nky_
                        ! in_x = in_xky(:,iky)
                        ! CALL dfftw_execute_dft(plan_x2kx_c2c,in_x,out_kx)
                        ! out_kxky(:,iky) = out_kx
                        f_x_l = in_xky(:,iky)
                        CALL dfftw_execute_dft(plan_x2kx_c2c,f_x_l,f_kx_l)
                        out_kxky(:,iky) = f_kx_l
                ENDDO
        ENDIF
#endif
    END SUBROUTINE FFT_xky_to_kxky

    !*****************************************************************************!
    ! Apply the inverse factor, exp(i*x*ky*S*J*dt), to the real Poisson Bracket sum [f,g]
    SUBROUTINE apply_inv_ExB_NL_factor(fyx,inv_ExB_NL_factor)
        IMPLICIT NONE
        ! REAL(xp), DIMENSION(2*NY_halved,local_nx_), INTENT(INOUT) :: fyx
        real(c_xp_r), pointer,                   INTENT(INOUT) :: fyx(:,:)
        COMPLEX(xp), DIMENSION(NY_/2+1,local_nx_),  INTENT(IN) :: inv_ExB_NL_factor
        ! local variables
        REAL(xp),    DIMENSION(NY_,    local_nx_) :: tmp_yx
        COMPLEX(xp), DIMENSION(NY_/2+1,local_nx_) :: tmp_kyx
        integer(C_INTPTR_T) :: ix, iy, iky
        ! Fill buffer
        DO ix = 1,local_nx_
            DO iy = 1,NY_
                tmp_yx(iy,ix) = fyx(iy,ix)
                ! tmp_yx(iy,ix) = bracket_sum_r(iy,ix)
            ENDDO
        ENDDO
        ! Fourier real to complex on the second buffer (first buffer is now unusable)
        CALL FFT_yx_to_kyx(tmp_yx,tmp_kyx)
        ! Treat the result with the ExB NL factor
        DO iky = 1,NY_/2+1
                DO ix = 1,local_nx_
                        tmp_kyx(iky,ix) = tmp_kyx(iky,ix)*inv_ExB_NL_factor(iky,ix)
                ENDDO
        ENDDO
        ! Back to Fourier space in the third buffer
        CALL iFFT_kyx_to_yx(tmp_kyx,tmp_yx)
        ! Fill the result array with normalization of iFFT
        DO ix = 1,local_nx_
            DO iy = 1,NY_
                fyx(iy,ix) = tmp_yx(iy,ix)*inv_Ny_
                ! bracket_sum_r(iy,ix) = tmp_yx(iy,ix)*inv_Ny_
            ENDDO
        ENDDO
    END SUBROUTINE apply_inv_ExB_NL_factor
    ! 1D Fourier transform from (y,x) to (ky,x), real to complex
    SUBROUTINE FFT_yx_to_kyx(in_yx,out_kyx)
        IMPLICIT NONE
        ! arguments
        REAL(xp),    DIMENSION(NY_,    local_nx_), INTENT(IN)  :: in_yx
        COMPLEX(xp), DIMENSION(NY_/2+1,local_nx_), INTENT(OUT) :: out_kyx
        ! local var.
        ! REAL(xp),    DIMENSION(NY_)     :: in_y
        ! COMPLEX(xp), DIMENSION(NY_/2+1) :: out_ky
        ! COMPLEX(xp), DIMENSION(NY_halved+1,local_nx_) :: out_kyx_test
        INTEGER :: ix, iy, iky
#ifdef SINGLE_PRECISION
        CALL sfftw_execute_dft_r2c(plan_many_y2ky_r2c, in_yx, out_kyx)
#else 
        IF (PLAN_MANY) THEN
        ! IF (.TRUE.) THEN
                !plan many 1D transforms
                ! CALL dfftw_execute_dft_r2c(plan_many_y2ky_r2c, in_yx, out_kyx)
                f_yx_g  = in_yx
                CALL dfftw_execute_dft_r2c(plan_many_y2ky_r2c, f_yx_g, f_kyx_g)
                out_kyx = f_kyx_g
        ELSE
                ! CALL dfftw_execute_dft_r2c(plan_many_y2ky_r2c, in_yx, out_kyx_test)
                !or loop over 1D transforms (less efficient but checkable)
                DO ix = 1,local_nx_
                        DO iy = 1,NY_
                                ! in_y(iy) = in_yx(iy,ix)                                
                                f_y_g(iy) = in_yx(iy,ix)                                
                        ENDDO
                        ! CALL dfftw_execute_dft_r2c(plan_y2ky_r2c,in_y,out_ky)
                        CALL dfftw_execute_dft_r2c(plan_y2ky_r2c,f_y_g,f_ky_g)
                        DO iky = 1,NY_/2+1
                                ! out_kyx(iky,ix) = out_ky(iky)
                                out_kyx(iky,ix) = f_ky_g(iky)
                        ENDDO
                ENDDO
        ENDIF
#endif
    END SUBROUTINE FFT_yx_to_kyx
    ! 1D Fourier transform from (ky,x) to (y,x), complex to real
    SUBROUTINE iFFT_kyx_to_yx(in_kyx,out_yx)
        IMPLICIT NONE
        ! arguments
        COMPLEX(xp), DIMENSION(NY_/2+1,local_nx_), INTENT(IN)  :: in_kyx
        REAL(xp),    DIMENSION(NY_    ,local_nx_), INTENT(OUT) :: out_yx
        ! local var.
        ! COMPLEX(xp), DIMENSION(Ny_/2+1) :: in_ky
        ! REAL(xp),    DIMENSION(NY_)     :: out_y
        ! REAL(xp),    DIMENSION(NY_,local_nx_) :: out_yx_test
        INTEGER :: ix, iy, iky
#ifdef SINGLE_PRECISION
        CALL sfftw_execute_dft_c2r(plan_many_ky2y_c2r, in_kyx, out_yx)
#else 
        IF (PLAN_MANY) THEN
        ! IF (.TRUE.) THEN
                !plan many 1D transforms
                ! CALL dfftw_execute_dft_c2r(plan_many_ky2y_c2r, in_kyx, out_yx)
                f_kyx_g = in_kyx
                CALL dfftw_execute_dft_c2r(plan_many_ky2y_c2r, f_kyx_g, f_yx_g)
                out_yx  = f_yx_g
        ELSE
                ! CALL dfftw_execute_dft_c2r(plan_many_ky2y_c2r, in_kyx, out_yx_test)
                !or loop over 1D transforms (less efficient)
                DO ix = 1,local_nx_
                        DO iky = 1,NY_/2+1
                                ! in_ky(iky) = in_kyx(iky,ix)
                                f_ky_g(iky) = in_kyx(iky,ix)
                        ENDDO
                        ! CALL dfftw_execute_dft_c2r(plan_ky2y_c2r,in_ky,out_y)
                        CALL dfftw_execute_dft_c2r(plan_ky2y_c2r,f_ky_g,f_y_g)
                        DO iy = 1,NY_
                                ! out_yx(iy,ix) = out_y(iy)
                                out_yx(iy,ix) = f_y_g(iy)
                        ENDDO
                ENDDO
        ENDIF
#endif
    END SUBROUTINE iFFT_kyx_to_yx

    !******************************************************************************!
    !******************************************************************************!

    SUBROUTINE finalize_plans
        USE basic, ONLY: speak
        IMPLICIT NONE
        CALL speak('..plan Destruction.')
        call fftw_destroy_plan(planb)
        call fftw_destroy_plan(planf)
        call fftw_mpi_cleanup()
        call fftw_free(cdatar_f)
        call fftw_free(cdatar_g)
        call fftw_free(cdatar_c)
        call fftw_free(cdatac_f)
        call fftw_free(cdatac_g)
        call fftw_free(cdatac_c)
        DEALLOCATE(f_xky_l,f_kxky_l)
        DEALLOCATE(f_kyx_g,f_yx_g)
    END SUBROUTINE finalize_plans

END MODULE fourier
