// SPDX-License-Identifier: Apache-2.0

#![cfg(test)]

mod tests {
    use crate::{cli, options_arg, Cli, Commands};
    use clap::{CommandFactory, Parser};
    use solang::codegen::Options;
    use std::path::PathBuf;

    #[test]
    fn test() {
        Cli::command().debug_assert();
    }

    #[test]
    fn parse_compile_options() {
        let mut command: Vec<&str> = "solang compile flipper.sol --target polkadot --value-length=31 --address-length=33 --no-dead-storage --no-constant-folding --no-strength-reduce --no-vector-to-slice --no-cse -O aggressive".split(' ').collect();
        let mut cli = Cli::parse_from(command);

        if let Commands::Compile(compile_args) = cli.command {
            assert_eq!(
                compile_args.package.input.unwrap(),
                vec![PathBuf::from("flipper.sol")]
            );
            assert_eq!(compile_args.target_arg.name.unwrap(), "polkadot");
            assert_eq!(compile_args.target_arg.address_length.unwrap(), 33_u64);
            assert_eq!(compile_args.target_arg.value_length.unwrap(), 31_u64);
            assert!(!compile_args.optimizations.common_subexpression_elimination,);
            assert!(!compile_args.optimizations.constant_folding);
            assert!(!compile_args.optimizations.dead_storage);
            assert!(!compile_args.optimizations.vector_to_slice);
            assert!(!compile_args.optimizations.strength_reduce);
            assert_eq!(compile_args.optimizations.opt_level.unwrap(), "aggressive");
        }

        command = "solang compile flipper.sol --target polkadot --no-log-runtime-errors --no-prints -g --release".split(' ').collect();
        cli = Cli::parse_from(command);

        if let Commands::Compile(compile_args) = cli.command {
            assert!(compile_args.debug_features.generate_debug_info);
            assert!(!compile_args.debug_features.log_prints);
            assert!(!compile_args.debug_features.log_runtime_errors);
            assert!(compile_args.debug_features.release);
        }
    }

    #[test]
    fn parse_package_from_toml() {
        let mut package_toml = r#"
        input_files = ["flipper.sol"]   # Files to be compiled. You can define multiple files as : input_files = ["file1", "file2", ..]
        contracts = ["flipper"] # Contracts to include from the compiled files
        import_path = ["path1", "path2"]
        import_map = {map1="path", map2="path2"}    # Maps to import. Define as : import_paths = ["map=path/to/map", "map2=path/to/map2", ..]"#;

        let package: cli::CompilePackage = toml::from_str(package_toml).unwrap();

        assert_eq!(package.input.unwrap(), [PathBuf::from("flipper.sol")]);
        assert_eq!(package.contracts.unwrap(), ["flipper".to_owned()]);
        assert_eq!(
            package.import_path.unwrap(),
            [PathBuf::from("path1"), PathBuf::from("path2")]
        );
        assert_eq!(
            package.import_map.unwrap(),
            [
                ("map1".to_owned(), PathBuf::from("path")),
                ("map2".to_owned(), PathBuf::from("path2"))
            ]
        );

        package_toml = r#"
            input_files = ["flipper.sol"]
            import_map = ["map_name.path"]
        "#;

        let res: Result<cli::CompilePackage, _> = toml::from_str(package_toml);

        match res {
            Ok(_) => unreachable!(),
            Err(error) => {
                assert_eq!("invalid type: sequence, expected a map", error.message())
            }
        }
    }

    #[test]
    fn parse_options_toml() {
        let toml_debug_features = r#"
        prints = true
    log-runtime-errors = false
    generate-debug-info = false"#;

        let debug_features: cli::DebugFeatures = toml::from_str(toml_debug_features).unwrap();

        assert!(debug_features.log_prints);
        assert!(!debug_features.log_runtime_errors);
        assert!(!debug_features.generate_debug_info);

        let default_debug: cli::DebugFeatures = toml::from_str("").unwrap();

        let default_optimize: cli::Optimizations = toml::from_str("").unwrap();

        let compiler_package = cli::CompilePackage {
            input: Some(vec![PathBuf::from("flipper.sol")]),
            contracts: Some(vec!["flipper".to_owned()]),
            import_path: Some(vec![]),
            import_map: Some(vec![]),
            authors: None,
            version: Some("0.1.0".to_string()),
            soroban_version: None,
        };

        let opt = options_arg(&default_debug, &default_optimize, &compiler_package);

        assert_eq!(opt, Options::default());

        let opt_toml = r#"
        dead-storage = false
        constant-folding = false
        strength-reduce = false
        vector-to-slice = false
        common-subexpression-elimination = true
        llvm-IR-optimization-level = "aggressive""#;

        let opt: cli::Optimizations = toml::from_str(opt_toml).unwrap();

        assert!(opt.common_subexpression_elimination);
        assert!(!opt.dead_storage);
        assert!(!opt.constant_folding);
        assert!(!opt.strength_reduce);
        assert!(!opt.vector_to_slice);
        assert_eq!(opt.opt_level.unwrap(), "aggressive");
    }

