/*
 * Copyright (c) 2022, Able <able@ablecorp.us>
 *
 * SPDX-License-Identifier: MPL-2.0
 */

use crate::arch::drivers::sysinfo::master;
use crate::arch::interrupts::{reset_pit_for_cpu, set_pit_2};
use crate::devices::pci::brute_force_scan;
use crate::image::mono_bitmap::bruh;
use crate::systeminfo::{KERNEL_VERSION, RELEASE_TYPE};
use crate::time::fetch_time;
use crate::KERNEL_STATE;
use crate::{
    arch::shutdown, filesystem::FILE_SYSTEM, rhai_shell::KEYBUFF, vterm::VTerm,
    wasm_jumploader::run_program,
};

use acpi::{AcpiTables, PlatformInfo};
use cpuio::{inb, outb};
use ext2::fs::sync::{DirectoryEntry, Synced};
use ext2::{fs::Ext2, sector::Size1024};
use genfs::{Fs, OpenOptions};
use kernel::allocator::ALLOCATOR;
use spin::Lazy;
use x86_64::instructions::interrupts::{disable, enable};

pub const BANNER_WIDTH: &str =
    "================================================================================";

// TODO: move to a better place
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AcpiStruct {}
impl acpi::AcpiHandler for AcpiStruct {
    unsafe fn map_physical_region<T>(
        &self,
        physical_address: usize,
        size: usize,
    ) -> acpi::PhysicalMapping<Self, T> {
        info!("PHYS ADDR: {:?}", physical_address);
        info!("Size: {:?}", size);
        todo!("map_physical_region");
    }
    fn unmap_physical_region<T>(_region: &acpi::PhysicalMapping<Self, T>) {
        todo!("unmap_physical_region");
    }
}

pub static TERM: Lazy<spin::Mutex<VTerm>> = Lazy::new(|| spin::Mutex::new(VTerm::new()));

#[derive(Debug)]
pub struct Path {
    pub path: Vec<String>,
}

impl Path {
    pub fn new(path: String) -> Self {
        let mut path_vec_string = vec![];

        for part in path.split(&['\\', '/'][..]) {
            path_vec_string.push(part.to_string());
        }

        Path {
            path: path_vec_string,
        }
    }
}

/// Experimental scratchpad for testing.
pub fn scratchpad() {
    // bruh();
    // panic!(":>");

    // for c in 0..144_697 {
    // trace!("{:?}", char::from_u32(c));
    // }

    // bruh();
    for x in brute_force_scan() {
        println!("{}", x);
    }

    disable();
    let tick_time = fetch_time();
    let hostname = &KERNEL_STATE.lock().hostname;

    let allocator = ALLOCATOR.lock();
    let size = allocator.size();
    let used = allocator.used();
    drop(allocator);
    enable();

    println!(
        "{}
    ,-------.        OS: \0BLUE\0AbleOS\0RESET\0
  ,'\\  _   _`.       Host: \0PINK\0{}\0RESET\0
 /   \\)_)-)_)-\\      Kernel: \0RED\0AKern-{}-v{}\0RESET\0
