neteq 0.8.3

NetEQ-inspired adaptive jitter buffer for audio decoding
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
/*
 * Copyright 2025 Security Union LLC
 *
 * Licensed under either of
 *
 * * Apache License, Version 2.0
 *   (http://www.apache.org/licenses/LICENSE-2.0)
 * * MIT license
 *   (http://opensource.org/licenses/MIT)
 *
 * at your option.
 *
 * Unless you explicitly state otherwise, any contribution intentionally
 * submitted for inclusion in the work by you, as defined in the Apache-2.0
 * license, shall be dual licensed as above, without any additional terms or
 * conditions.
 */

/// Q14 fixed-point math constants and utilities
///
/// ## What is Q14 Format?
///
/// Q14 is a **fixed-point number format** used throughout WebRTC's audio processing pipeline.
/// It represents fractional numbers using integers, which was crucial for embedded systems
/// and real-time audio processing when WebRTC was originally developed.
///
/// ### Format Breakdown:
/// ```text
/// Q14 in 16-bit integer:
/// [S][I][F F F F F F F F F F F F F F]
///  ↑  ↑  ← ---- 14 fractional bits ----→
///  │  └── 1 integer bit (range 0-1.99)  
///  └───── 1 sign bit
/// ```
///
/// ### The Math:
/// ```text
/// Q14_value = actual_floating_point_value × 2¹⁴
/// Q14_value = actual_floating_point_value × 16384
///
/// To convert back:
/// actual_value = Q14_value ÷ 16384
/// ```
///
/// ## Why Use Q14? (Historical Context)
///
/// 1. **No Floating Point Unit (FPU)**: Many embedded audio processors lacked FPUs
/// 2. **Deterministic Results**: Same calculation produces identical results across platforms
/// 3. **Performance**: Integer arithmetic was much faster than software floating point
/// 4. **Memory Efficient**: 16-bit integers vs 32-bit floats saved precious RAM
/// 5. **Sufficient Precision**: 14 bits = 1/16384 ≈ 0.00006 resolution (good for audio ratios)
///
/// ## Real-World Examples:
///
/// | Actual Ratio | Meaning | Q14 Value | Per-mille (‰) |
/// |--------------|---------|-----------|----------------|
/// | 0.0 | No expansion | 0 | 0‰ |
/// | 0.25 | 25% expansion | 4096 | 250‰ |
/// | 0.5 | 50% expansion | 8192 | 500‰ |
/// | 1.0 | 100% expansion | 16384 | 1000‰ |
///
/// ## WebRTC Usage Pattern:
///
/// WebRTC calculates audio processing ratios in Q14 format:
/// ```cpp
/// // WebRTC C++ code:
/// stats->expand_rate = CalculateQ14Ratio(expanded_samples, total_samples);
///
/// // Where CalculateQ14Ratio() returns:
/// return (numerator << 14) / denominator;  // Multiply by 16384
/// ```
///
/// Then everywhere they need to display it:
/// ```cpp
/// float display_rate = stats.expand_rate / 16384.0f;  // Convert back to ratio
/// ```
///
/// ## Our Improved Implementation:
///
/// Instead of scattering magic numbers like WebRTC does, we provide:
/// - **Centralized constants** (`SCALE = 16384`)
/// - **Clear conversion functions** (`to_per_mille()`, `from_float()`)
/// - **Self-documenting code** (no magic numbers)
/// - **Type safety** (explicit conversions)
///
/// ### Example Usage:
/// ```rust
/// use neteq::statistics::q14;
/// // Calculate 25% expansion ratio
/// let ratio = 320.0 / 1280.0;  // 0.25
/// let q14_value = q14::from_float(ratio);  // 4096
/// let display_rate = q14::to_per_mille(q14_value);  // ~250.0‰
/// assert_eq!(q14_value, 4096);
/// assert!((display_rate - 250.0).abs() < 0.01);
/// ```
///
/// ## Why Still Use Q14 Today?
///
/// While modern processors have powerful FPUs, we maintain Q14 compatibility because:
/// 1. **WebRTC Compatibility**: Our statistics must match WebRTC's format
/// 2. **Cross-Platform Consistency**: Same results on ARM, x86, WASM, etc.
/// 3. **Proven in Production**: Billions of WebRTC calls validate this approach
/// 4. **Network Protocol**: Q14 values are transmitted over the network
///
/// The key difference: we provide clean abstractions around the legacy format
/// rather than exposing raw magic numbers throughout the codebase.
pub mod q14 {
    /// Q14 scale factor: 2^14 = 16384
    ///
    /// This is the fundamental constant for Q14 fixed-point arithmetic.
    /// All Q14 values are actual_value × SCALE.
    pub const SCALE: f64 = 16384.0;

