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
use std::fmt::Display;

use nom::{
    bytes::complete::tag,
    character::complete::{multispace0, multispace1},
    combinator::map_res,
    multi::separated_list1,
    sequence::{delimited, preceded},
    IResult,
};

use crate::song_property::Property;

#[derive(Debug)]
pub struct Song<'a> {
    resolution: u32,
    properties: Vec<Property<'a>>,
}

impl<'a> Song<'a> {
    pub fn new(resolution: u32, properties: Vec<Property<'a>>) -> Self {
        Self {
            resolution,
            properties,
        }
    }

    pub fn multiply(&mut self, factor: u32) {
        self.resolution *= factor;
    }

    pub fn parse(input: &str) -> IResult<&str, Song> {
        map_res(
            preceded(
                preceded(tag("[Song]"), multispace0),
                delimited(
                    preceded(tag("{"), multispace0),
                    separated_list1(multispace1, Property::parse),
                    preceded(multispace1, tag("}")),
                ),
            ),
            Song::try_from,
        )(input)
    }
}

impl<'a> TryFrom<Vec<Property<'a>>> for Song<'a> {
    type Error = &'static str;

    fn try_from(value: Vec<Property<'a>>) -> Result<Self, Self::Error> {
        let resolution_entry = value
            .iter()
            .find(|x| x.name == "Resolution")
            .ok_or("resolution not found")?;
        let resolution = resolution_entry
            .value
            .parse::<u32>()
            .map_err(|_| "invalid resolution")?;
        let properties = value
            .into_iter()
            .filter(|x| x.name != "Resolution")
            .collect();
        Ok(Self {
            resolution,
            properties,
        })
    }
}

impl<'a> Display for Song<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}{}",
            Property::new("resolution", self.resolution.to_string()),
            self.properties
                .iter()
                .map(Property::to_string)
                .collect::<String>()
        )
    }
}

#[cfg(test)]
mod tests {
    #![allow(clippy::unwrap_used)]
    use super::*;

    #[test]
    fn test_song() {
        Song::parse(
            r#"[Song]
{
  Name = "Second Sight"
  Artist = "Adagio"
  Charter = "Peddy"
  Album = "Sanctus Ignis"
  Year = ", 2001"
  Offset = 0
  Resolution = 192
  Player2 = bass
  Difficulty = 0
  PreviewStart = 0
  PreviewEnd = 0
  Genre = "Neoclassical Metal"
  MediaType = "cd"
  MusicStream = "song.ogg"
}"#,
        )
        .unwrap();
    }
}