use syn::Token;
use syn::{
    LitInt, LitStr,
    parse::{Parse, ParseStream},
};

pub struct BinSetupInput {
    pub puzzles_count: LitInt,
    pub resources_directory: LitStr,
    pub input_extension: LitStr,
    pub answers_file: LitStr,
}

impl Parse for BinSetupInput {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let puzzles_count: LitInt = input.parse()?;
        input.parse::<Token![,]>()?;
        let resources_directory: LitStr = input.parse()?;
        input.parse::<Token![,]>()?;
        let input_extension: LitStr = input.parse()?;
        input.parse::<Token![,]>()?;
        let answers_file: LitStr = input.parse()?;

        Ok(Self {
            puzzles_count,
            resources_directory,
            input_extension,
            answers_file,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::BinSetupInput;

    #[test]
    fn binsetupinput_parse_missing_comma() {
        let input = [
            r#"5 "resources", ".in", "Answers.out""#,
            r#"5, "resources" ".in", "Answers.out""#,
            r#"5, "resources", ".in" "Answers.out""#,
        ];
        let expected = [false; 3];
        let output = input.map(|i| syn::parse_str::<BinSetupInput>(i).is_ok());
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn binsetupinput_parse_wrong_type() {
        let input = [
            r#""five", "resources", ".in", "Answers.out""#,
            r#"5, 0, ".in", "Answers.out""#,
            r#"5, "resources", 0, "Answers.out""#,
            r#"5, "resources", ".in", 0"#,
        ];
        let expected = [false; 4];
        let output = input.map(|i| syn::parse_str::<BinSetupInput>(i).is_ok());
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn binsetupinput_parse_missing_field() {
        let input = [
            r#""resources", ".in", "Answers.out""#,
            r#"5, ".in", "Answers.out""#,
            r#"5, "resources", "Answers.out""#,
            r#"5, "resources", ".in""#,
        ];
        let expected = [false; 4];
        let output = input.map(|i| syn::parse_str::<BinSetupInput>(i).is_ok());
        assert_eq!(expected, output, "\n input: {input:?}");
    }

    #[test]
    fn binsetupinput_parse_ok() {
        let input = r#"5, "resources", ".in", "Answers.out""#;
        let expected = (
            5,
            "resources".to_string(),
            ".in".to_string(),
            "Answers.out".to_string(),
        );
        let bin_setup_input: BinSetupInput = syn::parse_str(input).expect("Expected valid input");
        let output = (
            bin_setup_input
                .puzzles_count
                .base10_parse::<u8>()
                .expect("Expected valid number"),
            bin_setup_input.resources_directory.value(),
            bin_setup_input.input_extension.value(),
            bin_setup_input.answers_file.value(),
        );
        assert_eq!(expected, output, "\n input: {input:?}");
    }
}
