% PROP_FREE_NONPARALLEL_PLANE  Near field propagation into a surface nonparallel with beam
%
%    [U, H, h_tilted] = prop_free_nonparallel_plane(U, z, lambda, pixel_size, ax=1)
%     returns the propagated wavefield
%    Inputs: 
%       **U           stack of images 
%       **z             propagation distance along horizontal axis (vector)
%       **lambda        wavelenght [m]
%       **pixel_size    pixel size [m]
%       **ax            (optional)  axis along which is the tilted plane 
%    *returns*
%       ++U           propagated stack of images 
%
%
%     Example for tilted plane propagation:
%     Npix = size(img);
%     grid = ((-Npix(2)/2+1):Npix(2)/2)*pixel_size * tand(90-grazing_angle); 
%    [img_propag, H, h_tilted] = prop_free_tilted_plane(img, grid, lambda, pixel_size)
% 
%
    
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  Except where otherwise noted, this work is licensed under a          |
%|  Creative Commons Attribution-NonCommercial-ShareAlike 4.0            |
%|  International (CC BY-NC-SA 4.0) license.                             |
%|                                                                       |
%|  Copyright (c) 2017 by Paul Scherrer Institute (http://www.psi.ch)    |
%|                                                                       |
%|       Author: CXS group, PSI                                          |
%*-----------------------------------------------------------------------*
% You may use this code with the following provisions:
%
% If the code is fully or partially redistributed, or rewritten in another
%   computing language this notice should be included in the redistribution.
%
% If this code, or subfunctions or parts of it, is used for research in a 
%   publication or if it is fully or partially rewritten for another 
%   computing language the authors and institution should be acknowledged 
%   in written form in the publication: “Data processing was carried out 
%   using the “cSAXS software package” developed by the CXS group,
%   Paul Scherrer Institut, Switzerland.” 
%   Variations on the latter text can be incorporated upon discussion with 
%   the CXS group if needed to more specifically reflect the use of the package 
%   for the published work.
%
% A publication that focuses on describing features, or parameters, that
%    are already existing in the code should be first discussed with the
%    authors.
%   
% This code and subroutines are part of a continuous development, they 
%    are provided “as they are” without guarantees or liability on part
%    of PSI or the authors. It is the user responsibility to ensure its 
%    proper use and the correctness of the results.

function [U, H, h_tilted] = prop_free_nonparallel_plane(U, z, lambda, pixel_size, ax)
    %%  nearfield propagator into tilted plane 
    
    if all(z == 0)
        H = 1;
        h = 1; 
        U = U;
        return
    end
    if nargin  < 5
        ax = 1;
    end

    %% standard 1D ASM for vertical axis 
    Np = size(U);
    if ax == 1 
       Np = Np([2,1]);
       pixel_size = pixel_size(min(end,[2,1])); 
    end 
    
    assert(length(z) == Np(2), 'Shift vector z has wrong length')
    
    type = ones(1,'like',U);
    xgrid = type * (-fix(Np(1)/2):ceil(Np(1)/2)-1);
    k = 2 * pi / lambda(1);
    extent = Np .* pixel_size;
    kx =  2 * pi .*xgrid / extent(1) ;
    H = exp(1i*z(:)' .* (kx'.^2) / (2*k));
        
    H = fftshift(H, 1);  % 1D ASM propagator 
    
    %% convolution ASM for horizontal axis
    % fft transform to get real space convolution kernel
    h = ifftshift(fft(H,[],1),1)/(Np(1));
        
    h = utils.crop_pad(h, [Np(2), Np(2)]);

    % apply shift on the convolution propagator for the axis along the beam 
    h_tilted = utils.imshift_fft_ax(h, (1:Np(2))-Np(2)/2-1,1);
    
    % adjustnemnts to make the propagated image consistent with the original one 
    h_tilted = fliplr(rot90(h_tilted));
    H = fliplr(H); 

    %%%%%%%%%%%%%% propagate the image  %%%%%%%%%%%%%%%%%%%%%
    if ax == 1
        % propagate horizontal axis
        U = ifft(H.' .* fft(U,[],2),[],2);
        % propagate vertical axis !! use matrix multiplication !! in realspace
        % to describe the convolution that is needed for propagation to the
        % tilted axis ->   h_tilted * U
        U = utils.mtimes_stack(h_tilted,U);  % convolution applied by matrix multiplication, make it for entire image stack in parallel 
    else
        % propagate horizontal axis
        U = ifft(H .* fft(U,[],1),[],1);
        % propagate vertical axis !! use matrix multiplication !! in realspace
        % to describe the convolution that is needed for propagation to the
        % tilted axis 
        U = utils.mtimes_stack(U,h_tilted);
         
        
    end
    
end