// CelerisLab – boundary/inlet_outlet.cuh
// Inlet and outlet boundary conditions  (D2Q9).
//
// Parabolic inlet (non-equilibrium extrapolation, Zou-He style):
//   Left wall (x=0): reconstruct cx>0 populations (i=1,5,7)
//
// Pressure outlet (non-equilibrium extrapolation):
//   Right wall (x=NX-1): reconstruct cx<0 populations (i=2,6,8)
//
// New paired D2Q9 ordering:
//   cx = {0, 1,-1, 0, 0, 1,-1, 1,-1}
//   cy = {0, 0, 0, 1,-1, 1,-1,-1, 1}
// ============================================================================

#ifndef CELERIS_BOUNDARY_INLET_OUTLET_CUH
#define CELERIS_BOUNDARY_INLET_OUTLET_CUH

#if NQ == 9

// ---------------------------------------------------------------------------
// Parabolic inlet  (x = 0, non-equilibrium extrapolation)
//
// f, f_neb are local DDF arrays:
//   f      = populations at the boundary node (x=0)
//   f_neb  = populations at the interior neighbor (x=1)
//   y      = y-coordinate of the boundary node
//
// Reconstructs f[1], f[5], f[7]  (cx > 0 directions in new ordering)
// using: f_bc[i] = f_neb[i] - feq(rho_neb, u_neb)[i] + feq(rho_neb, u_target)[i]
// ---------------------------------------------------------------------------
__device__ inline void apply_parabolic_inlet(float* __restrict__ f,
                                             const float* __restrict__ f_neb,
                                             float y_coord)
{
    // Neighbor macros
    float p_neb = (f_neb[0]+f_neb[1]+f_neb[2]+f_neb[3]+f_neb[4]
                  +f_neb[5]+f_neb[6]+f_neb[7]+f_neb[8]) / 3.0f;

    // Target velocity (parabolic profile)
    float yy = (y_coord - 0.5f * (NY - 1)) / (NY - 2.0f);
    float u_target = U0 * 1.5f * (1.0f - 4.0f * yy * yy);
    float v_target = 0.0f;

    // Neighbor velocity
    float u_neb = (f_neb[1]-f_neb[2]+f_neb[5]-f_neb[6]+f_neb[7]-f_neb[8]) / RHO;
    float v_neb = (f_neb[3]-f_neb[4]+f_neb[5]-f_neb[6]-f_neb[7]+f_neb[8]) / RHO;

    // feq for direction i=1 (cx=1, cy=0), w=1/9:
    //   feq = (2p + RHO*(2u² + 2u - v²)) / 6
    float feq1_target = (2.0f*p_neb + RHO*(2.0f*u_target*u_target + 2.0f*u_target - v_target*v_target)) / 6.0f;
    float feq1_neb    = (2.0f*p_neb + RHO*(2.0f*u_neb*u_neb + 2.0f*u_neb - v_neb*v_neb)) / 6.0f;

    // feq for direction i=5 (cx=1, cy=1), w=1/36:
    //   feq = (p + RHO*(u² + 3uv + u + v² + v)) / 12
    float feq5_target = (p_neb + RHO*(u_target*u_target + 3.0f*u_target*v_target + u_target + v_target*v_target + v_target)) / 12.0f;
    float feq5_neb    = (p_neb + RHO*(u_neb*u_neb + 3.0f*u_neb*v_neb + u_neb + v_neb*v_neb + v_neb)) / 12.0f;

    // feq for direction i=7 (cx=1, cy=-1), w=1/36:
    //   feq = (p + RHO*(u² - 3uv + u + v² - v)) / 12
    float feq7_target = (p_neb + RHO*(u_target*u_target - 3.0f*u_target*v_target + u_target + v_target*v_target - v_target)) / 12.0f;
    float feq7_neb    = (p_neb + RHO*(u_neb*u_neb - 3.0f*u_neb*v_neb + u_neb + v_neb*v_neb - v_neb)) / 12.0f;

    // Non-equilibrium extrapolation
    f[1] = f_neb[1] - feq1_neb + feq1_target;
    f[5] = f_neb[5] - feq5_neb + feq5_target;
    f[7] = f_neb[7] - feq7_neb + feq7_target;
}

