//#[macro_use] extern crate serde;

use smallvec::SmallVec;
use clap::{
    Parser, Subcommand, ValueEnum,
};
use chain::{
    Chain,
};
use std::{
    num::NonZeroUsize,
    path::{
	PathBuf, Path,
    },
    io::{
	BufRead,
	self,
    },
};

#[derive(Debug, Parser)]
#[command(name = env!("CARGO_PKG_NAME"), version, about = env!("CARGO_PKG_DESCRIPTION"), long_about = None)]
pub struct Cli {
    /// Save the chain to the provided output file name after appending to it.
    #[arg(short, long)]
    save: Option<PathBuf>,
    /// Load the chain from the provided output file name before appending to it.
    #[arg(short, long)]
    load: Option<PathBuf>, // TODO: Group with `save` & add `-F/--file` opt for save & load from same file.
    /// Force over-writes of files, and force output of binary data to TTY.
    #[arg(short, long)]
    force: bool,

    /// Do not read into chain from `stdin` if there is a loaded chain that is not empty.
    #[arg(short, long)]
    no_consume: bool,

    /// Do not read into chain from `stdin` ever.
    #[arg(short, long="write")]
    write_only: bool, // XXX: Should we group this with `no_consume`?

    /// The number of lines to output from the chain. Default is 1.
    ///
    /// If 0, generation from chain is skipped.
    #[arg(default_value_t = 1)]
    lines: usize,
}

fn buffered_read_all_lines<T: BufRead+?Sized, F: FnMut(&str) -> io::Result<()>>(input: &mut T, mut then: F) -> io::Result<usize>
{
    let mut buffer = String::new();
    let mut read;
    let mut total=0;
    while {read = input.read_line(&mut buffer)?; read!=0} {
	if buffer.trim().len() > 0 {
	    then(&buffer[..])?;
	}
	buffer.clear();
	total += read;
    }
    Ok(total)
}

#[inline] 
fn parse_cli() -> Cli {
    use clap::Parser;
    Cli::parse()
}

mod format;

fn load_chain<S>(stream: &mut S) -> io::Result<Chain<String>>
where S: io::Read + ?Sized
{
    format::load_chain_from_sync(stream)
}

fn save_chain<S>(stream: &mut S, chain: &Chain<String>) -> io::Result<()>
where S: io::Write + ?Sized
{
    format::save_chain_to_sync(stream, chain, true) //TODO: Change compression to be off for small chains...? We will need to store the chain size info somewhere else.
}

fn create_chain(cli: &Cli) -> Chain<String>
{
    if let Some(load) = &cli.load {
	let mut input = std::fs::OpenOptions::new()
	    .read(true)
	    .open(&load).expect("Failed to open chain load file");
	load_chain(&mut input).expect("Failed to load chain from file")
    } else {
	Chain::new()
    }
}

fn complete_chain(cli: &Cli, chain: Chain<String>) -> io::Result<()>
{
    if let Some(save) = &cli.save {
	let mut output = std::fs::OpenOptions::new()
	    .create_new(! cli.force)
	    
	    .create(cli.force)
	    .write(true)
	    .truncate(cli.force)
	    .open(&save).expect("Failed to open chain save file");
	save_chain(&mut output, &chain).expect("Failed to save chain to file") // TODO: Error type
    }

    Ok(())
}

fn main() {
    let cli = parse_cli();
    
    let stdin = io::stdin();
    let mut stdin = stdin.lock();
    let mut chain = create_chain(&cli);

    if !(cli.write_only || (cli.no_consume && ! chain.is_empty())) {
	buffered_read_all_lines(&mut stdin, |string| {
            chain.feed(&string.split_whitespace()
                       .filter(|word| !word.is_empty())
                       .map(|s| s.to_owned()).collect::<SmallVec<[_; 16]>>());
	    
	    Ok(())
	}).expect("Failed to read from stdin");
    }

    if !chain.is_empty() {
	match cli.lines {
	    0 => (),
	    1 =>    println!("{}", chain.generate_str()),
	    lines => {
		for string in chain.str_iter_for(lines) {
		    println!("{}", string);
		}
	    },
	};
    }

    complete_chain(&cli, chain).unwrap();
}