use std::env;
use std::iter;

use anyhow::{bail, Result};

pub struct Args {
    args: iter::Peekable<env::Args>,
    /// Set to `true` once we saw a `--`.
    terminated: bool,
}

impl Args {
    pub fn new() -> Self {
        let mut args = Args { args: env::args().peekable(), terminated: false };
        args.args.next().unwrap(); // skip program name
        args
    }

    /// Get the next argument without any interpretation.
    pub fn next_raw(&mut self) -> Option<String> {
        self.args.next()
    }

    /// Consume a `-$f` flag if present.
    pub fn get_short_flag(&mut self, flag: char) -> Result<bool> {
        if self.terminated {
            return Ok(false);
        }
        if let Some(next) = self.args.peek() {
            if let Some(next) = next.strip_prefix("-") {
                if let Some(next) = next.strip_prefix(flag) {
                    if next.is_empty() {
                        self.args.next().unwrap(); // consume this argument
                        return Ok(true);
                    } else {
                        bail!("`-{flag}` followed by value");
                    }
                }
            }
        }
        Ok(false)
    }

    /// Consume a `--$name` flag if present.
    pub fn get_long_flag(&mut self, name: &str) -> Result<bool> {
        if self.terminated {
            return Ok(false);
        }
        if let Some(next) = self.args.peek() {
            if let Some(next) = next.strip_prefix("--") {
                if next == name {
                    self.args.next().unwrap(); // consume this argument
                    return Ok(true);
                }
            }
        }
        Ok(false)
    }

    /// Consume a `--$name val` or `--$name=val` option if present.
    pub fn get_long_opt(&mut self, name: &str) -> Result<Option<String>> {
        assert!(!name.is_empty());
        if self.terminated {
            return Ok(None);
        }
        let Some(next) = self.args.peek() else { return Ok(None) };
        let Some(next) = next.strip_prefix("--") else { return Ok(None) };
        let Some(next) = next.strip_prefix(name) else { return Ok(None) };
        // Starts with `--flag`.
        Ok(if let Some(val) = next.strip_prefix("=") {
            // `--flag=val` form
            let val = val.into();
            self.args.next().unwrap(); // consume this argument
            Some(val)
        } else if next.is_empty() {
            // `--flag val` form
            self.args.next().unwrap(); // consume this argument
            let Some(val) = self.args.next() else { bail!("`--{name}` not followed by value") };
            Some(val)
        } else {
            // Some unrelated flag, like `--flag-more` or so.
            None
        })
    }

    /// Consume a `--$name=val` or `--$name` option if present; the latter
    /// produces a default value. (`--$name val` is *not* accepted for this form
    /// of argument, it understands `val` already as the next argument!)
    pub fn get_long_opt_with_default(
        &mut self,
        name: &str,
        default: &str,
    ) -> Result<Option<String>> {
        assert!(!name.is_empty());
        if self.terminated {
            return Ok(None);
        }
        let Some(next) = self.args.peek() else { return Ok(None) };
        let Some(next) = next.strip_prefix("--") else { return Ok(None) };
        let Some(next) = next.strip_prefix(name) else { return Ok(None) };
        // Starts with `--flag`.
        Ok(if let Some(val) = next.strip_prefix("=") {
            // `--flag=val` form
            let val = val.into();
            self.args.next().unwrap(); // consume this argument
            Some(val)
        } else if next.is_empty() {
            // `--flag` form
            self.args.next().unwrap(); // consume this argument
            Some(default.into())
        } else {
            // Some unrelated flag, like `--flag-more` or so.
            None
        })
    }

    /// Returns the next free argument or uninterpreted flag, or `None` if there are no more
    /// arguments left. `--` is returned as well, but it is interpreted in the sense that no more
    /// flags will be parsed after this.
    pub fn get_other(&mut self) -> Option<String> {
        if self.terminated {
            return self.args.next();
        }
        let next = self.args.next()?;
        if next == "--" {
            self.terminated = true; // don't parse any more flags
            // This is where our parser is special, we do yield the `--`.
        }
        Some(next)
    }

    /// Return the rest of the aguments entirely unparsed.
    pub fn remainder(self) -> Vec<String> {
        self.args.collect()
    }
}
