#![allow(clippy::from_str_radix_10)]

mod attribute_bits;
mod commands;
mod display_bytes;
mod error;
mod opt;
mod table;
mod types;
mod uuid;

use self::commands::{execute, print};
use self::error::*;
use self::opt::*;
use self::uuid::generate_random_uuid;
use clap::Parser;
#[cfg(target_os = "linux")]
use gptman::linux::get_sector_size;
use gptman::GPT;
use linefeed::{Interface, ReadResult, Signal};
use std::fs;
use std::io::{Seek, SeekFrom};

macro_rules! main_unwrap {
    ($e:expr) => {{
        match $e {
            Ok(x) => x,
            Err(err) => {
                eprintln!("{}", err);
                std::process::exit(1);
            }
        }
    }};
}

fn main() {
    let opt = Opt::parse();

    if opt.print {
        let (mut gpt, len) = main_unwrap!(open_disk(&opt));

        if let Some(align) = opt.align {
            gpt.align = align;
        }

        main_unwrap!(print(&opt, &opt.device, &gpt, len, false));
        return;
    }

    let interface = Interface::new("gptman").expect("open terminal interface");
    let ask = |prompt: &str| -> Result<String> {
        main_unwrap!(interface.set_prompt(&format!("{} ", prompt)));
        interface.set_report_signal(Signal::Interrupt, true);
        match main_unwrap!(interface.read_line()) {
            ReadResult::Input(line) => Ok(line),
            ReadResult::Eof => Err("EOF".into()),
            _ => Err("^C".into()),
        }
    };

    let (mut gpt, len) = main_unwrap!(if opt.init {
        new_gpt(&opt, &ask)
    } else {
        open_disk(&opt)
    });

    if let Some(align) = opt.align {
        gpt.align = align;
    }

    loop {
        match ask("Command (m for help):") {
            Ok(command) => {
                if command == "q" {
                    break;
                } else if !command.is_empty() {
                    match execute(command.as_str(), &opt, len, &mut gpt, &ask) {
                        Ok(false) => {}
                        Ok(true) => break,
                        Err(err) => println!("{}", err),
                    }
                }
            }
            Err(_) => {
                println!();
                break;
            }
        }
    }
}

fn open_disk(opt: &Opt) -> Result<(GPT, u64)> {
    let mut f = fs::File::open(&opt.device)?;
    let gpt = if let Some(ss) = opt.sector_size {
        GPT::read_from(&mut f, ss)?
    } else {
        GPT::find_from(&mut f)?
    };
    let len = f.seek(SeekFrom::End(0))?;

    Ok((gpt, len))
}

fn new_gpt<F>(opt: &Opt, ask: &F) -> Result<(GPT, u64)>
where
    F: Fn(&str) -> Result<String>,
{
    println!("Initializing a new GPT on {}...", opt.device.display());

    let mut f = fs::File::open(&opt.device)?;
    let len = f.seek(SeekFrom::End(0))?;

    #[allow(unused_mut)]
    let mut sector_size = opt.sector_size.unwrap_or(512);

    #[cfg(target_os = "linux")]
    {
        match get_sector_size(&mut f) {
            Err(err) => println!("failed to get sector size of device: {}", err),
            Ok(x) => sector_size = x,
        }
    }

    println!("Sector size: {} bytes", sector_size);

    if GPT::find_from(&mut f).is_ok() {
        println!("WARNING: a GPT already exists on the device");
    }

    ask("Do you wish to continue (yes/no)?").and_then(|x| {
        if x == "yes" {
            Ok(())
        } else if x == "no" {
            Err(Error::new("Aborted."))
        } else {
            Err(Error::new(&format!(
                "Invalid answer '{}'. Please type 'yes' or 'no'.",
                x
            )))
        }
    })?;

    let guid = generate_random_uuid();
    let gpt = GPT::new_from(&mut f, sector_size, guid)?;
    println!("GPT created.");

    Ok((gpt, len))
}
