// tfclient, an open source client for the Defined Networking nebula management protocol.
//     Copyright (C) 2023 c0repwn3r
//
//     This program is free software: you can redistribute it and/or modify
//     it under the terms of the GNU General Public License as published by
//     the Free Software Foundation, either version 3 of the License, or
//     (at your option) any later version.
//
//     This program is distributed in the hope that it will be useful,
//     but WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//     GNU General Public License for more details.
//
//     You should have received a copy of the GNU General Public License
//     along with this program.  If not, see <https://www.gnu.org/licenses/>.

pub mod embedded_nebula;
pub mod dirs;
pub mod util;

pub mod nebula_bin {
    include!(concat!(env!("OUT_DIR"), "/nebula.bin.rs"));
}
pub mod nebula_cert_bin {
    include!(concat!(env!("OUT_DIR"), "/nebula_cert.bin.rs"));
}


use std::fs;

use clap::{Parser, ArgAction, Subcommand};
use log::{error, info};
use simple_logger::SimpleLogger;
use crate::dirs::get_data_dir;
use crate::embedded_nebula::{run_embedded_nebula, run_embedded_nebula_cert};

#[derive(Parser)]
#[command(author = "c0repwn3r", version, about, long_about = None)]
#[clap(disable_version_flag = true)]
struct Cli {
    #[arg(short = 'v', long = "version", action = ArgAction::SetTrue)]
    #[clap(global = true)]
    version: bool,

    #[command(subcommand)]
    subcommand: Commands
}

#[derive(Subcommand)]
enum Commands {
    /// Run the `nebula` binary. This is useful if you want to do debugging with tfclient's internal nebula.
    RunNebula {
        /// Arguments to pass to the `nebula` binary
        #[clap(trailing_var_arg=true, allow_hyphen_values=true)]
        args: Vec<String>
    },
    /// Run the `nebula-cert` binary. This is useful if you want to mess with certificates. Note: tfclient does not actually use nebula-cert for certificate operations, and instead uses trifid-pki internally
    RunNebulaCert {
        /// Arguments to pass to the `nebula-cert` binary
        #[clap(trailing_var_arg=true, allow_hyphen_values=true)]
        args: Vec<String>
    },
    /// Clear any cached data that tfclient may have added
    ClearCache {}
}

fn main() {
    SimpleLogger::new().init().unwrap();

    let args = Cli::parse();

    if args.version {
        print_version();
    }

    match args.subcommand {
        Commands::RunNebula { args } => {
            match run_embedded_nebula(&args) {
                Ok(mut c) => {
                    match c.wait() {
                        Ok(stat) => {
                            match stat.code() {
                                Some(code) => {
                                    if code != 0 {
                                        error!("Nebula process exited with nonzero status code {}", code);
                                    }
                                    std::process::exit(code);
                                },
                                None => {
                                    info!("Nebula process terminated by signal");
                                    std::process::exit(0);
                                }
                            }
                        },
                        Err(e) => {
                            error!("Unable to wait for child to exit: {}", e);
                            std::process::exit(1);
                        }
                    }
                },
                Err(e) => {
                    error!("Unable to start nebula binary: {}", e);
                    std::process::exit(1);
                }
            }
        },
        Commands::ClearCache { .. } => {
            let data_dir = match get_data_dir() {
                Some(dir) => dir,
                None => {
                    error!("Unable to get platform-specific data dir");
                    std::process::exit(1);
                }
            };
            match fs::remove_dir_all(&data_dir) {
                Ok(_) => (),
                Err(e) => {
                    error!("Unable to delete data dir: {}", e);
                    std::process::exit(0);
                }
            }
            info!("Removed data dir {}", data_dir.as_path().display());

            info!("Removed all cached data.");
            std::process::exit(0);
        },
        Commands::RunNebulaCert { args } => {
            match run_embedded_nebula_cert(&args) {
                Ok(mut c) => {
                    match c.wait() {
                        Ok(stat) => {
                            match stat.code() {
                                Some(code) => {
                                    if code != 0 {
                                        error!("nebula-cert process exited with nonzero status code {}", code);
                                    }
                                    std::process::exit(code);
                                },
                                None => {
                                    info!("nebula-cert process terminated by signal");
                                    std::process::exit(0);
                                }
                            }
                        },
                        Err(e) => {
                            error!("Unable to wait for child to exit: {}", e);
                            std::process::exit(1);
                        }
                    }
                },
                Err(e) => {
                    error!("Unable to start nebula-cert binary: {}", e);
                    std::process::exit(1);
                }
            }
        }
    }
}

fn print_version() {
    println!("tfclient v{} linked to trifid-pki v{}, embedding nebula v{} and nebula-cert v{}", env!("CARGO_PKG_VERSION"), trifid_pki::TRIFID_PKI_VERSION, crate::nebula_bin::NEBULA_VERSION, crate::nebula_cert_bin::NEBULA_CERT_VERSION);
}