use super::*;
use log::Level;

pub enum FFmpegDefaultArgs {
    None,
    Quiet,
    General,
}

pub struct FFmpegArgs {
    default_args: Option<FFmpegDefaultArgs>,
    filters: Vec<VideoFilter>,
    log_level: Level,
    params: Vec<String>,
}

impl FFmpegArgs {
    pub fn new(log_level: Level) -> Self {
        FFmpegArgs {
            default_args: Some(FFmpegDefaultArgs::General),
            log_level,
            filters: Vec::new(),
            params: Vec::new(),
        }
    }

    fn append_params<T>(mut self, param: T) -> Self
    where
        T: ToString,
    {
        self.params.push(param.to_string());
        self
    }

    pub fn default_args(mut self, default_args: Option<FFmpegDefaultArgs>) -> Self {
        self.default_args = default_args;
        self
    }

    pub fn attach<T>(self, format: T) -> Self
    where
        T: ToString,
    {
        self.append_params("-attach").append_params(format)
    }

    pub fn c<F, P>(self, format: F, prefix: P) -> Self
    where
        F: ToString,
        P: ToString,
    {
        let prefix = prefix.to_string();
        if !prefix.is_empty() {
            self.append_params(format!("-c:{}", prefix))
        } else {
            self.append_params("-c")
        }
        .append_params(format)
    }

    pub fn dump_attachment<P, O>(self, prefix: P, output: O) -> Self
    where
        P: ToString,
        O: ToString,
    {
        self.append_params(format!("-dump_attachment:{}", prefix.to_string()))
            .append_params(output)
    }

    pub fn f<T>(self, format: T) -> Self
    where
        T: ToString,
    {
        self.append_params("-f").append_params(format)
    }

    pub fn fflags<T>(self, param: T) -> Self
    where
        T: ToString,
    {
        self.append_params("-fflags").append_params(param)
    }

    pub fn flags<P1, P2>(self, prefix: P1, param: P2) -> Self
    where
        P1: ToString,
        P2: ToString,
    {
        self.append_params(format!("-flags:{}", prefix.to_string()))
            .append_params(param)
    }

    pub fn framerate<T>(self, fps: T) -> Self
    where
        T: ToString,
    {
        self.append_params("-framerate").append_params(fps)
    }

    pub fn i<T>(self, input: T) -> Self
    where
        T: ToString,
    {
        self.append_params("-i").append_params(input)
    }

    pub fn map<T>(self, map: T) -> Self
    where
        T: ToString,
    {
        self.append_params("-map").append_params(map)
    }

    pub fn map_metadata<T>(self, map_metadata: T) -> Self
    where
        T: ToString,
    {
        self.append_params("-map_metadata")
            .append_params(map_metadata)
    }

    pub fn metadata<P1, P2>(self, prefix: Option<P1>, param: P2) -> Self
    where
        P1: ToString,
        P2: ToString,
    {
        self.append_params(if let Some(prefix) = prefix {
            format!("-metadata:{}", prefix.to_string())
        } else {
            "-metadata".to_string()
        })
        .append_params(param)
    }

    pub fn profile<T>(self, profile: T) -> Self
    where
        T: ToString,
    {
        self.append_params("-profile").append_params(profile)
    }

    pub fn q<T>(self, q: T) -> Self
    where
        T: ToString,
    {
        self.append_params("-q").append_params(q)
    }

    pub fn raw<T>(self, raw_param: T) -> Self
    where
        T: ToString,
    {
        self.append_params(raw_param)
    }

    pub fn vcodec<T>(self, vcodec: T) -> Self
    where
        T: ToString,
    {
        self.append_params("-vcodec").append_params(vcodec)
    }

    pub fn vsync<T>(self, param: T) -> Self
    where
        T: ToString,
    {
        self.append_params("-vsync").append_params(param)
    }

    pub fn preset(self, preset: VideoPreset) -> Self {
        self.append_params("-preset").append_params(preset)
    }

    pub fn vf(mut self, vf: VideoFilter) -> Self {
        self.filters.push(vf);
        self
    }

    fn clear_filter(mut self) -> Self {
        self.filters.clear();
        self
    }

    pub fn build_filter(self) -> Self {
        if !self.filters.is_empty() {
            let filters = &self.filters.clone();
            match self.filters.len() {
                1 => if self.filters[0].get_inputs().len() > 1
                    || self.filters[0].get_outputs().len() > 1
                {
                    self.append_params("-filter_complex")
                } else {
                    self.append_params("-vf")
                }
                .append_params(filters[0].clone()),
                _ => self.append_params("-filter_complex").append_params(
                    filters
                        .iter()
                        .map(ToString::to_string)
                        .collect::<Vec<_>>()
                        .join(";"),
                ),
            }
            .clear_filter()
        } else {
            self
        }
    }

    pub fn build(self) -> Vec<String> {
        let mut args = vec!["-hide_banner", "-y"];
        args.append(&mut match self.default_args {
            Some(FFmpegDefaultArgs::None) => vec!["-loglevel", "quiet"],
            Some(FFmpegDefaultArgs::Quiet) => vec![
                "-loglevel",
                if self.log_level > Level::Info {
                    "warning"
                } else {
                    "error"
                },
            ],
            Some(FFmpegDefaultArgs::General) => vec![
                "-loglevel",
                if self.log_level > Level::Info {
                    "warning"
                } else {
                    "error"
                },
                "-stats",
            ],
            None => vec![],
        });
        args.append(&mut self.params.iter().map(AsRef::as_ref).collect());
        args.iter().map(ToString::to_string).collect::<Vec<_>>()
    }
}
