import numpy as np
import numpy.ma as mp
import scipy.integrate as spIntegrate
import logging
from itertools import repeat

log=logging.getLogger('root')

import xframe.library.pythonLibrary as pyLib
import xframe.library.mathLibrary as mLib
from .harmonic_transforms import generate_circular_harmonic_transform_pair
from .harmonic_transforms import HarmonicTransform
from .hankel_transforms import generateHT_SinCos
from .hankel_transforms import generate_zernike_ht
from .hankel_transforms import generate_spherical_zernike_ht
from .hankel_transforms import generate_spherical_zernike_ht_new
from .hankel_transforms import generate_spherical_zernike_ht_new2
from .hankel_transforms import generate_spherical_zernike_ht_big_sum
from .hankel_transforms import generate_spherical_zernike_ht_gpu
from .hankel_transforms import generate_ht_spherical_SinCos
from .hankel_transforms import generate_weightDict_zernike_spherical
from xframe import database
from xframe.settings import analysis as settings

doublePrecision=pyLib.doublePrecision



###@#########################
###polar Fourier via Hankel transforms###
def generatePolarFT_viaHT(FTGridPair,generateHankeltransforms):
    dht_inverse,dht_forwars=generateHankeltransforms(FTGridPair)

    def fourierTransform(dataGrid):
        harmonicCoefficients=circularHarmonicTransform(dataGrid)
#        log.info('harmonicCoefficients shape={}'.format(harmonicCoefficients.shape))
        reciprocalHarmonicCoefficients=dht_forward(harmonicCoefficients,quadratureWeights,realCutOff)
#        log.info('reciprocal harmonic coeff = {}'.format(reciprocalHarmonicCoefficients))
        reciprocalFunction=circularHarmonicTransform_inverse(reciprocalHarmonicCoefficients)
        return reciprocalFunction
    
    def inverseFourierTransform(dataGrid):
        reciprocalHarmonicCoefficients=circularHarmonicTransform(dataGrid)
        harmonicCoefficients=dht_inverse(reciprocalHarmonicCoefficients,quadratureWeights,reciprocalCutOff)
        realFunction=circularHarmonicTransform_inverse(harmonicCoefficients)
        return realFunction
    return fourierTransform,inverseFourierTransform

####################
###polar bessel forward####
def polarFT_forward_bessel(sampledPolarFunction):
    polarIndexList=generatePolarIndexList(sampledPolarFunction.shape[-1])
    indexCount=0
    def dht(inputData):
        nonlocal indexCount
#        log.info('indexCount={}'.format(indexCount))
        polarIndex=polarIndexList[indexCount]        
        #      log.info('input data ={}'.format(inputData))
        transformedData=mLib.dht_forward(inputData,polarIndex)
        transformedData=transformedData*2*np.pi*1.j**(-polarIndex)
        indexCount+=1
        return transformedData        
    
    shape=sampledPolarFunction.shape
    fftForward=mLib.fft_real_mixedRadix_forward
    fftInverse=mLib.fft_complex_mixedRadix_inverse
    
#    log.info('\n in=\n {}'.format(sampledPolarFunction.array))
    harmonicCoefficients=sampledPolarFunction.apply_along_axis(fftForward,len(shape)-1)
#    log.info('\n ft=\n {}'.format(harmonicCoefficients.array))
    reciprocalHarmonicCoefficients=harmonicCoefficients.apply_along_axis(dht,0)
#    log.info('\n dht =\n {}'.format(reciprocalHarmonicCoefficients.array))
    reciprocalFunction=reciprocalHarmonicCoefficients.apply_along_axis(fftInverse,len(shape)-1)
#    log.info('after forwart FT =\n {}'.format(reciprocalFunction.array))
    return reciprocalFunction


###################
###polar bessel inverse###
def polarFT_inverse_bessel(dataGrid):    
    polarIndexList=generatePolarIndexList(dataGrid.shape[-1])
    indexCount=0
    def inverseDHT(inputData):
        nonlocal indexCount
#        log.info('indexCount={}'.format(indexCount))
        polarIndex=polarIndexList[indexCount]        
        #log.info('input data ={}'.format(inputData))
        newInputData=inputData*1.j**polarIndex/(2*np.pi)
        transformedData=mLib.dht_inverse(newInputData,polarIndex)
        indexCount+=1
        return transformedData

    shape=dataGrid.shape
    fftForward=mLib.fft_complex_mixedRadix_forward
    fftInverse=mLib.fft_complex_mixedRadix_inverse
#    log.info('\n in=\n {}'.format(dataGrid.array))
    reciprocalHarmonicCoefficients=dataGrid.apply_along_axis(fftForward,len(shape)-1)
#    log.info('\n ft=\n {}'.format(reciprocalHarmonicCoefficients.array))
    harmonicCoefficients=reciprocalHarmonicCoefficients.apply_along_axis(inverseDHT,0)
#    log.info('\n dht^-1=\n {}'.format(harmonicCoefficients.array))
    function=harmonicCoefficients.apply_along_axis(fftInverse,len(shape)-1)
#    log.info('after inverse FT =\n {}'.format(function.array))
    return function



##################
### adaptiveQuad FTs ###
def generateAdaptive_polarFT(FTGridPair):
    #assumes FTGridPair was generated by the polarFTGridPair_Donatelli  method
    realGrid=FTGridPair.realGrid
    reciprocalGrid=FTGridPair.reciprocalGrid
    realOrigin=realGrid.flatten()[0]
    reciprocalOrigin=reciprocalGrid.flatten()[0]    
    gridDimension=len(realOrigin)

    realStepSizes=pyLib.uniformGridGetStepSizes(realGrid.array)
    reziprocalStepSizes=pyLib.uniformGridGetStepSizes(reziprocalGrid.array)
    realGridSpec=[realOrigin,realStepSizes]
    reziprocalGridSpec=[reziprocalOrigin,reziprocalStepSizes]
    
    def fourierTransform(dataGrid):
        harmonicCoefficients=circularHarmonicTransform(dataGrid)
        reciprocalHarmonicCoefficients=hankelTransform(harmonicCoefficients,realGridSpec,realCutOff)
        