    /// Q14 scale factor as f32 for performance-critical conversions
    pub const SCALE_F32: f32 = 16384.0;

    /// Convert Q14 integer to floating point ratio
    ///
    /// # Examples
    /// ```rust
    /// # use neteq::statistics::q14;
    /// assert_eq!(q14::to_float(0), 0.0);
    /// assert_eq!(q14::to_float(8192), 0.5);
    /// assert_eq!(q14::to_float(16384), 1.0);
    /// ```
    #[inline]
    pub fn to_float(q14_value: u16) -> f64 {
        q14_value as f64 / SCALE
    }

    /// Convert Q14 integer to per-mille (‰) for UI display
    ///
    /// Per-mille (‰) = parts per thousand = 0.1%
    /// This matches WebRTC's statistics display format.
    ///
    /// # Examples
    /// ```rust
    /// # use neteq::statistics::q14;
    /// assert_eq!(q14::to_per_mille(0), 0.0);       // 0‰
    /// assert!((q14::to_per_mille(4096) - 250.0).abs() < 0.01); // ~250‰ (25%)
    /// assert!((q14::to_per_mille(8192) - 500.0).abs() < 0.01); // ~500‰ (50%)
    /// ```
    #[inline]
    pub fn to_per_mille(q14_value: u16) -> f32 {
        q14_value as f32 / (SCALE_F32 / 1000.0) // Equivalent to / 16.384
    }

    /// Convert floating point ratio to Q14 integer
    ///
    /// Clamps the input to valid Q14 range [0.0, 1.0] to prevent overflow.
    ///
    /// # Examples
    /// ```rust
    /// # use neteq::statistics::q14;
    /// assert_eq!(q14::from_float(0.0), 0);
    /// assert_eq!(q14::from_float(0.25), 4096);
    /// assert_eq!(q14::from_float(1.0), 16384);
    /// assert_eq!(q14::from_float(2.0), 16384);  // Clamped to max
    /// ```
    #[inline]
    pub fn from_float(ratio: f64) -> u16 {
        (ratio * SCALE).clamp(0.0, SCALE) as u16
    }

    /// Convert per-mille (‰) back to Q14 integer
    ///
    /// Useful for testing or when receiving per-mille values that need
    /// to be stored in Q14 format.
    ///
    /// # Examples
    /// ```rust
    /// # use neteq::statistics::q14;
    /// assert_eq!(q14::from_per_mille(0.0), 0);
    /// assert_eq!(q14::from_per_mille(250.0), 4096);
    /// assert_eq!(q14::from_per_mille(500.0), 8192);
    /// ```
    #[inline]
    pub fn from_per_mille(per_mille: f32) -> u16 {
        from_float((per_mille / 1000.0) as f64)
    }
}

use crate::neteq::Operation;
use serde::{Deserialize, Serialize};
use web_time::{Duration, Instant};

