// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright © 2024 Mike Robeson [dijksterhuis]

//! Wav I/O handler (you should usually be using [`AudioFile`] by default).
//!
//! Only use this when [`AudioFile`] doesn't suit your needs, e.g. you
//! need to manually change the WAV specification data.
//!
//! Uses the [`hound`] crate.

use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::Path;

use hound::{self, SampleFormat, WavReader, WavSpec, WavWriter};
use log::trace;
use thiserror::Error;

use super::{AudioErrors, AudioFile, R};

/// Errors for [`WavFile`]s
#[derive(Debug, Error)]
pub enum WavErrors {
    /// Some other File I/O error
    #[error("File I/O Error: {0} (std::io::Error)")]
    FileIo(#[from] std::io::Error),
    #[error("Hound library error: {0}")]
    HoundLib(#[from] hound::Error),
    #[error("Only signed PCM WAV files are supported")]
    SampleFormat,
    #[error("Only 16/24-bit signed PCM WAV files are supported")]
    BitDepth,
    #[error("Only 44.1kHz WAV files are supported")]
    SampleRate,
    #[error("Only mono and stereo WAV files are supported")]
    ChannelCount,
}

/// Audio data which can be written as a WAV file
#[derive(PartialEq, Debug, Clone)]
pub struct WavFile {
    /// specification struct for the Wav file from the [`hound`] crate
    pub spec: WavSpec,
    /// Number of audio samples in the Wav file.
    pub len: u32,
    /// Audio samples
    pub samples: Vec<f32>,
}

// /// Is sample data in the WAV file Float or Int.
// enum HoundSampleFormat {
//     Float,
//     Int,
// }
//
// #[allow(clippy::from_over_into)]
// impl Into<SampleFormat> for HoundSampleFormat {
//     fn into(self) -> SampleFormat {
//         match self {
//             HoundSampleFormat::Float => SampleFormat::Float,
//             HoundSampleFormat::Int => SampleFormat::Int,
//         }
//     }
// }
//
//
// #[allow(clippy::from_over_into)]
// impl Into<SampleFormat> for &HoundSampleFormat {
//     fn into(self) -> SampleFormat {
//         match self {
//             HoundSampleFormat::Float => SampleFormat::Float,
//             HoundSampleFormat::Int => SampleFormat::Int,
//         }
//     }
// }

impl From<crate::audio::SampleFormat> for hound::SampleFormat {
    fn from(value: crate::audio::SampleFormat) -> hound::SampleFormat {
        match value {
            crate::audio::SampleFormat::Unsupported => SampleFormat::Float,
            crate::audio::SampleFormat::Int => SampleFormat::Int,
        }
    }
}

fn read_wav_i16_samples(reader: &mut WavReader<BufReader<File>>) -> R<Vec<f32>> {
    let mut buf = Vec::<f32>::new();
    for sample in reader.samples::<i16>() {
        buf.push(super::i16_to_f32(
            sample.map_err(WavErrors::from).map_err(AudioErrors::from)?,
        ))
    }
    Ok(buf)
}

fn read_wav_i24_samples(reader: &mut WavReader<BufReader<File>>) -> R<Vec<f32>> {
    let mut buf = Vec::<f32>::new();
    for sample in reader.samples::<i32>() {
        buf.push(super::i24_to_f32(
            sample.map_err(WavErrors::from).map_err(AudioErrors::from)?,
        ))
    }
    Ok(buf)
}

fn write_wav_16_bit(spec: &WavSpec, samples: &[f32], buf: &mut BufWriter<File>) -> R<()> {
    let mut writer = WavWriter::new(buf, *spec)
        .map_err(WavErrors::from)
        .map_err(AudioErrors::from)?;

    for sample in samples.iter() {
        writer
            .write_sample(super::f32_to_i16(*sample))
            .map_err(WavErrors::from)
            .map_err(AudioErrors::from)?;
    }

    writer
        .finalize()
        .map_err(WavErrors::from)
        .map_err(AudioErrors::from)?;

    Ok(())
}

fn write_wav_24_bit(spec: &WavSpec, samples: &[f32], buf: &mut BufWriter<File>) -> R<()> {
    let mut writer = WavWriter::new(buf, *spec)
        .map_err(WavErrors::from)
        .map_err(AudioErrors::from)?;

    for sample in samples.iter() {
        writer
            .write_sample(super::f32_to_i24(*sample))
            .map_err(WavErrors::from)
            .map_err(AudioErrors::from)?;
    }

    writer
        .finalize()
        .map_err(WavErrors::from)
        .map_err(AudioErrors::from)?;

    Ok(())
}

impl From<AudioFile> for WavFile {
    fn from(value: AudioFile) -> Self {
        Self {
            spec: WavSpec {
                channels: value.spec.channels,
                sample_rate: value.spec.sample_rate,
                bits_per_sample: value.spec.bit_depth,
                sample_format: value.spec.format.into(),
            },
            len: value.len,
            samples: value.samples,
        }
    }
}

impl WavFile {
    /// Open a wav file into a read buffer
    pub(crate) fn open_read_buf(path: &Path) -> hound::Result<WavReader<BufReader<File>>> {
        trace!("Opening WAV file: path={path:#?}");
        WavReader::open(path)
    }

    /// Read the hound WavSpec for an opened wav file buffer
    pub(crate) fn get_spec(reader: &mut WavReader<BufReader<File>>) -> R<WavSpec> {
        trace!("Reading WAV reader spec.");
        let spec = WavReader::spec(reader);
        Ok(spec)
    }

    /// Read a WAV file located at `path`.
    pub fn read(path: &Path) -> R<Self> {
        trace!("Reading WAV file from path: {path:#?}");
        let mut reader = WavFile::open_read_buf(path)
            .map_err(WavErrors::from)
            .map_err(AudioErrors::from)?;

        trace!("Reading WAV Spec: path={path:#?}");
        let spec = WavFile::get_spec(&mut reader)?;

        if !super::ALLOWED_SAMPLE_RATES.contains(&spec.sample_rate) {
            return Err(AudioErrors::Wav(WavErrors::SampleRate).into());
        }

        if !super::ALLOWED_CHANNELS.contains(&spec.channels) {
            return Err(AudioErrors::Wav(WavErrors::ChannelCount).into());
        }

        // TODO: Factor out into a `get_samples` method a la AiffFile
        trace!("Reading WAV Samples: path={path:#?}");
        let samples = match spec.sample_format {
            SampleFormat::Int => match spec.bits_per_sample {
                16 => read_wav_i16_samples(&mut reader),
                24 => read_wav_i24_samples(&mut reader),
                _ => Err(AudioErrors::Wav(WavErrors::BitDepth).into()),
            },
            SampleFormat::Float => Err(AudioErrors::Wav(WavErrors::SampleFormat).into()),
        }?;

        trace!("Read new WAV file: path={path:#?}");
        Ok(WavFile {
            samples: samples.clone(),
            len: samples.len() as u32 / spec.channels as u32,
            spec,
        })
    }

    /// Write data to a WAV file at `path`.
    pub fn write(&self, path: &Path) -> R<()> {
        trace!("Writing WAV data to file: path={path:#?}");
        let file = File::create(path)?;
        let mut buf_writer = BufWriter::new(file);

        match self.spec.bits_per_sample {
            16 => {
                trace!("Writing 16-bit WAV: path={path:#?}");
                write_wav_16_bit(&self.spec, &self.samples, &mut buf_writer)
            }
            24 => {
                trace!("Writing 24-bit WAV: path={path:#?}");
                write_wav_24_bit(&self.spec, &self.samples, &mut buf_writer)
            }
            _ => Err(AudioErrors::Wav(WavErrors::BitDepth).into()),
        }
    }
}
