use crate::candidate_selection::HashingStrategy;
use crate::ui::FromByteUnit;
use anyhow::{anyhow, Context, Result};
use clap::ArgMatches;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AnalysisMode {
    HashOnly,
    Exact,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LinkSelectionPreference {
    Oldest,
    Newest,

    #[allow(dead_code)]
    LinkEffort,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Format {
    JSON,
    CSV,
}

/// Configuration options for comparison
#[derive(Copy, Clone, Debug)]
pub struct Options {
    pub analysis_mode: AnalysisMode,
    pub hashing_strategy: HashingStrategy,
    pub block_size: u64,
    pub initial_block_size: u64,
    pub thread_count: usize,
    pub min_size: u64,
    pub max_size: Option<u64>,
    pub debug: bool,
    pub no_progress: bool,
    pub error_on_duplicate: bool,
    pub format: Format,
    pub force: bool,
    pub dry_run: bool,
    pub link_priority: LinkSelectionPreference,
}

impl Options {
    pub fn from_matches(matches: ArgMatches) -> Result<Self> {
        let res = Options {
            debug: matches.is_present("debug"),
            no_progress: matches.is_present("hide-progress"),
            analysis_mode: if matches.is_present("estimate") {
                AnalysisMode::HashOnly
            } else {
                AnalysisMode::Exact
            },
            hashing_strategy: if matches.is_present("no-sampling") {
                HashingStrategy::Complete
            } else {
                HashingStrategy::Sampling
            },
            block_size: matches
                .value_of("buffer-size")
                .map(u64::from_str_byte_unit)
                .transpose()
                .context("Failed to parse --buffer-size argument")?
                .unwrap_or(16384),
            initial_block_size: matches
                .value_of("start-size")
                .map(u64::from_str_byte_unit)
                .transpose()
                .context("Failed to parse --start-size argument")?
                .unwrap_or(10),
            thread_count: std::cmp::max(
                matches
                    .value_of("threads")
                    .map(|n| usize::from_str_radix(n, 10))
                    .transpose()
                    .context("Failed to parse number of threads")?
                    .map(|n| {
                        if n > 0 {
                            Ok(n)
                        } else {
                            Err(anyhow!("Number of threads must be positive."))
                        }
                    })
                    .transpose()?
                    .unwrap_or_else(|| num_cpus::get() * 2),
                1,
            ),
            error_on_duplicate: matches.is_present("error-on-duplicate"),
            min_size: matches
                .value_of("min-size")
                .map(u64::from_str_byte_unit)
                .transpose()
                .context("Failed to parse --min-size argument")?
                .map(|n| {
                    if n > 0 {
                        Ok(n)
                    } else {
                        Err(anyhow!("Minimal file size must be positive."))
                    }
                })
                .transpose()?
                .unwrap_or(1),
            max_size: matches
                .value_of("max-size")
                .map(u64::from_str_byte_unit)
                .transpose()
                .context("Failed to parse --max-size argument")?,
            format: matches
                .value_of("output-format")
                .map(|n| match n {
                    "json" => Ok(Format::JSON),
                    "csv" => Ok(Format::CSV),
                    _ => Err(anyhow!("Unsupported output format {}", n)),
                })
                .transpose()?
                .unwrap_or(Format::CSV),
            force: matches.is_present("force"),
            dry_run: matches.is_present("dry-run"),
            link_priority: matches
                .value_of("prefer")
                .map(|n| match n {
                    "oldest" => Ok(LinkSelectionPreference::Oldest),
                    "newest" => Ok(LinkSelectionPreference::Newest),
                    _ => Err(anyhow!("Unsupported link preference {}", n)),
                })
                .transpose()?
                .unwrap_or(LinkSelectionPreference::Oldest),
        };

        Ok(res)
    }
}