/// Decode operation counters for real-time monitoring
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct OperationCounters {
    /// Normal decode operations per second
    pub normal_per_sec: f32,
    /// Expand operations per second (packet loss concealment)
    pub expand_per_sec: f32,
    /// Accelerate operations per second (time compression)
    pub accelerate_per_sec: f32,
    /// Fast accelerate operations per second
    pub fast_accelerate_per_sec: f32,
    /// Preemptive expand operations per second (time expansion)
    pub preemptive_expand_per_sec: f32,
    /// Merge operations per second (blending)
    pub merge_per_sec: f32,
    /// Comfort noise operations per second
    pub comfort_noise_per_sec: f32,
    /// DTMF operations per second
    pub dtmf_per_sec: f32,
    /// Undefined/error operations per second
    pub undefined_per_sec: f32,
}

/// Network statistics similar to libWebRTC's NetEqNetworkStatistics
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NetworkStatistics {
    /// Current jitter buffer size in milliseconds
    pub current_buffer_size_ms: u16,
    /// Target buffer size in milliseconds
    pub preferred_buffer_size_ms: u16,
    /// Number of jitter peaks found
    pub jitter_peaks_found: u16,
    /// Fraction of audio data inserted through expansion (Q14 format)
    pub expand_rate: u16,
    /// Fraction of speech audio inserted through expansion (Q14 format)
    pub speech_expand_rate: u16,
    /// Fraction of data inserted through pre-emptive expansion (Q14 format)
    pub preemptive_rate: u16,
    /// Fraction of data removed through acceleration (Q14 format)
    pub accelerate_rate: u16,
    /// Statistics for packet waiting times
    pub mean_waiting_time_ms: i32,
    pub median_waiting_time_ms: i32,
    pub min_waiting_time_ms: i32,
    pub max_waiting_time_ms: i32,
    /// Packet reordering statistics
    pub reordered_packets: u32,
    pub total_packets_received: u32,
    pub reorder_rate_permyriad: u16, // Reordering rate in per-myriad (‰)
    pub max_reorder_distance: u16,   // Maximum sequence number distance for reordered packets
    /// Real-time decode operation counters
    pub operation_counters: OperationCounters,
}

/// Lifetime statistics that persist over the NetEQ lifetime
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LifetimeStatistics {
    /// Total samples received
    pub total_samples_received: u64,
    /// Total concealed samples
    pub concealed_samples: u64,
    /// Number of concealment events
    pub concealment_events: u64,
    /// Cumulative jitter buffer delay in milliseconds
    pub jitter_buffer_delay_ms: u64,
    /// Number of samples emitted from jitter buffer
    pub jitter_buffer_emitted_count: u64,
    /// Target delay accumulation
    pub jitter_buffer_target_delay_ms: u64,
    /// Samples inserted for deceleration
    pub inserted_samples_for_deceleration: u64,
    /// Samples removed for acceleration
    pub removed_samples_for_acceleration: u64,
    /// Silent concealed samples
    pub silent_concealed_samples: u64,
    /// Relative packet arrival delay accumulation
    pub relative_packet_arrival_delay_ms: u64,
    /// Number of packets received in jitter buffer
    pub jitter_buffer_packets_received: u64,
    /// Buffer flush events
    pub buffer_flushes: u64,
    /// Late packets discarded
    pub late_packets_discarded: u64,
}

/// Operations and internal state metrics
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct OperationStatistics {
    /// Cumulative preemptive samples
    pub preemptive_samples: u64,
    /// Cumulative accelerate samples
    pub accelerate_samples: u64,
    /// Buffer flush count
    pub packet_buffer_flushes: u64,
    /// Discarded primary packets
    pub discarded_primary_packets: u64,
    /// Last packet waiting time
    pub last_waiting_time_ms: u64,
    /// Current buffer size
    pub current_buffer_size_ms: u64,
    /// Current frame size
    pub current_frame_size_ms: u64,
    /// Next packet available flag
    pub next_packet_available: bool,
}