###############
### donatelli FTs ###
def generate_discrete_polarFT(maxR,n_radial_points,harmonic_orders):
    dht_forward,dht_inverse=mLib.generate_dht_2d(maxR,n_radial_points,harmonic_orders)
    cht_forward,cht_inverse=generate_circular_harmonic_transform_pair('complex',len(harmonic_orders))

    def ft_forward(real_function):
        return cht_inverse(dht_forward(cht_forward(real_function)))
    def ft_inverse(reciprocal_function):
        return cht_inverse(dht_inverse(cht_forward(reciprocal_function)))
    return ft_forward,ft_inverse

def generateDonatelli_polarFT_2(ft_grid_pair,weights_dict,mode):
    real_grid=ft_grid_pair.realGrid
    
    if mode=='real':
        cht_forward,cht_inverse=generate_circular_harmonic_transform_pair('real',real_grid.shape[-1])
        ht_forward,ht_inverse=generateHT_SinCos(ft_grid_pair,weights_dict,mode='real')
    elif mode=='complex':
        cht_forward,cht_inverse=generate_circular_harmonic_transform_pair('complex',real_grid.shape[-1])
        ht_forward,ht_inverse=generateHT_SinCos(ft_grid_pair,weights_dict,mode='complex')
        
        
        #    step_sizes=pyLib.uniformGridGetStepSizes(real_grid.array)
        
    def fourierTransform(data):
        #        log.info('grid dim before fourier={}'.format(dataGrid.dimension))
        harmonic_coefficients=cht_forward(data)
        #        log.info('harmonicCoefficients shape={}'.format(harmonicCoefficients.shape))
        #        log.info('harmCoefficient dim={}'.format(harmonicCoefficients.dimension))
        reciprocal_harmonic_coefficients=ht_forward(harmonic_coefficients)
        #        log.info('dim after HT={}'.format(reciprocalHarmonicCoefficients.dimension))
        #        reciprocalHarmonicCoefficients=harmonicCoefficients
        #        log.info('reciprocal harmonic coeff = {}'.format(reciprocalHarmonicCoefficients))
        reciprocal_function=cht_inverse(reciprocal_harmonic_coefficients)
        #        log.info('dim after FT={}'.format(reciprocalFunction.dimension))
        return reciprocal_function
    def inverseFourierTransform(data):
        reciprocal_harmonic_coefficients=cht_forward(data)
        harmonic_coefficients=ht_inverse(reciprocal_harmonic_coefficients)
        #        harmonicCoefficients=reciprocalHarmonicCoefficients
        real_function=cht_inverse(harmonic_coefficients)
        return real_function
    return fourierTransform,inverseFourierTransform

def generateDonatelli_polarFT(ft_grid_pair,weights_dict,mode):
    real_grid=ft_grid_pair.realGrid
    n_angular_points=real_grid.shape[-1]
    n_independent_coeff=int(np.floor((n_angular_points+1)/2)+n_angular_points%2)
    ht,iht=generateHT_SinCos(ft_grid_pair,weights_dict)
    complex_cht,complex_icht=generate_circular_harmonic_transform_pair('complex',real_grid.shape[-1])
    if mode=='complex':
        hankel=ht
        i_hankel=iht
        ft_cht=complex_cht
        ft_icht=complex_icht
        ift_cht=complex_cht
        ift_icht=complex_icht     
    elif mode=='real':
        ft_cht,ift_icht=generate_circular_harmonic_transform_pair('real',real_grid.shape[-1])
        def hankel(harmonic_coefficients):
#            log.info('real harm coef shape={}'.format(harmonic_coefficients.array.shape))
            harmonic_coefficients=np.concatenate((harmonic_coefficients,np.conjugate(harmonic_coefficients[:,n_independent_coeff-(n_angular_points+1)%2:0:-1])),axis=-1)
#            log.info('complex harm coef shape={}'.format(harmonic_coefficients.array.shape))
            reciprocal_harmonic_coefficients=ht(harmonic_coefficients)
            return reciprocal_harmonic_coefficients
        def i_hankel(reciprocal_harmonic_coefficients):
            harmonic_coefficients=iht(reciprocal_harmonic_coefficients)[:,:n_independent_coeff]
            return harmonic_coefficients
        
        def ft_icht(harmonic_coefficients):
            reciprocal_function=complex_icht(harmonic_coefficients)
            zero_to_pi=(reciprocal_function[:,:n_independent_coeff]+reciprocal_function[:,n_independent_coeff:].conjugate())/2
            reciprocal_function[:,:n_independent_coeff]=zero_to_pi
            reciprocal_function[:,n_independent_coeff:]=zero_to_pi.conjugate()
            return reciprocal_function
        ift_cht=complex_cht

#    step_sizes=pyLib.uniformGridGetStepSizes(real_grid.array)

    def fourierTransform(data):
#        log.info('grid dim before fourier={}'.format(dataGrid.dimension))
        harmonic_coefficients=ft_cht(data)
        #log.info('harmonic coeff size={}'.format(harmonic_coefficients.array.shape))
        #log.info('harmonic coeff dtype={}'.format(harmonic_coefficients.array.dtype))
        #        log.info('harmonicCoefficients shape={}'.format(harmonicCoefficients.shape))
#        log.info('harmCoefficient dim={}'.format(harmonicCoefficients.dimension))
        reciprocal_harmonic_coefficients=hankel(harmonic_coefficients)
#        log.info('dim after HT={}'.format(reciprocalHarmonicCoefficients.dimension))
#        reciprocalHarmonicCoefficients=harmonicCoefficients
#        log.info('reciprocal harmonic coeff = {}'.format(reciprocalHarmonicCoefficients))
        reciprocal_function=ft_icht(reciprocal_harmonic_coefficients)
#        log.info('dim after FT={}'.format(reciprocalFunction.dimension))
        return reciprocal_function
    
    def inverseFourierTransform(data):
        reciprocal_harmonic_coefficients=ift_cht(data)

        harmonic_coefficients=i_hankel(reciprocal_harmonic_coefficients)
        #        harmonicCoefficients=reciprocalHarmonicCoefficients
        real_function=ift_icht(harmonic_coefficients)
        return real_function
    return fourierTransform,inverseFourierTransform

