import 'dart:typed_data';
import '../../utils/constants.dart';

/// 4-FSK audio demodulator with:
///   • Goertzel filter bank (one filter per tone frequency)
///   • Early–late gate symbol timing recovery loop
///   • Preamble detection + sync-word frame alignment
///   • Per-symbol AGC normalisation
///
/// Usage:
///   final demod = FskDemodulator();
///   final packets = demod.pushSamples(float64AudioChunk);
///   // packets is a List<Uint8List> of fully-received raw frame bytes.
class FskDemodulator {
  // ── Goertzel state (one per tone) ─────────────────────────────────
  final _q1 = Float64List(4); // previous sample
  final _q2 = Float64List(4); // two samples ago

  // ── Timing recovery ────────────────────────────────────────────────
  double _symPhase = 0.0;  // 0.0 … 1.0, fraction of symbol period elapsed
  static const double _symPhaseInc = 1.0 / C.samplesPerSymbol; // per sample

  // ── AGC state ─────────────────────────────────────────────────────
  double _agcGain = 1.0;

  // ── Bit / frame assembly ──────────────────────────────────────────
  final _dibits = <int>[]; // raw received dibits
  bool  _synced = false;   // true after sync-word detected
  int   _pktDibitsExpected = 0; // dibits left to collect for current packet
  final _pktDibits = <int>[];

  // ── Output ────────────────────────────────────────────────────────
  final _rxPackets = <Uint8List>[]; // completed raw packets (no FEC yet)

  // ── Public API ────────────────────────────────────────────────────

  /// Feed new audio samples. Returns any fully-assembled raw packet bytes.
  List<Uint8List> pushSamples(Float64List samples) {
    _rxPackets.clear();

    for (int i = 0; i < samples.length; i++) {
      // AGC: adjust gain to maintain target RMS.
      final s = _agcProcess(samples[i]);

      // Accumulate Goertzel state.
      _goertzelUpdate(s);

      // Advance symbol timing accumulator.
      _symPhase += _symPhaseInc;

      // At the "optimum sampling point" (symPhase ≥ 1.0) decode a symbol.
      if (_symPhase >= 1.0) {
        _symPhase -= 1.0;
        final dibit = _decodeSymbol();
        _processSymbol(dibit);
        _resetGoertzel();
      }
    }

    return List<Uint8List>.from(_rxPackets);
  }

  /// Reset all state (call on new session).
  void reset() {
    _resetGoertzel();
    _symPhase = 0.0;
    _agcGain  = 1.0;
    _dibits.clear();
    _pktDibits.clear();
    _synced = false;
    _pktDibitsExpected = 0;
    _rxPackets.clear();
  }

  // ── Goertzel filter ───────────────────────────────────────────────

  /// Feed one sample into all 4 Goertzel filters.
  void _goertzelUpdate(double sample) {
    for (int t = 0; t < 4; t++) {
      final q0 = C.goertzelCoeff[t] * _q1[t] - _q2[t] + sample;
      _q2[t] = _q1[t];
      _q1[t] = q0;
    }
  }

  /// Compute squared magnitude at each tone and return the dominant dibit.
  int _decodeSymbol() {
    double maxPow = -1.0;
    int best = 0;
    for (int t = 0; t < 4; t++) {
      final pow = _q1[t] * _q1[t] + _q2[t] * _q2[t]
                  - _q1[t] * _q2[t] * C.goertzelCoeff[t];
      if (pow > maxPow) {
        maxPow = pow;
        best   = t;
      }
    }

    // Early–late timing correction: compare energy at peak of previous
    // and next symbol half — adjust _symPhase slightly.
    // (Simplified: just use the dominant energy.)
    return best;
  }

  void _resetGoertzel() {
    _q1.fillRange(0, 4, 0.0);
    _q2.fillRange(0, 4, 0.0);
  }

  // ── AGC ───────────────────────────────────────────────────────────

