use crate::{
	lock::Lock,
	task::{api as task_api, Task},
	vm::{api as vm_api, VmFlags, VmProtect, VmSpace},
};
use alloc::boxed::Box;
use core::ops::{Deref, DerefMut};
use cstr_core::{CStr, CString};
use libc::{c_char, c_void};

#[derive(Debug)]
#[repr(transparent)]
pub struct Process(*mut ProcessInner);

impl Process {
	/// Create a new pongoOS process, with an optional parent process.
	pub fn new<S>(parent: Option<Process>, name: S) -> Self
	where
		S: AsRef<str>,
	{
		let name = CString::new(name.as_ref()).expect("nul bytes in process name");
		Self::new_impl(parent, name.as_ptr() as *const _)
	}

	fn new_impl(parent: Option<Process>, name: *const c_char) -> Self {
		let parent = match parent {
			Some(s) => s.0,
			None => core::ptr::null_mut(),
		};
		let process = unsafe { api::proc_create(parent, name, 0) };
		let mut addr = 0u64;
		unsafe {
			vm_api::vm_allocate(
				(*process).vm_space,
				&mut addr as *mut _,
				0x4000,
				(VmFlags::ANYWHERE | VmFlags::NOMAP).bits(),
			);
			let phys = vm_api::ppage_alloc();
			let ins: *mut u32 = vm_api::phystokv(phys);
			core::ptr::write(ins, 0xa9bf7bfd);
			core::ptr::write(ins.offset(1), 0xd4000841);
			core::ptr::write(ins.offset(2), 0xd280002f);
			core::ptr::write(ins.offset(3), 0xd4000841);
			core::ptr::write(ins.offset(4), 0xa8c17bfd);
			core::ptr::write(ins.offset(5), 0xd65f03c0);
			vm_api::invalidate_icache();
			vm_api::vm_space_map_page_physical_prot(
				(*process).vm_space,
				addr,
				phys,
				(VmProtect::READ | VmProtect::WRITE | VmProtect::EXEC).bits(),
			);
		}
		Self(process)
	}

	/// Create a task for this process.
	pub fn create_task<F>(&self, entrypoint: F) -> Task
	where
		F: FnOnce() + 'static,
	{
		self.create_task_impl(Box::new(entrypoint))
	}

	fn create_task_impl(&self, entry: Box<dyn FnOnce()>) -> Task {
		unsafe {
			unsafe extern "C" fn thread_start() {
				let ourself = task_api::task_current();
				// Parent has injected our data now that we're unsuspended.
				let parent_task =
					Box::from_raw((*ourself).initial_state[0] as *mut Box<dyn FnOnce()>);
				parent_task();
			}
			let mut task = api::proc_create_task(self.0, thread_start);
			let p: *mut Box<dyn FnOnce()> = Box::into_raw(Box::new(entry));
			(*task).initial_state[0] = p as u64;
			// Add the task back to the scheduler queue
			task_api::task_link(task);
			Task(task)
		}
	}

	/// Get the name of the process.
	pub fn name(&self) -> &str {
		let name = unsafe { CStr::from_ptr(&self.name as *const _) };
		name.to_str().unwrap_or("")
	}

	pub fn as_ptr(&self) -> *mut ProcessInner {
		self.0
	}
}

impl AsRef<ProcessInner> for Process {
	fn as_ref(&self) -> &ProcessInner {
		unsafe { &*self.0 }
	}
}

impl AsMut<ProcessInner> for Process {
	fn as_mut(&mut self) -> &mut ProcessInner {
		unsafe { &mut *self.0 }
	}
}

impl Deref for Process {
	type Target = ProcessInner;

	fn deref(&self) -> &Self::Target {
		unsafe { &*self.0 }
	}
}

impl DerefMut for Process {
	fn deref_mut(&mut self) -> &mut Self::Target {
		unsafe { &mut *self.0 }
	}
}

impl Clone for Process {
	fn clone(&self) -> Self {
		unsafe {
			api::proc_reference(self.0);
		}
		Self(self.0)
	}
}

impl Drop for Process {
	fn drop(&mut self) {
		unsafe {
			api::proc_release(self.0);
		}
	}
}

unsafe impl Send for Process {}
unsafe impl Sync for Process {}

#[derive(Debug)]
#[repr(C)]
pub struct ProcessInner {
	refcount: u32,
	_file_table: *mut c_void,
	vm_space: *mut VmSpace,
	name: [c_char; 64],
	task_list: Task,
	lock: Lock,
}

pub mod api {
	use crate::{process::ProcessInner, task::TaskInner};
	use libc::c_char;

	extern "C" {
		pub fn proc_create(
			parent: *mut ProcessInner,
			name: *const c_char,
			flags: u32,
		) -> *mut ProcessInner;
		pub fn proc_create_task(
			process: *mut ProcessInner,
			entry: unsafe extern "C" fn(),
		) -> *mut TaskInner;
		pub fn proc_reference(process: *mut ProcessInner);
		pub fn proc_release(process: *mut ProcessInner);
	}
}
