#![warn(missing_docs)]
#![doc = include_str!("../README.md")]

use std::fmt::Display;
use std::io::Read;
use binread::{BinRead, BinReaderExt};
use chrono::DateTime;
use num::Complex;

#[derive(BinRead, Debug, PartialEq, Eq)]
#[br(repr(u8))]
/// Enum representing possible sample formats
pub enum SampleFormat {
    /// Each sample consist of two `f32`s
    Complex64 = 1,
    /// Each sample consist of two `u8`s
    Uint8 = 2,
    /// Each sample consist of two `i16`s
    Int16 = 3,
    /// Each sample consist of two `i8`s
    Int8 = 4,
}

#[derive(BinRead, Debug, PartialEq, Eq)]
#[br(repr(u8))]
/// Defines possible endianness of the capture
pub enum Endianness {
    /// Little endian
    Little = 0,
    /// Big endian
    Big = 1,
}

#[derive(BinRead, Debug, PartialEq)]
#[br(magic = b"RFCAP1")]
/// Structure representing a parsed `rfcap` file header
pub struct CaptureHeader {
    /// Number of nanoseconds since the Unix Epoch when this recording was done
    pub capture_time: i64,
    /// Center Frequency of the capture, in Hz
    pub center_frequency: f64,
    /// Number of IQ samples per second this capture was taken at. Each IQ sample comprises the real and imaginary sample
    pub sample_rate: u32,
    /// Sample format
    pub sample_format: SampleFormat,
    /// In order to retain compatibility with an earlier rfcap version, Little Endian files are denoted with a 0, rather than a 1
    pub endianness: Endianness,
    /// Unused
    pub reserved: [u8; 20],
}

/// Structure representing a capture file
pub struct Capture {
    /// Capture header
    pub header: CaptureHeader,
    #[allow(unused)]
    reader: Box<dyn Read>,
}

impl Capture {
    /// Create an instance from a reader
    pub fn from_reader(mut reader: impl Read + BinReaderExt + 'static) -> Result<Capture, binread::Error> {
        let header: CaptureHeader = reader.read_ne()?;
        Ok(Self {
            header,
            reader: Box::new(reader),
        })
    }

    /// Get the next sample from this capture
    pub fn next_sample(&mut self) -> std::io::Result<Option<Complex<f32>>> {
        // match over self sample format and determine the size of a single complex number inside the file,
        // then read that exact amount of bytes and feed them to [`iq-reader`]
        let bytes = match self.header.sample_format {
            SampleFormat::Uint8 => 2,
            SampleFormat::Int8 => 2,
            SampleFormat::Int16 => 4,
            SampleFormat::Complex64 => 8,
        } * 2; // multiply by two becAuse there's two separate values for a single complex number
        let mut buf = vec![0; bytes];
        match self.reader.read_exact(&mut buf) {
            Ok(()) => {}
            Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
                return Ok(None);
            }
            Err(e) => return Err(e),
        }

        let sample = match self.header.sample_format {
            SampleFormat::Uint8 => {
                iq_reader::samples::normalize_u8([buf[0], buf[1]])
            }
            SampleFormat::Int8 => {
                iq_reader::samples::normalize_i8([buf[0], buf[1]])
            }
            SampleFormat::Int16 => {
                iq_reader::samples::normalize_i16([buf[0], buf[1], buf[2], buf[3]])
            }
            SampleFormat::Complex64 => {
                let re = f32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
                let im = f32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]);

                Complex { re, im }
            }
        };

        Ok(Some(sample))
    }
}

impl Display for CaptureHeader {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f,
            "Capture: {}, center_frequency={}, sample_rate={:?}, sample_format={:?}, endianness={:?}",
            DateTime::from_timestamp_nanos(self.capture_time),
            self.center_frequency,
            self.sample_rate,
            self.sample_format,
            self.endianness
        )
    }
}

/// Exports every type needed for work
pub mod prelude {
    pub use super::*;
    pub use std::fs::File;
    pub use binread::BinReaderExt;
    pub use num::Complex;
}

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

    #[test]
    fn read_capture_header() {
        let mut reader = File::open("../assets/10hz_1024sps_c64.rfcap").unwrap();
        let capture: CaptureHeader = reader.read_ne().unwrap();
        println!("{}", capture);
        assert_eq!(capture.capture_time, -6795364578871345152);
        assert_eq!(capture.sample_format, SampleFormat::Complex64);
    }

    #[test]
    fn read_capture_fully() {
        let reader = File::open("../assets/10hz_1024sps_c64.rfcap").unwrap();
        let mut capture: Capture = Capture::from_reader(reader).unwrap();
        let sample = capture.next_sample().unwrap().unwrap();
        println!("{:?}", sample);
        assert_eq!(sample, Complex { re: 0.9981181, im: 0.061320737 });
    }
}
