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
use soulog::*;
use lazy_db::*;
use crate::entry::*;
use toml::{Table, Value};

// Some ease of life macros
macro_rules! get {
    ($key:ident at ($moc:ident, $idx:ident) from $table:ident as $func:ident with $logger:ident) => {{
        let key = stringify!($key);
        let obj = unwrap_opt!(($table.get(key)) with $logger, format: Collection("moc '{0}', collection {1} must have '{key}' attribute", $moc, $idx));

        unwrap_opt!((obj.$func()) with $logger, format: Collection("moc '{0}', collection {1}'s '{key}' attribute must be of the correct type", $moc, $idx))
    }};

    ($var:ident = $key:ident at ($moc:ident, $idx:ident) from $table:ident as $func:ident with $logger:ident or $default:expr) => {
        let key = stringify!($key);
        let default = $default;
        let $var = $table.get(key)
            .map(|x| unwrap_opt!((x.$func()) with $logger, format: Collection("moc '{0}'s '{key}' attribute must be of the correct type", $moc)))
            .unwrap_or(&default);
    };
}

pub struct Collection {
    pub container: LazyContainer,
    pub title: Option<String>,
    pub notes: Option<Box<[String]>>,
    pub include: Option<Box<[String]>>,
}

impl Collection {
    pub fn new(table: &Table, container: LazyContainer, moc: &str, idx: u8, mut logger: impl Logger) -> Self {
        log!((logger) Collection("Parsing moc '{moc}'s collection {idx}..."));

        // Get the basic needed data
        log!((logger) Collection("Reading collection's data..."));
        let title = get!(title at (moc, idx) from table as as_str with logger).to_string();
        get!(raw_notes = notes at (moc, idx) from table as as_array with logger or Vec::<toml::Value>::with_capacity(0));
        let raw_include = get!(include at (moc, idx) from table as as_array with logger);

        // Parse arrays
        unpack_array!(notes from raw_notes with logger by x
            => unwrap_opt!((x.as_str()) with logger, format: Collection("All notes in moc '{moc}', collection '{idx}' must be strings")).to_string()
        );

        unpack_array!(include from raw_include with logger by x
            => unwrap_opt!((x.as_str()) with logger, format: Collection("All included groups in moc '{moc}', collection '{idx}' must be strings")).to_string()
        );

        log!((logger) Collection("Writing moc '{moc}'s collection {idx} into archive..."));
        let mut this = Self {
            container,
            title: Some(title),
            notes: Some(notes.into_boxed_slice()),
            include: Some(include.into_boxed_slice()),
        };

        this.store_lazy(logger.hollow());
        this.clear_cache();
        log!((logger) Collection("Successfully parsed and written moc's collection {idx} into archive"));
        log!((logger) Collection("")); // spacer
        this
    }

    pub fn pull(&mut self, logger: impl Logger) -> Table {
        let mut map = Table::new();

        // Insert title and notes
        map.insert("title".into(), Value::String(self.title(logger.hollow()).clone()));
        map.insert("notes".into(), self.notes(logger.hollow()).to_vec().into());
        map.insert("include".into(), self.include(logger.hollow()).to_vec().into());

        self.clear_cache();

        map
    }

    pub fn store_lazy(&self, mut logger: impl Logger) {
        // Only store them if they are accessed (maybe modified)
        if let Some(x) = &self.title { write_db_container!(Collection(self.container) title = new_string(x) with logger); }
        if let Some(x) = &self.notes {
            list::write(
                x.as_ref(),
                |file, data| LazyData::new_string(file, data),
                &if_err!((logger) [Collection, err => ("While writing collection's notes to archive: {:?}", err)] retry self.container.new_container("notes")),
                logger.hollow()
            );
        }
        if let Some(x) = &self.include {
            list::write(
                x.as_ref(),
                |file, data| LazyData::new_string(file, data),
                &if_err!((logger) [Collection, err => ("While writing collection's included groups to archive: {:?}", err)] retry self.container.new_container("include")),
                logger
            );
        }
    }

    pub fn load_lazy(container: LazyContainer) -> Self {
        Self {
            container,
            title: None,
            notes: None,
            include: None,
        }
    }

    pub fn clear_cache(&mut self) {
        self.title = None;
        self.notes = None;
        self.include = None;
    }

    pub fn fill_cache(&mut self, logger: impl Logger) {
        self.title(logger.hollow());
        self.include(logger.hollow());
        self.notes(logger.hollow());
    }

    cache_field!(notes(this, logger) -> Box<[String]> {
        list::read(
            |data| data.collect_string(),
            &if_err!((logger) [Collection, err => ("While reading from collection's notes: {err:?}")] retry this.container.child_container("notes")),
            logger
        )
    });

    cache_field!(title(this, logger) -> String {
        read_db_container!(title from Collection(this.container) as collect_string with logger)
    });

    cache_field!(include(this, logger) -> Box<[String]> {
        list::read(
            |data| data.collect_string(),
            &if_err!((logger) [Collection, err => ("While reading from collection's included groups: {err:?}")] retry this.container.child_container("include")),
            logger
        )
    });
}