use lazy_static::lazy_static;
use string_error::into_err;

use crate::utils::transcoder::types::Preset;
use crate::utils::transcoder::types::PresetCategory;
use crate::utils::transcoder::types::TranscodeConfig;

lazy_static! {
    #[derive(Debug)]
    pub static ref TRANSCODE_CONFIGS: Vec<PresetCategory> = {
        let mut preset_categories: Vec<PresetCategory> = Vec::new();

        add_mp3_presets(&mut preset_categories);
        add_opus_presets(&mut preset_categories);
        add_vorbis_presets(&mut preset_categories);
        add_g726_presets(&mut preset_categories);
        add_speex_presets(&mut preset_categories);
        add_flac_preset(&mut preset_categories);
        add_wav_preset(&mut preset_categories);

        preset_categories
    };
}

fn add_mp3_presets(preset_categories: &mut Vec<PresetCategory>) {
    let mut presets: Vec<Preset> = Vec::new();
    for bitrate in [
        8, 16, 24, 32, 40, 48, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320,
    ] {
        presets.push(Preset {
            name: format!("mp3-{}k", bitrate).to_string(),
            config: TranscodeConfig {
                file_extension: Some("mp3".to_string()),
                encoder: Some("libmp3lame".to_string()),
                container: Some("mp3".to_string()),
                bitrate: Some(format!("{}k", bitrate).to_string()),
                ..TranscodeConfig::default()
            },
        })
    }

    for quality in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] {
        presets.push(Preset {
            name: format!("mp3-v{}", quality).to_string(),
            config: TranscodeConfig {
                file_extension: Some("mp3".to_string()),
                encoder: Some("libmp3lame".to_string()),
                container: Some("mp3".to_string()),
                quality: Some(format!("{}", quality).to_string()),
                ..TranscodeConfig::default()
            },
        })
    }

    preset_categories.push(PresetCategory {
        name: "mp3".to_string(),
        presets,
    });
}

fn add_opus_presets(preset_categories: &mut Vec<PresetCategory>) {
    let mut presets: Vec<Preset> = Vec::new();
    for bitrate in [16, 24, 32, 64, 96, 128, 256] {
        presets.push(Preset {
            name: format!("opus-{}k", bitrate).to_string(),
            config: TranscodeConfig {
                file_extension: Some("opus".to_string()),
                encoder: Some("libopus".to_string()),
                container: Some("ogg".to_string()),
                bitrate: Some(format!("{}k", bitrate).to_string()),
                ..TranscodeConfig::default()
            },
        })
    }

    preset_categories.push(PresetCategory {
        name: "opus".to_string(),
        presets,
    });
}

fn add_vorbis_presets(preset_categories: &mut Vec<PresetCategory>) {
    let mut presets: Vec<Preset> = Vec::new();
    for quality in [-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] {
        presets.push(Preset {
            name: format!("vorbis-v{}", quality).to_string(),
            config: TranscodeConfig {
                file_extension: Some("ogg".to_string()),
                encoder: Some("libvorbis".to_string()),
                container: Some("ogg".to_string()),
                quality: Some(format!("{}", quality).to_string()),
                ..TranscodeConfig::default()
            },
        })
    }

    preset_categories.push(PresetCategory {
        name: "vorbis".to_string(),
        presets,
    });
}

fn add_g726_presets(preset_categories: &mut Vec<PresetCategory>) {
    let mut presets: Vec<Preset> = Vec::new();
    for bitrate in [16, 24, 32, 64, 96, 128, 256] {
        presets.push(Preset {
            name: format!("g726-{}k", bitrate).to_string(),
            config: TranscodeConfig {
                file_extension: Some("mka".to_string()),
                encoder: Some("g726".to_string()),
                container: Some("matroska".to_string()),
                sample_rate: Some("8000".to_string()),
                channels: Some("1".to_string()),
                bitrate: Some(format!("{}k", bitrate).to_string()),
                ..TranscodeConfig::default()
            },
        })
    }

    preset_categories.push(PresetCategory {
        name: "g726".to_string(),
        presets,
    });
}

fn add_speex_presets(preset_categories: &mut Vec<PresetCategory>) {
    let mut presets: Vec<Preset> = Vec::new();
    for quality in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] {
        presets.push(Preset {
            name: format!("speex-q{}", quality).to_string(),
            config: TranscodeConfig {
                file_extension: Some("ogg".to_string()),
                encoder: Some("libspeex".to_string()),
                container: Some("ogg".to_string()),
                quality: Some(format!("{}", quality).to_string()),
                ..TranscodeConfig::default()
            },
        })
    }

    preset_categories.push(PresetCategory {
        name: "speex".to_string(),
        presets,
    });
}

fn add_flac_preset(preset_categories: &mut Vec<PresetCategory>) {
    preset_categories.push(PresetCategory {
        name: "flac".to_string(),
        presets: Vec::from([Preset {
            name: "flac".to_string(),
            config: TranscodeConfig {
                encoder: Some("flac".to_string()),
                container: Some("flac".to_string()),
                file_extension: Some("flac".to_string()),
                ..TranscodeConfig::default()
            },
        }]),
    })
}

fn add_wav_preset(preset_categories: &mut Vec<PresetCategory>) {
    preset_categories.push(PresetCategory {
        name: "wav".to_string(),
        presets: Vec::from([Preset {
            name: "wav".to_string(),
            config: TranscodeConfig {
                container: Some("wav".to_string()),
                file_extension: Some("wav".to_string()),
                ..TranscodeConfig::default()
            },
        }]),
    })
}

pub fn print_presets() {
    for category in TRANSCODE_CONFIGS.iter() {
        println!("Category {}:", category.name);
        for preset in category.presets.iter() {
            println!("- {}", preset.name)
        }
    }
}

pub fn get_preset(name: String) -> Option<TranscodeConfig> {
    for category in TRANSCODE_CONFIGS.iter() {
        for preset in category.presets.iter() {
            if preset.name == name {
                return Some(preset.config.clone());
            }
        }
    }

    None
}

pub fn transcode_preset_or_config(
    preset_name: Option<&String>,
    config_path: Option<&String>,
) -> Result<TranscodeConfig, Box<dyn std::error::Error>> {
    if let Some(preset_name) = preset_name {
        let preset_config = get_preset(preset_name.to_string());

        match preset_config {
            Some(config) => Ok(config),
            None => Err(into_err("invalid preset name".to_string())),
        }
    } else if let Some(config_path) = config_path {
        let config = TranscodeConfig::load(config_path.to_string())?;

        Ok(config)
    } else {
        Err(into_err(
            "please provide a transcode config or preset".to_string(),
        ))
    }
}