use std::path::PathBuf;

use id3::TagLike;

use crate::types::{AudioFileInfo, ReplayGainData, ReplayGainRawData, Tags};

use super::{AudioContainer, AudioContainerFormat, AudioFormatError, BoxedError};

pub struct ID3AudioFormat {
    container_type: AudioContainer,
    id3_tags: id3::Tag,
    path: Box<PathBuf>,
}

impl AudioContainerFormat for ID3AudioFormat {
    fn get_tags(&self, allow_missing: bool) -> Result<Tags, BoxedError> {
        let title = self.id3_tags.title();
        let artist = self.id3_tags.artist();

        if !allow_missing {
            if title.is_none() {
                return Err(Box::new(AudioFormatError::MissingTitle));
            }
            if artist.is_none() {
                return Err(Box::new(AudioFormatError::MissingArtist));
            }
        }

        Ok(Tags {
            title: String::from(title.unwrap()),
            artist: String::from(artist.unwrap()),
        })
    }

    fn get_replaygain_data(&self) -> Option<ReplayGainData> {
        let frames = self.id3_tags.frames();

        let mut contains_replaygain_tags = false;
        let mut replaygain_data = ReplayGainData {
            track_gain: "".to_string(),
            track_peak: "".to_string(),
        };

        for frame in frames {
            if frame.id() == "TXXX" {
                if let Some(extended_text) = frame.content().extended_text() {
                    match extended_text.description.as_str() {
                        "REPLAYGAIN_TRACK_GAIN" => {
                            contains_replaygain_tags = true;
                            replaygain_data.track_gain = extended_text.value.clone()
                        }
                        "REPLAYGAIN_TRACK_PEAK" => {
                            contains_replaygain_tags = true;
                            replaygain_data.track_peak = extended_text.value.clone()
                        }
                        _ => {}
                    }
                }
            }
        }

        if !contains_replaygain_tags {
            None
        } else {
            Some(replaygain_data)
        }
    }

    fn supports_replaygain(&self) -> bool {
        true
    }

    fn set_title(&mut self, title: String) -> Result<(), BoxedError> {
        self.id3_tags.set_title(title);

        Ok(())
    }

    fn set_artist(&mut self, artist: String) -> Result<(), BoxedError> {
        self.id3_tags.set_artist(artist);

        Ok(())
    }

    fn set_replaygain_data(&mut self, data: ReplayGainRawData) -> Result<(), BoxedError> {
        let frames = self.id3_tags.remove("TXXX");

        for frame in frames {
            if let Some(extended_text) = frame.content().extended_text() {
                if extended_text.description.starts_with("REPLAYGAIN") {
                    continue;
                }
            }
            self.id3_tags.add_frame(frame);
        }

        self.id3_tags.add_frame(id3::Frame::with_content(
            "TXXX",
            id3::Content::ExtendedText(id3::frame::ExtendedText {
                description: "REPLAYGAIN_TRACK_GAIN".to_string(),
                value: format!("{:.2} dB", data.track_gain),
            }),
        ));

        self.id3_tags.add_frame(id3::Frame::with_content(
            "TXXX",
            id3::Content::ExtendedText(id3::frame::ExtendedText {
                description: "REPLAYGAIN_TRACK_PEAK".to_string(),
                value: format!("{:.6}", data.track_peak),
            }),
        ));

        Ok(())
    }

    fn save_changes(&mut self) -> Result<(), BoxedError> {
        self.id3_tags
            .write_to_path(self.path.as_path(), id3::Version::Id3v24)?;
        Ok(())
    }

    fn get_audio_file_info(&self, allow_missing_tags: bool) -> Result<AudioFileInfo, BoxedError> {
        return Ok(AudioFileInfo {
            tags: self.get_tags(allow_missing_tags)?,
            replaygain: self.get_replaygain_data(),
            supports_replaygain: self.supports_replaygain(),
            container: self.container_type,
        });
    }
}

pub fn new_id3_format_handler(
    path: &PathBuf,
    container_type: AudioContainer,
) -> Result<ID3AudioFormat, BoxedError> {
    let id3_tags = match container_type {
        // Only works on ID3 containing WAV files, but doesn't seem very widespread
        AudioContainer::WAV => id3::Tag::read_from_wav_path(path)?,
        AudioContainer::AIFF => id3::Tag::read_from_aiff_path(path)?,

        _ => id3::Tag::read_from_path(path)?,
    };

    Ok(ID3AudioFormat {
        container_type,
        id3_tags,
        path: Box::new(path.clone()),
    })
}