def generate_Donatelli_spherical_ft(weights_dict, ht : HarmonicTransform, mode : str):
    pos_orders=weights_dict['posHarmOrders']
    n_radial_points=weights_dict['weights'].shape[-1]
    l_max_zht=int(max(pos_orders))
    m_zht_indices = np.concatenate((np.arange(l_max_zht+1,dtype = int),-np.arange(l_max_zht,0,-1,dtype = int)))
    zht,izht=generate_ht_spherical_SinCos(weights_dict)    
    #grid_shape=(weights.shape[1],sh.)

    trfs=ht.transforms_by_indices['ml']
    complex_cht,complex_icht=trfs['forward'], trfs['inverse']
    if mode=='complex':
        hankel=zht
        i_hankel=izht
        ft_sht=complex_cht
        ft_isht=complex_icht
        ift_sht=complex_cht
        ift_isht=complex_icht
        m_orders = ht.m
        l_orders = ht.l
        temp_zeros=tuple(np.zeros((n_radial_points,l_max_zht-np.abs(m)+1),dtype=np.complex) for m in m_orders)
    elif mode=='real':
        log.error('real ft using SinCos_3D not implemented at the moment.')

    orders=pos_orders.astype(np.int)

    def fourierTransform(data):
        #        log.info('grid dim before fourier={}'.format(dataGrid.dimension))
        harmonic_coefficients=ft_sht(data)
        #log.info('harmonic shape ={}'.format(harmonic_coefficients[0].shape))
        #log.info('harmonic coeff size={}'.format(harmonic_coefficients.array.shape))
        #log.info('harmonic coeff dtype={}'.format(harmonic_coefficients.array.dtype))
        #log.info('harmonicCoefficients shape={}'.format(harmonic_coefficients.shape))
        #        log.info('harmCoefficient dim={}'.format(harmonicCoefficients.dimension))
        temp_coeffs=zht(harmonic_coefficients)
        #log.info('coeffs shape = {}'.format(tuple(temp_coeffs[m].shape for m in m_zht_indices )))
        reciprocal_harmonic_coefficients = tuple(z.copy() for z in temp_zeros)
        for m in m_zht_indices:
            #log.info('m_zht = {} zeros shape = {}'.format(m,temp_zeros[m].shape))
            coeff=temp_coeffs[m]
            reciprocal_harmonic_coefficients[m][:,:coeff.shape[1]]=coeff
        #        log.info('dim after HT={}'.format(reciprocalHarmonicCoefficients.dimension))
        #        reciprocalHarmonicCoefficients=harmonicCoefficients
        #        log.info('reciprocal harmonic coeff = {}'.format(reciprocalHarmonicCoefficients))
        reciprocal_function=ft_isht(reciprocal_harmonic_coefficients)
        #        log.info('dim after FT={}'.format(reciprocalFunction.dimension))
        #log.info('fourier transform done!')
        
        #assuming q_0 = 0 F(0,theta,phi) needs to be constant
        reciprocal_function[0] = reciprocal_function[0].mean()
        return reciprocal_function
    def inverseFourierTransform(data):
        reciprocal_harmonic_coefficients=ift_sht(data)

        temp_coeffs=izht(reciprocal_harmonic_coefficients)
        
        harmonic_coefficients = tuple(z.copy() for z in temp_zeros)
        for m in m_zht_indices:
            #log.info('m_zht = {} zeros shape = {}'.format(m,temp_zeros[m].shape))
            coeff=temp_coeffs[m]
            harmonic_coefficients[m][:,:coeff.shape[1]]=coeff
        
        #        harmonicCoefficients=reciprocalHarmonicCoefficients
        real_function=ift_isht(harmonic_coefficients)
        #assuming r_0 = 0 f(0,theta,phi) needs to be constant
        real_function[0] = real_function[0].mean()
        return real_function
    return fourierTransform,inverseFourierTransform

###@##################
###  Zernike FTs  ###
def generate_zernike_polar_ft(ft_grid_pair,weights_dict,harm_trf,mode):
    real_grid=ft_grid_pair.realGrid
    n_radial_points=real_grid.shape[0]+1
    maxQ=real_grid[-1,0,0]
    maxR=n_radial_points/maxQ
    log.info('maxR={} maxQ={} n_radial_points={}'.format(maxR,maxQ,n_radial_points))
    n_angular_points=real_grid.shape[-1]
    n_independent_coeff=int(np.floor((n_angular_points+1)/2)+n_angular_points%2)
    weights=weights_dict['weights']
    pos_orders=weights_dict['posHarmOrders']
    zht,izht=generate_zernike_ht(weights,pos_orders,maxR)
    complex_cht,complex_icht=harm_trf
    if mode=='complex':
        hankel=zht
        i_hankel=izht
        ft_cht=complex_cht
        ft_icht=complex_icht
        ift_cht=complex_cht
        ift_icht=complex_icht
        temp_zeros=np.zeros(real_grid.shape,dtype=np.complex)
    elif mode=='real':
        log.error('real ft using Zernike not implemented at the moment.')
        
    if pos_orders[0]==0:
        orders=np.concatenate((pos_orders,-1*pos_orders[:0:-1]))
    else:
        orders=np.concatenate((pos_orders,-1*pos_orders[::-1]))
        
    def fourierTransform(data):
        #        log.info('grid dim before fourier={}'.format(dataGrid.dimension))
        harmonic_coefficients=ft_cht(data)[:,orders]
        #log.info('harmonic coeff size={}'.format(harmonic_coefficients.array.shape))
        #log.info('harmonic coeff dtype={}'.format(harmonic_coefficients.array.dtype))
        #log.info('harmonicCoefficients shape={}'.format(harmonic_coefficients.shape))
        #        log.info('harmCoefficient dim={}'.format(harmonicCoefficients.dimension))
        reciprocal_harmonic_coefficients=temp_zeros.copy()
        reciprocal_harmonic_coefficients[:,orders]=hankel(harmonic_coefficients)
        #        log.info('dim after HT={}'.format(reciprocalHarmonicCoefficients.dimension))
        #        reciprocalHarmonicCoefficients=harmonicCoefficients
        #        log.info('reciprocal harmonic coeff = {}'.format(reciprocalHarmonicCoefficients))
        reciprocal_function=ft_icht(reciprocal_harmonic_coefficients)
        #        log.info('dim after FT={}'.format(reciprocalFunction.dimension))
        return reciprocal_function
    def inverseFourierTransform(data):
        reciprocal_harmonic_coefficients=ift_cht(data)[:,orders]
        harmonic_coefficients=temp_zeros.copy()
        harmonic_coefficients[:,orders]=i_hankel(reciprocal_harmonic_coefficients)
        #        harmonicCoefficients=reciprocalHarmonicCoefficients
        real_function=ift_icht(harmonic_coefficients)
        return real_function
    return fourierTransform,inverseFourierTransform

