use std::{collections::VecDeque, fs::File};

use audioadapter_buffers::direct::InterleavedSlice;
use lunar_lib::trace;
use rubato::{Fft, FixedSync, Resampler, ResamplerConstructionError};
use selene_core::container::ContainerFormat;
use symphonia::{
    core::{
        audio::SampleBuffer,
        codecs::{CODEC_TYPE_NULL, Decoder, DecoderOptions},
        errors::Error as SymphoniaError,
        formats::{FormatOptions, FormatReader, SeekMode, SeekTo},
        io::MediaSourceStream,
        units::Time,
    },
    default::formats::{FlacReader, OggReader, WavReader},
};

use crate::{daemon::DaemonError, playlist::TracklistTrack};

pub struct OpenedDecoder {
    pub decoded_from: TracklistTrack,

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

    pub for_stream: symphonia::core::formats::Track,
    pub format_reader: Box<dyn FormatReader>,
    pub decoder: Box<dyn Decoder>,
    pub current_frame: usize,

    pub at_eof: bool,
}

impl OpenedDecoder {
    pub fn time(&self) -> f64 {
        let sample_rate = f64::from(self.for_stream.codec_params.sample_rate.unwrap());
        self.current_frame as f64 / sample_rate
    }

    pub fn decode_next_packet(&mut self) -> Result<VecDeque<f32>, DaemonError> {
        loop {
            let packet = match self.format_reader.next_packet() {
                Ok(packet) => packet,
                Err(SymphoniaError::ResetRequired) => {
                    self.decoder.reset();
                    continue;
                }
                Err(SymphoniaError::IoError(err))
                    if matches!(err.kind(), std::io::ErrorKind::UnexpectedEof) =>
                {
                    let mut samples = self.resample_buffer.drain(..).collect::<Vec<_>>();
                    let channel_count = self.resampler.nbr_channels();
                    let frame_count = self.resampler.input_frames_next();

                    let needed_samples = frame_count * channel_count;
                    samples.resize(needed_samples, 0.0);

                    let buffer_in = InterleavedSlice::new(&samples, channel_count, frame_count)
                        .expect("Sampler buffer contained less capacity then expected");

                    let resampled = self.resampler.process(&buffer_in, 0, None).expect(
                        "Resampler expects input and output to have the same number of channels",
                    );

                    self.at_eof = true;
                    return Ok(resampled.take_data().into());
                }
                Err(err) => return Err(DaemonError::Symphonia(err)),
            };

            while !self.format_reader.metadata().is_latest() {
                self.format_reader.metadata().pop();
            }

            if packet.track_id() != self.for_stream.id {
                continue;
            }

            let decoded = match self.decoder.decode(&packet) {
                Ok(decoded) => decoded,
                Err(SymphoniaError::DecodeError(_)) => continue,
                Err(SymphoniaError::IoError(_)) => continue,
                Err(err) => return Err(DaemonError::Symphonia(err)),
            };

            let frame_count = decoded.frames();
            let channel_count = decoded.spec().channels.count();

            self.current_frame += frame_count;

            let mut sample_buf = SampleBuffer::<f32>::new(frame_count as u64, *decoded.spec());
            sample_buf.copy_interleaved_ref(decoded);

            self.resample_buffer.extend(sample_buf.samples());
            let mut output = Vec::new();

            let chunk_frames = self.resampler.input_frames_next();
            while self.resample_buffer.len() >= chunk_frames * channel_count {
                let samples: Vec<f32> = self
                    .resample_buffer
                    .drain(..chunk_frames * channel_count)
                    .collect();

                let buffer_in = InterleavedSlice::new(&samples, channel_count, chunk_frames)
                    .expect("Sampler buffer contained less capacity then expected");

                let resampled = self.resampler.process(&buffer_in, 0, None).expect(
                    "Resampler expects input and output to have the same number of channels",
                );

                output.extend(resampled.take_data());
            }

            if !output.is_empty() {
                return Ok(output.into());
            }

            continue;
        }
    }

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

        let timestamp = Time::from(seconds);

        let seeked_to = self.format_reader.seek(
            SeekMode::Accurate,
            SeekTo::Time {
                time: timestamp,
                track_id: Some(self.for_stream.id),
            },
        )?;

        self.decoder.reset();

        let time_base = self.for_stream.codec_params.time_base.unwrap();
        let raw_time = time_base.calc_time(seeked_to.actual_ts);
        let time = raw_time.seconds as f64 + raw_time.frac;

        let sample_rate = self.for_stream.codec_params.sample_rate.unwrap();
        self.current_frame = (time * sample_rate as f64) as usize;

        Ok(time)
    }
}

impl OpenedDecoder {
    pub fn from_tracklist_track(
        playable: TracklistTrack,
        output_sample_rate: usize,
    ) -> Result<Self, DaemonError> {
        let container = playable
            .playable
            .container()
            .expect("Engine should handle tracks without containers");

        trace!(
            "Opening a new decoder from track: '{}'",
            container.path().display()
        );

        let file = File::open(container.path())?;
        let mss = MediaSourceStream::new(Box::new(file), Default::default());

        let fmt_opts = FormatOptions {
            enable_gapless: true,
            ..Default::default()
        };

        let format_reader: Box<dyn FormatReader> = match container.container() {
            ContainerFormat::Flac => Box::new(FlacReader::try_new(mss, &fmt_opts)?),
            ContainerFormat::Mp3 => {
                todo!("Import mp3")
            }
            ContainerFormat::Ogg => Box::new(OggReader::try_new(mss, &fmt_opts)?),
            ContainerFormat::Wav => Box::new(WavReader::try_new(mss, &fmt_opts)?),
        };

        let track = format_reader
            .tracks()
            .iter()
            .find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
            .expect("Playable file does not have any supported codecs");
        let dec_opts: DecoderOptions = Default::default();
        let decoder = symphonia::default::get_codecs().make(&track.codec_params, &dec_opts)?;

        let resampler = new_resampler(
            track.codec_params.sample_rate.unwrap() as usize,
            output_sample_rate,
            track.codec_params.channels.unwrap().count(),
        )?;

        Ok(OpenedDecoder {
            for_stream: track.clone(),
            format_reader,
            decoder,
            current_frame: 0,
            decoded_from: playable,
            resampler,
            resample_buffer: VecDeque::with_capacity(1024),
            at_eof: 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,
    )
}
