1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use crate::*;
use std::path::{Path, PathBuf};
use std::fs;

/// Used for reading from a `LazyContainer` with less boiler-plate
#[macro_export]
macro_rules! write_container {
    (($container:expr) $($item:ident)?$(($obj:expr))? = $func:ident($value:expr)) => {(|| {
        let container = &$container;
        $(LazyData::$func(container.data_writer(stringify!($item))?, $value)?;)?
        $(LazyData::$func(container.data_writer($obj)?, $value)?;)?
        Result::<(), LDBError>::Ok(())
    })()};

    (($container:expr) /$($($con:ident)?$(($can:expr))?)/ *::$($item:ident)?$(($obj:expr))? = $func:ident($value:expr)) => {(|| {
        let mut container = &mut $container;
        $({
            let con = $(stringify!($con))?$($can)?;
            container = match container.read_container(con) {
                Ok(x) => x,
                Err(LDBError::DirNotFound(_)) => container.new_container(con)?,
                Err(e) => return Err(e),
            }
        };)*

        $(LazyData::$func(container.data_writer(stringify!($item))?, $value)?;)?
        $(LazyData::$func(container.data_writer($obj)?, $value)?;)?
        Result::<(), LDBError>::Ok(())
    })()}
}

/// Used for reading from a `LazyContainer` with less boiler-plate
#[macro_export]
macro_rules! search_container {
    (($container:expr) /$($($con:ident)?$(($can:expr))?)/ *) => {(|| {
        let container = &$container;
        $(
            $(let container = container.read_container(stringify!($con))?;)?
            $(let container = container.read_container($can)?;)?
        )*
        let result: Result<LazyContainer, LDBError> = Ok(container);
        result
    })()};

    (($container:expr) /$($($con:ident)?$(($can:expr))?)/ *::$($item:ident)?$(($obj:expr))?) => {(|| {
        let container = search_container!(($container) /$($($con)?$(($can))?)/ *)?;
        $(let result: Result<LazyData, LDBError> = container.read_data(stringify!($item));)?
        $(let result: Result<LazyData, LDBError> = container.read_data($obj);)?
        result
    })()};

    (($container:expr) $($item:ident)?$(($obj:expr))?) => {(|| {
        let container = &$container;
        $(let result: Result<LazyData, LDBError> = container.read_data(stringify!($item));)?
        $(let result: Result<LazyData, LDBError> = container.read_data($obj);)?
        result
    })()};
}

/// A wrapper for a directory that holds individual `LazyData` files
pub struct LazyContainer {
    path: PathBuf,
}

impl LazyContainer {
    /// Initialises a new, empty `LazyContainer` at the specified path.
    pub fn init(path: impl AsRef<Path>) -> Result<Self, std::io::Error> {
        let path = path.as_ref();

        // Checks if path exists or not
        if !path.is_dir() { fs::create_dir_all(path)? };
        
        // Constructs self
        Ok(Self {
            path: path.to_path_buf(),
        })
    }

    /// Loads a pre-existing `LazyContainer` directory at a specified path.
    /// 
    /// Will throw an error if the directory doesn't exist or there is an `io::Error`.
    pub fn load(path: impl AsRef<Path>) -> Result<Self, LDBError> {
        let path = path.as_ref().to_path_buf();

        // Checks if path exists or not
        if !path.is_dir() { return Err(LDBError::DirNotFound(path)) };

        // Constructs self
        Ok(Self {
            path,
        })
    }

    /// Generates a `FileWrapper` in write mode from a key (like a relative file path)
    /// 
    /// If the data already exists, it will try to remove it
    pub fn data_writer(&self, key: impl AsRef<Path>) -> Result<FileWrapper, LDBError> {
        let path = self.path.join(key);
        if path.is_file() { let _ = fs::remove_file(&path); }; // if files exists try remove it
        let file = unwrap_result!((fs::File::create(path)) err => LDBError::IOError(err));
        Ok(FileWrapper::new_writer(file))
    }

    /// Generates a nested `LazyContainer` within this container
    /// 
    /// If container already exists it will **wipe** and **replace** it.
    pub fn new_container(&self, key: impl AsRef<Path>) -> Result<LazyContainer, LDBError> {
        let path = self.path.join(&key);
        if path.is_dir() { unwrap_result!((fs::remove_dir_all(&path)) err => LDBError::IOError(err)) }; // If exists wipe it
        Ok(unwrap_result!((LazyContainer::init(path)) err => LDBError::IOError(err)))
    }

    /// Gets a nested `LazyContainer` within this container
    /// 
    /// If container already exists it will load it
    /// Otherwise it will initialise a new one
    pub fn child_container(&self, key: impl AsRef<Path>) -> Result<LazyContainer, LDBError> {
        let path = self.path.join(&key);
        if path.is_dir() { return self.read_container(key) }; // If exists load instead
        Ok(unwrap_result!((LazyContainer::init(path)) err => LDBError::IOError(err)))
    }

    /// Reads nested `LazyData` within this container
    pub fn read_data(&self, key: impl AsRef<Path>) -> Result<LazyData, LDBError> {
        let path = self.path.join(key);
        if !path.is_file() { return Err(LDBError::FileNotFound(path)) };
        LazyData::load(path)
    }

    /// Reads nexted `LazyContainer` within this container
    pub fn read_container(&self, key: impl AsRef<Path>) -> Result<LazyContainer, LDBError> {
        let path = self.path.join(key);
        if !path.is_dir() { return Err(LDBError::DirNotFound(path)) };
        LazyContainer::load(path)
    }

    /// Tries to remove item at specified key; returns result
    pub fn remove(&self, key: impl AsRef<Path>) -> Result<(), std::io::Error> {
        let path = self.path.join(key);
        if path.is_dir() {
            fs::remove_dir_all(path)
        } else {
            fs::remove_file(path)
        }
    }

    /// Tries to wipe container's contents; returns result
    pub fn wipe(&self) -> Result<(), std::io::Error> {
        fs::remove_dir_all(&self.path)?;
        fs::create_dir_all(&self.path)
    }

    /// Returns a reference to the container's path
    #[inline]
    pub fn path(&self) -> &Path {
        &self.path
    }
}