#![allow(dead_code)]
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ReelFormat {
Film16mm,
Film35mm,
Film65mm,
DigitalTape,
FileBased,
Unknown,
}
impl ReelFormat {
#[must_use]
pub fn is_film(self) -> bool {
matches!(self, Self::Film16mm | Self::Film35mm | Self::Film65mm)
}
#[must_use]
pub fn is_file_based(self) -> bool {
matches!(self, Self::FileBased)
}
#[must_use]
pub fn label(self) -> &'static str {
match self {
Self::Film16mm => "16mm Film",
Self::Film35mm => "35mm Film",
Self::Film65mm => "65mm Film",
Self::DigitalTape => "Digital Tape",
Self::FileBased => "File-Based",
Self::Unknown => "Unknown",
}
}
}
#[derive(Debug, Clone)]
pub struct ReelInfo {
pub name: String,
pub format: ReelFormat,
pub description: Option<String>,
pub total_frames: Option<u64>,
}
impl ReelInfo {
#[must_use]
pub fn new(name: impl Into<String>, format: ReelFormat) -> Self {
Self {
name: name.into(),
format,
description: None,
total_frames: None,
}
}
#[must_use]
pub fn is_valid(&self) -> bool {
!self.name.is_empty() && self.name.len() <= 8
}
pub fn set_description(&mut self, desc: impl Into<String>) {
self.description = Some(desc.into());
}
pub fn set_total_frames(&mut self, frames: u64) {
self.total_frames = Some(frames);
}
}
#[derive(Debug, Clone, Default)]
pub struct ReelRegistry {
reels: HashMap<String, ReelInfo>,
}
impl ReelRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, info: ReelInfo) {
self.reels.insert(info.name.clone(), info);
}
pub fn register_by_name(&mut self, name: impl Into<String>, format: ReelFormat) {
let info = ReelInfo::new(name, format);
self.register(info);
}
#[must_use]
pub fn lookup(&self, name: &str) -> Option<&ReelInfo> {
self.reels.get(name)
}
pub fn lookup_mut(&mut self, name: &str) -> Option<&mut ReelInfo> {
self.reels.get_mut(name)
}
#[must_use]
pub fn reel_count(&self) -> usize {
self.reels.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.reels.is_empty()
}
#[must_use]
pub fn by_format(&self, format: ReelFormat) -> Vec<&ReelInfo> {
self.reels.values().filter(|r| r.format == format).collect()
}
#[must_use]
pub fn names(&self) -> Vec<&str> {
let mut names: Vec<&str> = self.reels.keys().map(String::as_str).collect();
names.sort_unstable();
names
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_film16mm_is_film() {
assert!(ReelFormat::Film16mm.is_film());
}
#[test]
fn test_film35mm_is_film() {
assert!(ReelFormat::Film35mm.is_film());
}
#[test]
fn test_digital_tape_not_film() {
assert!(!ReelFormat::DigitalTape.is_film());
}
#[test]
fn test_file_based_is_file_based() {
assert!(ReelFormat::FileBased.is_file_based());
}
#[test]
fn test_film_not_file_based() {
assert!(!ReelFormat::Film35mm.is_file_based());
}
#[test]
fn test_format_label() {
assert_eq!(ReelFormat::Film35mm.label(), "35mm Film");
assert_eq!(ReelFormat::FileBased.label(), "File-Based");
assert_eq!(ReelFormat::Unknown.label(), "Unknown");
}
#[test]
fn test_reel_info_valid() {
let r = ReelInfo::new("A001", ReelFormat::DigitalTape);
assert!(r.is_valid());
}
#[test]
fn test_reel_info_empty_name_invalid() {
let r = ReelInfo::new("", ReelFormat::FileBased);
assert!(!r.is_valid());
}
#[test]
fn test_reel_info_name_too_long_invalid() {
let r = ReelInfo::new("VERYLONGNAME", ReelFormat::FileBased);
assert!(!r.is_valid());
}
#[test]
fn test_reel_info_set_description() {
let mut r = ReelInfo::new("B002", ReelFormat::Film35mm);
assert!(r.description.is_none());
r.set_description("Scene 3 daylight");
assert_eq!(r.description.as_deref(), Some("Scene 3 daylight"));
}
#[test]
fn test_reel_info_set_frames() {
let mut r = ReelInfo::new("C003", ReelFormat::DigitalTape);
r.set_total_frames(86400);
assert_eq!(r.total_frames, Some(86400));
}
#[test]
fn test_registry_register_and_count() {
let mut reg = ReelRegistry::new();
assert!(reg.is_empty());
reg.register_by_name("A001", ReelFormat::DigitalTape);
reg.register_by_name("B002", ReelFormat::Film35mm);
assert_eq!(reg.reel_count(), 2);
}
#[test]
fn test_registry_lookup_found() {
let mut reg = ReelRegistry::new();
reg.register_by_name("X001", ReelFormat::FileBased);
let r = reg.lookup("X001");
assert!(r.is_some());
assert_eq!(r.expect("r should be valid").name, "X001");
}
#[test]
fn test_registry_lookup_missing() {
let reg = ReelRegistry::new();
assert!(reg.lookup("GHOST").is_none());
}
#[test]
fn test_registry_overwrite() {
let mut reg = ReelRegistry::new();
reg.register_by_name("A001", ReelFormat::DigitalTape);
reg.register_by_name("A001", ReelFormat::Film35mm);
assert_eq!(reg.reel_count(), 1);
assert_eq!(
reg.lookup("A001").expect("lookup should succeed").format,
ReelFormat::Film35mm
);
}
#[test]
fn test_registry_by_format() {
let mut reg = ReelRegistry::new();
reg.register_by_name("A001", ReelFormat::Film35mm);
reg.register_by_name("A002", ReelFormat::Film35mm);
reg.register_by_name("B001", ReelFormat::DigitalTape);
assert_eq!(reg.by_format(ReelFormat::Film35mm).len(), 2);
assert_eq!(reg.by_format(ReelFormat::DigitalTape).len(), 1);
}
#[test]
fn test_registry_names_sorted() {
let mut reg = ReelRegistry::new();
reg.register_by_name("C001", ReelFormat::FileBased);
reg.register_by_name("A001", ReelFormat::FileBased);
reg.register_by_name("B001", ReelFormat::FileBased);
assert_eq!(reg.names(), vec!["A001", "B001", "C001"]);
}
}