def generate_zernike_spherical_ft_old(maxR,weights_dict, ht : HarmonicTransform, mode : str):   
    weights=weights_dict['weights']
    pos_orders=weights_dict['posHarmOrders']
    l_max=int(max(pos_orders))
    zht,izht=generate_spherical_zernike_ht(weights,pos_orders,maxR)    
    #grid_shape=(weights.shape[1],sh.)

    trfs=ht.transforms_by_indices['ml']
    complex_cht,complex_icht=trfs['forward'], trfs['inverse']
    if mode=='complex':
        hankel=zht
        i_hankel=izht
        ft_sht=complex_cht
        ft_isht=complex_icht
        ift_sht=complex_cht
        ift_isht=complex_icht
        temp_zeros=np.zeros((weights.shape[1],l_max+1),dtype=np.complex)
    elif mode=='real':
        log.error('real ft using Zernike not implemented at the moment.')

    orders=pos_orders.astype(np.int)

    def apply_hankel(data):
        tmp=temp_zeros.copy()
        tmp[:,orders]=hankel(data[:,orders])
        return tmp
    
    def apply_inverse_hankel(data):
        tmp=temp_zeros.copy()
        tmp[:,orders]=i_hankel(data[:,orders])
        return tmp
        
    def fourierTransform(data):
        #        log.info('grid dim before fourier={}'.format(dataGrid.dimension))
        harmonic_coefficients=ft_sht(data)
        #log.info('harmonic shape ={}'.format(harmonic_coefficients[0].shape))
        #log.info('harmonic coeff size={}'.format(harmonic_coefficients.array.shape))
        #log.info('harmonic coeff dtype={}'.format(harmonic_coefficients.array.dtype))
        #log.info('harmonicCoefficients shape={}'.format(harmonic_coefficients.shape))
        #        log.info('harmCoefficient dim={}'.format(harmonicCoefficients.dimension))
        reciprocal_harmonic_coefficients=list(map(apply_hankel,harmonic_coefficients))
        #        log.info('dim after HT={}'.format(reciprocalHarmonicCoefficients.dimension))
        #        reciprocalHarmonicCoefficients=harmonicCoefficients
        #        log.info('reciprocal harmonic coeff = {}'.format(reciprocalHarmonicCoefficients))
        reciprocal_function=ft_isht(reciprocal_harmonic_coefficients)
        #        log.info('dim after FT={}'.format(reciprocalFunction.dimension))
        #log.info('fourier transform done!')
        return reciprocal_function
    def inverseFourierTransform(data):
        reciprocal_harmonic_coefficients=ift_sht(data)        
        harmonic_coefficients=list(map(apply_inverse_hankel,reciprocal_harmonic_coefficients))
        #        harmonicCoefficients=reciprocalHarmonicCoefficients
        real_function=ift_isht(harmonic_coefficients)
        return real_function
    return fourierTransform,inverseFourierTransform

def generate_zernike_spherical_ft_new(maxR,weights_dict, ht : HarmonicTransform, mode : str):   
    weights=weights_dict['weights']
    pos_orders=weights_dict['posHarmOrders']
    l_max=int(max(pos_orders))
    zht,izht=generate_spherical_zernike_ht_new(weights,pos_orders,maxR)    
    #grid_shape=(weights.shape[1],sh.)

    trfs=ht.transforms_by_indices['ml']
    complex_cht,complex_icht=trfs['forward'], trfs['inverse']
    if mode=='complex':
        hankel=zht
        i_hankel=izht
        ft_sht=complex_cht
        ft_isht=complex_icht
        ift_sht=complex_cht
        ift_isht=complex_icht
        temp_zeros=tuple(np.zeros((weights.shape[1],l+1),dtype=np.complex) for l in range(l_max+1))
        m_indices = ht.m_indices
    elif mode=='real':
        log.error('real ft using Zernike not implemented at the moment.')

    orders=pos_orders.astype(np.int)

    def apply_hankel(data,l_mask,zeros):
        tmp=zeros.copy()
        log.info('temp_zeros shape = {}'.format(tmp.shape))
        tmp[:,l_mask]=hankel(data[:,l_mask])
        return tmp
    
    def apply_inverse_hankel(data):
        tmp=temp_zeros.copy()
        tmp[:,orders]=i_hankel(data[:,orders])
        return tmp
        
    def fourierTransform(data):
        #        log.info('grid dim before fourier={}'.format(dataGrid.dimension))
        harmonic_coefficients=ft_sht(data)
        #log.info('harmonic shape ={}'.format(harmonic_coefficients[0].shape))
        #log.info('harmonic coeff size={}'.format(harmonic_coefficients.array.shape))
        #log.info('harmonic coeff dtype={}'.format(harmonic_coefficients.array.dtype))
        #log.info('harmonicCoefficients shape={}'.format(harmonic_coefficients.shape))
        #        log.info('harmCoefficient dim={}'.format(harmonicCoefficients.dimension))
        reciprocal_harmonic_coefficients=list(apply_hankel(coeff,l_mask,zeros) for coeff,l_mask,zeros in zip(harmonic_coefficients,l_ids,temp_zeros))
        #        log.info('dim after HT={}'.format(reciprocalHarmonicCoefficients.dimension))
        #        reciprocalHarmonicCoefficients=harmonicCoefficients
        #        log.info('reciprocal harmonic coeff = {}'.format(reciprocalHarmonicCoefficients))
        reciprocal_function=ft_isht(reciprocal_harmonic_coefficients)
        #        log.info('dim after FT={}'.format(reciprocalFunction.dimension))
        #log.info('fourier transform done!')
        
        #assuming q_0 = 0 F(0,theta,phi) needs to be constant
        reciprocal_function[0] = reciprocal_function[0].mean()
        return reciprocal_function
    def inverseFourierTransform(data):
        reciprocal_harmonic_coefficients=ift_sht(data)        
        harmonic_coefficients=list(map(apply_inverse_hankel,reciprocal_harmonic_coefficients))
        #        harmonicCoefficients=reciprocalHarmonicCoefficients
        real_function=ift_isht(harmonic_coefficients)
        #assuming r_0 = 0 f(0,theta,phi) needs to be constant
        real_function[0] = real_function[0].mean()
        return real_function
    return fourierTransform,inverseFourierTransform

