use core::mem::MaybeUninit;

use alloc::{boxed::Box, sync::Arc};
use async_trait::async_trait;
use device_api::device::Device;
use libk_mm::{
    address::PhysicalAddress, table::MapAttributes, OnDemandPage, PageProvider, VirtualPage,
    L3_PAGE_SIZE,
};
use yggdrasil_abi::{
    bitflags,
    error::Error,
    io::device::{self, DeviceRequestVariant, Framebuffer, VideoRequestVariant},
    option::RequestValue,
    process::ProcessId,
};

pub mod access;
pub mod color;
pub mod console;
pub mod fb_console;
pub mod font;

pub use color::Color;

use crate::{dma::DmaBuffer, task::thread::Thread};

use super::block::BlockDevice;

bitflags! {
    /// Init-time display driver flags
    pub struct DriverFlags: u32 {
        /// If set, replaces a previosly present boot framebuffer if one was present
        const REPLACES_BOOT_FRAMEBUFFER: bit 0;
    }
}

/// Represents the current lock holder for a display device
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DisplayOwner {
    /// Display is not locked
    None,
    /// Kernel is using the display
    Kernel,
    /// Userspace process is using the display
    Process(ProcessId),
}

/// Resolution of the display device
#[derive(Clone, Copy, Debug)]
pub struct DisplayDimensions {
    /// Width of the display in pixels
    pub width: u32,
    /// Height of the display in pixels
    pub height: u32,
}

/// Describes how pixels are laid out in the framebuffer used by a display
#[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub enum PixelFormat {
    B8G8R8A8,
    B8G8R8X8,
    A8R8G8B8,
    X8R8G8B8,
    R8G8B8A8,
    X8B8G8R8,
    A8B8G8R8,
    R8G8B8X8,
}

/// Describes a display mode
#[derive(Clone, Copy, Debug)]
pub struct DisplayMode {
    /// Index for setting this display mode
    pub index: u32,
    /// Width in pixels
    pub width: u32,
    /// Height in pixels
    pub height: u32,
    /// Max frames per second supported by this mode, [u32::MAX] if unlimited.
    pub frames_per_second: u32,
    /// Pixel format of this mode
    pub pixel_format: PixelFormat,
}

/// Describes display device framebuffer layout
#[derive(Clone, Copy, Debug)]
pub struct FramebufferInfo {
    /// Base address
    pub base: PhysicalAddress,
    /// Kernel-accessible virtual base address
    pub kernel_base: Option<usize>,
    /// Stride between the start of one row and the start of the next one.
    pub stride: usize,
    /// Overall size of the framebuffer
    pub size: usize,
}

/// Abstract display device interface
pub trait DisplayDevice: Device + PageProvider {
    /// Returns the init-time driver flags
    fn driver_flags(&self) -> DriverFlags;

    /// Returns the active display mode
    fn active_display_mode(&self) -> Option<DisplayMode>;
    /// Returns the list of available display modes
    fn query_display_modes(&self, modes: &mut [MaybeUninit<DisplayMode>]) -> Result<usize, Error>;

    /// Requests the device to set its display mode to the one described by `index`. If
    /// double buffering is supported, `double_buffer` is used to indicate a request to
    /// set up multiple frame buffers
    fn set_display_mode(&self, index: u32, double_buffer: bool) -> Result<(), Error>;

    /// Reads the currently active framebuffer information. Will return an error with the
    /// framebuffer count if `output` is too small to contain all the framebuffer infos.
    fn active_framebuffers(
        &self,
        output: &mut [MaybeUninit<FramebufferInfo>],
    ) -> Result<usize, usize>;

    /// Returns the currently active framebuffer, if there is exactly one. Returns an error
    /// otherwise.
    fn active_framebuffer(&self) -> Result<FramebufferInfo, usize> {
        let mut buffer = [MaybeUninit::uninit()];
        if self.active_framebuffers(&mut buffer)? == 1 {
            Ok(unsafe { buffer[0].assume_init() })
        } else {
            // No active framebuffer
            Err(0)
        }
    }

    /// Indicates to the driver that a frame was finished and can be sent to the device.
    fn synchronize(&self) -> Result<(), Error>;

