extern crate game_kernel_utils;

use std::cell::{Ref, RefCell};
use std::collections::HashMap;
use std::io::{Read, Seek, Write};
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, RwLock, RwLockReadGuard};

use game_kernel_utils::{MapAbstract, MapMap};

mod path;

pub trait OpenVFile: OpenVROFile + Write {}
pub trait OpenVROFile: Read + Seek {}

trait VFile: VROFile {
    fn open_rw(&self) -> Box<dyn OpenVROFile>;
}

trait VROFile: AsVROFile {
    fn open(&mut self) -> Box<dyn OpenVROFile>;
}

trait AsVROFile {
    fn as_ro(&self) -> &VROFile;
}

impl<T: VROFile> AsVROFile for T {
    fn as_ro(&self) -> &VROFile {
        self
    }
}

enum FsObjTypes {
    file(Box<VFile>),
    ro_file(Box<VROFile>),
    directory(Directory),
}

#[derive(Clone)]
pub enum Permissions {
    Read,
    ReadWrite,
}

pub struct FsObject {
    //parent: Option<RefCell<Directory>>,
    obj_type: FsObjTypes,
    name: String,
    acl: HashMap<u32, Permissions>,
}

impl FsObject {
    pub fn get_acl(&self) -> &HashMap<u32, Permissions> {
        &self.acl
    }

    pub fn get_permissions(&self, user: &u32) -> Option<Permissions> {
        self.get_acl().get(user).map(|t| (*t).clone())
    }

    pub fn can_read(&self, user: &u32) -> bool {
        self.get_permissions(user).is_some()
    }

    pub fn can_write(&self, user: &u32) -> bool {
        if let Some(Permissions::ReadWrite) = self.get_permissions(user) {
            true
        } else {
            false
        }
    }
}

pub struct FileMeta {
    name: String,
    ro: bool,
    acl: HashMap<u32, Permissions>,
}

pub struct Directory {
    contents: HashMap<String, Arc<FsObject>>,
    driver: Option<Box<dyn FsDriver>>,
    driver_path: Option<path::Path>,
    read_only: bool,
}

impl Directory {
    pub fn new() -> Self {
        Self {
            contents: HashMap::new(),
            driver: None,
            driver_path: None,
            read_only: false,
        }
    }

    pub fn get_contents(&self) -> HashMap<String, Arc<FsObject>> {
        self.contents.clone()
    }

    pub fn get_contents_mut(&mut self) -> HashMap<String, Arc<FsObject>> {
        self.contents.clone()
    }

    pub fn put_object(&mut self, obj: FsObject) -> Result<(), ()> {
        if self.read_only {
            return Err(());
        }

        if let (Some(ref mut driver), Some(ref d_path)) = (&mut self.driver, &self.driver_path) {
            driver.put_object(&d_path, obj)
        } else {
            self.contents.insert(obj.name.clone(), Arc::new(obj));
            Ok(())
        }
    }

    pub fn new_file(&mut self, meta: FileMeta) -> Result<&FsObject, ()> {
        if self.read_only || self.driver.is_none() {
            return Err(());
        }

        if let (Some(driver), Some(path)) = (&mut self.driver, &self.driver_path) {
            driver.put_new_object(meta, path)
        } else {
            Err(())
        }
    }
}

pub trait FsDriver {
    fn get_root(&self) -> Directory;
    fn put_object(&mut self, path: &path::Path, obj: FsObject) -> Result<(), ()>;
    fn put_new_object<'a>(
        &'a mut self,
        meta: FileMeta,
        path: &path::Path,
    ) -> Result<&'a FsObject, ()>;
}

pub struct Vfs {
    root: Arc<FsObject>,
}

impl Vfs {
    /*pub fn new() -> Self
    {
        Self{
            root: Directory::new()
        }
    }*/

    pub fn get_object_mut(&mut self, path: &path::Path) -> Result<Arc<FsObject>, ()> {
        let mut curr_obj = self.root.clone();
        for obj_name in path.obj_name_iter() {
            match (curr_obj.obj_type) {
                FsObjTypes::directory(ref dir) => {
                    let contents = dir.get_contents();
                    if let Some(obj) = contents.get(obj_name) {
                        curr_obj = obj.clone();
                    } else {
                        return Err(());
                    }
                }

                _ => return Err(()),
            }
        }

        Ok(curr_obj)
    }