/// Statistics calculator and tracker
#[derive(Debug)]
pub struct StatisticsCalculator {
    network_stats: NetworkStatistics,
    lifetime_stats: LifetimeStatistics,
    operation_stats: OperationStatistics,
    start_time: Instant,
    waiting_times: Vec<i32>,
    _last_update: Instant,
    /// Total output samples for rate calculations
    total_output_samples: u64,
    /// Total expanded samples for rate calculations
    total_expanded_samples: u64,
    /// Operation counters for rolling window tracking
    operation_counts: [u32; 9], // One for each Operation variant
    /// Last time operation rates were calculated
    last_operation_update: Instant,
    /// Window duration for operation rate calculation (1 second)
    operation_window_duration: Duration,
}

impl Default for StatisticsCalculator {
    fn default() -> Self {
        Self::new()
    }
}

impl StatisticsCalculator {
    /// Create a new statistics calculator
    pub fn new() -> Self {
        let now = Instant::now();
        Self {
            network_stats: NetworkStatistics::default(),
            lifetime_stats: LifetimeStatistics::default(),
            operation_stats: OperationStatistics::default(),
            start_time: now,
            waiting_times: Vec::new(),
            _last_update: now,
            total_output_samples: 0,
            total_expanded_samples: 0,
            operation_counts: [0; 9],
            last_operation_update: now,
            operation_window_duration: Duration::from_secs(1),
        }
    }

    /// Update buffer size statistics
    pub fn update_buffer_size(&mut self, current_ms: u16, preferred_ms: u16) {
        self.network_stats.current_buffer_size_ms = current_ms;
        self.network_stats.preferred_buffer_size_ms = preferred_ms;
        self.operation_stats.current_buffer_size_ms = current_ms as u64;
    }

    /// Record a packet arrival and calculate waiting time
    pub fn packet_arrived(&mut self, arrival_delay_ms: i32) {
        self.lifetime_stats.jitter_buffer_packets_received += 1;
        self.waiting_times.push(arrival_delay_ms);

        // Keep only recent waiting times (last 100 packets)
        if self.waiting_times.len() > 100 {
            self.waiting_times.remove(0);
        }

        self.update_waiting_time_stats();
    }

    /// Record jitter buffer delay
    pub fn jitter_buffer_delay(&mut self, delay_ms: u64, emitted_samples: u64) {
        self.lifetime_stats.jitter_buffer_delay_ms += delay_ms;
        self.lifetime_stats.jitter_buffer_emitted_count += emitted_samples;
    }

    /// Record concealment event
    pub fn concealment_event(&mut self, concealed_samples: u64, is_silent: bool) {
        self.lifetime_stats.concealment_events += 1;
        self.lifetime_stats.concealed_samples += concealed_samples;

        if is_silent {
            self.lifetime_stats.silent_concealed_samples += concealed_samples;
        }
    }

    /// Record time-stretching operation
    pub fn time_stretch_operation(&mut self, operation: TimeStretchOperation, samples: u64) {
        // Track total output samples for rate calculations
        self.total_output_samples += samples;

        match operation {
            TimeStretchOperation::Accelerate => {
                self.lifetime_stats.removed_samples_for_acceleration += samples;
                self.operation_stats.accelerate_samples += samples;
                // Calculate cumulative accelerate rate: (removed_samples / total_output_samples) in Q14
                if self.total_output_samples > 0 {
                    let ratio = self.lifetime_stats.removed_samples_for_acceleration as f64
                        / self.total_output_samples as f64;
                    self.network_stats.accelerate_rate = q14::from_float(ratio);
                }
            }
            TimeStretchOperation::PreemptiveExpand => {
                self.lifetime_stats.inserted_samples_for_deceleration += samples;
                self.operation_stats.preemptive_samples += samples;
                // Calculate cumulative preemptive rate: (inserted_samples / total_output_samples) in Q14
                if self.total_output_samples > 0 {
                    let ratio = self.lifetime_stats.inserted_samples_for_deceleration as f64
                        / self.total_output_samples as f64;
                    self.network_stats.preemptive_rate = q14::from_float(ratio);
                }
            }
            TimeStretchOperation::Expand => {
                self.total_expanded_samples += samples;
                // Calculate cumulative expand rate: (expanded_samples / total_output_samples) in Q14
                if self.total_output_samples > 0 {
                    let ratio =
                        self.total_expanded_samples as f64 / self.total_output_samples as f64;
                    self.network_stats.expand_rate = q14::from_float(ratio);
                }
            }
        }
    }

