use std::{collections::VecDeque, num::NonZero, time::SystemTime};

use audioadapter_buffers::direct::InterleavedSlice;
use rubato::{Fft, FixedSync, Resampler, ResamplerConstructionError};
use selene_core::{
    library::loudnorm::LoudnormAnalysis, symphonia_helpers::raw_decoder::RawDecoder,
};

use symphonia::core::{
    audio::{Audio, AudioBuffer},
    errors::{Error as SymphoniaError, SeekErrorKind},
    formats::{SeekMode, SeekTo},
    units::{Time, TimeBase},
};

use crate::{player::PlayerError, playlist::PlayingTrack};

pub struct OpenedDecoder {
    pub decoded_from: PlayingTrack,
    pub started_at: Option<SystemTime>,
    gain: f64,

    inner: RawDecoder,

    resampler: Fft<f32>,
    resample_buffer: VecDeque<f32>,

    pub current_frame: usize,
    pub decoded_frames: usize,

    pub at_eof: bool,
    pub sent_scrobble: bool,
}

impl OpenedDecoder {
    #[must_use]
    /// Returns the current time within the track
    pub fn time(&self) -> f64 {
        let sample_rate = f64::from(self.inner.stream.codec_params.sample_rate);
        self.current_frame as f64 / sample_rate
    }

    #[must_use]
    /// Returns the amount of 'time' that has been decoded total, regardless of seeking
    pub fn decoded_time(&self) -> f64 {
        let sample_rate = f64::from(self.inner.stream.codec_params.sample_rate);
        self.decoded_frames as f64 / sample_rate
    }

    #[must_use]
    pub fn start_time(&self) -> Option<u64> {
        let time = self
            .started_at?
            .duration_since(SystemTime::UNIX_EPOCH)
            .expect("Time went backwards")
            .as_secs();

        Some(time)
    }

    pub(crate) fn started(&mut self) -> bool {
        if self.started_at.is_none() {
            self.started_at = Some(SystemTime::now());
            return true;
        }

        false
    }

    pub fn decode_next_packet(&mut self) -> Result<VecDeque<f32>, PlayerError> {
        let channel_count = self.resampler.nbr_channels();

        loop {
            let next_frame_size = self.resampler.input_frames_next();
            let chunk_size = next_frame_size * channel_count;

            if self.resample_buffer.len() >= chunk_size {
                let mut resampled = resample(
                    &mut self.resampler,
                    &self.resample_buffer.make_contiguous()[..chunk_size],
                    channel_count,
                    next_frame_size,
                );

                drop(self.resample_buffer.drain(..chunk_size));

                for sample in &mut resampled {
                    *sample *= self.gain as f32;
                }
                return Ok(resampled.into());
            }

            let Some(packet) = self.inner.decode_next_packet()? else {
                let mut samples = std::mem::take(&mut self.resample_buffer);
                samples.resize(chunk_size, 0.0);

                let mut resampled = resample(
                    &mut self.resampler,
                    samples.make_contiguous(),
                    channel_count,
                    next_frame_size,
                );

                self.at_eof = true;

                for sample in &mut resampled {
                    *sample *= self.gain as f32;
                }
                return Ok(resampled.into());
            };

            let mut audio_buffer = AudioBuffer::<f32>::new(packet.spec().clone(), packet.frames());
            audio_buffer.render_silence(Some(packet.frames()));
            packet.copy_to(&mut audio_buffer);

            self.current_frame += packet.frames();
            self.decoded_frames += packet.frames();

            self.resample_buffer.extend(audio_buffer.iter_interleaved());
        }
    }

    pub fn seek(&mut self, seconds: f64, increment: bool) -> Result<f64, SymphoniaError> {
        let seconds = if increment {
            (self.time() + seconds).max(0.0)
        } else {
            seconds
        }
        .max(0.0);

        let timestamp = Time::try_from_secs_f64(seconds).unwrap();

        let seeked_to = match self.inner.format_reader.seek(
            SeekMode::Accurate,
            SeekTo::Time {
                time: timestamp,
                track_id: Some(self.inner.stream.id),
            },
        ) {
            Ok(seeked_to) => seeked_to,
            Err(SymphoniaError::SeekError(SeekErrorKind::OutOfRange)) => {
                self.at_eof = true;
                return Ok(self.inner.stream.duration());
            }
            Err(other) => return Err(other),
        };

        self.inner.decoder.reset();
        self.at_eof = false;

        let time_base = self
            .inner
            .stream
            .time_base
            .map(|(n, d)| TimeBase {
                numer: NonZero::new(n).unwrap(),
                denom: NonZero::new(d).unwrap(),
            })
            .unwrap();

        let raw_time = time_base.calc_time(seeked_to.actual_ts).unwrap();
        let time = raw_time.as_secs_f64();

        let sample_rate = self.inner.stream.codec_params.sample_rate;
        self.current_frame = (time * f64::from(sample_rate)) as usize;

        Ok(time)
    }
}

impl OpenedDecoder {
    pub fn new(playable: PlayingTrack, output_sample_rate: usize) -> Result<Self, PlayerError> {
        let container = playable.source.container();

        let inner = RawDecoder::from_container(container, 64 * 1024)?;

        let resampler = new_resampler(
            inner.stream.codec_params.sample_rate as usize,
            output_sample_rate,
            inner.stream.codec_params.channels,
        )?;

        let resample_buffer =
            VecDeque::with_capacity(resampler.input_frames_max() * resampler.nbr_channels());

        let gain = playable
            .source
            .loudnorm_analysis()
            .map_or(1.0, LoudnormAnalysis::calculated_gain);

        Ok(OpenedDecoder {
            inner,
            gain,
            current_frame: 0,
            decoded_frames: 0,
            started_at: None,
            decoded_from: playable,
            resampler,
            resample_buffer,
            at_eof: false,
            sent_scrobble: false,
        })
    }
}

fn new_resampler(
    input_sample_rate: usize,
    output_sample_rate: usize,
    channel_count: usize,
) -> Result<Fft<f32>, ResamplerConstructionError> {
    Fft::new(
        input_sample_rate,
        output_sample_rate,
        1024,
        1,
        channel_count,
        FixedSync::Both,
    )
}

fn resample(
    resampler: &mut Fft<f32>,
    samples: &[f32],
    channel_count: usize,
    frame_size: usize,
) -> Vec<f32> {
    let buffer_in = InterleavedSlice::new(samples, channel_count, frame_size)
        .expect("Sampler buffer contained less capacity then expected");

    resampler
        .process(&buffer_in, 0, None)
        .expect("Resampler expects input and output to have the same number of channels")
        .take_data()
}
