// CelerisLab – step/one_step_double.cu
// Double-buffer PULL streaming step kernel.
// Included by kernel_v2.cu.  No standalone compilation.
// ============================================================================

#ifndef CELERIS_STEP_ONE_STEP_DOUBLE_CU
#define CELERIS_STEP_ONE_STEP_DOUBLE_CU

#ifndef Y_WALL_BC
#define Y_WALL_BC 0
#endif

// ---- Helper: apply inlet/outlet/wall BC in pull-double mode ----
#if DIM == 2
__device__ __forceinline__ void apply_boundary_pull(
    float* f, uint16_t fl, unsigned int x, unsigned int y,
    const fpxx* fi_in, unsigned long k)
{
    // Curved-BC nodes are handled by CurvedBoundaryKernel before OneStep (see stepper).
    if (is_curved(fl)) return;

    bool interior_y = (y > 0u) && (y < (unsigned int)(NY - 1));

    if (is_inlet(fl) && interior_y) {
        float f_neb[NQ];
        unsigned long k_neb = linear_index(x + 1u, y);
        for (int i = 0; i < NQ; i++)
            f_neb[i] = load_ddf(fi_in, index_f(k_neb, (unsigned int)i));
        apply_parabolic_inlet(f, f_neb, (float)y);
    }
    else if (is_outlet(fl) && interior_y) {
        float f_neb[NQ];
        unsigned long k_neb = linear_index(x - 1u, y);
        for (int i = 0; i < NQ; i++)
            f_neb[i] = load_ddf(fi_in, index_f(k_neb, (unsigned int)i));
        apply_pressure_outlet(f, f_neb, (float)y);
    }
    else {
        bounce_back_swap(f);
    }
}
#endif // DIM == 2

#if DIM == 3
__device__ __forceinline__ void apply_boundary_pull_3d(
    float* f, uint16_t fl, unsigned int x, unsigned int y, unsigned int z,
    const fpxx* fi_in, unsigned long k)
{
    // Curved-BC nodes are handled by CurvedBoundaryKernel before OneStep (see stepper).
    if (is_curved(fl)) return;

    bool interior_y = (y > 0u) && (y < (unsigned int)(NY - 1));

    if (is_inlet(fl) && interior_y) {
        float f_neb[NQ];
        unsigned long k_neb = linear_index(x + 1u, y, z);
        for (int i = 0; i < NQ; i++)
            f_neb[i] = load_ddf(fi_in, index_f(k_neb, (unsigned int)i));
        apply_parabolic_inlet_3d(f, f_neb, (float)y);
    }
    else if (is_outlet(fl) && interior_y) {
        float f_neb[NQ];
        unsigned long k_neb = linear_index(x - 1u, y, z);
        for (int i = 0; i < NQ; i++)
            f_neb[i] = load_ddf(fi_in, index_f(k_neb, (unsigned int)i));
        apply_pressure_outlet_3d(f, f_neb, (float)y);
    }
    else {
        bounce_back_swap(f);
    }
}
#endif

// ============================================================================
// OneStep — main pull double-buffer step kernel
// ============================================================================
__global__ __launch_bounds__(NT, 2)
void OneStep(
    uint16_t* flag, fpxx* fi_in, fpxx* fi_out)
{
#if DIM == 2
    unsigned int x, y; unsigned long k;
    index_from_thread(x, y, k);
    if (x >= (unsigned int)NX || y >= (unsigned int)NY) return;

    uint16_t fl = flag[k];
    unsigned long j[NQ];
    compute_neighbors(x, y, j);

    float f[NQ];
    stream_pull_load(k, f, fi_in, j);

    // Boundary handling
    // has_bc(fl): BC subtype != NONE -> wall/inlet/outlet nodes only
    // Obstacle nodes (FLAG_OBSTACLE, BC subtype=NONE) are excluded here
    // and handled solely by the is_obstacle branch below.
    if (is_solid(fl) && has_bc(fl))
        apply_boundary_pull(f, fl, x, y, fi_in, k);

    if (is_obstacle(fl) && !is_curved(fl))
        bounce_back_swap(f);

    if (is_fluid(fl) && (y == 1u || y == (unsigned int)(NY - 2))) {
#if Y_WALL_BC == 1
        apply_wall_freeslip_d2q9_y_pull(y, f, fi_in, k);
#else
        apply_wall_bb_d2q9_y_pull(y, f, fi_in, k);
#endif
    }

    // Collision (fluid only)
    if (is_fluid(fl)) {
        float rho_n, ux, uy;
        compute_rho_u(f, rho_n, ux, uy);
        collide_dispatch(f, rho_n, ux, uy);
    }

    stream_pull_store(k, f, fi_out);

#elif DIM == 3
    unsigned int x, y, z; unsigned long k;
    index_from_thread(x, y, z, k);
    if (x >= (unsigned int)NX || y >= (unsigned int)NY || z >= (unsigned int)NZ) return;

    uint16_t fl = flag[k];
    unsigned long j[NQ];
    compute_neighbors(k, j);

    float f[NQ];
    stream_pull_load(k, f, fi_in, j);

    if (is_solid(fl) && has_bc(fl))
        apply_boundary_pull_3d(f, fl, x, y, z, fi_in, k);

    if (is_obstacle(fl) && !is_curved(fl))
        bounce_back_swap(f);

    if (is_fluid(fl) && (y == 1u || y == (unsigned int)(NY - 2)))
        apply_wall_bb_d3q19_y_pull(y, f, fi_in, k);

    if (is_fluid(fl)) {
        float rho_n, ux, uy, uz;
        compute_rho_u(f, rho_n, ux, uy, uz);
        collide_dispatch(f, rho_n, ux, uy, uz);
    }

    stream_pull_store(k, f, fi_out);
#endif
}

#endif // CELERIS_STEP_ONE_STEP_DOUBLE_CU