// ---------------------------------------------------------------------------
// Pressure outlet  (x = NX-1, non-equilibrium extrapolation)
//
// Reconstructs f[2], f[6], f[8]  (cx < 0 directions in new ordering)
// p_out = 0 (gauge pressure), uses velocity from neighbor.
// ---------------------------------------------------------------------------
__device__ inline void apply_pressure_outlet(float* __restrict__ f,
                                             const float* __restrict__ f_neb,
                                             float y_coord)
{
    float p_out = 0.0f;

    // Target velocity (parabolic, same as inlet for consistency)
    float yy = (y_coord - 0.5f * (NY - 1)) / (NY - 2.0f);
    float u_target = U0 * 1.5f * (1.0f - 4.0f * yy * yy);
    float v_target = 0.0f;

    // Neighbor velocity
    float u_neb = (f_neb[1]-f_neb[2]+f_neb[5]-f_neb[6]+f_neb[7]-f_neb[8]) / RHO;
    float v_neb = (f_neb[3]-f_neb[4]+f_neb[5]-f_neb[6]-f_neb[7]+f_neb[8]) / RHO;

    // feq for direction i=2 (cx=-1, cy=0), w=1/9:
    //   feq = (2p - RHO*(-2u² + 2u + v²)) / 6
    float feq2_target = (2.0f*p_out - RHO*(-2.0f*u_target*u_target + 2.0f*u_target + v_target*v_target)) / 6.0f;
    float feq2_neb    = (2.0f*p_out - RHO*(-2.0f*u_neb*u_neb + 2.0f*u_neb + v_neb*v_neb)) / 6.0f;

    // feq for direction i=8 (cx=-1, cy=1), w=1/36:
    //   feq = (p + RHO*(u² - 3uv - u + v² + v)) / 12
    float feq8_target = (p_out + RHO*(u_target*u_target - 3.0f*u_target*v_target - u_target + v_target*v_target + v_target)) / 12.0f;
    float feq8_neb    = (p_out + RHO*(u_neb*u_neb - 3.0f*u_neb*v_neb - u_neb + v_neb*v_neb + v_neb)) / 12.0f;

    // feq for direction i=6 (cx=-1, cy=-1), w=1/36:
    //   feq = (p + RHO*(u² + 3uv - u + v² - v)) / 12
    float feq6_target = (p_out + RHO*(u_target*u_target + 3.0f*u_target*v_target - u_target + v_target*v_target - v_target)) / 12.0f;
    float feq6_neb    = (p_out + RHO*(u_neb*u_neb + 3.0f*u_neb*v_neb - u_neb + v_neb*v_neb - v_neb)) / 12.0f;

    f[2] = f_neb[2] - feq2_neb + feq2_target;
    f[8] = f_neb[8] - feq8_neb + feq8_target;
    f[6] = f_neb[6] - feq6_neb + feq6_target;
}

#endif // NQ == 9

// ============================================================================
// D3Q19 inlet / outlet  (non-equilibrium extrapolation)
//
// Parabolic inlet (x=0):  reconstruct cx>0 populations i=1,7,9,13,15
// Pressure outlet (x=NX-1): reconstruct cx<0 populations i=2,8,10,14,16
//
// Uses generic feq computation from macro.cuh to avoid hand-expanded formulas.
// ============================================================================
#if NQ == 19

__device__ inline void apply_parabolic_inlet_3d(float* __restrict__ f,
                                                const float* __restrict__ f_neb,
                                                float y_coord)
{
    // Neighbor macros
    float rho_neb, un, vn, wn;
    compute_rho_u(f_neb, rho_neb, un, vn, wn);

    // Target velocity (parabolic in y, uniform in z)
    float yy = (y_coord - 0.5f * (NY - 1)) / (NY - 2.0f);
    float u_tar = U0 * 1.5f * (1.0f - 4.0f * yy * yy);

    // feq arrays
    float feq_tar[19], feq_neb[19];
    compute_feq(rho_neb, u_tar, 0.0f, 0.0f, feq_tar);
    compute_feq(rho_neb, un, vn, wn, feq_neb);

    // Reconstruct cx>0 directions: i = 1, 7, 9, 13, 15
    f[1]  = f_neb[1]  - feq_neb[1]  + feq_tar[1];
    f[7]  = f_neb[7]  - feq_neb[7]  + feq_tar[7];
    f[9]  = f_neb[9]  - feq_neb[9]  + feq_tar[9];
    f[13] = f_neb[13] - feq_neb[13] + feq_tar[13];
    f[15] = f_neb[15] - feq_neb[15] + feq_tar[15];
}

__device__ inline void apply_pressure_outlet_3d(float* __restrict__ f,
                                                const float* __restrict__ f_neb,
                                                float y_coord)
{
    // Neighbor macros
    float rho_neb, un, vn, wn;
    compute_rho_u(f_neb, rho_neb, un, vn, wn);

    // Target: p_out = 0 gauge → rho = 1.0  (using neighbor velocity)
    float yy = (y_coord - 0.5f * (NY - 1)) / (NY - 2.0f);
    float u_tar = U0 * 1.5f * (1.0f - 4.0f * yy * yy);

    float feq_tar[19], feq_neb[19];
    compute_feq(RHO, u_tar, 0.0f, 0.0f, feq_tar);
    compute_feq(RHO, un, vn, wn, feq_neb);

    // Reconstruct cx<0 directions: i = 2, 8, 10, 14, 16
    f[2]  = f_neb[2]  - feq_neb[2]  + feq_tar[2];
    f[8]  = f_neb[8]  - feq_neb[8]  + feq_tar[8];
    f[10] = f_neb[10] - feq_neb[10] + feq_tar[10];
    f[14] = f_neb[14] - feq_neb[14] + feq_tar[14];
    f[16] = f_neb[16] - feq_neb[16] + feq_tar[16];
}

#endif // NQ == 19

#endif // CELERIS_BOUNDARY_INLET_OUTLET_CUH