    /// Record a decode operation for per-second tracking
    pub fn record_decode_operation(&mut self, operation: Operation) {
        let now = Instant::now();

        // Convert operation to array index
        let index = match operation {
            Operation::Normal => 0,
            Operation::Merge => 1,
            Operation::Expand => 2,
            Operation::ExpandStart => 3,
            Operation::ExpandEnd => 4,
            Operation::Accelerate => 5,
            Operation::FastAccelerate => 6,
            Operation::PreemptiveExpand => 7,
            Operation::TimeStretchBuffer => 8,
            Operation::ComfortNoise => 9,
            Operation::Dtmf => 10,
            Operation::Undefined => 11,
        };

        // Increment counter
        self.operation_counts[index] += 1;

        // Update operation counters per second if window has elapsed
        if now.duration_since(self.last_operation_update) >= self.operation_window_duration {
            self.update_operation_rates(now);
        }
    }

    /// Update operation rates (operations per second)
    fn update_operation_rates(&mut self, now: Instant) {
        let elapsed = now.duration_since(self.last_operation_update);
        let elapsed_secs = elapsed.as_secs_f32();

        if elapsed_secs > 0.0 {
            self.network_stats.operation_counters.normal_per_sec =
                self.operation_counts[0] as f32 / elapsed_secs;
            self.network_stats.operation_counters.merge_per_sec =
                self.operation_counts[1] as f32 / elapsed_secs;
            self.network_stats.operation_counters.expand_per_sec =
                self.operation_counts[2] as f32 / elapsed_secs;
            self.network_stats.operation_counters.accelerate_per_sec =
                self.operation_counts[3] as f32 / elapsed_secs;
            self.network_stats
                .operation_counters
                .fast_accelerate_per_sec = self.operation_counts[4] as f32 / elapsed_secs;
            self.network_stats
                .operation_counters
                .preemptive_expand_per_sec = self.operation_counts[5] as f32 / elapsed_secs;
            self.network_stats.operation_counters.comfort_noise_per_sec =
                self.operation_counts[6] as f32 / elapsed_secs;
            self.network_stats.operation_counters.dtmf_per_sec =
                self.operation_counts[7] as f32 / elapsed_secs;
            self.network_stats.operation_counters.undefined_per_sec =
                self.operation_counts[8] as f32 / elapsed_secs;

            // Reset counters for next window
            self.operation_counts.fill(0);
            self.last_operation_update = now;
        }
    }

    /// Record buffer flush event
    pub fn buffer_flush(&mut self) {
        self.lifetime_stats.buffer_flushes += 1;
        self.operation_stats.packet_buffer_flushes += 1;
    }

    /// Record discarded packet
    pub fn packet_discarded(&mut self, is_late: bool) {
        if is_late {
            self.lifetime_stats.late_packets_discarded += 1;
        }
        self.operation_stats.discarded_primary_packets += 1;
    }

    /// Record packet reordering event
    pub fn packet_reordered(&mut self, sequence_distance: u16) {
        self.network_stats.reordered_packets += 1;
        self.network_stats.total_packets_received += 1;

        // Update maximum reorder distance
        if sequence_distance > self.network_stats.max_reorder_distance {
            self.network_stats.max_reorder_distance = sequence_distance;
        }

        // Calculate reorder rate in per-myriad
        let rate = (self.network_stats.reordered_packets as f64
            / self.network_stats.total_packets_received as f64)
            * 10000.0;
        self.network_stats.reorder_rate_permyriad = rate as u16;
    }