:              :     Uptime: \0GREEN\0{}\0RESET\0
\\              /     Packages: None
 \\            /      Shell: BuiltinShell
  `.        ,'       Resolution: 640x480
    `.    ,'         Terminal: VGABuffer
      `.,'           CPU: {}
       /\\`.   ,-._   GPU: VGA Compatible 
           `-'       Memory: {}/{}
{}",
        // include_str!("../assets/balloon.txt"),
        // kstate.hostname,
        BANNER_WIDTH,
        hostname,
        RELEASE_TYPE,
        KERNEL_VERSION,
        tick_time,
        master().unwrap().brand_string().unwrap(),
        // "",
        // mem
        used,
        size,
        BANNER_WIDTH
    );

    real_shell();
}

pub fn acpi() {
    let acpi_handler = AcpiStruct {};
    let _table;
    unsafe {
        _table = AcpiTables::search_for_rsdp_bios(acpi_handler);
    }
    match _table.unwrap().platform_info().unwrap() {
        PlatformInfo {
            power_profile,
            interrupt_model,
            ..
        } => {
            info!("{:?}", power_profile);
            info!("{:?}", interrupt_model);
            // info!("{:?}", processor_info.unwrap());
            // info!("{:?}", pm_timer.unwrap());
        }
    }
}

pub fn real_shell() {
    let prompt = "-> ";

    let _current_dir = "/".to_string();
    let current_user = "able".to_string();

    let mut buf = String::new();
    print!("{}", prompt);
    // panic!(":<");
    loop {
        match x86_64::instructions::interrupts::without_interrupts(|| KEYBUFF.lock().pop()) {
            Some('\n') => {
                // panic!();
                println!();
                // match engine.eval_with_scope::<rhai::Dynamic>(&mut scope, &buf) {
                // Ok(o) => println!("{o}"),

                // Err(e) => println!("Eval error: {e}"),
                // };
                if !buf.is_empty() {
                    command_parser(current_user.clone(), buf.clone());
                }

                buf.clear();
                print!("{}", prompt);
            }
            Some('\u{8}') => {
                print!("\u{8}");

                buf.pop();
            }

            Some('\u{0009}') => {
                buf.push(' ');
                buf.push(' ');
                buf.push(' ');
                buf.push(' ');
            }

            Some(chr) => {
                buf.push(chr);
                print!("{}", chr);
            }
            None => {
                // trace!("{}", buf);
            }
        }
    }
}

pub fn command_parser(user: String, command: String) {
    let fs = &*FILE_SYSTEM.lock();
    let mut iter = command.split_whitespace();

    // TODO: update the open() function to take either a ableOS path or a b"/" type path
    let current_path = Path::new("/home/able".to_string());
    trace!("Current path: {:?}", current_path);
    let current_path = b"/home/able/";

    let bin_name = iter.next().unwrap();

    match bin_name {
        // note: able asked for rhaish to stay in the repo but will be removed
        //       in the future so just comment it out for now
        // "rhai" => {
        //     drop(fs);
        //     shell();
        // }
        "list" | "ls" => {
            for dir_entry in list_files_in_dir(fs, current_path) {
                println!("{}", dir_entry.file_name_string());
            }
        }

        "echo" => {
            echo_file(iter.next().unwrap().to_string(), fs);
        }

        "quit" => shutdown(),

        _ => {
            let mut options = OpenOptions::new();
            options.read(true);
            let file = {
                let path = format!("/home/{user}/bins/{bin_name}.wasm");
                if let Ok(file) = fs.open(&path.as_bytes(), &options) {
                    file
                } else {
                    let path = format!("/shared/bins/{bin_name}.wasm");
                    if let Ok(file) = fs.open(&path.as_bytes(), &options) {
                        file
                    } else {
                        let path = format!("/system/bins/{bin_name}.wasm");
                        match fs.open(&path.as_bytes(), &options) {
                            Ok(file) => file,
                            Err(error) => {
                                trace!("{:?}", error);
                                println!("No such binary: {}", bin_name);
                                error!("No such binary: {}", bin_name);
                                return;
                            }
                        }
                    }
                }
            };

            let mut binary = vec![];
            file.read_to_end(&mut binary).unwrap();

            let args = iter.collect::<Vec<&str>>();
            println!("{:?}", args);
            run_program(&binary);
        }
    }
}

pub fn sound(n_frequency: u32) {
    let div: u32;
    let tmp: u8;

    div = 1193180 / n_frequency;
    unsafe {
        outb(0xb6, 0x43);

        set_pit_2(div);

        // And play the sound using the PC speaker
        tmp = inb(0x61);
        if tmp != (tmp | 3) {
            outb(tmp | 3, 0x61);
        }
    }
}

pub fn sound_off() {
    unsafe {
        let tmp = inb(0x61) & 0xFC;
        outb(tmp, 0x61)
    };
    reset_pit_for_cpu();
}

pub fn list_files_in_dir(
    fs: &Synced<Ext2<Size1024, Vec<u8>>>,
    _path: &[u8],
) -> Vec<DirectoryEntry> {
    let mut entry_list = vec![];

    let dirr = fs.read_dir(b"/").unwrap();
    for dir_entry in dirr {
        entry_list.push(dir_entry.unwrap());
    }

    entry_list
}

pub static CURRENT_DIR: Lazy<spin::Mutex<String>> = Lazy::new(|| spin::Mutex::new("/".to_string()));

pub fn echo_file(path: String, fs: &Synced<Ext2<Size1024, Vec<u8>>>) {
    let mut current_dir = CURRENT_DIR.lock();

    current_dir.push_str(&path);

    let file = fs
        .open(current_dir.as_bytes(), OpenOptions::new().read(true))
        .unwrap();

    if file.is_dir() {
        // println!("{} is a directory", path);
    } else {
        let mut file_contents = Vec::new();

        let _ret = file.read_to_end(&mut file_contents).unwrap();

        let file_contents_str = String::from_utf8_lossy(&file_contents);

        println!("{}", file_contents_str);
    }
}