/*
 * SPDX-License-Identifier: LicenseRef-CSSL-1.0
 */

#include <errno.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "common/utils/LOG/log.h"
#include "common/utils/assertions.h"
#include "SIMULATION/TOOLS/sim.h"

#include "cirdb_provider.h"
#include "cirdb_yaml.h"

#define NUM_TAPS_BUFFERS 4
#define MAX_L_PUBLISH 8

static inline double dabs(double x)
{
  return x < 0.0 ? -x : x;
}

typedef struct {
  void *taps_blob; /* contiguous complexf for all links */
  channel_desc_t *ch; /* ch_ps points into taps_blob */
} cirdb_buffer_t;

typedef struct {
  /* I/O */
  FILE *fh;

  /* Selected entry meta */
  int model_id;
  int n_tx;
  int n_rx;
  int L_full;
  int S;
  double fs_hz;
  double snapshot_dt_s;
  float ds_ns;
  float speed_mps;
  uint64_t sel_offset;
  uint64_t sel_nbytes;

  /* Publication control */
  uint32_t L_out;
  uint32_t snap_idx;

  /* Shape in publication */
  int num_tx;
  int num_rx;

  /* Double buffering for readers */
  cirdb_buffer_t bufs[NUM_TAPS_BUFFERS];
  int cur;

  /* Temporary read buffer for one snapshot of full L_full taps */
  void *snapshot_tmp;
  size_t snapshot_tmp_bytes;

  /* Published pointer location owned by caller */
  channel_desc_t **channel_desc_out;

  /* Bookkeeping of last step computed from elapsed time */
  int64_t last_step_applied; /* -1 until first update */
} cirdb_g;

static cirdb_g G;

static inline void fread_exact(void *dst, size_t sz, FILE *fh)
{
  size_t r = fread(dst, 1, sz, fh);
  if (r != sz) {
    LOG_E(HW, "CIRDB short read: expected %zu got %zu errno=%d\n", sz, r, errno);
    abort();
  }
}

static inline size_t cf_bytes(size_t n)
{
  return n * sizeof(struct complexf);
}

/* Point channel_desc ch_ps into a compacted taps buffer */
static void point_channel_desc(channel_desc_t *ch, struct complexf *base, int num_tx, int num_rx, size_t L_out)
{
  for (int aarx = 0; aarx < num_rx; aarx++) {
    for (int aatx = 0; aatx < num_tx; aatx++) {
      int link = aarx + num_rx * aatx;
      ch->ch_ps[link] = &base[link * L_out];
    }
  }
  ch->channel_length = L_out;
  ch->path_loss_dB = 0;
  ch->nb_tx = num_tx;
  ch->nb_rx = num_rx;
}

/* Copy first L_out taps per link from a full L_full snapshot */
static void compact_L_out(struct complexf *dst,
                          const struct complexf *src_full,
                          int num_tx,
                          int num_rx,
                          size_t L_full,
                          size_t L_out)
{
  for (int aatx = 0; aatx < num_tx; aatx++) {
    for (int aarx = 0; aarx < num_rx; aarx++) {
      size_t link = aarx + num_rx * aatx;
      const struct complexf *src = &src_full[link * L_full];
      struct complexf *dst_link = &dst[link * L_out];
      memcpy(dst_link, src, cf_bytes(L_out));
    }
  }
}

/* Load snapshot index s into the next publication buffer and flip */
static void load_snapshot_and_publish(uint32_t s)
{
  uint64_t stride = (uint64_t)G.n_tx * G.n_rx * G.L_full * sizeof(struct complexf);
  uint64_t off = G.sel_offset + (uint64_t)s * stride;

  if (fseeko(G.fh, (off_t)off, SEEK_SET) != 0) {
    LOG_E(HW, "CIRDB fseoko failed errno=%d\n", errno);
    abort();
  }
  fread_exact(G.snapshot_tmp, (size_t)stride, G.fh);

  int next = (G.cur + 1) % NUM_TAPS_BUFFERS;
  cirdb_buffer_t *buf = &G.bufs[next];

  compact_L_out((struct complexf *)buf->taps_blob, (const struct complexf *)G.snapshot_tmp, G.n_tx, G.n_rx, G.L_full, G.L_out);

  point_channel_desc(buf->ch, (struct complexf *)buf->taps_blob, G.n_tx, G.n_rx, G.L_out);

  *G.channel_desc_out = buf->ch;
  G.cur = next;
}

