// Copyright (C) 2026 Kiyotsugu Arai
// SPDX-License-Identifier: LGPL-3.0-or-later

// fft3d.hpp — 3-dimensional FFT / IFFT
//
// New implementation:
//   - Realizes 3D FFT by applying 1D FFT sequentially along x→y→z
//   - Tensor3D<complex> container + fft3d / ifft3d / fft3d_real
//   - Uses sangi::FFT internally

#ifndef SANGI_FFT_3D_HPP
#define SANGI_FFT_3D_HPP

#include <math/fft/fft.hpp>
#include <math/core/Complex.hpp>
#include <vector>
#include <span>
#include <stdexcept>
#include <cmath>

namespace sangi {

    // ── 3D tensor container ──
    // row-major: data[z * ny*nx + y * nx + x]
    template<typename T>
    class Tensor3D {
    public:
        Tensor3D() : nx_(0), ny_(0), nz_(0) {}

        Tensor3D(std::size_t nx, std::size_t ny, std::size_t nz)
            : nx_(nx), ny_(ny), nz_(nz), data_(nx * ny * nz) {}

        Tensor3D(std::size_t nx, std::size_t ny, std::size_t nz, const T& val)
            : nx_(nx), ny_(ny), nz_(nz), data_(nx * ny * nz, val) {}

        std::size_t nx() const { return nx_; }
        std::size_t ny() const { return ny_; }
        std::size_t nz() const { return nz_; }
        std::size_t size() const { return data_.size(); }

        T& operator()(std::size_t x, std::size_t y, std::size_t z) {
            return data_[z * ny_ * nx_ + y * nx_ + x];
        }
        const T& operator()(std::size_t x, std::size_t y, std::size_t z) const {
            return data_[z * ny_ * nx_ + y * nx_ + x];
        }

        T* data() { return data_.data(); }
        const T* data() const { return data_.data(); }

    private:
        std::size_t nx_, ny_, nz_;
        std::vector<T> data_;
    };


    // ── 3D FFT (along x→y→z) ──
    template<typename Real = double>
    Tensor3D<Complex<Real>> fft3d(const Tensor3D<Complex<Real>>& input) {
        using C = Complex<Real>;
        std::size_t nx = input.nx(), ny = input.ny(), nz = input.nz();

        Tensor3D<C> result = input;

        // FFT along x
        {
            std::vector<C> buf(nx);
            for (std::size_t z = 0; z < nz; ++z) {
                for (std::size_t y = 0; y < ny; ++y) {
                    for (std::size_t x = 0; x < nx; ++x) buf[x] = result(x, y, z);
                    FFT<C, Real>::fft(std::span<C>(buf));
                    for (std::size_t x = 0; x < nx; ++x) result(x, y, z) = buf[x];
                }
            }
        }

        // FFT along y
        {
            std::vector<C> buf(ny);
            for (std::size_t z = 0; z < nz; ++z) {
                for (std::size_t x = 0; x < nx; ++x) {
                    for (std::size_t y = 0; y < ny; ++y) buf[y] = result(x, y, z);
                    FFT<C, Real>::fft(std::span<C>(buf));
                    for (std::size_t y = 0; y < ny; ++y) result(x, y, z) = buf[y];
                }
            }
        }

        // FFT along z
        {
            std::vector<C> buf(nz);
            for (std::size_t y = 0; y < ny; ++y) {
                for (std::size_t x = 0; x < nx; ++x) {
                    for (std::size_t z = 0; z < nz; ++z) buf[z] = result(x, y, z);
                    FFT<C, Real>::fft(std::span<C>(buf));
                    for (std::size_t z = 0; z < nz; ++z) result(x, y, z) = buf[z];
                }
            }
        }

        return result;
    }


    // ── 3D IFFT (along x→y→z) ──
    template<typename Real = double>
    Tensor3D<Complex<Real>> ifft3d(const Tensor3D<Complex<Real>>& input) {
        using C = Complex<Real>;
        std::size_t nx = input.nx(), ny = input.ny(), nz = input.nz();

        Tensor3D<C> result = input;

        // IFFT along x
        {
            std::vector<C> buf(nx);
            for (std::size_t z = 0; z < nz; ++z) {
                for (std::size_t y = 0; y < ny; ++y) {
                    for (std::size_t x = 0; x < nx; ++x) buf[x] = result(x, y, z);
                    FFT<C, Real>::ifft(std::span<C>(buf));
                    for (std::size_t x = 0; x < nx; ++x) result(x, y, z) = buf[x];
                }
            }
        }

        // IFFT along y
        {
            std::vector<C> buf(ny);
            for (std::size_t z = 0; z < nz; ++z) {
                for (std::size_t x = 0; x < nx; ++x) {
                    for (std::size_t y = 0; y < ny; ++y) buf[y] = result(x, y, z);
                    FFT<C, Real>::ifft(std::span<C>(buf));
                    for (std::size_t y = 0; y < ny; ++y) result(x, y, z) = buf[y];
                }
            }
        }

        // IFFT along z
        {
            std::vector<C> buf(nz);
            for (std::size_t y = 0; y < ny; ++y) {
                for (std::size_t x = 0; x < nx; ++x) {
                    for (std::size_t z = 0; z < nz; ++z) buf[z] = result(x, y, z);
                    FFT<C, Real>::ifft(std::span<C>(buf));
                    for (std::size_t z = 0; z < nz; ++z) result(x, y, z) = buf[z];
                }
            }
        }

        return result;
    }


    // ── Real tensor → 3D FFT ──
    template<typename Real = double>
    Tensor3D<Complex<Real>> fft3d_real(const Tensor3D<Real>& input) {
        using C = Complex<Real>;
        std::size_t nx = input.nx(), ny = input.ny(), nz = input.nz();

        Tensor3D<C> complexInput(nx, ny, nz);
        for (std::size_t z = 0; z < nz; ++z)
            for (std::size_t y = 0; y < ny; ++y)
                for (std::size_t x = 0; x < nx; ++x)
                    complexInput(x, y, z) = C(input(x, y, z), Real{0});

        return fft3d(complexInput);
    }


    // ── fftshift3d: move the DC component to the center ──
    template<typename T>
    Tensor3D<T> fftshift3d(const Tensor3D<T>& input) {
        std::size_t nx = input.nx(), ny = input.ny(), nz = input.nz();
        Tensor3D<T> out(nx, ny, nz);
        std::size_t hx = nx / 2, hy = ny / 2, hz = nz / 2;
        for (std::size_t z = 0; z < nz; ++z)
            for (std::size_t y = 0; y < ny; ++y)
                for (std::size_t x = 0; x < nx; ++x)
                    out((x + hx) % nx, (y + hy) % ny, (z + hz) % nz) = input(x, y, z);
        return out;
    }

} // namespace sangi

#endif // SANGI_FFT_3D_HPP
