#![feature(iter_chain)]
use std::{env, iter, process::ExitCode};

use cross::fs::{update_file_mode, FileMode, FileModeUpdate};

fn parse_delta_bit(
    bit: &str,
    set: &mut FileMode,
    clear: &mut FileMode,
) -> Result<(), &'static str> {
    let chars = bit.as_bytes();
    let (target, plus_or_minus, bit) = match chars.len() {
        2 => (b'a', chars[0], chars[1]),
        3 => (chars[0], chars[1], chars[2]),
        _ => return Err("Expected mode to be in format X[+-]Y or [+-]Y"),
    };
    let (add_in, remove_in) = match plus_or_minus {
        b'+' => (set, clear),
        b'-' => (clear, set),
        _ => return Err("Expected +/- sign"),
    };
    let mask = match (target, bit) {
        (b'a', b'r') => FileMode::USER_READ | FileMode::GROUP_READ | FileMode::OTHER_READ,
        (b'a', b'w') => FileMode::USER_WRITE | FileMode::GROUP_WRITE | FileMode::OTHER_WRITE,
        (b'a', b'x') => FileMode::USER_EXEC | FileMode::GROUP_EXEC | FileMode::OTHER_EXEC,
        (b'u', b'r') => FileMode::USER_READ,
        (b'u', b'w') => FileMode::USER_WRITE,
        (b'u', b'x') => FileMode::USER_EXEC,
        (b'g', b'r') => FileMode::GROUP_READ,
        (b'g', b'w') => FileMode::GROUP_WRITE,
        (b'g', b'x') => FileMode::GROUP_EXEC,
        (b'o', b'r') => FileMode::OTHER_READ,
        (b'o', b'w') => FileMode::OTHER_WRITE,
        (b'o', b'x') => FileMode::OTHER_EXEC,
        (_, _) => return Err("Invalid mode value"),
    };

    *add_in |= mask;
    *remove_in &= !mask;

    Ok(())
}

fn parse_mode_update(text: &str) -> Result<FileModeUpdate, &'static str> {
    if let Some(digits) = text.strip_prefix("0") {
        // Decode as absolute octal
        let bits = u32::from_str_radix(digits, 8).map_err(|_| "Expected octal permission mask")?;
        Ok(FileModeUpdate::Set(FileMode::from(bits)))
    } else {
        let mut set = FileMode::empty();
        let mut clear = FileMode::empty();
        // Try to decode as a sequence of +x,-y,+z... flags
        for bit in text.split(',') {
            parse_delta_bit(bit.trim_matches(',').trim(), &mut set, &mut clear)?;
        }

        Ok(FileModeUpdate::Modify { set, clear })
    }
}

fn usage(invocation: &str) {
    eprintln!("Usage: {invocation} OCT-MODE FILENAME [FILENAME...]");
    eprintln!("    OR {invocation} [ugoa][+-][rwx] FILENAME [FILENAME...]");
}

fn run<I: Iterator<Item = String>>(mut args: I) -> Result<ExitCode, &'static str> {
    let mode_str = args.next().ok_or("Too few arguments")?;
    let first = args.next().ok_or("No filenames provided")?;
    let args = iter::chain(iter::once(first), args);
    let mode = parse_mode_update(&mode_str)?;

    let mut code = ExitCode::SUCCESS;
    for arg in args {
        match update_file_mode(None, &arg, mode) {
            Ok(()) => (),
            Err(error) => {
                eprintln!("{arg}: {error}");
                code = ExitCode::FAILURE;
            }
        }
    }
    Ok(code)
}

fn main() -> ExitCode {
    let mut args = env::args();
    let invocation = args.next().unwrap();
    match run(args) {
        Ok(code) => code,
        Err(usage_error) => {
            eprintln!("{invocation}: {usage_error}");
            usage(&invocation);
            ExitCode::FAILURE
        }
    }
}