    /// Record normal packet arrival (in order)
    pub fn packet_in_order(&mut self) {
        self.network_stats.total_packets_received += 1;

        // Recalculate reorder rate
        let rate = (self.network_stats.reordered_packets as f64
            / self.network_stats.total_packets_received as f64)
            * 10000.0;
        self.network_stats.reorder_rate_permyriad = rate as u16;
    }

    /// Get current network statistics
    pub fn network_statistics(&self) -> &NetworkStatistics {
        &self.network_stats
    }

    /// Get lifetime statistics
    pub fn lifetime_statistics(&self) -> &LifetimeStatistics {
        &self.lifetime_stats
    }

    /// Get operation statistics
    pub fn operation_statistics(&self) -> &OperationStatistics {
        &self.operation_stats
    }

    /// Get uptime duration
    pub fn uptime(&self) -> Duration {
        self.start_time.elapsed()
    }

    /// Reset all statistics
    pub fn reset(&mut self) {
        *self = Self::new();
    }

    fn update_waiting_time_stats(&mut self) {
        if self.waiting_times.is_empty() {
            return;
        }

        let mut sorted_times = self.waiting_times.clone();
        sorted_times.sort_unstable();

        self.network_stats.min_waiting_time_ms = *sorted_times.first().unwrap();
        self.network_stats.max_waiting_time_ms = *sorted_times.last().unwrap();

        let sum: i32 = sorted_times.iter().sum();
        self.network_stats.mean_waiting_time_ms = sum / sorted_times.len() as i32;

        let median_idx = sorted_times.len() / 2;
        self.network_stats.median_waiting_time_ms = sorted_times[median_idx];

        self.operation_stats.last_waiting_time_ms = *self.waiting_times.last().unwrap() as u64;
    }
}

