1use anyhow::{Context, Result};
6use std::collections::{HashMap, HashSet};
7use std::fs::{File, OpenOptions};
8use std::io::{BufRead, BufReader, BufWriter, Write};
9use std::path::Path;
10
11use crate::error::LinkError;
12use crate::link::Link;
13
14pub struct LinkStorage {
17 links: HashMap<u32, Link>,
18 names: HashMap<u32, String>,
19 name_to_id: HashMap<String, u32>,
20 next_id: u32,
21 db_path: String,
22 trace: bool,
23}
24
25impl LinkStorage {
26 pub fn new(db_path: &str, trace: bool) -> Result<Self> {
28 let mut storage = Self {
29 links: HashMap::new(),
30 names: HashMap::new(),
31 name_to_id: HashMap::new(),
32 next_id: 1,
33 db_path: db_path.to_string(),
34 trace,
35 };
36
37 if Path::new(db_path).exists() {
39 storage.load()?;
40 }
41
42 Ok(storage)
43 }
44
45 fn load(&mut self) -> Result<()> {
47 let file = File::open(&self.db_path)
48 .with_context(|| format!("Failed to open database: {}", self.db_path))?;
49
50 let reader = BufReader::new(file);
51
52 for line in reader.lines() {
53 let line = line?;
54 let line = line.trim();
55
56 if line.is_empty() || line.starts_with('#') {
57 continue;
58 }
59
60 if let Some((link, name)) = self.parse_link_line(line) {
62 self.links.insert(link.index, link);
63 if link.index >= self.next_id {
64 self.next_id = link.index + 1;
65 }
66 if let Some(name) = name {
67 self.names.insert(link.index, name.clone());
68 self.name_to_id.insert(name, link.index);
69 }
70 }
71 }
72
73 if self.trace {
74 eprintln!(
75 "[TRACE] Loaded {} links from {}",
76 self.links.len(),
77 self.db_path
78 );
79 }
80
81 Ok(())
82 }
83
84 fn parse_link_line(&self, line: &str) -> Option<(Link, Option<String>)> {
86 let line = line.trim_matches(|c| c == '(' || c == ')');
88 let parts: Vec<&str> = line.split_whitespace().collect();
89
90 if parts.len() >= 3 {
91 let index = parts[0].parse().ok()?;
92 let source = parts[1].parse().ok()?;
93 let target = parts[2].parse().ok()?;
94 let name = if parts.len() > 3 {
95 Some(parts[3].trim_matches('"').to_string())
96 } else {
97 None
98 };
99 return Some((Link::new(index, source, target), name));
100 }
101
102 None
103 }
104
105 pub fn save(&self) -> Result<()> {
107 let file = OpenOptions::new()
108 .write(true)
109 .create(true)
110 .truncate(true)
111 .open(&self.db_path)
112 .with_context(|| format!("Failed to create database: {}", self.db_path))?;
113
114 let mut writer = BufWriter::new(file);
115
116 let mut links: Vec<_> = self.links.values().collect();
118 links.sort_by_key(|l| l.index);
119
120 for link in links {
121 if let Some(name) = self.names.get(&link.index) {
122 writeln!(
123 writer,
124 "({} {} {} \"{}\")",
125 link.index, link.source, link.target, name
126 )?;
127 } else {
128 writeln!(writer, "({} {} {})", link.index, link.source, link.target)?;
129 }
130 }
131
132 writer.flush()?;
133
134 if self.trace {
135 eprintln!(
136 "[TRACE] Saved {} links to {}",
137 self.links.len(),
138 self.db_path
139 );
140 }
141
142 Ok(())
143 }
144
145 pub fn create(&mut self, source: u32, target: u32) -> u32 {
147 let id = self.next_id;
148 self.next_id += 1;
149
150 let link = Link::new(id, source, target);
151 self.links.insert(id, link);
152
153 if self.trace {
154 eprintln!("[TRACE] Created link: ({} {} {})", id, source, target);
155 }
156
157 id
158 }
159
160 pub fn ensure_created(&mut self, id: u32) -> u32 {
162 if self.links.contains_key(&id) {
163 return id;
164 }
165
166 if self.next_id > id {
167 let link = Link::new(id, 0, 0);
168 self.links.insert(id, link);
169 if self.trace {
170 eprintln!("[TRACE] Ensured link: ({} 0 0)", id);
171 }
172 return id;
173 }
174
175 while self.next_id <= id {
177 let placeholder_id = self.next_id;
178 self.next_id += 1;
179 if placeholder_id == id {
180 let link = Link::new(id, 0, 0);
181 self.links.insert(id, link);
182 if self.trace {
183 eprintln!("[TRACE] Ensured link: ({} 0 0)", id);
184 }
185 return id;
186 }
187 }
188
189 id
190 }
191
192 pub fn get(&self, id: u32) -> Option<&Link> {
194 self.links.get(&id)
195 }
196
197 pub fn exists(&self, id: u32) -> bool {
199 self.links.contains_key(&id)
200 }
201
202 pub fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link> {
204 if let Some(link) = self.links.get_mut(&id) {
205 let before = *link;
206 if self.trace {
207 eprintln!(
208 "[TRACE] Updating link {} from ({} {}) to ({} {})",
209 id, link.source, link.target, source, target
210 );
211 }
212 link.source = source;
213 link.target = target;
214 Ok(before)
215 } else {
216 Err(LinkError::NotFound(id).into())
217 }
218 }
219
220 pub fn delete(&mut self, id: u32) -> Result<Link> {
222 if let Some(name) = self.names.remove(&id) {
224 self.name_to_id.remove(&name);
225 }
226
227 if let Some(link) = self.links.remove(&id) {
228 if self.trace {
229 eprintln!(
230 "[TRACE] Deleted link: ({} {} {})",
231 link.index, link.source, link.target
232 );
233 }
234 Ok(link)
235 } else {
236 Err(LinkError::NotFound(id).into())
237 }
238 }
239
240 pub fn all(&self) -> Vec<&Link> {
242 self.links.values().collect()
243 }
244
245 pub fn query(
247 &self,
248 index: Option<u32>,
249 source: Option<u32>,
250 target: Option<u32>,
251 ) -> Vec<&Link> {
252 self.links
253 .values()
254 .filter(|link| {
255 (index.is_none() || index == Some(link.index))
256 && (source.is_none() || source == Some(link.source))
257 && (target.is_none() || target == Some(link.target))
258 })
259 .collect()
260 }
261
262 pub fn search(&self, source: u32, target: u32) -> Option<u32> {
264 for link in self.links.values() {
265 if link.source == source && link.target == target {
266 return Some(link.index);
267 }
268 }
269 None
270 }
271
272 pub fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
274 if let Some(id) = self.search(source, target) {
275 id
276 } else {
277 self.create(source, target)
278 }
279 }
280
281 pub fn format(&self, link: &Link) -> String {
283 let index_str = self
285 .names
286 .get(&link.index)
287 .cloned()
288 .unwrap_or_else(|| link.index.to_string());
289 let source_str = self
290 .names
291 .get(&link.source)
292 .cloned()
293 .unwrap_or_else(|| link.source.to_string());
294 let target_str = self
295 .names
296 .get(&link.target)
297 .cloned()
298 .unwrap_or_else(|| link.target.to_string());
299 format!("({} {} {})", index_str, source_str, target_str)
300 }
301
302 pub fn format_lino(&self, link: &Link) -> String {
304 format!(
305 "({}: {} {})",
306 self.format_lino_reference(link.index),
307 self.format_lino_reference(link.source),
308 self.format_lino_reference(link.target)
309 )
310 }
311
312 pub fn lino_lines(&self) -> Vec<String> {
314 let mut links: Vec<_> = self.all();
315 links.sort_by_key(|l| l.index);
316 links
317 .into_iter()
318 .map(|link| self.format_lino(link))
319 .collect()
320 }
321
322 pub fn write_lino_output<P: AsRef<Path>>(&self, path: P) -> Result<()> {
324 let path = path.as_ref();
325 let file = OpenOptions::new()
326 .write(true)
327 .create(true)
328 .truncate(true)
329 .open(path)
330 .with_context(|| format!("Failed to create LiNo output: {}", path.display()))?;
331
332 let mut writer = BufWriter::new(file);
333 for line in self.lino_lines() {
334 writeln!(writer, "{line}")?;
335 }
336 writer.flush()?;
337 Ok(())
338 }
339
340 pub fn format_structure(&self, id: u32) -> Result<String> {
342 let mut visited = HashSet::new();
343 self.format_structure_recursive(id, &mut visited)
344 }
345
346 fn format_structure_recursive(&self, id: u32, visited: &mut HashSet<u32>) -> Result<String> {
348 let link = self.get(id).ok_or(LinkError::NotFound(id))?;
349 if !visited.insert(id) {
350 return Ok(self.format_lino_reference(id));
351 }
352
353 let source = if self.exists(link.source) && !visited.contains(&link.source) {
354 self.format_structure_recursive(link.source, visited)?
355 } else {
356 self.format_lino_reference(link.source)
357 };
358 let target = self.format_lino_reference(link.target);
359 let index = self.format_lino_reference(link.index);
360 visited.remove(&id);
361
362 Ok(format!("({index}: {source} {target})"))
363 }
364
365 pub fn print_all_links(&self) {
367 let mut links: Vec<_> = self.all();
368 links.sort_by_key(|l| l.index);
369 for link in links {
370 println!("{}", self.format(link));
371 }
372 }
373
374 pub fn print_change(&self, before: &Option<Link>, after: &Option<Link>) {
376 let before_text = before.map(|l| self.format(&l)).unwrap_or_default();
377 let after_text = after.map(|l| self.format(&l)).unwrap_or_default();
378 println!("({}) ({})", before_text, after_text);
379 }
380
381 pub fn get_or_create_named(&mut self, name: &str) -> u32 {
385 if let Some(&id) = self.name_to_id.get(name) {
386 id
387 } else {
388 let id = self.create(0, 0);
390 self.update(id, id, id).ok();
391 self.names.insert(id, name.to_string());
392 self.name_to_id.insert(name.to_string(), id);
393 if self.trace {
394 eprintln!("[TRACE] Created named link: {} => {}", name, id);
395 }
396 id
397 }
398 }
399
400 pub fn set_name(&mut self, id: u32, name: &str) {
402 if let Some(old_name) = self.names.remove(&id) {
404 self.name_to_id.remove(&old_name);
405 }
406 self.names.insert(id, name.to_string());
407 self.name_to_id.insert(name.to_string(), id);
408 if self.trace {
409 eprintln!("[TRACE] Set name: {} => {}", id, name);
410 }
411 }
412
413 pub fn get_name(&self, id: u32) -> Option<&String> {
415 self.names.get(&id)
416 }
417
418 pub fn get_by_name(&self, name: &str) -> Option<u32> {
420 self.name_to_id.get(name).copied()
421 }
422
423 pub fn remove_name(&mut self, id: u32) {
425 if let Some(name) = self.names.remove(&id) {
426 self.name_to_id.remove(&name);
427 if self.trace {
428 eprintln!("[TRACE] Removed name: {} => {}", id, name);
429 }
430 }
431 }
432
433 pub fn is_trace_enabled(&self) -> bool {
435 self.trace
436 }
437
438 fn format_lino_reference(&self, id: u32) -> String {
439 self.names
440 .get(&id)
441 .map(|name| escape_lino_reference(name))
442 .unwrap_or_else(|| id.to_string())
443 }
444}
445
446fn escape_lino_reference(reference: &str) -> String {
447 if reference.is_empty() || reference.trim().is_empty() {
448 return String::new();
449 }
450
451 let has_single_quote = reference.contains('\'');
452 let has_double_quote = reference.contains('"');
453 let needs_quoting = reference.contains(':')
454 || reference.contains('(')
455 || reference.contains(')')
456 || reference.contains(' ')
457 || reference.contains('\t')
458 || reference.contains('\n')
459 || reference.contains('\r')
460 || has_single_quote
461 || has_double_quote;
462
463 if has_single_quote && has_double_quote {
464 return format!("'{}'", reference.replace('\'', "\\'"));
465 }
466
467 if has_double_quote {
468 return format!("'{reference}'");
469 }
470
471 if has_single_quote {
472 return format!("\"{reference}\"");
473 }
474
475 if needs_quoting {
476 return format!("'{reference}'");
477 }
478
479 reference.to_string()
480}