use crate::arch;
use crate::arch::mm::VirtAddr;
use crate::arch::percore::*;
use crate::arch::processor::msb;
use crate::arch::scheduler::{TaskStacks, TaskTLS};
use crate::scheduler::CoreId;
use alloc::collections::{LinkedList, VecDeque};
use alloc::rc::Rc;
use core::cell::RefCell;
use core::convert::TryInto;
use core::fmt;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum TaskStatus {
	TaskInvalid,
	TaskReady,
	TaskRunning,
	TaskBlocked,
	TaskFinished,
	TaskIdle,
}
#[derive(Clone, Copy, PartialEq)]
pub enum WakeupReason {
	Custom,
	Timer,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub struct TaskId(u32);
impl TaskId {
	pub const fn into(self) -> u32 {
		self.0
	}
	pub const fn from(x: u32) -> Self {
		TaskId(x)
	}
}
impl fmt::Display for TaskId {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "{}", self.0)
	}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub struct Priority(u8);
impl Priority {
	pub const fn into(self) -> u8 {
		self.0
	}
	pub const fn from(x: u8) -> Self {
		Priority(x)
	}
}
impl fmt::Display for Priority {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "{}", self.0)
	}
}
#[allow(dead_code)]
pub const HIGH_PRIO: Priority = Priority::from(3);
#[allow(dead_code)]
pub const NORMAL_PRIO: Priority = Priority::from(2);
#[allow(dead_code)]
pub const LOW_PRIO: Priority = Priority::from(1);
#[allow(dead_code)]
pub const IDLE_PRIO: Priority = Priority::from(0);
pub const NO_PRIORITIES: usize = 31;
#[derive(Copy, Clone, Debug)]
pub struct TaskHandle {
	id: TaskId,
	priority: Priority,
	core_id: CoreId,
}
impl TaskHandle {
	pub fn new(id: TaskId, priority: Priority, core_id: CoreId) -> Self {
		Self {
			id,
			priority,
			core_id,
		}
	}
	pub fn get_core_id(&self) -> CoreId {
		self.core_id
	}
	pub fn get_id(&self) -> TaskId {
		self.id
	}
	pub fn get_priority(&self) -> Priority {
		self.priority
	}
}
pub struct TaskHandlePriorityQueue {
	queues: [Option<VecDeque<TaskHandle>>; NO_PRIORITIES],
	prio_bitmap: u64,
}
impl TaskHandlePriorityQueue {
	
	pub const fn new() -> Self {
		Self {
			queues: [
				None, None, None, None, None, None, None, None, None, None, None, None, None, None,
				None, None, None, None, None, None, None, None, None, None, None, None, None, None,
				None, None, None,
			],
			prio_bitmap: 0,
		}
	}
	
	pub fn push(&mut self, task: TaskHandle) {
		let i = task.priority.into() as usize;
		
		self.prio_bitmap |= (1 << i) as u64;
		if let Some(queue) = &mut self.queues[i] {
			queue.push_back(task);
		} else {
			let mut queue = VecDeque::new();
			queue.push_back(task);
			self.queues[i] = Some(queue);
		}
	}
	fn pop_from_queue(&mut self, queue_index: usize) -> Option<TaskHandle> {
		if let Some(queue) = &mut self.queues[queue_index] {
			let task = queue.pop_front();
			if queue.is_empty() {
				self.prio_bitmap &= !(1 << queue_index as u64);
			}
			task
		} else {
			None
		}
	}
	
	pub fn pop(&mut self) -> Option<TaskHandle> {
		if let Some(i) = msb(self.prio_bitmap) {
			return self.pop_from_queue(i as usize);
		}
		None
	}
	
	pub fn remove(&mut self, task: TaskHandle) {
		let queue_index = task.priority.into() as usize;
		
		if let Some(queue) = &mut self.queues[queue_index] {
			let mut i = 0;
			while i != queue.len() {
				if queue[i].id == task.id {
					queue.remove(i);
				} else {
					i += 1;
				}
			}
			if queue.is_empty() {
				self.prio_bitmap &= !(1 << queue_index as u64);
			}
		}
	}
}
struct QueueHead {
	head: Option<Rc<RefCell<Task>>>,
	tail: Option<Rc<RefCell<Task>>>,
}
impl QueueHead {
	pub const fn new() -> Self {
		Self {
			head: None,
			tail: None,
		}
	}
}
impl Default for QueueHead {
	fn default() -> Self {
		Self {
			head: None,
			tail: None,
		}
	}
}
pub struct PriorityTaskQueue {
	queues: [QueueHead; NO_PRIORITIES],
	prio_bitmap: u64,
}
impl PriorityTaskQueue {
	
