// CelerisLab -- streaming/esopull_semantic_helpers.cuh
// EsoPull physical-value semantic helpers -- single source of truth.
//
// These functions translate between "physical distributions at cell k"
// and the EsoPull single-buffer backing layout.  All kernels that need
// to read or write physical DDF in EsoPull mode must call these helpers,
// never raw fi[k,i] access.
//
// The parity/cell/slot rules must match esopull_single_buffer.cuh exactly.
//
// Usage convention (see also stepper.py for launch order):
//   PrepareEsoPullCurvedKernel(t):     reads fi with semantics t  [correct]
//   EsoPullStep(t):                    load_f_esopull(t) -> collide -> store_f_esopull(t)
//   ForceRegionKernel(t):              reads/writes fi with semantics t  [correct]
//   SensorKernel(t):                   reads fi with semantics t         [correct]
//   MacroscopicEsoPullKernel(t):       reads fi with semantics t         [correct]
//
// The "t" parameter always represents the completed step count corresponding
// to the current physical interpretation of the backing layout.
// Double-buffer mode: t = 0, raw fi[k,i] = physical direction i.
// Verified: Kan99b K2 (200k steps, MRT) -- bit-identical to double-buffer.
// ============================================================================

#ifndef CELERIS_STREAMING_ESOPULL_SEMANTIC_HELPERS_CUH
#define CELERIS_STREAMING_ESOPULL_SEMANTIC_HELPERS_CUH

// ---------------------------------------------------------------------------
// Slot and source-cell resolution
// ---------------------------------------------------------------------------

// Return the storage slot for physical direction *dir* at timestep *t*.
__device__ __forceinline__ unsigned int esopull_slot(unsigned int dir, unsigned long t) {
    return (t & 1ul) ? dir : (unsigned int)opp_dir((int)dir);
}

// Return the source cell for physical direction *dir* at cell *k*.
// Odd directions read locally; even directions read from the opposite neighbor.
__device__ __forceinline__ unsigned long esopull_src_cell(
    unsigned long k, unsigned int dir, const unsigned long* j)
{
    return (dir & 1u) ? k : j[(unsigned int)opp_dir((int)dir)];
}

// ---------------------------------------------------------------------------
// Single-direction physical read
// ---------------------------------------------------------------------------

// Read the physical value of direction *dir* at cell *k* using EsoPull semantics.
__device__ __forceinline__ float load_physical_dir_esopull(
    unsigned long k, unsigned int dir,
    const fpxx* fi, const unsigned long* j, unsigned long t)
{
    const unsigned long src = esopull_src_cell(k, dir, j);
    const unsigned int slot = esopull_slot(dir, t);
    return load_ddf(fi, index_f(src, slot));
}

// ---------------------------------------------------------------------------
// Full-node physical read (reconstruct f[] from backing layout)
// ---------------------------------------------------------------------------

// Reconstruct the full physical f[] at cell *k* from the EsoPull backing layout.
__device__ __forceinline__ void load_physical_node_esopull(
    unsigned long k, const fpxx* fi, const unsigned long* j,
    unsigned long t, float* f)
{
    f[0] = load_ddf(fi, index_f(k, 0u));
    for (unsigned int i = 1; i < NQ; ++i) {
        f[i] = load_physical_dir_esopull(k, i, fi, j, t);
    }
}

// ---------------------------------------------------------------------------
// Full-node physical write (encode f[] into backing layout)
// ---------------------------------------------------------------------------

// Encode a physical f[] at cell *k* back into the EsoPull backing layout.
// The encoded result is valid for subsequent interpretation at the SAME *t*.
__device__ __forceinline__ void store_physical_node_esopull(
    unsigned long k, const float* f,
    fpxx* fi, const unsigned long* j, unsigned long t)
{
    store_ddf(fi, index_f(k, 0u), f[0]);
    for (unsigned int i = 1; i < NQ; i += 2) {
        if (t & 1ul) {
            store_ddf(fi, index_f(j[i], i + 1), f[i]);
            store_ddf(fi, index_f(k,    i),     f[i + 1]);
        } else {
            store_ddf(fi, index_f(j[i], i),     f[i]);
            store_ddf(fi, index_f(k,    i + 1), f[i + 1]);
        }
    }
}

#endif // CELERIS_STREAMING_ESOPULL_SEMANTIC_HELPERS_CUH