    #[cfg(feature = "wasm_opt")]
    #[test]
    fn wasm_opt_option() {
        use contract_build::OptimizationPasses;

        let opt: cli::Optimizations = toml::from_str(r#"wasm-opt = "Zero""#).unwrap();
        assert_eq!(opt.wasm_opt_passes, Some(OptimizationPasses::Zero));
    }

    #[test]
    fn parse_target() {
        let target_toml = r#"
        name = "polkadot"  # Valid targets are "solana" and "polkadot"
        address_length = 32
        value_length = 16"#;

        let target: cli::CompileTargetArg = toml::from_str(target_toml).unwrap();

        assert_eq!(target.name.unwrap(), "polkadot");
        assert_eq!(target.address_length.unwrap(), 32);
        assert_eq!(target.value_length.unwrap(), 16);
    }

    #[test]
    fn parse_compiler_output() {
        let compiler_out = r#"
        verbose = true
        std_json_output = false
        emit = "ast-dot"
        output_directory = "output"
        output_meta = "metadata"
        "#;

        let out: cli::CompilerOutput = toml::from_str(compiler_out).unwrap();

        assert!(out.verbose);
        assert!(!out.std_json_output);
        assert_eq!(out.emit, Some("ast-dot".to_owned()));
        assert_eq!(out.output_directory, Some("output".to_owned()));
        assert_eq!(out.output_meta, Some("metadata".to_owned()));

        let default_out: cli::CompilerOutput = toml::from_str("").unwrap();

        assert!(!default_out.verbose);
        assert!(!default_out.std_json_output);
    }

    #[test]
    fn overwrite_with_matches() {
        let toml = include_str!("../../../examples/solana/solana_config.toml");
        let mut compile_config: cli::Compile = toml::from_str(toml).unwrap();

        assert_eq!(
            compile_config,
            cli::Compile {
                configuration_file: None,
                package: cli::CompilePackage {
                    input: Some(vec![PathBuf::from("flipper.sol")]),
                    contracts: Some(vec!["flipper".to_owned()]),
                    import_path: Some(vec![]),
                    import_map: Some(vec![]),
                    authors: None,
                    version: Some("0.1.0".to_string()),
                    soroban_version: None
                },
                compiler_output: cli::CompilerOutput {
                    emit: None,
                    std_json_output: false,
                    output_directory: None,
                    output_meta: None,
                    verbose: false
                },
                target_arg: cli::CompileTargetArg {
                    name: Some("solana".to_owned()),
                    address_length: None,
                    value_length: None
                },
                debug_features: cli::DebugFeatures {
                    log_runtime_errors: true,
                    log_prints: true,
                    generate_debug_info: false,
                    release: false
                },
                optimizations: cli::Optimizations {
                    dead_storage: true,
                    constant_folding: true,
                    strength_reduce: true,
                    vector_to_slice: true,
                    common_subexpression_elimination: true,
                    opt_level: Some("aggressive".to_owned()),
                    #[cfg(feature = "wasm_opt")]
                    wasm_opt_passes: None
                }
            }
        );

        let command = "solang compile flipper.sol sesa.sol --config-file solang.toml --contract-authors not_sesa --target polkadot --value-length=31 --address-length=33 --no-dead-storage --no-constant-folding --no-strength-reduce --no-vector-to-slice --no-cse -O aggressive".split(' ');

        let matches = Cli::command().get_matches_from(command);

        let compile_matches = matches.subcommand_matches("compile").unwrap();

        compile_config.overwrite_with_matches(compile_matches);

        assert_eq!(
            compile_config,
            cli::Compile {
                configuration_file: None,
                package: cli::CompilePackage {
                    input: Some(vec![
                        PathBuf::from("flipper.sol"),
                        PathBuf::from("sesa.sol")
                    ]),
                    contracts: Some(vec!["flipper".to_owned()]),
                    import_path: Some(vec![]),
                    import_map: Some(vec![]),
                    authors: Some(vec!["not_sesa".to_owned()]),
                    version: Some("0.1.0".to_string()),
                    soroban_version: None
                },
                compiler_output: cli::CompilerOutput {
                    emit: None,
                    std_json_output: false,
                    output_directory: None,
                    output_meta: None,
                    verbose: false
                },
                target_arg: cli::CompileTargetArg {
                    name: Some("polkadot".to_owned()),
                    address_length: Some(33),
                    value_length: Some(31)
                },
                debug_features: cli::DebugFeatures {
                    log_runtime_errors: true,
                    log_prints: true,
                    generate_debug_info: false,
                    release: false
                },
                optimizations: cli::Optimizations {
                    dead_storage: false,
                    constant_folding: false,
                    strength_reduce: false,
                    vector_to_slice: false,
                    common_subexpression_elimination: false,
                    opt_level: Some("aggressive".to_owned()),
                    #[cfg(feature = "wasm_opt")]
                    wasm_opt_passes: None
                }
            }
        );
    }
}
