use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use std::process;

use docopt::Docopt;
use fs_err::PathExt;
use serde::Deserialize;
use wal::Wal;

static USAGE: &str = "
Commands:

  check   Checks that the write ahead log is well-formed, and prints the number
          of segments and entries to standard out.

  entry   Reads an entry from the write ahead log, and prints it to standard
          out.

  append  Appends an entry from stdin to the write ahead log, and prints the
          index.

Usage:
  wal-ctl check [--path=<path>]
  wal-ctl entry <index> [--path=<path>]
  wal-ctl append [--path=<path>]
  wal-ctl (-h | --help)

Options:
  --path=<path>     Path to the write ahead log directory, [default: .].
  -h --help         Show this help message.
";

#[derive(Debug, Deserialize)]
struct Args {
    cmd_check: bool,
    cmd_entry: bool,
    cmd_append: bool,

    arg_index: u64,
    flag_path: String,
}

fn main() {
    env_logger::init();
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.deserialize())
        .unwrap_or_else(|e| e.exit());

    let path: PathBuf = PathBuf::from(&args.flag_path)
        .fs_err_canonicalize()
        .unwrap_or_else(|error| {
            eprintln!(
                "Unable to open write ahead log in directory {:?}: {}.",
                &args.flag_path, error
            );
            process::exit(1);
        });

    if !path.is_dir() {
        eprintln!("Unable to open write ahead log: path {path:?} is not a directory.");
        process::exit(1);
    }

    let wal = open_wal(&path);

    if args.cmd_check {
        check(wal);
    } else if args.cmd_entry {
        entry(wal, args.arg_index);
    } else if args.cmd_append {
        let mut buffer = Vec::new();
        io::stdin().read_to_end(&mut buffer).unwrap();

        append(wal, &buffer[..]);
    }
}

fn open_wal(path: &Path) -> Wal {
    Wal::open(path).unwrap_or_else(|error| {
        eprintln!("Unable to open write ahead log in directory {path:?}: {error}.");
        process::exit(1);
    })
}

fn check(wal: Wal) {
    println!("segment count: {}", wal.num_segments());
    println!("entry count: {}", wal.num_entries());
}

fn entry(wal: Wal, index: u64) {
    match wal.entry(index) {
        Some(entry) => {
            io::stdout().write_all(&entry).unwrap();
        }
        None => {
            eprintln!(
                "No entry at index {} in the write ahead log in directory {:?}.",
                index,
                &wal.path()
            );
            process::exit(1);
        }
    }
}

fn append(mut wal: Wal, entry: &[u8]) {
    println!("{}", wal.append(&entry).unwrap());
}