def generate_zernike_spherical_ft_new2(maxR,weights_dict, ht : HarmonicTransform, mode : str):   
    weights=weights_dict['weights']
    pos_orders=weights_dict['posHarmOrders']
    l_max_zht=int(max(pos_orders))
    m_zht_indices = np.concatenate((np.arange(l_max_zht+1,dtype = int),-np.arange(l_max_zht,0,-1,dtype = int)))
    zht,izht=generate_spherical_zernike_ht_new2(weights,pos_orders,maxR)    
    #grid_shape=(weights.shape[1],sh.)

    trfs=ht.transforms_by_indices['ml']
    complex_cht,complex_icht=trfs['forward'], trfs['inverse']
    if mode=='complex':
        hankel=zht
        i_hankel=izht
        ft_sht=complex_cht
        ft_isht=complex_icht
        ift_sht=complex_cht
        ift_isht=complex_icht
        m_orders = ht.m
        l_orders = ht.l
        temp_zeros=tuple(np.zeros((weights.shape[2],l_orders.max()-np.abs(m)+1),dtype=np.complex) for m in m_orders)
    elif mode=='real':
        log.error('real ft using Zernike not implemented at the moment.')

    orders=pos_orders.astype(np.int)

    def populate_zeros(zeros,coeff):
        zeros[:len(coeff)]=coeff        

    def fourierTransform(data):
        #        log.info('grid dim before fourier={}'.format(dataGrid.dimension))
        harmonic_coefficients=ft_sht(data)
        #log.info('harmonic shape ={}'.format(harmonic_coefficients[0].shape))
        #log.info('harmonic coeff size={}'.format(harmonic_coefficients.array.shape))
        #log.info('harmonic coeff dtype={}'.format(harmonic_coefficients.array.dtype))
        #log.info('harmonicCoefficients shape={}'.format(harmonic_coefficients.shape))
        #        log.info('harmCoefficient dim={}'.format(harmonicCoefficients.dimension))
        temp_coeffs=zht(harmonic_coefficients)
        #log.info('coeffs shape = {}'.format(tuple(temp_coeffs[m].shape for m in m_zht_indices )))
        reciprocal_harmonic_coefficients = tuple(z.copy() for z in temp_zeros)
        for m in m_zht_indices:
            #log.info('m_zht = {} zeros shape = {}'.format(m,temp_zeros[m].shape))
            coeff=temp_coeffs[m]
            reciprocal_harmonic_coefficients[m][:,:coeff.shape[1]]=coeff
        #        log.info('dim after HT={}'.format(reciprocalHarmonicCoefficients.dimension))
        #        reciprocalHarmonicCoefficients=harmonicCoefficients
        #        log.info('reciprocal harmonic coeff = {}'.format(reciprocalHarmonicCoefficients))
        reciprocal_function=ft_isht(reciprocal_harmonic_coefficients)
        #        log.info('dim after FT={}'.format(reciprocalFunction.dimension))
        #log.info('fourier transform done!')
        
        #assuming q_0 = 0 F(0,theta,phi) needs to be constant
        reciprocal_function[0] = reciprocal_function[0].mean()
        return reciprocal_function
    def inverseFourierTransform(data):
        reciprocal_harmonic_coefficients=ift_sht(data)

        temp_coeffs=izht(reciprocal_harmonic_coefficients)
        
        harmonic_coefficients = tuple(z.copy() for z in temp_zeros)
        for m in m_zht_indices:
            #log.info('m_zht = {} zeros shape = {}'.format(m,temp_zeros[m].shape))
            coeff=temp_coeffs[m]
            harmonic_coefficients[m][:,:coeff.shape[1]]=coeff
        
        #        harmonicCoefficients=reciprocalHarmonicCoefficients
        real_function=ift_isht(harmonic_coefficients)
        #assuming r_0 = 0 f(0,theta,phi) needs to be constant
        real_function[0] = real_function[0].mean()
        return real_function
    return fourierTransform,inverseFourierTransform

