//! Abstract linear framebuffer device implementation

use core::mem::MaybeUninit;

use abi::error::Error;
use device_api::device::Device;
use libk::device::display::{
    DisplayDevice, DisplayMode, DisplayOwner, DriverFlags, FramebufferInfo, PixelFormat,
};
use libk_mm::{
    address::PhysicalAddress,
    device::{DeviceMemoryAttributes, DeviceMemoryCaching, RawDeviceMemoryMapping},
    table::{EntryLevel, MapAttributes},
    OnDemandPage, PageProvider, VirtualPage,
};
use libk_util::sync::IrqSafeSpinlock;

use crate::arch::L3;

struct Inner {
    mapping: RawDeviceMemoryMapping,
    stride: usize,
    owner: DisplayOwner,
}

// #[doc(hidden)]
// pub struct FramebufferAccess {
//     dimensions: DisplayDimensions,
//     base: usize,
//     stride: usize,
// }

/// Linear framebuffer wrapper
pub struct LinearFramebuffer {
    inner: IrqSafeSpinlock<Inner>,
    phys_base: PhysicalAddress,
    mode: DisplayMode,
    size: usize,
}

impl LinearFramebuffer {
    /// Constructs a liner framebuffer struct from its components.
    ///
    /// # Safety
    ///
    /// Unsafe: the caller must ensure the validity of all the arguments.
    pub unsafe fn from_physical_bits(
        phys_base: PhysicalAddress,
        size: usize,
        stride: usize,
        width: u32,
        height: u32,
        pixel_format: PixelFormat,
    ) -> Result<Self, Error> {
        let mapping = unsafe {
            RawDeviceMemoryMapping::map(
                phys_base.into(),
                size,
                DeviceMemoryAttributes {
                    caching: DeviceMemoryCaching::Cacheable,
                },
            )
        }?;

        let inner = Inner {
            mapping,
            stride,
            owner: DisplayOwner::None,
        };
        let mode = DisplayMode {
            index: 0,
            width,
            height,
            frames_per_second: u32::MAX,
            pixel_format,
        };

        let res = Self {
            inner: IrqSafeSpinlock::new(inner),
            phys_base,
            size,
            mode,
        };

        // TODO clear the screen

        Ok(res)
    }
}

impl PageProvider for LinearFramebuffer {
    fn ondemand_fetch(&self, _opaque: u64) -> Result<OnDemandPage, Error> {
        unreachable!()
    }

    fn get_page(&self, offset: u64) -> Result<VirtualPage, Error> {
        let offset = offset as usize;
        if offset + L3::SIZE > self.size {
            log::warn!(
                "Tried to map offset {:#x}, but size is {:#x}",
                offset,
                self.size
            );
            Err(Error::InvalidMemoryOperation)
        } else {
            let page = self.phys_base.add(offset);
            Ok(VirtualPage::Immediate(page))
        }
    }

    fn release_page(
        &self,
        _offset: u64,
        _phys: PhysicalAddress,
        _dirty: bool,
    ) -> Result<(), Error> {
        Ok(())
    }

    fn clone_page(
        &self,
        _offset: u64,
        _src_phys: PhysicalAddress,
        _src_attrs: MapAttributes,
    ) -> Result<PhysicalAddress, Error> {
        todo!()
    }
    //     fn get_page(&self, offset: u64) -> Result<PhysicalAddress, Error> {
    //     }
    //
    //     fn release_page(&self, _offset: u64, _phys: PhysicalAddress) -> Result<(), Error> {
    //         Ok(())
    //     }
    //
    //     fn clone_page(
    //         &self,
    //         _offset: u64,
    //         _src_phys: PhysicalAddress,
    //         _src_attrs: MapAttributes,
    //     ) -> Result<PhysicalAddress, Error> {
    //         todo!()
    //     }
}

impl Device for LinearFramebuffer {
    fn display_name(&self) -> &str {
        "Linear Framebuffer"
    }
}

impl DisplayDevice for LinearFramebuffer {
    fn driver_flags(&self) -> DriverFlags {
        DriverFlags::empty()
    }

    fn synchronize(&self) -> Result<(), Error> {
        Ok(())
    }

    fn set_display_mode(&self, _index: u32, _double_buffer: bool) -> Result<(), Error> {
        Err(Error::InvalidOperation)
    }

    fn active_display_mode(&self) -> Option<DisplayMode> {
        Some(self.mode)
    }

    fn query_display_modes(&self, modes: &mut [MaybeUninit<DisplayMode>]) -> Result<usize, Error> {
        if modes.is_empty() {
            return Err(Error::BufferTooSmall);
        }

        modes[0].write(self.mode);
        Ok(1)
    }

    fn lock(&self, owner: DisplayOwner) -> Result<(), Error> {
        let mut inner = self.inner.lock();
        if inner.owner == owner {
            return Ok(());
        }
        match (inner.owner, owner) {
            (DisplayOwner::None | DisplayOwner::Kernel, DisplayOwner::Process(_)) => {
                inner.owner = owner;
                Ok(())
            }
            (DisplayOwner::None, DisplayOwner::Kernel) => {
                inner.owner = owner;
                Ok(())
            }
            (_, _) => Err(Error::WouldBlock),
        }
    }

    fn unlock(&self, owner: DisplayOwner) -> Result<(), Error> {
        let mut inner = self.inner.lock();
        if inner.owner == owner {
            inner.owner = DisplayOwner::None;
            Ok(())
        } else {
            Err(Error::InvalidArgument)
        }
    }

    fn active_framebuffers(
        &self,
        output: &mut [MaybeUninit<FramebufferInfo>],
    ) -> Result<usize, usize> {
        if output.is_empty() {
            return Err(1);
        }

        let inner = self.inner.lock();
        output[0].write(FramebufferInfo {
            base: self.phys_base,
            kernel_base: Some(inner.mapping.address),
            size: self.size,
            stride: inner.stride,
        });
        Ok(1)
    }
}