	pub const fn new() -> PriorityTaskQueue {
		PriorityTaskQueue {
			queues: [
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
				QueueHead::new(),
			],
			prio_bitmap: 0,
		}
	}
	
	pub fn push(&mut self, task: Rc<RefCell<Task>>) {
		let i = task.borrow().prio.into() as usize;
		
		self.prio_bitmap |= (1 << i) as u64;
		match self.queues[i].tail {
			None => {
				
				self.queues[i].head = Some(task.clone());
				let mut borrow = task.borrow_mut();
				borrow.next = None;
				borrow.prev = None;
			}
			Some(ref mut tail) => {
				
				tail.borrow_mut().next = Some(task.clone());
				let mut borrow = task.borrow_mut();
				borrow.next = None;
				borrow.prev = Some(tail.clone());
			}
		}
		self.queues[i].tail = Some(task);
	}
	fn pop_from_queue(&mut self, queue_index: usize) -> Option<Rc<RefCell<Task>>> {
		let new_head;
		let task;
		match self.queues[queue_index].head {
			None => {
				return None;
			}
			Some(ref mut head) => {
				let mut borrow = head.borrow_mut();
				match borrow.next {
					Some(ref mut nhead) => {
						nhead.borrow_mut().prev = None;
					}
					None => {}
				}
				new_head = borrow.next.clone();
				borrow.next = None;
				borrow.prev = None;
				task = head.clone();
			}
		}
		self.queues[queue_index].head = new_head;
		if self.queues[queue_index].head.is_none() {
			self.queues[queue_index].tail = None;
			self.prio_bitmap &= !(1 << queue_index as u64);
		}
		Some(task)
	}
	
	pub fn pop(&mut self) -> Option<Rc<RefCell<Task>>> {
		if let Some(i) = msb(self.prio_bitmap) {
			return self.pop_from_queue(i as usize);
		}
		None
	}
	
	pub fn pop_with_prio(&mut self, prio: Priority) -> Option<Rc<RefCell<Task>>> {
		if let Some(i) = msb(self.prio_bitmap) {
			if i >= u64::from(prio.into()) {
				return self.pop_from_queue(i as usize);
			}
		}
		None
	}
	
	pub fn get_highest_priority(&self) -> Priority {
		if let Some(i) = msb(self.prio_bitmap) {
			Priority::from(i.try_into().unwrap())
		} else {
			IDLE_PRIO
		}
	}
}
#[cfg_attr(target_arch = "x86_64", repr(align(128)))]
#[cfg_attr(not(target_arch = "x86_64"), repr(align(64)))]
pub struct Task {
	
	pub id: TaskId,
	
	pub status: TaskStatus,
	
	pub prio: Priority,
	
	pub last_stack_pointer: VirtAddr,
	
	pub user_stack_pointer: VirtAddr,
	
	pub last_fpu_state: arch::processor::FPUState,
	
	pub core_id: CoreId,
	
	pub stacks: TaskStacks,
	
	pub next: Option<Rc<RefCell<Task>>>,
	
	pub prev: Option<Rc<RefCell<Task>>>,
	
	pub tls: Option<TaskTLS>,
	
	pub last_wakeup_reason: WakeupReason,
	
	#[cfg(feature = "newlib")]
	pub lwip_errno: i32,
}
pub trait TaskFrame {
	
