/*
* Copyright (c) 2022, Umut İnan Erdoğan <umutinanerdogan@pm.me>
* Copyright (c) 2022, able <abl3theabove@gmail.com>
*
* SPDX-License-Identifier: MPL-2.0
*/

use std::{fs, process::Command};

use colored::*;

struct Options {
   pub subcommand: Subcommand,
   pub arguments: Vec<String>,
}

enum Subcommand {
   Doc,
   Help,
   Run,
   Empty,
   /// Run all tests for all architectures
   Test,
   Unknown(String),
}

impl Subcommand {
   fn from_str<S: AsRef<str>>(str: S) -> Subcommand {
      match str.as_ref() {
         "doc" => Subcommand::Doc,
         "help" => Subcommand::Help,
         "run" | "r" => Subcommand::Run,
         "test" | "t" => Subcommand::Test,
         "" => Subcommand::Empty,
         unknown => Subcommand::Unknown(unknown.to_string()),
      }
   }
}

enum MachineType {
   X86_64,
   RiscV64,
   AArch64,
   Unknown(String),
}

fn main() {
   let options = options();

   match options.subcommand {
      Subcommand::Test => {
         Command::new("cargo")
            .args(["test", "--target=json_targets/x86_64-ableos.json"])
            .current_dir(fs::canonicalize("./ableos").unwrap())
            .status()
            .unwrap();

         // panic!("Test Infrastructure missing");
      }
      Subcommand::Doc => {
         let machine_text = options.arguments.get(0).cloned().unwrap_or_default();

         match machine(machine_text) {
            MachineType::X86_64 => {
               Command::new("cargo")
                  .args(["doc", "--open"])
                  .current_dir(fs::canonicalize("./ableos").unwrap())
                  .status()
                  .unwrap();
            }
            MachineType::RiscV64 => {
               Command::new("cargo")
                  .args(["doc", "--open", "--target=riscv64gc-unknown-none-elf"])
                  .current_dir(fs::canonicalize("./ableos").unwrap())
                  .status()
                  .unwrap();
            }
            MachineType::AArch64 => {
               Command::new("cargo")
                  .args(["doc", "--open", "--target=json_targets/aarch64-ableos.json"])
                  .current_dir(fs::canonicalize("./ableos").unwrap())
                  .status()
                  .unwrap();
            }
            MachineType::Unknown(unknown) => {
               eprintln!(
                  "{}: unknown machine type `{}`",
                  "error".red().bold(),
                  unknown.bold(),
               );
               eprintln!("expected one of x86_64, riscv64 or aarch64");
            }
         }
      }
      Subcommand::Help => help(),
      Subcommand::Run => {
         let machine_text = options.arguments.get(0).cloned().unwrap_or_default();
         let debug = options.arguments.get(1).cloned().unwrap_or_default();
         let debug = matches!(debug.as_str(), "--debug" | "--dbg" | "-d");

         match machine(machine_text) {
            MachineType::X86_64 if debug => {
               Command::new("cargo")
                  .args(["run", "--", "-S", "-gdb", "tcp:9000"])
                  .current_dir(fs::canonicalize("./ableos").unwrap())
                  .status()
                  .unwrap();
            }
            MachineType::X86_64 => {
               Command::new("cargo")
                  .args(["run", "--release"])
                  .current_dir(fs::canonicalize("./ableos").unwrap())
                  .status()
                  .unwrap();
            }
            MachineType::RiscV64 if debug => {
               eprintln!(
                  "{}: debug is not implemented for riscv64",
                  "error".red().bold()
               );
            }
            MachineType::RiscV64 => {
               Command::new("cargo")
                  .args(["build", "--release", "--target=riscv64gc-unknown-none-elf"])
                  .current_dir(fs::canonicalize("./ableos").unwrap())
                  .status()
                  .unwrap();

               Command::new("qemu-system-riscv64")
                  .args(["-machine", "virt"])
                  .args(["-cpu", "rv64"])
                  .args(["-smp", "8"])
                  .args(["-m", "128M"])
                  .arg("-bios")
                  .arg("src/arch/riscv/firmwear/opensbi-riscv64-generic-fw_jump.bin")
                  .arg("-kernel")
                  .arg("target/riscv64gc-unknown-none-elf/release/ableos")
                  .current_dir(fs::canonicalize("./ableos").unwrap())
                  .status()
                  .unwrap();
            }
            MachineType::AArch64 if debug => {
               eprintln!(
                  "{}: debug is not implemented for aarch64",
                  "error".red().bold()
               );
            }
            MachineType::AArch64 => {
               Command::new("cargo")
                  .args([
                     "build",
                     "--release",
                     "--target=json_targets/aarch64-ableos.json",
                  ])
                  .current_dir(fs::canonicalize("./ableos").unwrap())
                  .status()
                  .unwrap();

               Command::new("qemu-system-aarch64")
                  .args(["-machine", "virt"])
                  .args(["-m", "1024M"])
                  .args(["-cpu", "cortex-a53"])
                  .args(["-kernel", "target/aarch64-ableos/release/ableos"])
                  .args(["-device", "virtio-keyboard"])
                  .current_dir(fs::canonicalize("./ableos").unwrap())
                  .status()
                  .unwrap();
            }
            MachineType::Unknown(unknown) => {
               eprintln!(
                  "{}: unknown machine type `{}`",
                  "error".red().bold(),
                  unknown.bold(),
               );
               eprintln!("expected one of x86_64, riscv64 or aarch64");
            }
         }
      }
      Subcommand::Empty => {
         eprintln!("{}: no subcommand passed", "error".red().bold());
         help();
      }
      Subcommand::Unknown(unknown) => {
         eprintln!(
            "{}: unknown subcommand `{}`",
            "error".red().bold(),
            unknown.bold()
         );
         help();
      }
   }
}

fn options() -> Options {
   let subcommand = std::env::args().nth(1).unwrap_or_default();
   let arguments = std::env::args().skip(2).collect();

   Options {
      subcommand: Subcommand::from_str(subcommand),
      arguments,
   }
}

fn machine<S: AsRef<str>>(text: S) -> MachineType {
   match text.as_ref() {
      "x86" | "x86_64" => MachineType::X86_64,
      "riscv" | "riscv64" => MachineType::RiscV64,
      "arm" | "arm64" | "aarch64" => MachineType::AArch64,
      "" => {
         eprintln!(
            "{}: no machine type passed, defaulting to x86_64",
            "warning".yellow().bold()
         );
         MachineType::X86_64
      }
      unknown => MachineType::Unknown(unknown.to_string()),
   }
}

fn help() {
   todo!("`help`")
}
