use std::any::type_name;
use std::fmt::Display;
pub use std::path::Path;

pub use color_eyre::eyre::Result;
use tracing::info;
use tracing_subscriber::EnvFilter;

pub trait Solution<Input, Output: Display> {
    fn day() -> String {
        let mut day = String::from(type_name::<Self>().split("::").next().unwrap());
        day.make_ascii_lowercase();

        day.to_string()
    }

    fn parse_input<P: AsRef<Path>>(pathname: P) -> Input;

    fn part_1(input: &Input) -> Output;
    fn part_2(input: &Input) -> Output;

    fn run(type_of_input: &str) -> Result<()>
    where
        Self: Sized,
    {
        tracing_subscriber::fmt()
            .with_env_filter(EnvFilter::from_default_env())
            .with_target(false)
            .with_file(true)
            .with_line_number(true)
            .without_time()
            .compact()
            .init();
        color_eyre::install()?;

        let input = Self::parse_input(format!("{}s/{}.txt", type_of_input, Self::day()));

        info!("Part 1: {}", Self::part_1(&input));
        info!("Part 2: {}", Self::part_2(&input));

        Ok(())
    }

    fn main() -> Result<()>
    where
        Self: Sized,
    {
        Self::run("input")
    }
}

#[macro_export]
macro_rules! test_sample {
    ($mod_name:ident, $day_struct:tt, $part_1:expr, $part_2:expr) => {
        #[cfg(test)]
        mod $mod_name {
            use super::*;

            #[test]
            fn test_part_1() {
                let sample =
                    $day_struct::parse_input(&format!("samples/{}.txt", $day_struct::day()));
                assert_eq!($day_struct::part_1(&sample), $part_1);
            }

            #[test]
            fn test_part_2() {
                let sample =
                    $day_struct::parse_input(&format!("samples/{}.txt", $day_struct::day()));
                assert_eq!($day_struct::part_2(&sample), $part_2);
            }
        }
    };
}