	fn create_stack_frame(&mut self, func: extern "C" fn(usize), arg: usize);
}
impl Task {
	pub fn new(
		tid: TaskId,
		core_id: CoreId,
		task_status: TaskStatus,
		task_prio: Priority,
		stack_size: usize,
	) -> Task {
		debug!("Creating new task {} on core {}", tid, core_id);
		Task {
			id: tid,
			status: task_status,
			prio: task_prio,
			last_stack_pointer: VirtAddr(0u64),
			user_stack_pointer: VirtAddr(0u64),
			last_fpu_state: arch::processor::FPUState::new(),
			core_id,
			stacks: TaskStacks::new(stack_size),
			next: None,
			prev: None,
			tls: None,
			last_wakeup_reason: WakeupReason::Custom,
			#[cfg(feature = "newlib")]
			lwip_errno: 0,
		}
	}
	pub fn new_idle(tid: TaskId, core_id: CoreId) -> Task {
		debug!("Creating idle task {}", tid);
		Task {
			id: tid,
			status: TaskStatus::TaskIdle,
			prio: IDLE_PRIO,
			last_stack_pointer: VirtAddr(0u64),
			user_stack_pointer: VirtAddr(0u64),
			last_fpu_state: arch::processor::FPUState::new(),
			core_id,
			stacks: TaskStacks::from_boot_stacks(),
			next: None,
			prev: None,
			tls: None,
			last_wakeup_reason: WakeupReason::Custom,
			#[cfg(feature = "newlib")]
			lwip_errno: 0,
		}
	}
	pub fn clone(tid: TaskId, core_id: CoreId, task: &Task) -> Task {
		debug!("Cloning task {} from task {}", tid, task.id);
		Task {
			id: tid,
			status: TaskStatus::TaskReady,
			prio: task.prio,
			last_stack_pointer: VirtAddr(0u64),
			user_stack_pointer: VirtAddr(0u64),
			last_fpu_state: arch::processor::FPUState::new(),
			core_id,
			stacks: task.stacks.clone(),
			next: None,
			prev: None,
			tls: task.tls.clone(),
			last_wakeup_reason: task.last_wakeup_reason,
			#[cfg(feature = "newlib")]
			lwip_errno: 0,
		}
	}
}
struct BlockedTask {
	task: Rc<RefCell<Task>>,
	wakeup_time: Option<u64>,
}
pub struct BlockedTaskQueue {
	list: LinkedList<BlockedTask>,
}
impl BlockedTaskQueue {
	pub const fn new() -> Self {
		Self {
			list: LinkedList::new(),
		}
	}
	fn wakeup_task(task: Rc<RefCell<Task>>, reason: WakeupReason) {
		{
			let mut borrowed = task.borrow_mut();
			debug!(
				"Waking up task {} on core {}",
				borrowed.id, borrowed.core_id
			);
			assert!(
				borrowed.core_id == core_id(),
				"Try to wake up task {} on the wrong core {} != {}",
				borrowed.id,
				borrowed.core_id,
				core_id()
			);
			assert!(
				borrowed.status == TaskStatus::TaskBlocked,
				"Trying to wake up task {} which is not blocked",
				borrowed.id
			);
			borrowed.status = TaskStatus::TaskReady;
			borrowed.last_wakeup_reason = reason;
		}
		
		core_scheduler().ready_queue.push(task);
	}
	
	pub fn add(&mut self, task: Rc<RefCell<Task>>, wakeup_time: Option<u64>) {
		{
			
			let mut borrowed = task.borrow_mut();
			debug!("Blocking task {}", borrowed.id);
			assert_eq!(
				borrowed.status,
				TaskStatus::TaskRunning,
				"Trying to block task {} which is not running",
				borrowed.id
			);
			borrowed.status = TaskStatus::TaskBlocked;
		}
		let new_node = BlockedTask { task, wakeup_time };
		
		if let Some(wt) = wakeup_time {
			let mut first_task = true;
			let mut cursor = self.list.cursor_front_mut();
			while let Some(node) = cursor.current() {
				let node_wakeup_time = node.wakeup_time;
				if node_wakeup_time.is_none() || wt < node_wakeup_time.unwrap() {
					cursor.insert_before(new_node);
					
					
					if first_task {
						arch::set_oneshot_timer(wakeup_time);
					}
					return;
				}
				first_task = false;
				cursor.move_next();
			}
		} else {
			
			self.list.push_back(new_node);
		}
	}
	
	pub fn custom_wakeup(&mut self, task: TaskHandle) {
		let mut first_task = true;
		let mut cursor = self.list.cursor_front_mut();
		
		while let Some(node) = cursor.current() {
			if node.task.borrow().id == task.get_id() {
				
				Self::wakeup_task(node.task.clone(), WakeupReason::Custom);
				cursor.remove_current();
				
				
				if first_task {
					if let Some(next_node) = cursor.current() {
						arch::set_oneshot_timer(next_node.wakeup_time);
					}
				}
				break;
			}
			first_task = false;
			cursor.move_next();
		}
	}
	
	
	
	
	pub fn handle_waiting_tasks(&mut self) {
		
		let time = arch::processor::get_timer_ticks();
		let mut cursor = self.list.cursor_front_mut();
		
		while let Some(node) = cursor.current() {
			
			
			let node_wakeup_time = node.wakeup_time;
			if node_wakeup_time.is_none() || time < node_wakeup_time.unwrap() {
				
				
				arch::set_oneshot_timer(node_wakeup_time);
				break;
			}
			
			Self::wakeup_task(node.task.clone(), WakeupReason::Timer);
			cursor.remove_current();
		}
	}
}