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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
use std::collections::{BTreeSet, HashMap, VecDeque};

use std::sync::Arc;

use crate::card::SavedCard;
use crate::common::{get_last_modified, system_time_as_unix_time};
use crate::Id;

#[derive(Debug, Default)]
pub struct CardCache(HashMap<Id, Arc<SavedCard>>);

impl CardCache {
    /// Checks that the card in the cache is up to date, and fixes it if it's not.
    /// There's three possibilities:
    ///     1. its up to date, no need to do anything.
    ///     2. It's outdated, we simply deserialize the card from the same path, so that its updated.
    ///     3. card isn't even found in the location, we search through all the cards to find it. Panicking if it's not found.
    fn maybe_update(&mut self, id: Id) {
        let card_needs_update = match self.0.get(&id) {
            Some(cached_card) => {
                let path = cached_card.as_path();
                if path.exists() {
                    // Get the file's last_modified time
                    let metadata = std::fs::metadata(path.as_path()).unwrap();
                    let last_modified_time = system_time_as_unix_time(metadata.modified().unwrap());

                    // Check if the file has been modified since we cached it
                    Some(last_modified_time > cached_card.last_modified())
                } else {
                    None
                }
            }
            None => None, // If card isn't in the cache, then we definitely need to update
        };

        match card_needs_update {
            Some(true) => {
                let path = self.0.get(&id).unwrap().as_path();
                let updated_card = SavedCard::from_path(path.as_path());
                self.0.insert(id, updated_card.into());
            }
            // if you find the card, and it's up to date, then no need to do anything.
            Some(false) => {}
            None => {
                // Read the card from the disk
                // expensive! it'll comb through all the cards linearly.
                if let Some(card) = SavedCard::from_id(&id) {
                    self.0.insert(id, card.into());
                };
            }
        };
    }

    pub fn ids_as_vec(&self) -> Vec<Id> {
        self.0.keys().copied().collect()
    }

    /// gets all the Ids (keys) sorted by recent modified
    pub fn all_ids(&self) -> Vec<Id> {
        let mut pairs: Vec<_> = self.0.iter().collect();
        pairs.sort_by_key(|&(_, v)| {
            if v.is_outdated() {
                get_last_modified(v.as_path())
            } else {
                v.last_modified().to_owned()
            }
        });
        pairs.reverse();
        pairs.into_iter().map(|(k, _)| k.to_owned()).collect()
    }

    pub fn exists(&self, id: &Id) -> bool {
        self.0.get(id).is_some()
    }

    pub fn insert(&mut self, card: SavedCard) {
        let id = card.id();
        self.0.insert(id, card.into());
    }

    pub fn remove(&mut self, id: Id) {
        self.0.remove(&id);
    }

    pub fn dependencies(&mut self, id: Id) -> BTreeSet<Id> {
        let Some(card) = self.try_get_ref(id) else {
            return Default::default();
        };

        card.dependency_ids()
            .iter()
            .map(ToOwned::to_owned)
            .collect()
    }

    pub fn dependents(&mut self, id: Id) -> BTreeSet<Id> {
        let Some(card) = self.try_get_ref(id) else {
            return Default::default();
        };

        card.dependent_ids().iter().map(ToOwned::to_owned).collect()
    }

    pub fn recursive_dependencies(&mut self, id: Id) -> BTreeSet<Id> {
        let mut dependencies = BTreeSet::new();
        let mut stack = VecDeque::new();
        stack.push_back(id);

        while let Some(card) = stack.pop_back() {
            if !dependencies.contains(&card) {
                dependencies.insert(card);

                let card_dependencies = self.dependencies(card);

                for dependency in card_dependencies {
                    stack.push_back(dependency);
                }
            }
        }

        dependencies.remove(&id);
        dependencies
    }

    pub fn recursive_dependents(&mut self, id: Id) -> BTreeSet<Id> {
        let mut dependencies = BTreeSet::new();
        let mut stack = VecDeque::new();
        stack.push_back(id);

        while let Some(card) = stack.pop_back() {
            if !dependencies.contains(&card) {
                dependencies.insert(card);

                let card_dependencies = self.dependents(card);

                for dependency in card_dependencies {
                    stack.push_back(dependency);
                }
            }
        }

        dependencies.remove(&id);
        dependencies
    }

    pub fn try_get_ref(&mut self, id: Id) -> Option<Arc<SavedCard>> {
        self.maybe_update(id);
        self.0.get(&id).cloned()
    }

    pub fn get_owned(&mut self, id: Id) -> SavedCard {
        (*self.get_ref(id)).clone()
    }

    pub fn get_ref(&mut self, id: Id) -> Arc<SavedCard> {
        self.try_get_ref(id).unwrap()
    }

    pub fn new() -> Self {
        let mut cache = Self::default();
        cache.cache_all();
        cache
    }

    pub fn refresh(&mut self) {
        *self = Self::new();
    }

    fn cache_all(&mut self) {
        let all_cards = SavedCard::load_all_cards();
        for card in all_cards {
            self.cache_one(card);
        }
    }

    pub fn cache_one(&mut self, card: SavedCard) {
        self.0.insert(card.id(), card.into());
    }

    pub fn new_empty() -> Self {
        CardCache(HashMap::new())
    }
}