use clap::{Args, Parser, Subcommand};

mod checkout;
mod commit;
mod create_hooks;
mod diff;
mod init;
mod list;
mod mount;

#[derive(Debug, Parser)]
#[clap(name = "gbl")]
/// A tool used to link borg and git together
///
/// This tool was created to link borg and git together and ease the management of developpment artifact versionning using git
struct Cli {
    #[clap(subcommand)]
    commands: Commands,
}

#[derive(Debug, Subcommand)]
enum Commands {
    /// Initialize a borg repository inside a git project
    Init(Init),
    /// Save the results folder of a git repository in an archive
    ///
    /// The archive will be named as the current commit in git
    Commit(Commit),
    /// List the content of the .borg archive
    List(List),
    /// Check if a checkout can be performed without losing data
    #[clap(name = "pre-co")]
    PreCo,
    /// Checkout results to the current git commit
    #[clap(alias = "co")]
    Checkout(Checkout),
    /// Create github hooks to use gbl automaticaly after commit, before and after checkout
    #[clap(
        name = "create-hooks",
        alias = "ch",
        long_about = "Create github hooks to use gbl automaticaly after commit, \
    before and after checkout \n \n\
    This command should be used after git init and glb init. \n \n\
    This will create 2 hooks file: \n\
    1. post-commit: `glb commit` will be launched after git commit \n\
    2. post-checkout: `gbl checkout` will be launched after git checkout
     "
    )]
    CreateHooks(CreateHooks),
    /// Remove the post-checkout and the post-commit hooks
    #[clap(name = "delete-hooks", alias = "dh")]
    DeleteHooks,
    /// Show differences between two commits of the `results` folder
    Diff(Diff),
    /// Mount an old file/directory from one or multiple archive named afert
    /// git commits into the .mount folder inside de project directory.
    Mount(Mount),
    /// Unmount everything in the folder .mount
    Umount,
}

#[derive(Debug, Args)]
struct Init {
    /// If specified, hooks are created inside `.git/hooks repository`
    #[clap(takes_value = false, short = 'H', long)]
    hooks: bool,
    /// The compression to use automatically at each commit if hooks are created
    #[clap(short, long, default_value = "lz4")]
    compression: String,
    /// The checkout mode used by gblk automatically after a git checkout: soft or hard.
    /// This option is only used if hooks are created.

    /// The hard mode will delete every file in your results folder and extract
    /// those corresponding to the commit targeted by the checkout.
    ///
    /// The soft mode will only update files that existed in the targeted checkout
    #[clap(short, long, default_value = "hard")]
    mode: String,
}

#[derive(Debug, Args)]
struct Commit {
    /// The compression used to save the results folder (no, lz4, zstd, zlib or lzma)
    #[clap(short, long, default_value = "lz4")]
    compression: String,
    /// Use this flag to update the content of the current commit archive if it has changed
    #[clap(takes_value = false, short, long)]
    update: bool,
    /// Use this flag to revert your results folder like it was before for this commit
    #[clap(takes_value = false, short, long)]
    revert: bool,
}

#[derive(Debug, Args)]
struct List {
    /// consider first N archives
    #[clap(short, long, default_value_t = 0)]
    first: i32,
    /// consider last N archives
    #[clap(short, long, default_value_t = 0)]
    last: i32,
    /// If set list the files in this archive
    #[clap(short, long, default_value = "")]
    archive: String,
}

#[derive(Debug, Args)]
struct Checkout {
    /// The checkout mode: hard or soft
    ///
    /// The hard mode will delete every file in your results folder and extract
    /// those corresponding to the commit targeted by the checkout.
    ///
    /// The soft mode will only update files that existed in the targeted checkout
    #[clap(short, long, default_value = "soft")]
    mode: String,
}

#[derive(Debug, Args)]
struct CreateHooks {
    /// The compression that will automatically be used after each commit
    #[clap(short, long, default_value = "lz4")]
    compression: String,
    /// The checkout mode used by gblk automatically after a git checkout: soft or hard.

    /// The hard mode will delete every file in your results folder and extract
    /// those corresponding to the commit targeted by the checkout.
    ///
    /// The soft mode will only update files that existed in the targeted checkout
    #[clap(short, long, default_value = "hard")]
    mode: String,
}

#[derive(Debug, Args)]
struct Diff {
    /// The SHA1 of a commit
    commit1: String,
    /// The SHA1 of another commit.
    /// If you leave this blank,
    /// it will check the different between the commit1 and
    ///  your current result folder
    commit2: Option<String>,
}

#[derive(Debug, Args)]
struct Mount {
    /// Commit name, sh: Glob is supported. This is an optional parameter: if
    /// not set then all commit archives will be mounted into the .mount directory
    #[clap(short, long)]
    commit: Option<String>,
    /// The file/directory to extract. This is an optional parameter. If not set
    /// then all files in the archive will be displayed
    #[clap(short, long)]
    path: Option<String>,
    /// If set, displays the .mount directory in 'version view'.
    ///
    /// - Normal view: The `.mount` directory contains a subfolder with the name
    ///   of archives.
    ///
    /// - Version view: The `.mount` directory contains the results folder and
    ///   every file within it becomes a directory storing every version of
    ///   that file
    #[clap(short, long)]
    versions: bool,
    /// Displays the differences between two files mounted corresponding to the
    /// given path.
    ///
    /// Note that if only one file is recovered then, the other is taken from
    /// the current result folder
    ///
    /// This option is deactivated when used with --diff
    #[clap(short, long)]
    diff: bool,
    /// Consider last N archive after  other filter were applied
    #[clap(short, long)]
    last: Option<u8>,
}

fn main() {
    let args = Cli::parse();

    match args.commands {
        Commands::Init(init) => {
            init::init_and_hook(init.hooks, &init.compression, &init.mode);
        }
        Commands::Commit(commit) => {
            mount::umount_archive(true);
            if commit.revert {
                if commit.revert == commit.update {
                    eprintln!("Error: You moust choose between --revert and --update option")
                }
            }
            if !commit.revert {
                commit::commit(commit.compression, String::from(""), commit.update);
            } else {
                commit::revert_commit();
            }
        }
        Commands::List(list) => {
            list::borg_list(list.first, list.last, &list.archive);
        }
        Commands::PreCo => {
            mount::umount_archive(true);
            checkout::prepare_checkout();
        }
        Commands::Checkout(co) => {
            mount::umount_archive(true);
            checkout::checkout(&co.mode);
        }
        Commands::CreateHooks(ch) => {
            create_hooks::create_hooks(&ch.compression, &ch.mode);
        }
        Commands::Diff(diff) => {
            diff::compute_diff(&diff.commit1, &diff.commit2);
        }
        Commands::DeleteHooks => {
            create_hooks::delete_hooks();
        }
        Commands::Mount(mount) => {
            mount::umount_archive(true);
            mount::mount_archive(
                &mount.commit,
                &mount.path,
                mount.versions,
                mount.diff,
                mount.last,
            );
        }
        Commands::Umount => {
            mount::umount_archive(false);
        }
    }
}