// CelerisLab – boundary/curved_boundary.cuh
// Interpolated curved-boundary bounce-back.
//
// For curved solid surfaces that don't align with the grid, the
// reflected population is interpolated using the fractional distance q
// between the fluid node and the wall intersection.
//
// Migrated from existing kernel.cu + ref/main.cu.
//
// Delta pool layout per curved-boundary node (11 floats for D2Q9):
//   delta[offset + 0]  : encoded object id  (bitcast int → float)
//   delta[offset + 1..NQ-1] : q values for each direction (0 if no wall hit)
//   delta[offset + NQ]  : normal_y  (wall normal y-component / R)
//   delta[offset + NQ+1]: normal_x  (wall normal x-component / R)
// ============================================================================

#ifndef CELERIS_BOUNDARY_CURVED_BOUNDARY_CUH
#define CELERIS_BOUNDARY_CURVED_BOUNDARY_CUH

#if NQ == 9

// ---------------------------------------------------------------------------
// apply_curved_boundary:
//   Interpolated bounce-back with wall velocity correction.
//   Operates on a SOLID+INTERFACE node's neighbors.
//
// Parameters:
//   n       – current node (solid + interface)
//   x, y    – coordinates of current node
//   f_out   – the output DDF buffer (double-buffer scheme) being modified
//   delta   – parameter pool
//   id_off  – offset into delta for this node (= indx[n])
//   Uw, Vw  – wall velocity at this node
//   obs_fx, obs_fy – accumulated force observation (atomicAdd targets)
// ---------------------------------------------------------------------------
__device__ inline void apply_curved_boundary(
    unsigned long n, unsigned int x, unsigned int y,
    fpxx* __restrict__ f_out,
    const float* __restrict__ delta,
    int id_off,
    float Uw, float Vw,
    float* __restrict__ obs_fx,
    float* __restrict__ obs_fy)
{
    // New paired direction ordering:
    //   cx = {0, 1,-1, 0, 0, 1,-1, 1,-1}
    //   cy = {0, 0, 0, 1,-1, 1,-1,-1, 1}

    for (int i = 1; i < NQ; i++) {
        int x_neb = x + d_cx[i];
        int y_neb = y + d_cy[i];

        // Check bounds (skip if neighbor is outside domain)
        if (x_neb < 0 || x_neb >= NX || y_neb < 0 || y_neb >= NY) continue;

        unsigned long k_neb = linear_index((unsigned int)x_neb, (unsigned int)y_neb);

        // Only process if neighbor is FLUID
        // (We read the flag from global memory – this is a boundary kernel,
        //  called infrequently per node, so the extra read is acceptable.)
        // The caller should ensure this node IS solid+interface.

        float q = delta[id_off + i];  // fractional distance (0 if no wall hit)
        if (q <= 0.0f) continue;       // no wall intersection in this direction

        int oi = opp_dir(i);
        float ci_dot_uw = (float)d_cx[i] * Uw + (float)d_cy[i] * Vw;
        float wall_term = 6.0f * d_w[i] * ci_dot_uw;

        // Second neighbor for quadratic interpolation
        int x_neb2 = x + 2 * d_cx[i];
        int y_neb2 = y + 2 * d_cy[i];
        // Clamp to domain (simple, could be improved)
        x_neb2 = max(0, min(NX - 1, x_neb2));
        y_neb2 = max(0, min(NY - 1, y_neb2));
        unsigned long k_neb2 = linear_index((unsigned int)x_neb2, (unsigned int)y_neb2);

        // Read current DDF values from output buffer
        float f_self_opp  = load_ddf(f_out, index_f(n,      (unsigned int)oi));
        float f_neb_opp   = load_ddf(f_out, index_f(k_neb,  (unsigned int)oi));
        float f_neb2_fwd  = load_ddf(f_out, index_f(k_neb2, (unsigned int)i));

        // Interpolated bounce-back (Yu, Mei, Shyy, 2003)
        float f_reflected = (q * f_self_opp + (1.0f - q) * f_neb_opp
                             + q * f_neb2_fwd + wall_term) / (1.0f + q);

        store_ddf(f_out, index_f(k_neb, (unsigned int)i), f_reflected);

        // Force observation (momentum exchange)
        float f_neb_fwd = load_ddf(f_out, index_f(k_neb, (unsigned int)i));
        float f_self_fwd = load_ddf(f_out, index_f(n, (unsigned int)i));
        float f_sum = f_neb_fwd + f_self_opp;

        int x_back = x - d_cx[i];
        int y_back = y - d_cy[i];
        x_back = max(0, min(NX - 1, x_back));
        y_back = max(0, min(NY - 1, y_back));

        // Accumulate force
        if (obs_fx != nullptr) {
            atomicAdd(obs_fx, -f_sum * (float)d_cx[i] + f_self_fwd);
            atomicAdd(obs_fy, -f_sum * (float)d_cy[i] +
                      load_ddf(f_out, index_f(linear_index((unsigned int)x_back, (unsigned int)y_back),
                                              (unsigned int)i)));
        }
    }
}

#endif // NQ == 9

#endif // CELERIS_BOUNDARY_CURVED_BOUNDARY_CUH
