/*
 * 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::ide::{Channel, Drive};
use crate::devices::pci::{PciDevice, PCI_DEVICES};
use crate::filesystem;
use crate::filesystem::vfs::VFS;
use crate::systeminfo::{KERNEL_VERSION, RELEASE_TYPE};
use crate::time::fetch_time;
use crate::{
    arch::shutdown, rhai_shell::KEYBUFF, vterm::VTerm, wasm_jumploader::run_program, KERNEL_STATE,
};

use acpi::{AcpiTables, PlatformInfo};
use cpuio::{inb, outb};
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(|| {
//     trace!("mutex");

//     loop {}
//     let mutex = spin::Mutex::new({
//         trace!("vterm");
//         let vterm = VTerm::new();
//         trace!("vterm-done");
//         vterm
//     });
//     trace!("mutex-done");
//     mutex
// });

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

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

    // bruh();

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

    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
    );

    let pci_ide_device = {
        let pci_devices = PCI_DEVICES.lock();
        pci_devices
            .iter()
            .find_map(|device_ref| {
                let device = device_ref.lock();
                if let PciDevice::Ide(_) = &*device {
                    Some(device_ref.clone())
                } else {
                    None
                }
            })
            .unwrap()
    };

    {
        let mut pci_ide_device = pci_ide_device.lock();
        if let PciDevice::Ide(device) = &mut *pci_ide_device {
            let mut first_sector = Vec::with_capacity(512);
            device
                .read(Channel::Primary, Drive::Master, 0, 1, &mut first_sector)
                .unwrap();
            trace!("IDE Primary/Master sector 0: {first_sector:?}");
        }
    }

    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) {
    unimplemented!()
}

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 echo_file(path: String) {
    let mut current_path = String::from("/");
    current_path.push_str(&path);
    let mut vfs = VFS.lock();
    let handle = vfs.resolve(&current_path).unwrap();
    let file = vfs.fs_node(handle).unwrap();

    if file.is_dir() {
        // println!("{} is a directory", path);
    } else {
        let mut file_contents = Vec::new();
        file.read(0, file.size(), &mut file_contents).unwrap();
        let file_contents_str = String::from_utf8_lossy(&file_contents);
        println!("{}", file_contents_str);
    }
}
