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

use core::cmp;

use alloc::sync::{Arc, Weak};
use hashbrown::HashMap;
use lazy_static::lazy_static;
use spin::Mutex;

use super::{errors::FsError, FsResult as Result};
use crate::{
    handle::{Handle, HandleResource},
    KERNEL_STATE,
};

lazy_static! {
    pub static ref VFS: Mutex<VirtualFileSystem> = Default::default();
}

pub struct VirtualFileSystem {
    fs_nodes: HashMap<Handle, Arc<FsNode>>,
    root_node: Weak<FsNode>,
}

impl VirtualFileSystem {
    pub fn init(&mut self, root_node: Arc<FsNode>) {
        self.root_node = Arc::downgrade(&root_node)
    }

    pub fn add_fs_node(&mut self, fs_node: Arc<FsNode>) -> Handle {
        let handle = Handle::new(HandleResource::FsNode);
        self.fs_nodes.insert(handle, fs_node);
        handle
    }

    pub fn find_fs_node(&mut self, inode: usize, device_handle: Handle) -> Option<Handle> {
        self.fs_nodes.iter().find_map(|(handle, fs_node)| {
            if fs_node.inode == inode && fs_node.device_handle == device_handle {
                Some(*handle)
            } else {
                None
            }
        })
    }

    pub fn fs_node(&mut self, handle: Handle) -> Option<Arc<FsNode>> {
        self.fs_nodes.get(&handle).cloned()
    }

    pub fn root_node(&self) -> Arc<FsNode> {
        // okay to unwrap since this should never be called before init
        self.root_node.upgrade().unwrap()
    }
}

impl Default for VirtualFileSystem {
    fn default() -> Self {
        Self {
            fs_nodes: HashMap::new(),
            root_node: Weak::new(),
        }
    }
}

/// 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 handle = state.open_file_descriptor(self);

        Ok(handle)
    }

    /// 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, buffer: &mut Vec<u8>) -> Result<usize> {
        let state = KERNEL_STATE.lock();
        let device = state
            .storage_device(self.device_handle)
            .ok_or_else(|| FsError::InvalidDevice)?;

        if self.is_dir() {
            Err(FsError::IsDirectory)?;
        }

        if offset > self.length {
            Err(FsError::EndOfFile)?;
        }

        let readable_size = cmp::min(size, self.length - offset);
        device.read(self, offset, readable_size, buffer)?;
        Ok(readable_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: &[u8]) -> Result<()> {
        let state = KERNEL_STATE.lock();
        let device = state
            .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
            .storage_device(self.device_handle)
            .ok_or_else(|| FsError::InvalidDevice)?;

        device.read_dir(self, index)
    }

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

        device.find_dir(self, name)
    }

    pub fn directory(self: Arc<Self>) -> Option<Directory> {
        if self.is_dir() {
            Some(Directory::new(self))
        } else {
            None
        }
    }

    pub fn is_dir(&self) -> bool {
        (self.flags & FsFlags::DIRECTORY) == FsFlags::DIRECTORY
    }

    pub fn inode(&self) -> usize {
        self.inode
    }
}

impl Drop for FsNode {
    fn drop(&mut self) {
        trace!("dropping VFS node: {self:#?}");
        // TODO: flush to disk here
    }
}

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;
    }
}

pub struct Directory {
    node: Arc<FsNode>,
    index: usize,
}

impl Directory {
    fn new(node: Arc<FsNode>) -> Self {
        Self { node, index: 0 }
    }
}

impl Iterator for Directory {
    type Item = DirectoryEntry;

    fn next(&mut self) -> Option<Self::Item> {
        let value = self.node.read_dir(self.index).ok();
        self.index += 1;
        value
    }
}

#[derive(Debug)]
pub struct DirectoryEntry {
    name: String,
    node: Handle,
}

impl DirectoryEntry {
    pub(super) fn new(name: String, node: Handle) -> Self {
        Self { name, node }
    }

    pub fn name(&self) -> String {
        self.name.clone()
    }

    pub fn node(&self) -> Handle {
        self.node
    }
}
