// Syd: rock-solid application kernel
// src/syd-bit.rs: Utility to flip bits in a file
//
// Copyright (c) 2024, 2025 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{
    fs::OpenOptions,
    io::{Read, Seek, SeekFrom, Write},
    process::ExitCode,
};

use hex::DisplayHex;
use syd::{
    err::SydResult,
    fs::fillrandom,
    path::{XPath, XPathBuf},
};

fn main() -> SydResult<ExitCode> {
    use lexopt::prelude::*;

    syd::set_sigpipe_dfl()?;

    // Parse CLI options.
    let mut opt_index = None;
    let mut opt_random = false;
    let mut opt_filename = None;

    let mut parser = lexopt::Parser::from_env();
    while let Some(arg) = parser.next()? {
        match arg {
            Short('h') => {
                help();
                return Ok(ExitCode::SUCCESS);
            }
            Short('i') => opt_index = Some(parser.value()?.parse::<usize>()?),
            Short('r') => opt_random = true,
            Value(filename) if opt_filename.is_none() => {
                opt_filename = Some(XPathBuf::from(filename))
            }
            _ => return Err(arg.unexpected().into()),
        }
    }

    if let Some(filename) = opt_filename {
        match (opt_index, opt_random) {
            (Some(idx), false) => flip_bit_in_file(&filename, idx)?,
            (None, true) => flip_random_bit_in_file(&filename)?,
            _ => {
                eprintln!("syd-bit: Exactly one of -i <index> or -r must be given!");
                help();
                return Ok(ExitCode::FAILURE);
            }
        }
    } else {
        eprintln!("syd-bit: File name not specified!");
        help();
        return Ok(ExitCode::FAILURE);
    }

    Ok(ExitCode::SUCCESS)
}

fn flip_bit_in_file(file_name: &XPath, bit_index: usize) -> SydResult<()> {
    eprintln!("syd-bit: opening file {file_name}...");
    #[allow(clippy::disallowed_methods)]
    let mut file = OpenOptions::new().read(true).write(true).open(file_name)?;

    // Calculate the byte index from the bit index.
    let byte_index = bit_index / 8;
    let bit_in_byte = bit_index % 8;

    // Seek to the byte containing the bit.
    eprintln!("syd-bit: seeking to byte index {byte_index}...");
    file.seek(SeekFrom::Start(byte_index as u64))?;

    // Read the single byte.
    let mut byte = [0u8; 1];
    file.read_exact(&mut byte)?;
    eprintln!(
        "syd-bit: read byte 0x{} at index {byte_index}.",
        byte.to_lower_hex_string()
    );

    // Flip the specified bit in the byte
    byte[0] ^= 1 << bit_in_byte;
    eprintln!(
        "syd-bit: flipped bit {bit_in_byte} resulting in 0x{}.",
        byte.to_lower_hex_string()
    );

    // Move the file pointer back to the byte to overwrite it
    eprintln!("syd-bit: moving back to file offset {byte_index}.");
    file.seek(SeekFrom::Start(byte_index as u64))?;

    // Write the modified byte back to the file
    eprintln!(
        "syd-bit: writing byte 0x{} at index {byte_index}...",
        byte.to_lower_hex_string()
    );
    file.write_all(&byte)?;

    eprintln!("syd-bit: flipped bit:{bit_index} of byte:{byte_index}.");
    Ok(())
}

fn flip_random_bit_in_file(file_name: &XPath) -> SydResult<()> {
    #[allow(clippy::disallowed_methods)]
    let mut file = OpenOptions::new().read(true).write(true).open(file_name)?;

    let file_len = file.seek(SeekFrom::End(0))?; // Get file length
    let total_bits = file_len * 8;

    let mut rng_buf = [0u8; std::mem::size_of::<usize>()];
    fillrandom(&mut rng_buf)?; // secure random fill
    let bit_index = usize::from_ne_bytes(rng_buf) % total_bits as usize;

    flip_bit_in_file(file_name, bit_index)
}

fn help() {
    println!("Usage: syd-bit [-h] -i <idx> <file> | -r <file>");
    println!("Utility to flip bits in files");
    println!("  -i <idx>    Flip the bit at index <idx> in the file");
    println!("  -r          Flip a random bit in the file");
}
