#![deny(warnings)]
#![feature(exit_status_error)]

use std::{fs, io::stdout, path::PathBuf, process::ExitCode};

use build::CheckAction;
use clap::{Parser, Subcommand};
use env::{Arch, Board, Profile, XTaskConfig};
use error::Error;

#[macro_use]
pub mod util;

pub mod build;
pub mod check;
pub mod env;
pub mod error;
pub mod qemu;

#[derive(Parser)]
struct Args {
    #[clap(long, env, help = "Use --release profile for built components")]
    release: bool,
    #[clap(
        short,
        long,
        env,
        default_value_t,
        value_enum,
        help = "Target architecture"
    )]
    arch: Arch,
    #[clap(short, long, env, default_value_t, value_enum, help = "Target board")]
    board: Board,
    #[clap(
        short,
        long,
        help = "Do a clean userspace rebuild (e.g. toolchain has changed)"
    )]
    clean_userspace: bool,
    #[clap(short, long, help = "Be verbose where possible")]
    verbose: bool,

    #[clap(short = 'C', long, help = "Configuration path")]
    config_path: Option<PathBuf>,

    #[clap(subcommand)]
    action: Option<SubArgs>,
}

#[derive(Default, Subcommand)]
enum SubArgs {
    #[default]
    #[clap(about = "Build the OS")]
    Build,
    #[clap(about = "Run `cargo check` on components")]
    Check,
    #[clap(about = "Run `cargo clippy` on components")]
    Clippy,
    #[clap(about = "Run `cargo test` on components")]
    Test,
    #[clap(about = "Clean all build artifacts")]
    Clean {
        #[clap(short, long, help = "Clean toolchain as well")]
        toolchain: bool,
        #[clap(short, long, help = "Clean cloned repositories")]
        repos: bool,
    },

    // #[clap(about = "Print `git status` for the components", alias = "gst")]
    // GitStatus,
    #[clap(about = "Run the OS image in QEMU")]
    Qemu {
        #[clap(
            short,
            long,
            env,
            help = "Override the default QEMU binary for the target"
        )]
        qemu: Option<PathBuf>,
        #[clap(short, long, help = "Disk image to use for persistent data")]
        disk: Option<PathBuf>,
        #[clap(help = "Extra arguments for QEMU")]
        extra_args: Vec<String>,
    },

    #[clap(about = "Build the Yggdrasil OS Rust toolchain")]
    Toolchain { branch: Option<String> },
    #[clap(about = "Print the host triple")]
    HostTriple,
    #[clap(about = "Print information about the build environment and configuration")]
    Env,
}

fn run(args: Args) -> Result<(), Error> {
    let action = args.action.unwrap_or_default();

    let workspace_root = std::env::current_dir()?;
    let profile = if args.release {
        Profile::Release
    } else {
        Profile::Debug
    };

    let default_config = workspace_root.join("xtask.toml");
    let config_path = args.config_path.as_ref().or_else(|| {
        if default_config.exists() {
            Some(&default_config)
        } else {
            None
        }
    });

    let config = if let Some(path) = config_path {
        toml::from_str(&fs::read_to_string(path)?)
            .map_err(|e| Error::TomlParseError(path.to_owned(), e))?
    } else {
        XTaskConfig::default()
    };
    let env = env::BuildEnv::new(
        config,
        args.verbose,
        profile,
        args.arch,
        args.board,
        workspace_root,
    );

    if args.clean_userspace && !matches!(&action, SubArgs::Clean { .. }) {
        build::clean_userspace(&env)?;
    }

    match action {
        SubArgs::Build => build::build_all(&env).map(|_| ()),
        SubArgs::Check => build::check_all(env, CheckAction::Check),
        SubArgs::Clippy => build::check_all(env, CheckAction::Clippy),
        SubArgs::Test => build::test_all(env),
        SubArgs::Clean { toolchain, repos } => build::clean_all(&env, toolchain, repos),
        // SubArgs::GitStatus => util::git_status_all(&env),
        SubArgs::Qemu {
            qemu,
            disk,
            extra_args,
        } => qemu::run(env, qemu, disk, extra_args),
        SubArgs::Toolchain { branch } => {
            let branch = branch.as_ref().unwrap_or(&env.config.toolchain.branch);
            build::build_toolchain(&env, branch)
        }
        SubArgs::HostTriple => {
            println!("{}", env.host_triple);
            Ok(())
        }
        SubArgs::Env => {
            println!("---   Build information   ---");
            println!();
            match config_path {
                Some(path) => println!(" * Using {}:", path.display()),
                None => println!(" * Using default xtask config:"),
            }
            println!();
            env.dump(&mut stdout());
            qemu::dump_config(&env, &mut stdout());
            println!("--- End build information ---");

            Ok(())
        }
    }
}

fn main() -> ExitCode {
    env_logger::builder()
        .filter_level(log::LevelFilter::Info)
        .parse_default_env()
        .init();

    let args = Args::parse();

    match run(args) {
        Ok(()) => ExitCode::SUCCESS,
        Err(Error::MissingTools(list)) => {
            eprintln!("Missing some required tools:");
            for (tool, guide) in list {
                eprintln!(" * {}: {}", tool, guide);
            }
            ExitCode::FAILURE
        }
        Err(err) => {
            eprintln!("Error: {}", err);
            ExitCode::FAILURE
        }
    }
}
