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

use derive_getters::{Dissolve, Getters};

/// Represents a problem for Advent of Code.
///
/// There are a few different ways to create a `Problem`:
/// with `new`, a `(Year, Day)` tuple implements `From`, or
/// `(u16, u8)` tuple implements `TryFrom` by validating a
/// valid year and day is provided.
///
/// ```rust
/// # use advent_of_code_client::{Problem, Year};
/// let a = Problem::new(Year::Y2017, 4);
/// let b = (Year::Y2017, 4).into();
/// let c = (2017_u16, 4_u8).try_into().unwrap();
///
/// assert_eq!(a, b);
/// assert_eq!(b, c);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Getters, Dissolve)]
pub struct Problem {
    year: Year,
    day: Day,
}

impl Problem {
    /// Create a new problem for a given year and day.
    pub const fn new(year: Year, day: Day) -> Self {
        Self { year, day }
    }
}

impl From<(Year, Day)> for Problem {
    fn from((year, day): (Year, Day)) -> Self {
        Self { year, day }
    }
}

impl TryFrom<(u16, u8)> for Problem {
    type Error = String;

    fn try_from((year, day): (u16, u8)) -> Result<Self, Self::Error> {
        let Some(year) = Year::from_repr(year) else {
            return Err(format!(
                "Invalid year provided. Valid years are from 2015 to {}",
                Year::max()
            ));
        };
        if day == 0 || day > 25 {
            return Err("Invalid day provided. Valid days are from 1 to 25".to_string());
        }

        Ok(Self { year, day })
    }
}

impl Display for Problem {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}/{}", self.year, self.day)
    }
}

/// Represents a year of advent of code challenges.
#[derive(Copy, Clone, Debug, PartialEq, Eq, clap::ValueEnum, strum::FromRepr)]
#[cfg_attr(test, derive(fake::Dummy))]
#[repr(u16)]
pub enum Year {
    Y2016 = 2016,
    Y2017 = 2017,
    Y2018 = 2018,
    Y2019 = 2019,
    Y2020 = 2020,
    Y2021 = 2021,
    Y2022 = 2022,
    Y2023 = 2023,
}

impl Year {
    pub const fn as_int(self) -> u16 {
        self as u16
    }

    pub const fn max() -> Year {
        Self::Y2023
    }
}

impl Display for Year {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", self)
    }
}

/// Indicates the day for a given problem.
pub type Day = u8;

/// Indicates the level for a given daily problem. Also known as part 1 and 2.
#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy)]
pub enum Level {
    A = 1,
    B = 2,
}

impl Level {
    pub fn as_int(self) -> u8 {
        self as u8
    }
}

impl From<u8> for Level {
    fn from(value: u8) -> Self {
        match value {
            1 => Self::A,
            2 => Self::B,
            _ => panic!("Cannot convert value {} to Level", value),
        }
    }
}

#[cfg(test)]
mod test {
    use std::convert::Into;

    use fake::{Fake, Faker};

    use super::*;

    #[test]
    fn from_year_and_day_tuple() {
        let year: Year = Faker.fake();
        let day: Day = (1..=25).fake();
        assert_eq!(Into::<Problem>::into((year, day)), Problem { year, day });
    }
}