    /// Locks the display to only be accessible by a single process.
    fn lock(&self, owner: DisplayOwner) -> Result<(), Error>;
    /// Unlocks the display. `owner` describes who this request comes from.
    fn unlock(&self, owner: DisplayOwner) -> Result<(), Error>;
}

#[derive(Clone)]
pub struct DisplayWrapper {
    pub device: Arc<dyn DisplayDevice>,
    pub boot: bool,
}

impl DisplayWrapper {
    pub fn new(device: Arc<dyn DisplayDevice>, boot: bool) -> Result<Self, Error> {
        Ok(Self { device, boot })
    }
}

#[async_trait]
impl BlockDevice for DisplayWrapper {
    fn allocate_buffer(&self, _size: usize) -> Result<DmaBuffer<[MaybeUninit<u8>]>, Error> {
        Err(Error::NotImplemented)
    }

    async fn read(&self, _pos: u64, _buf: &mut [u8]) -> Result<usize, Error> {
        Err(Error::InvalidOperation)
    }

    async fn write(&self, _pos: u64, _buf: &[u8]) -> Result<usize, Error> {
        Err(Error::InvalidOperation)
    }

    fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result<usize, Error> {
        let _ = len;

        if let Ok(option) = DeviceRequestVariant::try_from(option) {
            let process = Thread::current().process_id();
            return match option {
                DeviceRequestVariant::AcquireDevice => {
                    self.lock(DisplayOwner::Process(process))?;
                    device::AcquireDevice::store_response(&(), buffer)
                }
                DeviceRequestVariant::ReleaseDevice => {
                    self.unlock(DisplayOwner::Process(process))?;
                    device::ReleaseDevice::store_response(&(), buffer)
                }
            };
        }

        let option = VideoRequestVariant::try_from(option)?;
        match option {
            VideoRequestVariant::GetActiveFramebuffer => {
                let mode = self.active_display_mode().ok_or(Error::DoesNotExist)?;
                let info = self.active_framebuffer().map_err(|_| Error::DoesNotExist)?;
                let framebuffer = Framebuffer {
                    width: mode.width,
                    height: mode.height,
                    size: info.size,
                    stride: info.stride,
                };
                device::GetActiveFramebuffer::store_response(&framebuffer, buffer)
            }
            VideoRequestVariant::FlushDisplay => {
                self.synchronize()?;
                device::FlushDisplay::store_response(&(), buffer)
            }
        }
    }

    fn block_size(&self) -> usize {
        L3_PAGE_SIZE
    }

    fn block_count(&self) -> u64 {
        0
    }

    fn max_blocks_per_request(&self) -> usize {
        0
    }
}

impl PageProvider for DisplayWrapper {
    // fn clone_page(
    //     &self,
    //     offset: u64,
    //     src_phys: PhysicalAddress,
    //     src_attrs: MapAttributes,
    // ) -> Result<PhysicalAddress, Error> {
    //     self.device.clone_page(offset, src_phys, src_attrs)
    // }

    // fn get_page(&self, offset: u64) -> Result<VirtualPage, Error> {
    //     self.device.get_page(offset)
    // }

    // fn release_page(&self, offset: u64, phys: PhysicalAddress) -> Result<(), Error> {
    //     self.device.release_page(offset, phys)
    // }
    fn ondemand_fetch(&self, opaque: u64) -> Result<OnDemandPage, Error> {
        self.device.ondemand_fetch(opaque)
    }

    fn get_page(&self, offset: u64) -> Result<VirtualPage, Error> {
        self.device.get_page(offset)
    }

    fn release_page(&self, offset: u64, phys: PhysicalAddress, dirty: bool) -> Result<(), Error> {
        self.device.release_page(offset, phys, dirty)
    }

    fn clone_page(
        &self,
        offset: u64,
        src_phys: PhysicalAddress,
        src_attrs: MapAttributes,
    ) -> Result<PhysicalAddress, Error> {
        self.device.clone_page(offset, src_phys, src_attrs)
    }
}

impl Device for DisplayWrapper {
    fn display_name(&self) -> &str {
        self.device.display_name()
    }
}