def generate_zernike_spherical_ft_big_sum(maxR,weights_dict, ht : HarmonicTransform, mode : str):   
    weights=weights_dict['weights']
    pos_orders=weights_dict['posHarmOrders']
    l_max_zht=int(max(pos_orders))
    m_zht_indices = np.concatenate((np.arange(l_max_zht+1,dtype = int),-np.arange(l_max_zht,0,-1,dtype = int)))
    zht,izht=generate_spherical_zernike_ht_big_sum(weights,pos_orders,maxR)    
    #grid_shape=(weights.shape[1],sh.)

    trfs=ht.transforms_by_indices['direct']
    complex_cht,complex_icht=trfs['forward'], trfs['inverse']
    if mode=='complex':
        hankel=zht
        i_hankel=izht
        ft_sht=complex_cht
        ft_isht=complex_icht
        ift_sht=complex_cht
        ift_isht=complex_icht
        m_orders = ht.m
        l_orders = ht.l
        temp_zeros=tuple(np.zeros((weights.shape[2],l_orders.max()-np.abs(m)+1),dtype=np.complex) for m in m_orders)
    elif mode=='real':
        log.error('real ft using Zernike not implemented at the moment.')

    orders=pos_orders.astype(np.int)

    def populate_zeros(zeros,coeff):
        zeros[:len(coeff)]=coeff        

    def fourierTransform(data):
        #        log.info('grid dim before fourier={}'.format(dataGrid.dimension))
        harmonic_coefficients=ft_sht(data.astype(complex))
        #log.info('harmonic shape ={}'.format(harmonic_coefficients[0].shape))
        #log.info('harmonic coeff size={}'.format(harmonic_coefficients.array.shape))
        #log.info('harmonic coeff dtype={}'.format(harmonic_coefficients.array.dtype))
        #log.info('harmonicCoefficients shape={}'.format(harmonic_coefficients.shape))
        #        log.info('harmCoefficient dim={}'.format(harmonicCoefficients.dimension))
        reciprocal_harmonic_coefficients=zht(harmonic_coefficients)
        #log.info('coeffs shape = {}'.format(tuple(temp_coeffs[m].shape for m in m_zht_indices )))
        # reciprocal_harmonic_coefficients = tuple(z.copy() for z in temp_zeros)
        # for m in m_zht_indices:
        #     #log.info('m_zht = {} zeros shape = {}'.format(m,temp_zeros[m].shape))
        #     coeff=temp_coeffs[m]
        #     reciprocal_harmonic_coefficients[m][:,:coeff.shape[1]]=coeff
        #        log.info('dim after HT={}'.format(reciprocalHarmonicCoefficients.dimension))
        #        reciprocalHarmonicCoefficients=harmonicCoefficients
        #        log.info('reciprocal harmonic coeff = {}'.format(reciprocalHarmonicCoefficients))
        reciprocal_function=ft_isht(reciprocal_harmonic_coefficients)
        #        log.info('dim after FT={}'.format(reciprocalFunction.dimension))
        #log.info('fourier transform done!')
        
        #assuming q_0 = 0 F(0,theta,phi) needs to be constant
        reciprocal_function[0] = reciprocal_function[0].mean()
        return reciprocal_function
    def inverseFourierTransform(data):
        reciprocal_harmonic_coefficients=ift_sht(data)

        harmonic_coefficients=izht(reciprocal_harmonic_coefficients)
        
        # harmonic_coefficients = tuple(z.copy() for z in temp_zeros)
        # for m in m_zht_indices:
        #     #log.info('m_zht = {} zeros shape = {}'.format(m,temp_zeros[m].shape))
        #     coeff=temp_coeffs[m]
        #     harmonic_coefficients[m][:,:coeff.shape[1]]=coeff
        
        #        harmonicCoefficients=reciprocalHarmonicCoefficients
        real_function=ift_isht(harmonic_coefficients)
        #assuming r_0 = 0 f(0,theta,phi) needs to be constant
        real_function[0] = real_function[0].mean()
        return real_function
    return fourierTransform,inverseFourierTransform

def generate_zernike_spherical_ft_gpu_old(maxR,weights_dict, ht : HarmonicTransform, mode : str,mp_lock):
    log.info('use_gpu')
    weights=weights_dict['weights']
    pos_orders=weights_dict['posHarmOrders']
    l_max_zht=int(max(pos_orders))
    m_zht_indices = np.concatenate((np.arange(l_max_zht+1,dtype = int),-np.arange(l_max_zht,0,-1,dtype = int)))
    zht,izht=generate_spherical_zernike_ht_gpu(weights,pos_orders,maxR)    
    #grid_shape=(weights.shape[1],sh.)

    trfs=ht.transforms_by_indices['direct']
    complex_cht,complex_icht=trfs['forward'], trfs['inverse']
    if mode=='complex':
        hankel=zht
        i_hankel=izht
        ft_sht=complex_cht
        ft_isht=complex_icht
        ift_sht=complex_cht
        ift_isht=complex_icht
        m_orders = ht.m
        l_orders = ht.l
        temp_zeros=tuple(np.zeros((weights.shape[2],l_orders.max()-np.abs(m)+1),dtype=np.complex) for m in m_orders)
    elif mode=='real':
        log.error('real ft using Zernike not implemented at the moment.')

    orders=pos_orders.astype(np.int)

    def populate_zeros(zeros,coeff):
        zeros[:len(coeff)]=coeff        

    def fourierTransform(data):
        #        log.info('grid dim before fourier={}'.format(dataGrid.dimension))
        harmonic_coefficients=ft_sht(data.astype(complex))
        #log.info('harmonic shape ={}'.format(harmonic_coefficients[0].shape))
        #log.info('harmonic coeff size={}'.format(harmonic_coefficients.array.shape))
        #log.info('harmonic coeff dtype={}'.format(harmonic_coefficients.array.dtype))
        #log.info('harmonicCoefficients shape={}'.format(harmonic_coefficients.shape))
        #        log.info('harmCoefficient dim={}'.format(harmonicCoefficients.dimension))
        #        with mp_lock: #only one process at a time will call the GPU
        reciprocal_harmonic_coefficients=zht(harmonic_coefficients)            
        #log.info('coeffs shape = {}'.format(tuple(temp_coeffs[m].shape for m in m_zht_indices )))
        # reciprocal_harmonic_coefficients = tuple(z.copy() for z in temp_zeros)
        # for m in m_zht_indices:
        #     #log.info('m_zht = {} zeros shape = {}'.format(m,temp_zeros[m].shape))
        #     coeff=temp_coeffs[m]
        #     reciprocal_harmonic_coefficients[m][:,:coeff.shape[1]]=coeff
        #        log.info('dim after HT={}'.format(reciprocalHarmonicCoefficients.dimension))
        #        reciprocalHarmonicCoefficients=harmonicCoefficients
        #        log.info('reciprocal harmonic coeff = {}'.format(reciprocalHarmonicCoefficients))
        reciprocal_function=ft_isht(reciprocal_harmonic_coefficients)
        #        log.info('dim after FT={}'.format(reciprocalFunction.dimension))
        #log.info('fourier transform done!')
        
        #assuming q_0 = 0 F(0,theta,phi) needs to be constant
        reciprocal_function[0] = reciprocal_function[0].mean()
        return reciprocal_function
    def inverseFourierTransform(data):
        reciprocal_harmonic_coefficients=ift_sht(data)

        #        with mp_lock: #only one process at a time will call the GPU
        harmonic_coefficients=izht(reciprocal_harmonic_coefficients)
        
        # harmonic_coefficients = tuple(z.copy() for z in temp_zeros)
        # for m in m_zht_indices:
        #     #log.info('m_zht = {} zeros shape = {}'.format(m,temp_zeros[m].shape))
        #     coeff=temp_coeffs[m]
        #     harmonic_coefficients[m][:,:coeff.shape[1]]=coeff
        
        #        harmonicCoefficients=reciprocalHarmonicCoefficients
        real_function=ift_isht(harmonic_coefficients)
        #assuming r_0 = 0 f(0,theta,phi) needs to be constant
        real_function[0] = real_function[0].mean()
        return real_function
    return fourierTransform,inverseFourierTransform


