use std::collections::HashSet;
use std::env::{current_dir, current_exe};
use std::fs::File;
use std::iter::once;
use std::ops::Deref;
use std::path::PathBuf;

use log::info;
use vfs::{
    impls::{overlay::OverlayFS, physical::PhysicalFS},
    VfsPath,
};
use vfs_zip::ZipReadOnly as ZipFS;

use crate::Error;

pub use vfs::error::VfsError;

#[derive(Clone)]
pub struct Vfs {
    pub root: VfsPath,
    pub layers: Vec<PathBuf>,
}

impl Vfs {
    pub fn new(dirs: &[&str]) -> Result<Self, Error> {
        let dirs = vec![
            current_exe()
                .ok()
                .as_ref()
                .and_then(|p| p.parent())
                .map(|p| p.to_owned()),
            current_dir().ok(),
        ]
        .into_iter()
        .filter_map(|d| d)
        .map(|p| once(&"").chain(dirs).map(move |d| p.join(d)))
        .flatten();

        let mut paths = HashSet::new();
        let mut layers = Vec::new();
        let mut overlays = Vec::new();

        for dir in dirs.clone() {
            if paths.insert(dir.clone()) {
                layers.push(dir.to_owned());

                let layer = VfsPath::new(PhysicalFS::new(dir));

                overlays.push(layer);
            }
        }

        for dir in dirs {
            let path = dir.join("resources.bin");
            if path.is_file() && paths.insert(path.to_owned()) {
                layers.push(dir.to_owned());

                let zip = File::open(path)?;
                let layer = VfsPath::new(ZipFS::new_relaxed(zip)?);

                overlays.push(layer);
            }
        }

        Ok(Self {
            root: VfsPath::new(OverlayFS::new(&overlays)),
            layers,
        })
    }

    pub fn log_info(&self) {
        for layer in &self.layers {
            info!("VFS Layer: {}", layer.display());
        }
    }
}

impl Deref for Vfs {
    type Target = VfsPath;

    fn deref(&self) -> &Self::Target {
        &self.root
    }
}