/// Types of time-stretching operations
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TimeStretchOperation {
    Accelerate,
    PreemptiveExpand,
    Expand,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_statistics_calculator() {
        let mut calc = StatisticsCalculator::new();

        // Test buffer size update
        calc.update_buffer_size(100, 120);
        assert_eq!(calc.network_statistics().current_buffer_size_ms, 100);
        assert_eq!(calc.network_statistics().preferred_buffer_size_ms, 120);

        // Test packet arrival
        calc.packet_arrived(50);
        assert_eq!(calc.lifetime_statistics().jitter_buffer_packets_received, 1);
        assert_eq!(calc.network_statistics().mean_waiting_time_ms, 50);

        // Test concealment
        calc.concealment_event(160, false);
        assert_eq!(calc.lifetime_statistics().concealment_events, 1);
        assert_eq!(calc.lifetime_statistics().concealed_samples, 160);

        // Test time stretch
        calc.time_stretch_operation(TimeStretchOperation::Accelerate, 80);
        assert_eq!(
            calc.lifetime_statistics().removed_samples_for_acceleration,
            80
        );

        // Test buffer flush
        calc.buffer_flush();
        assert_eq!(calc.lifetime_statistics().buffer_flushes, 1);
    }

    #[test]
    fn test_waiting_time_statistics() {
        let mut calc = StatisticsCalculator::new();

        // Add several waiting times
        calc.packet_arrived(10);
        calc.packet_arrived(20);
        calc.packet_arrived(30);
        calc.packet_arrived(15);
        calc.packet_arrived(25);

        let stats = calc.network_statistics();
        assert_eq!(stats.min_waiting_time_ms, 10);
        assert_eq!(stats.max_waiting_time_ms, 30);
        assert_eq!(stats.mean_waiting_time_ms, 20); // (10+20+30+15+25)/5 = 20
        assert_eq!(stats.median_waiting_time_ms, 20); // Middle value when sorted
    }

    #[test]
    fn test_expand_rate_calculation_fix() {
        let mut stats = StatisticsCalculator::new();

        // Simulate normal operation: 10 frames of normal audio (160 samples each)
        for _ in 0..10 {
            stats.time_stretch_operation(TimeStretchOperation::Accelerate, 160);
            // Not expand, just to track output
        }

        // Reset accelerate stats to focus on expand rate
        stats.network_stats.accelerate_rate = 0;
        stats.lifetime_stats.removed_samples_for_acceleration = 0;

        // Simulate 2 expand operations (160 samples each)
        stats.time_stretch_operation(TimeStretchOperation::Expand, 160);
        stats.time_stretch_operation(TimeStretchOperation::Expand, 160);

        // Total: 1600 + 320 = 1920 output samples
        // Expanded: 320 samples
        // Expected rate: (320 / 1920) * 16384 = 2730.67 ≈ 2731 (Q14)
        // Expected UI display: 2731 / 16.384 = 166.7‰

        let expected_q14 = ((320.0 / 1920.0) * 16384.0) as u16;
        assert_eq!(stats.network_stats.expand_rate, expected_q14);

        // The UI should now show ~166.7‰ instead of 7864.0
        let ui_display_rate = stats.network_stats.expand_rate as f32 / 16.384;
        assert!(
            (ui_display_rate - 166.7).abs() < 1.0,
            "Expected ~166.7‰, got {ui_display_rate:.1}‰"
        );

        println!("✅ Expand rate calculation fix verified:");
        println!("   Q14 value: {}", stats.network_stats.expand_rate);
        println!("   UI display: {ui_display_rate:.1}‰ (was showing 7864.0)");
    }

    #[test]
    fn test_expand_rate_no_longer_spikes_to_7864() {
        let mut stats = StatisticsCalculator::new();

        // Simulate the scenario: peer starts talking → buffer empty → expansion
        // This used to cause expand_rate = 7864.0

        // First, some normal operation
        for _ in 0..5 {
            stats.time_stretch_operation(TimeStretchOperation::Accelerate, 160);
        }

        // Reset to focus on expansion
        stats.network_stats.accelerate_rate = 0;
        stats.lifetime_stats.removed_samples_for_acceleration = 0;

        // Expansion happens (this used to set expand_rate = 7864.0)
        stats.time_stretch_operation(TimeStretchOperation::Expand, 480); // 30ms at 16kHz

        // With the fix, the rate should be reasonable
        let ui_rate = stats.network_stats.expand_rate as f32 / 16.384;

        // Should be much less than the old broken value of 7864.0
        assert!(
            ui_rate < 1000.0,
            "Expand rate still too high: {ui_rate:.1}‰ (was 7864.0)"
        );

        // Should be approximately: (480 / (800 + 480)) * 1000 = 375‰
        let expected_rate = (480.0 / 1280.0) * 1000.0; // ≈ 375‰
        assert!(
            (ui_rate - expected_rate).abs() < 50.0,
            "Expected ~{expected_rate:.1}‰, got {ui_rate:.1}‰"
        );

        println!("🚀 Bug fix verified: expand rate no longer spikes to 7864.0");
        println!("   Old broken value: 7864.0‰");
        println!("   New correct value: {ui_rate:.1}");
    }

    #[test]
    fn test_q14_documentation_examples() {
        use super::q14;

        println!("🧮 Testing Q14 documentation examples...");

        // Test basic conversions from documentation table
        assert_eq!(q14::to_float(0), 0.0);
        assert_eq!(q14::to_float(4096), 0.25);
        assert_eq!(q14::to_float(8192), 0.5);
        assert_eq!(q14::to_float(16384), 1.0);

        // Test per-mille conversions (use tolerance for floating point)
        assert_eq!(q14::to_per_mille(0), 0.0);
        assert!((q14::to_per_mille(4096) - 250.0).abs() < 0.01);
        assert!((q14::to_per_mille(8192) - 500.0).abs() < 0.01);
        assert!((q14::to_per_mille(16384) - 1000.0).abs() < 0.01);

        // Test round-trip conversions
        assert_eq!(q14::from_float(0.0), 0);
        assert_eq!(q14::from_float(0.25), 4096);
        assert_eq!(q14::from_float(0.5), 8192);
        assert_eq!(q14::from_float(1.0), 16384);

        // Test clamping
        assert_eq!(q14::from_float(2.0), 16384); // Clamped to max
        assert_eq!(q14::from_float(-1.0), 0); // Clamped to min

        // Test per-mille round-trip
        assert_eq!(q14::from_per_mille(0.0), 0);
        assert_eq!(q14::from_per_mille(250.0), 4096);
        assert_eq!(q14::from_per_mille(500.0), 8192);
        assert_eq!(q14::from_per_mille(1000.0), 16384);

        // Test the worked example from documentation
        let ratio = 320.0 / 1280.0; // 0.25
        let q14_value = q14::from_float(ratio); // Should be 4096
        let display_rate = q14::to_per_mille(q14_value); // Should be 250.0‰

        assert_eq!(q14_value, 4096);
        assert!((display_rate - 250.0).abs() < 0.01);

        println!("✅ All Q14 documentation examples validated!");

        // Demonstrate the precision of Q14
        let precision = 1.0 / q14::SCALE;
        println!("📏 Q14 precision: {precision:.6} (~0.00006)");

        // Show the difference between our approach and WebRTC's magic numbers
        let webrtc_conversion = 4096.0 / 16384.0; // WebRTC style
        let our_conversion = q14::to_float(4096); // Our style
        assert_eq!(webrtc_conversion, our_conversion);
        println!("🎯 WebRTC compatibility: {webrtc_conversion} == {our_conversion}");
    }

    #[test]
    fn test_q14_real_world_scenarios() {
        use super::q14;

        println!("🌍 Testing real-world Q14 scenarios...");

        // Scenario 1: 15% audio expansion (realistic NetEQ operation)
        let expanded_samples = 150_u64;
        let total_samples = 1000_u64;
        let ratio = expanded_samples as f64 / total_samples as f64;
        let q14_rate = q14::from_float(ratio);
        let ui_display = q14::to_per_mille(q14_rate);

        println!("📊 15% expansion: {expanded_samples} samples / {total_samples} total");
        println!("   Ratio: {ratio:.3}");
        println!("   Q14:   {q14_rate}");
        println!("   UI:    {ui_display:.1}");

        assert_eq!(ratio, 0.15);
        assert_eq!(q14_rate, 2457); // (0.15 * 16384) = 2457.6 ≈ 2457
        assert!((ui_display - 150.0).abs() < 0.1);

        // Scenario 2: The original bug case (480ms expansion in 1000ms)
        let bug_scenario_ratio = 480.0 / 1000.0;
        let bug_q14 = q14::from_float(bug_scenario_ratio);
        let bug_display = q14::to_per_mille(bug_q14);

        println!("🐛 Original bug scenario:");
        println!(
            "   WebRTC raw magic: {:.1} (broken)",
            bug_scenario_ratio * 16384.0
        );
        println!("   Our Q14 value:    {bug_q14}");
        println!("   Our UI display:   {bug_display:.1}‰ (correct)");

        assert_eq!(bug_q14, 7864); // Correct Q14 encoding
                                   // Note: due to precision, 7864/16.384 = 479.98..., which is very close to 480
        assert!((bug_display - 480.0).abs() < 0.1); // Correct per-mille display (within 0.1‰)

        // Demonstrate that raw 7864 would be wrong to display directly
        assert_ne!(7864.0, bug_display); // Proves the original bug

        println!("✅ Real-world scenarios validated!");
    }
}