def generate_zernike_spherical_ft(maxR,weights_dict, ht : HarmonicTransform, mode : str, pi_in_q : bool, use_gpu = False):

    weights=weights_dict['weights']
    pos_orders=weights_dict['posHarmOrders']
    l_max_zht=int(max(pos_orders))
    m_zht_indices = np.concatenate((np.arange(l_max_zht+1,dtype = int),-np.arange(l_max_zht,0,-1,dtype = int)))
    if use_gpu:
        zht,izht=generate_spherical_zernike_ht_gpu(weights,pos_orders,maxR,pi_in_q = pi_in_q)
        trfs=ht.transforms_by_indices['direct']
    else:
        zht,izht=generate_spherical_zernike_ht(weights,pos_orders,maxR,pi_in_q = pi_in_q)
        trfs=ht.transforms_by_indices['ml']
    #grid_shape=(weights.shape[1],sh.)
    log.info(trfs)
    complex_cht,complex_icht=trfs['forward'], trfs['inverse']
    if mode=='complex':
        hankel=zht
        i_hankel=izht
        ft_sht=complex_cht
        ft_isht=complex_icht
        ift_sht=complex_cht
        ift_isht=complex_icht
        m_orders = ht.m
        l_orders = ht.l
        temp_zeros=tuple(np.zeros((weights.shape[2],l_orders.max()-np.abs(m)+1),dtype=np.complex) for m in m_orders)
    elif mode=='real':
        log.error('real ft using Zernike not implemented at the moment.')

    orders=pos_orders.astype(np.int)
    #log.info('ft_sht = {}'.format(ft_sht))
    def populate_zeros(zeros,coeff):
        zeros[:len(coeff)]=coeff        

    def fourierTransform(data):
        #        log.info('grid dim before fourier={}'.format(dataGrid.dimension))
        harmonic_coefficients=ft_sht(data)
        #log.info('harmonic shape ={}'.format(harmonic_coefficients[0].shape))
        #log.info('harmonic coeff size={}'.format(harmonic_coefficients.array.shape))
        #log.info('harmonic coeff dtype={}'.format(harmonic_coefficients.array.dtype))
        #log.info('harmonicCoefficients shape={}'.format(harmonic_coefficients.shape))
        #        log.info('harmCoefficient dim={}'.format(harmonicCoefficients.dimension))
        temp_coeffs=zht(harmonic_coefficients)
        #log.info('coeffs shape = {}'.format(tuple(temp_coeffs[m].shape for m in m_zht_indices )))
        reciprocal_harmonic_coefficients = tuple(z.copy() for z in temp_zeros)
        for m in m_zht_indices:
            #log.info('m_zht = {} zeros shape = {}'.format(m,temp_zeros[m].shape))
            coeff=temp_coeffs[m]
            reciprocal_harmonic_coefficients[m][:,:coeff.shape[1]]=coeff
        #        log.info('dim after HT={}'.format(reciprocalHarmonicCoefficients.dimension))
        #        reciprocalHarmonicCoefficients=harmonicCoefficients
        #        log.info('reciprocal harmonic coeff = {}'.format(reciprocalHarmonicCoefficients))
        reciprocal_function=ft_isht(reciprocal_harmonic_coefficients)
        #        log.info('dim after FT={}'.format(reciprocalFunction.dimension))
        #log.info('fourier transform done!')
        
        #assuming q_0 = 0 F(0,theta,phi) needs to be constant
        reciprocal_function[0] = reciprocal_function[0].mean()
        return reciprocal_function
    def inverseFourierTransform(data):
        reciprocal_harmonic_coefficients=ift_sht(data)

        temp_coeffs=izht(reciprocal_harmonic_coefficients)
        
        harmonic_coefficients = tuple(z.copy() for z in temp_zeros)
        for m in m_zht_indices:
            #log.info('m_zht = {} zeros shape = {}'.format(m,temp_zeros[m].shape))
            coeff=temp_coeffs[m]
            harmonic_coefficients[m][:,:coeff.shape[1]]=coeff
        
        #        harmonicCoefficients=reciprocalHarmonicCoefficients
        real_function=ift_isht(harmonic_coefficients)
        #assuming r_0 = 0 f(0,theta,phi) needs to be constant
        real_function[0] = real_function[0].mean()
        return real_function

    def fourierTransform_gpu(data):
        #        log.info('grid dim before fourier={}'.format(dataGrid.dimension))
        harmonic_coefficients=ft_sht(data.astype(complex))
        #log.info('harmonic shape ={}'.format(harmonic_coefficients[0].shape))
        #log.info('harmonic shape ={}'.format(harmonic_coefficients[0].shape))
        #log.info('harmonic len ={}'.format(len(harmonic_coefficients)))
        #log.info('harmonic coeff size={}'.format(harmonic_coefficients.array.shape))
        #log.info('harmonic coeff dtype={}'.format(harmonic_coefficients.array.dtype))
        #log.info('harmonicCoefficients shape={}'.format(harmonic_coefficients.shape))
        #        log.info('harmCoefficient dim={}'.format(harmonicCoefficients.dimension))
        #        with mp_lock: #only one process at a time will call the GPU
        reciprocal_harmonic_coefficients=zht(harmonic_coefficients)
        #log.info('reziprocal harmonic shape ={}'.format(reciprocal_harmonic_coefficients[0].shape))
        #log.info('reziprocal harmonic len ={}'.format(len(reciprocal_harmonic_coefficients)))
        #log.info('coeffs shape = {}'.format(tuple(temp_coeffs[m].shape for m in m_zht_indices )))
        # reciprocal_harmonic_coefficients = tuple(z.copy() for z in temp_zeros)
        # for m in m_zht_indices:
        #     #log.info('m_zht = {} zeros shape = {}'.format(m,temp_zeros[m].shape))
        #     coeff=temp_coeffs[m]
        #     reciprocal_harmonic_coefficients[m][:,:coeff.shape[1]]=coeff
        #        log.info('dim after HT={}'.format(reciprocalHarmonicCoefficients.dimension))
        #        reciprocalHarmonicCoefficients=harmonicCoefficients
        #        log.info('reciprocal harmonic coeff = {}'.format(reciprocalHarmonicCoefficients))
        reciprocal_function=ft_isht(reciprocal_harmonic_coefficients)
        #        log.info('dim after FT={}'.format(reciprocalFunction.dimension))
        #log.info('fourier transform done!')
        
        #assuming q_0 = 0 F(0,theta,phi) needs to be constant
        reciprocal_function[0] = reciprocal_function[0].mean()
        return reciprocal_function
    def inverseFourierTransform_gpu(data):
        reciprocal_harmonic_coefficients=ift_sht(data)

        #        with mp_lock: #only one process at a time will call the GPU
        harmonic_coefficients=izht(reciprocal_harmonic_coefficients)
        
        # harmonic_coefficients = tuple(z.copy() for z in temp_zeros)
        # for m in m_zht_indices:
        #     #log.info('m_zht = {} zeros shape = {}'.format(m,temp_zeros[m].shape))
        #     coeff=temp_coeffs[m]
        #     harmonic_coefficients[m][:,:coeff.shape[1]]=coeff
        
        #        harmonicCoefficients=reciprocalHarmonicCoefficients
        real_function=ift_isht(harmonic_coefficients)
        #assuming r_0 = 0 f(0,theta,phi) needs to be constant
        real_function[0] = real_function[0].mean()
        return real_function
    if use_gpu:
        ft = fourierTransform_gpu
        ift = inverseFourierTransform_gpu
    else:
        ft = fourierTransform
        ift = inverseFourierTransform_gpu
    return ft,ift


