// CelerisLab – boundary/bounce_back.cuh
// Full-way bounce-back for grid-aligned solid walls.
//
// For nodes adjacent to a solid wall, the incoming population from the
// wall direction is reflected:
//   f[i] = f[opp(i)]   (+ wall velocity correction for moving walls)
// ============================================================================

#ifndef CELERIS_BOUNDARY_BOUNCE_BACK_CUH
#define CELERIS_BOUNDARY_BOUNCE_BACK_CUH

// ---------------------------------------------------------------------------
// Simple bounce-back:  replace f[i] with f[opp(i)] for solid neighbors
// This version checks the flag of each neighbor.
// ---------------------------------------------------------------------------
__device__ inline void apply_bounce_back(float* __restrict__ f,
                                         const unsigned long* __restrict__ j,
                                         const uint8_t* __restrict__ flags_arr)
{
    // For each direction pair (i, i+1):
    //   If the neighbor in direction i is SOLID → f[i] should bounce
    //   from the opposite direction.
    //
    // With paired ordering, opp(i) = i+1 and opp(i+1) = i.
    // In pull streaming, f[i] was loaded from j[opp(i)] = j[i+1].
    // If j[i+1] is solid, we need bounce-back.

    for (int i = 1; i < NQ; i += 2) {
        // Direction i: check if source (j[i+1]) is solid
        if (flags_arr[j[i + 1]] & LEGACY_SOLID) {
            // Bounce: f[i] = f_post[opp(i)] at current node = pre-collision f[i+1]
            // But in pull-streaming context, we already loaded from neighbor.
            // For simple BB on a flat wall, just swap f[i] and f[i+1]:
            float temp = f[i];
            f[i] = f[i + 1];
            f[i + 1] = temp;
        }
    }
}

// ---------------------------------------------------------------------------
// Top/bottom wall bounce-back (specialized for channel flow)
// For nodes at y=1 (adjacent to y=0 wall) or y=NY-2 (adjacent to y=NY-1 wall)
// ---------------------------------------------------------------------------
#if NQ == 9
__device__ inline void apply_wall_bb_d2q9(unsigned int y,
                                           float* __restrict__ f)
{
    if (y == 1) {
        float temp;
        temp = f[3]; f[3] = f[4]; f[4] = temp;   // ±y swap
        temp = f[5]; f[5] = f[6]; f[6] = temp;   // ±(x+y) swap
        temp = f[7]; f[7] = f[8]; f[8] = temp;   // ±(x-y) swap
    }
    else if (y == NY - 2) {
        float temp;
        temp = f[3]; f[3] = f[4]; f[4] = temp;
        temp = f[5]; f[5] = f[6]; f[6] = temp;
        temp = f[7]; f[7] = f[8]; f[8] = temp;
    }
}
#endif

// ---------------------------------------------------------------------------
// D3Q19 wall bounce-back on y=0/NY-1 (channel walls)
// Pairs with non-zero cy: (3,4)±y, (7,8)±(x+y), (11,12)±(y+z),
//                          (13,14)±(x-y), (17,18)±(y-z)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// D3Q19 y-wall half-way bounce-back for pull double-buffer streaming.
//
// Instead of swapping f[i] <-> f[opp(i)] (which corrupts the toward-wall
// direction with garbage from the wall node), we read the opposite direction
// at the SAME node from the INPUT buffer.  That value is the post-collision
// population from the previous time step — exactly the correct half-way BB.
//
// At y=1  (wall at y=0):  directions that arrive FROM y=0 have cy_src = -1.
//   The pull loaded them from the wall (garbage).  Replace with fi_in[k, opp].
//
// At y=NY-2 (wall at y=NY-1): directions from y=NY-1, cy_src = +1.
// ---------------------------------------------------------------------------
#if NQ == 19
__device__ inline void apply_wall_bb_d3q19_y_pull(unsigned int y,
                                                   float* __restrict__ f,
                                                   const fpxx* __restrict__ fi_in,
                                                   unsigned long k)
{
    if (y == 1) {
        // Directions whose pull-source is at y=0 (wall):
        //   f[3]  (+y)    ← source j[4]  = (x, 0, z)
        //   f[7]  (+x+y)  ← source j[8]  = (x-1, 0, z)
        //   f[11] (+y+z)  ← source j[12] = (x, 0, z-1)
        //   f[14] (-x+y)  ← source j[13] = (x+1, 0, z)
        //   f[17] (+y-z)  ← source j[18] = (x, 0, z+1)
        f[3]  = load_ddf(fi_in, index_f(k, 4u));
        f[7]  = load_ddf(fi_in, index_f(k, 8u));
        f[11] = load_ddf(fi_in, index_f(k, 12u));
        f[14] = load_ddf(fi_in, index_f(k, 13u));
        f[17] = load_ddf(fi_in, index_f(k, 18u));
    }
    else if (y == (unsigned int)(NY - 2)) {
        // Directions whose pull-source is at y=NY-1 (wall):
        //   f[4]  (-y)    ← source j[3]  = (x, NY-1, z)
        //   f[8]  (-x-y)  ← source j[7]  = (x+1, NY-1, z)
        //   f[12] (-y-z)  ← source j[11] = (x, NY-1, z+1)
        //   f[13] (+x-y)  ← source j[14] = (x-1, NY-1, z)
        //   f[18] (-y+z)  ← source j[17] = (x, NY-1, z-1)
        f[4]  = load_ddf(fi_in, index_f(k, 3u));
        f[8]  = load_ddf(fi_in, index_f(k, 7u));
        f[12] = load_ddf(fi_in, index_f(k, 11u));
        f[13] = load_ddf(fi_in, index_f(k, 14u));
        f[18] = load_ddf(fi_in, index_f(k, 17u));
    }
}

// Keep the old swap version for backward compatibility
__device__ inline void apply_wall_bb_d3q19_y(unsigned int y,
                                              float* __restrict__ f)
{
    if (y == 1 || y == NY - 2) {
        float temp;
        temp = f[3];  f[3]  = f[4];  f[4]  = temp;   // ±y
        temp = f[7];  f[7]  = f[8];  f[8]  = temp;   // ±(x+y)
        temp = f[11]; f[11] = f[12]; f[12] = temp;   // ±(y+z)
        temp = f[13]; f[13] = f[14]; f[14] = temp;   // ±(x-y)
        temp = f[17]; f[17] = f[18]; f[18] = temp;   // ±(y-z)
    }
}
#endif

#endif // CELERIS_BOUNDARY_BOUNCE_BACK_CUH
