/*
 * Copyright (c) 2022, Umut İnan Erdoğan <umutinanerdogan@pm.me>
 *
 * SPDX-License-Identifier: MPL-2.0
 */

pub mod errors;
pub mod ext2;

use alloc::sync::{Weak, Arc};
use bitflags::bitflags;

use crate::{handle::Handle, KERNEL_STATE};

use self::errors::FsError;
use FsResult as Result;

pub type FsResult<T> = core::result::Result<T, FsError>;

pub trait StorageDevice
where
    Self: Send,
{
    fn open(&self, node: Arc<FsNode> /* TODO: flags */) -> Result<FileDescriptor>;
    fn release(&self, node: FsNode) -> Result<()>;
    fn read(&self, node: &FsNode, offset: usize, size: usize) -> Result<Box<[u8]>>;
    fn write(&self, node: &FsNode, offset: usize, buffer: Box<[u8]>) -> Result<()>;
    fn read_dir(&self, node: &FsNode, index: usize) -> Result<DirectoryEntry>;
    fn find_dir(&self, node: &FsNode, name: &str) -> Result<FsNode>;
}

/// A VFS node, that can either be a file or a directory.
///
/// VFS nodes are created whenever a file that doesn't have an open VFS node is
/// opened. When there are no open file descriptors to a file, the associated
/// VFS node is dropped.
#[derive(Debug)]
pub struct FsNode {
    flags: FsFlags,
    length: usize,         // in bytes
    inode: usize,          // implementation specific identifier for the node
    device_handle: Handle, // uniquely assigned device handle
    ptr: Weak<FsNode>,     // used by mountpoints and symlinks
                           // TODO: permissions mask
                           // TODO: owning user/group
}

impl FsNode {
    pub fn new(
        flags: FsFlags,
        length: usize,
        inode: usize,
        device_handle: Handle,
        ptr: Weak<FsNode>,
    ) -> Self {
        Self {
            flags,
            length,
            inode,
            device_handle,
            ptr,
        }
    }

    /// This method opens a new file descriptor for this VFS node.
    // TODO: make this take flags
    pub fn open(self: Arc<Self>) -> Result<Handle> {
        let mut state = KERNEL_STATE.lock();
        let device = state
            .get_storage_device(self.device_handle)
            .ok_or_else(|| FsError::InvalidDevice)?;
        let fd = device.open(self)?;
        let handle = state.open_file_descriptor(fd);

        Ok(handle)
    }

    /// This method is for closing the VFS node, which is usually done when no
    /// open file descriptors for this file are left. File descriptors have
    /// weak references to the VFS node, meaning it is okay to close the VFS
    /// node before all file descriptors are closed.
    pub fn release(self) -> Result<()> {
        let state = KERNEL_STATE.lock();
        let device = state
            .get_storage_device(self.device_handle)
            .ok_or_else(|| FsError::InvalidDevice)?;

        device.release(self)?;
        // self is moved into device.release, and at the end of it, self should
        // be dropped.
        Ok(())
    }

    /// This method reads from the VFS node without opening a new file
    /// descriptor. This is intended to be used internally, if you're trying to
    /// read a file then you probably want to read from a file descriptor.
    pub fn read(&self, offset: usize, size: usize) -> Result<Box<[u8]>> {
        let state = KERNEL_STATE.lock();
        let device = state
            .get_storage_device(self.device_handle)
            .ok_or_else(|| FsError::InvalidDevice)?;

        device.read(self, offset, size)
    }

    /// This method writes to the VFS node without opening a new file
    /// descriptor. This is intended to be used internally, if you're trying to
    /// write to a file then you probably want to write to a file descriptor.
    pub fn write(&self, offset: usize, buffer: Box<[u8]>) -> Result<()> {
        let state = KERNEL_STATE.lock();
        let device = state
            .get_storage_device(self.device_handle)
            .ok_or_else(|| FsError::InvalidDevice)?;

        device.write(self, offset, buffer)
    }

    pub fn read_dir(&self, index: usize) -> Result<DirectoryEntry> {
        let state = KERNEL_STATE.lock();
        let device = state
            .get_storage_device(self.device_handle)
            .ok_or_else(|| FsError::InvalidDevice)?;

        device.read_dir(self, index)
    }

    pub fn find_dir(&self, name: &str) -> Result<FsNode> {
        let state = KERNEL_STATE.lock();
        let device = state
            .get_storage_device(self.device_handle)
            .ok_or_else(|| FsError::InvalidDevice)?;

        device.find_dir(self, name)
    }
}

impl Drop for FsNode {
    fn drop(&mut self) {
        trace!("dropping: {self:#?}")
    }
}

bitflags! {
    /// Flags associated with VFS nodes and file descriptors
    ///
    /// 0x00000MST
    /// T is set to 0 for files, 1 for directories
    /// S is set when the file is a symbolic link
    /// M is set if the file is an active mount point
    pub struct FsFlags: u8 {
        const FILE = 0b00000000;
        const DIRECTORY = 0b00000001;
        const SYMBOLIC_LINK = 0b00000010;
        const MOUNT_POINT = 0b00000100;
    }
}

/// A file descriptor.
#[derive(Debug)]
pub struct FileDescriptor {
    vfs_node: Weak<FsNode>, // ptr to the associated VFS node
    flags: FsFlags,
    length: usize, // in bytes
    inode: usize,  // implementation specific identifier for the node
                   // TODO: permissions mask
                   // TODO: owning user/group
                   // TODO: read file position?
}

impl FileDescriptor {
    fn new(vfs_node: Weak<FsNode>, flags: FsFlags, length: usize, inode: usize) -> Self {
        Self {
            vfs_node,
            flags,
            length,
            inode,
        }
    }

    /// This method is for closing the file descriptor.
    pub fn close(fd_handle: Handle) {
        let mut state = KERNEL_STATE.lock();
        state.close_file_descriptor(fd_handle);
    }
}

impl Drop for FileDescriptor {
    fn drop(&mut self) {
        trace!("dropping: {self:#?}")
    }
}

pub struct DirectoryEntry {
    name: String,
    inode: usize,
}