void cirdb_connect(int id,
                   int num_tx_antennas,
                   int num_rx_antennas,
                   const cirdb_select_opts_t *sel,
                   channel_desc_t **channel_desc_out)
{
  (void)id;
  memset(&G, 0, sizeof(G));
  G.channel_desc_out = channel_desc_out;
  G.num_tx = num_tx_antennas;
  G.num_rx = num_rx_antennas;
  G.cur = 0;
  G.last_step_applied = -1;

  /* Require explicit paths in cirdb_select_opts_t */
  AssertFatal(sel != NULL, "cirdb_select_opts_t must not be NULL\n");
  AssertFatal(sel->bin_path && sel->bin_path[0], "CIRDB requires explicit bin_path in cirdb_select_opts_t\n");
  AssertFatal(sel->yaml_path && sel->yaml_path[0], "CIRDB requires explicit yaml_path in cirdb_select_opts_t\n");

  const char *use_bin = sel->bin_path;
  const char *sidecar = sel->yaml_path;

  LOG_I(HW, "CIRDB: using database %s with metadata %s\n", use_bin, sidecar);

  G.fh = fopen(use_bin, "rb");
  AssertFatal(G.fh != NULL, "Cannot open CIRDB database file '%s': %s\n", use_bin, strerror(errno));

  /* Selection request */
  int want_model_id = sel->want_model_id;
  AssertFatal(want_model_id >= 0 && want_model_id <= 4,
            "Invalid model_id=%d (valid: 0..4)\n",
            want_model_id);
  float want_ds = (sel && sel->want_ds_ns > 0) ? sel->want_ds_ns : -1.0f;
  float want_speed = (sel && sel->want_speed_mps > 0) ? sel->want_speed_mps : -1.0f;

  LOG_I(HW,
        "CIRDB: Searching for entry with model_id=%d, antennas=%dx%d, DS=%.1fns, speed=%.1fm/s, AoA=%.1fdeg\n",
        want_model_id,
        G.num_tx,
        G.num_rx,
        want_ds,
        want_speed,
        sel->want_aoa_deg);

  cirdb_entry_meta_t m = (cirdb_entry_meta_t){0};
  cirdb_select_req_t req = {.want_model_id = want_model_id,
                            .want_tx = G.num_tx,
                            .want_rx = G.num_rx,
                            .want_ds_ns = want_ds,
                            .want_speed_mps = want_speed,
                            .want_aoa_deg = sel->want_aoa_deg,
                            .allow_shape_swap = 0,
                            .w_ds = 1.0f,
                            .w_speed = 0.2f,
                            .yaml_path = sidecar};

  int ok = cirdb_yaml_select(&req, &m);

  // STRICT validation: fail if no match found
  AssertFatal(ok > 0,
              "No suitable CIR entry found in YAML %s\n"
              "  Requested: model_id=%d, antennas=%dx%d, DS=%.1fns, speed=%.1fm/s\n"
              "  Possible causes:\n"
              "    - No entry with model_id=%d in database\n"
              "    - No entry with antenna configuration %dx%d in database\n"
              "  Check the YAML file for available entries\n",
              sidecar,
              want_model_id,
              G.num_tx,
              G.num_rx,
              want_ds,
              want_speed,
              want_model_id,
              G.num_tx,
              G.num_rx);

  // STRICT validation: antenna dimensions must match exactly
  AssertFatal(m.n_tx == G.num_tx && m.n_rx == G.num_rx,
              "CIRDB antenna mismatch: requested %dx%d but selected entry has %dx%d\n"
              "  This should never happen - indicates a bug in selection logic\n",
              G.num_tx,
              G.num_rx,
              m.n_tx,
              m.n_rx);

  // STRICT validation: model_id must match if specified
  if (want_model_id >= 0) {
    AssertFatal(m.model_id == want_model_id,
                "CIRDB model_id mismatch: requested %d but selected entry has %d\n"
                "  This should never happen - indicates a bug in selection logic\n",
                want_model_id,
                m.model_id);
  }

  // FLEXIBLE: warn if delay spread doesn't match exactly (using closest available)
  if (want_ds > 0 && dabs(m.ds_ns - want_ds) > 0.1) {
    LOG_W(HW, "CIRDB: Requested delay spread %.1f ns not available in database, using closest match: %.1f ns\n", want_ds, m.ds_ns);
  }

  // FLEXIBLE: warn if speed doesn't match exactly (using closest available)
  if (want_speed > 0 && dabs(m.speed_mps - want_speed) > 0.1) {
    LOG_W(HW,
          "CIRDB: Requested speed %.1f m/s not available in database, using closest match: %.1f m/s\n",
          want_speed,
          m.speed_mps);
  }

  G.model_id = m.model_id;
  G.n_tx = m.n_tx;
  G.n_rx = m.n_rx;
  G.L_full = m.L;
  G.S = m.S;
  G.fs_hz = m.fs_hz;
  G.snapshot_dt_s = m.snapshot_dt_s;
  G.ds_ns = m.ds_ns;
  G.speed_mps = m.speed_mps;
  G.sel_offset = m.offset_bytes;
  G.sel_nbytes = m.nbytes;
  G.L_out = (m.L <= MAX_L_PUBLISH ? (uint32_t)m.L : MAX_L_PUBLISH);
  G.snap_idx = 0;

  G.snapshot_tmp_bytes = (size_t)G.n_tx * G.n_rx * G.L_full * sizeof(struct complexf);
  G.snapshot_tmp = calloc_or_fail(1, G.snapshot_tmp_bytes);

  for (int i = 0; i < NUM_TAPS_BUFFERS; i++) {
    size_t blob_cf = (size_t)G.n_tx * G.n_rx * G.L_out;
    G.bufs[i].taps_blob = calloc_or_fail(blob_cf, sizeof(struct complexf));
    G.bufs[i].ch = calloc_or_fail(1, sizeof(channel_desc_t));
    G.bufs[i].ch->ch_ps = calloc_or_fail(G.n_rx * G.n_tx, sizeof(struct complexf *));
    G.bufs[i].ch->nb_tx = G.n_tx;
    G.bufs[i].ch->nb_rx = G.n_rx;
  }

  if (G.S > 0) {
    load_snapshot_and_publish(0);
  }

  LOG_I(HW,
        "CIRDB: Selected entry - model=%d DS=%.3fns shape=%ux%u L=%u/%u S=%u fs=%.0f dt=%.6fs speed=%.3fm/s aoa=%.1fdeg\n",
        G.model_id,
        G.ds_ns,
        G.n_tx,
        G.n_rx,
        G.L_out,
        G.L_full,
        G.S,
        G.fs_hz,
        G.snapshot_dt_s,
        G.speed_mps,
        sel->want_aoa_deg);
}

void cirdb_update(uint64_t ns_since_start)
{
  if (!G.channel_desc_out || !*G.channel_desc_out)
    return;
  if (G.S <= 0)
    return;

  double dt_s = (G.snapshot_dt_s > 0.0 ? G.snapshot_dt_s : 0.5);
  if (dt_s <= 0.0)
    dt_s = 0.5;

  double steps_f = (ns_since_start * 1e-9) / dt_s;
  int64_t step = (int64_t)(steps_f >= 0.0 ? steps_f : 0.0);

  if (step != G.last_step_applied) {
    uint32_t s = (uint32_t)(step % G.S);
    load_snapshot_and_publish(s);
    G.snap_idx = s;
    G.last_step_applied = step;
  }
}

void cirdb_stop(void)
{
  for (int i = 0; i < NUM_TAPS_BUFFERS; i++) {
    free(G.bufs[i].taps_blob);
    if (G.bufs[i].ch) {
      free(G.bufs[i].ch->ch_ps);
      free(G.bufs[i].ch);
    }
  }
  free(G.snapshot_tmp);
  if (G.fh)
    fclose(G.fh);
  memset(&G, 0, sizeof(G));
  G.last_step_applied = -1;
}