    pub fn get_object(&self, path: &path::Path) -> Result<Arc<FsObject>, ()> {
        let mut curr_obj = self.root.clone();
        for obj_name in path.obj_name_iter() {
            match (curr_obj.obj_type) {
                FsObjTypes::directory(ref dir) => {
                    let contents = dir.get_contents();
                    if let Some(obj) = contents.get(obj_name) {
                        curr_obj = obj.clone();
                    } else {
                        return Err(());
                    }
                }

                _ => return Err(()),
            }
        }

        Ok(curr_obj)
    }

    pub fn put_object(&mut self, path: path::Path) -> Result<(), ()> {
        let (path, name) = path.split_at_base();
        let mut curr_obj = self.root.clone();
        for obj_name in path.obj_name_iter() {
            match (curr_obj.obj_type) {
                FsObjTypes::directory(ref dir) => {
                    let contents = dir.get_contents();
                    if let Some(obj) = contents.get(obj_name) {
                        curr_obj = obj.clone();
                    } else {
                        return Err(());
                    }
                }

                _ => return Err(()),
            }
        }

        Ok(())
    }
}

pub fn mount<T: FsDriver>(vfs: &mut Vfs, driver: T, path: path::Path) -> Result<(), ()> {
    let driver_tobject = Box::new(driver);
    let (base, filename) = path.split_at_base();
    let mut b = vfs.get_object_mut(&base)?;
    let dir = match (&mut Arc::get_mut(&mut b).ok_or(())?.obj_type) {
        FsObjTypes::directory(dir) => Ok(dir),
        _ => Err(()),
    }?;
    dir.put_object(FsObject {
        //parent: Some(RefCell::new(*dir)),
        obj_type: FsObjTypes::directory(driver_tobject.get_root()),
        name: filename,
        acl: HashMap::new(),
    });
    Ok(())
}

pub struct VfsHanfle<'a> {
    user: u32,
    vfs: &'a Vfs,
}

impl<'a> VfsHanfle<'a> {
    pub fn new(user: u32, vfs: &'a mut Vfs) -> Self {
        Self { user, vfs }
    }

    fn check_permission<T>(&self, obj: T, perm: Permissions) -> Result<T, ()>
    where
        T: Deref<Target = FsObject>,
    {
        match ((obj.can_read(&self.user), obj.can_write(&self.user), perm)) {
            (true, _, Permissions::Read) => Ok(obj),
            (true, true, Permissions::ReadWrite) => Ok(obj),
            _ => Err(()),
        }
    }

    fn check_permission_mut<T>(&self, obj: T, perm: Permissions) -> Result<T, ()>
    where
        T: DerefMut<Target = FsObject>,
    {
        match ((obj.can_read(&self.user), obj.can_write(&self.user), perm)) {
            (true, _, Permissions::Read) => Ok(obj),
            (true, true, Permissions::ReadWrite) => Ok(obj),
            _ => Err(()),
        }
    }

    fn own_acl(&self) -> HashMap<u32, Permissions> {
        let mut acl = HashMap::new();
        acl.insert(self.user, Permissions::ReadWrite);
        acl
    }

    /*pub fn open(&self, path: &path::Path) -> Result<&'a VROFile, ()>
    {
        let obj = self.vfs.get_object(path)?;
        if let FsObject{obj_type: FsObjTypes::file(file), ..} = self.check_permission(obj.as_ref(), Permissions::Read)?
        {
            Ok(file.as_ref().clone().as_ro())
        }
        else if let FsObject{obj_type: FsObjTypes::ro_file(file), ..} = self.check_permission(obj.as_ref(), Permissions::Read)?
        {
            Ok(file.as_ref().clone())
        }
        else
        {
            Err(())
        }
    }

    pub fn open_rw(&self, path: path::Path) -> Result<&'a VFile, ()>
    {
        let obj = self.vfs.get_object(&path)?;
        /*let obj = obj.unwrap_or({
            let (base, filename) = path.split_at_base();
            if let Ok(FsObject{obj_type: FsObjTypes::directory(dir), ..}) = self.check_permission_mut(self.vfs.get_object_mut(&base)?, Permissions::ReadWrite)
            {
                dir.new_file(FileMeta{name: filename, ro: false, acl: self.own_acl()})?
            }
            else
            {
                return Err(())
            }
        });*/
        if let FsObject{obj_type: FsObjTypes::file(ref file), ..} = *self.check_permission(obj, Permissions::ReadWrite)?
        {
            Ok(file.as_ref())
        }
        else
        {
            Err(())
        }
    }*/
}