def zernike_radial_cutoff_relation(cutoff:float,n_points:int,pi_in_q = True):
    '''
    Reciprocity relation between the real and reciprocal cutoff.
    If pi_in_q is true it is assumed that the reciprocal unit is 2Pi/length otherwise it is just 1/length.    
    $2*R*2*Q = 2*N$
    '''
    if pi_in_q:
        other_cutoff = np.pi*n_points/cutoff
    else:
        other_cutoff = n_points/(2*cutoff)
    return other_cutoff
    
def zernike_radial_step_relation(step:float,n_points:int,pi_in_q = True):
    '''
    Reciprocity relation between the real and reciprocal step sizes.
    If pi_in_q is true it is assumed that the reciprocal unit is 2Pi/length otherwise it is just 1/length.
    $\delta R*\delta Q = 1/(2*N)$    
    '''
    if pi_in_q:
        other_step = np.pi*1/(n_points*step)
    else:
        other_step = 1/(2*n_points*step)
    return other_step
    
def zernike_radial_step_cutoff_relation(step:float,n_points:int,pi_in_q = True):
    '''
    Reciprocity relation between the real and reciprocal step sizes.
    If pi_in_q is true it is assumed that the reciprocal unit is 2Pi/length otherwise it is just 1/length.
    $\delta R*\delta Q = 1/(2*N)$    
    '''
    if pi_in_q:
        other_cutoff = np.pi/step
    else:
        other_cutoff = 1/(2*step)
    return other_cutoff
    

################# general selection logic ##################
'''
The following method depends on runtime the runtime variables:
xframe.database.analysis
'''
def generate_fourier_transforms(grid_pair, opt, harm_trf):
    dimension = opt['dimensions']
    ft_type = opt['type']
    data_type = opt['data_type']
    n_radial_points = grid_pair.reciprocal.shape[0]
    harmonic_orders=opt['pos_orders']
    maxOrder=np.max(harmonic_orders)
    nOrders=len(harmonic_orders)
    db = database.analysis
    if dimension == 2:
        if ft_type=='Zernike':
            name_postfix = 'N'+str(n_radial_points)+'mO'+str(maxOrder)+'nO'+str(nOrders)
            weights_dict = db.load('ft_weights',path_modifiers={'postfix':name_postfix,'type':ft_type+'_'+str(dimension)+'D'})
            fourierTransform,inverseFourierTransform = generate_zernike_polar_ft(grid_pair,weights_dict,harm_trf,data_type)
        else:
            log.error("foruier mode {} not known. Replace ourier Transfomrs by identity map.")
            fourierTransform=mLib.id
            inverseFourierTransform=mLib.id
    if dimension==3:
        max_R = grid_pair.real[-1,0,0,0]
        if ft_type=='Zernike':
            pi_in_q = opt['pi_in_q']
            use_gpu = opt['use_GPU']
            if pi_in_q:
                name_postfix='N'+str(n_radial_points)+'mO'+str(maxOrder)+'nO'+str(nOrders)+'_pi'
            else:
                name_postfix='N'+str(n_radial_points)+'mO'+str(maxOrder)+'nO'+str(nOrders)
            try:
                weights_dict = db.load('ft_weights',path_modifiers={'postfix':name_postfix,'type':ft_type+'_'+str(dimension)+'D'})
            except FileNotFoundError as e:
                if opt['allow_weight_calculation']:
                    weights_dict = generate_weightDict_zernike_spherical(maxOrder,n_radial_points,n_cpus=opt.get('n_cpus_weight_generation',False))
                    if opt['allow_weight_saving']:
                        db.save('ft_weights',weights_dict,path_modifiers={'postfix':name_postfix,'type':ft_type+'_'+str(dimension)+'D'})
                                         
            fourierTransform,inverseFourierTransform=generate_zernike_spherical_ft(max_R,weights_dict,harm_trf,data_type,pi_in_q,use_gpu = use_gpu)
           
    return fourierTransform,inverseFourierTransform