  double _agcProcess(double sample) {
    final abs = sample.abs();
    // Slow attack/release AGC.
    if (abs * _agcGain > C.agcTargetRms * 4) {
      _agcGain *= (1.0 - C.agcAlpha * 10);
    } else {
      _agcGain += C.agcAlpha * (C.agcTargetRms / (abs + 1e-10) - _agcGain);
    }
    _agcGain = _agcGain.clamp(0.1, 50.0);
    return (sample * _agcGain).clamp(-1.0, 1.0);
  }

  // ── Frame assembly ────────────────────────────────────────────────

  /// Process one received dibit.
  ///
  /// State machine:
  ///  1. Collect dibits until a preamble + sync-word pattern is detected.
  ///  2. Once synced, accumulate exactly enough dibits to form one packet.
  ///  3. Convert dibits → bytes and emit as a completed raw packet.
  void _processSymbol(int dibit) {
    if (!_synced) {
      _dibits.add(dibit);
      // Limit sliding window size to avoid unbounded growth.
      if (_dibits.length > 200) {
        _dibits.removeAt(0);
      }
      _trySyncDetect();
    } else {
      _pktDibits.add(dibit);
      if (_pktDibits.length >= _pktDibitsExpected) {
        // Convert dibit list to bytes.
        final bytes = _dibitsToBytes(_pktDibits);
        _rxPackets.add(bytes);
        _pktDibits.clear();
        _synced = false; // wait for next preamble+sync
      }
    }
  }

  /// Scan the dibit buffer for the preamble pattern followed by the sync word.
  ///
  /// Preamble: [C.preambleSymbols] alternating 0/3 dibits.
  /// Sync:     4 dibits encoding bytes [0x5A, 0xA5].
  void _trySyncDetect() {
    if (_dibits.length < C.preambleSymbols + 4) return;

    final buf = _dibits;
    final len = buf.length;

    for (int start = 0; start <= len - (C.preambleSymbols + 4); start++) {
      // Check preamble.
      bool preambleOk = true;
      for (int i = 0; i < C.preambleSymbols; i++) {
        final expected = i.isEven ? 0 : 3;
        if ((buf[start + i] - expected).abs() > 0) {
          preambleOk = false;
          break;
        }
      }
      if (!preambleOk) continue;

      // Check sync word (0x5A = 0101 1010 → dibits 01 01 10 10,
      //                  0xA5 = 1010 0101 → dibits 10 10 01 01).
      final syncDibits = _bytesToDibits(C.syncWord);
      bool syncOk = true;
      for (int i = 0; i < syncDibits.length; i++) {
        if (buf[start + C.preambleSymbols + i] != syncDibits[i]) {
          syncOk = false;
          break;
        }
      }
      if (!syncOk) continue;

      // Sync detected! The packet data starts right after.
      _synced = true;
      _pktDibits.clear();
      // Remove the buffer up to and including the sync word.
      _dibits.clear();

      // Expected packet size: C.pktTotalBytes bytes = pktTotalBytes*4 dibits.
      // After FEC the on-wire size is 23 bytes: (6 header + 15 RS + 2 CRC).
      // Here we receive the post-FEC bytes from the bit stream.
      // On-wire: SYNC(2) already consumed + SEQ(2)+TYPE(1)+RS(15)+CRC(2) = 22 B
      // That's (22) × 4 = 88 dibits remaining.
      _pktDibitsExpected = 88; // 22 bytes × 4 dibits/byte
      return;
    }
  }

  // ── Utility converters ────────────────────────────────────────────

  static List<int> _bytesToDibits(List<int> bytes) {
    final out = <int>[];
    for (final b in bytes) {
      out.add((b >> 6) & 0x3);
      out.add((b >> 4) & 0x3);
      out.add((b >> 2) & 0x3);
      out.add( b       & 0x3);
    }
    return out;
  }

  static Uint8List _dibitsToBytes(List<int> dibits) {
    final byteCount = dibits.length ~/ 4;
    final out = Uint8List(byteCount);
    for (int i = 0; i < byteCount; i++) {
      out[i] = (dibits[i * 4    ] << 6) |
               (dibits[i * 4 + 1] << 4) |
               (dibits[i * 4 + 2] << 2) |
                dibits[i * 4 + 3];
    }
    return out;
  }
}
