#![feature(error_generic_member_access, error_reporter)]

use std::backtrace::Backtrace;
use std::error::{Error, Report, Request};
use std::fmt;

#[derive(Debug, PartialEq)]
struct A;
#[derive(Debug, PartialEq)]
struct B;

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

impl Error for A {}
impl Error for B {}

#[test]
fn downcasting() {
    let mut a = A;
    let a = &mut a as &mut (dyn Error + 'static);
    assert_eq!(a.downcast_ref::<A>(), Some(&A));
    assert_eq!(a.downcast_ref::<B>(), None);
    assert_eq!(a.downcast_mut::<A>(), Some(&mut A));
    assert_eq!(a.downcast_mut::<B>(), None);

    let a: Box<dyn Error> = Box::new(A);
    match a.downcast::<B>() {
        Ok(..) => panic!("expected error"),
        Err(e) => assert_eq!(*e.downcast::<A>().unwrap(), A),
    }
}

#[derive(Debug)]
struct SuperError {
    source: SuperErrorSideKick,
}

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

impl Error for SuperError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        Some(&self.source)
    }
}

#[derive(Debug)]
struct SuperErrorSideKick;

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

impl Error for SuperErrorSideKick {}

#[test]
fn single_line_formatting() {
    let error = SuperError { source: SuperErrorSideKick };
    let report = Report::new(&error);
    let actual = report.to_string();
    let expected = String::from("SuperError is here!: SuperErrorSideKick is here!");

    assert_eq!(expected, actual);
}

#[test]
fn multi_line_formatting() {
    let error = SuperError { source: SuperErrorSideKick };
    let report = Report::new(&error).pretty(true);
    let actual = report.to_string();
    let expected = String::from(
        "\
SuperError is here!

Caused by:
      SuperErrorSideKick is here!",
    );

    assert_eq!(expected, actual);
}

#[test]
fn error_with_no_sources_formats_single_line_correctly() {
    let report = Report::new(SuperErrorSideKick);
    let actual = report.to_string();
    let expected = String::from("SuperErrorSideKick is here!");

    assert_eq!(expected, actual);
}

#[test]
fn error_with_no_sources_formats_multi_line_correctly() {
    let report = Report::new(SuperErrorSideKick).pretty(true);
    let actual = report.to_string();
    let expected = String::from("SuperErrorSideKick is here!");

    assert_eq!(expected, actual);
}

#[test]
fn error_with_backtrace_outputs_correctly_with_one_source() {
    let trace = Backtrace::force_capture();
    let expected = format!(
        "\
The source of the error

Caused by:
      Error with backtrace

Stack backtrace:
{}",
        trace
    );
    let error = GenericError::new("Error with backtrace");
    let mut error = GenericError::new_with_source("The source of the error", error);
    error.backtrace = Some(trace);
    let report = Report::new(error).pretty(true).show_backtrace(true);

    println!("Error: {report}");
    assert_eq!(expected.trim_end(), report.to_string());
}

#[test]
fn error_with_backtrace_outputs_correctly_with_two_sources() {
    let trace = Backtrace::force_capture();
    let expected = format!(
        "\
Error with two sources

Caused by:
   0: The source of the error
   1: Error with backtrace

Stack backtrace:
{}",
        trace
    );
    let mut error = GenericError::new("Error with backtrace");
    error.backtrace = Some(trace);
    let error = GenericError::new_with_source("The source of the error", error);
    let error = GenericError::new_with_source("Error with two sources", error);
    let report = Report::new(error).pretty(true).show_backtrace(true);

    println!("Error: {report}");
    assert_eq!(expected.trim_end(), report.to_string());
}

#[derive(Debug)]
struct GenericError<D> {
    message: D,
    backtrace: Option<Backtrace>,
    source: Option<Box<dyn Error + 'static>>,
}

impl<D> GenericError<D> {
    fn new(message: D) -> GenericError<D> {
        Self { message, backtrace: None, source: None }
    }

    fn new_with_source<E>(message: D, source: E) -> GenericError<D>
    where
        E: Error + 'static,
    {
        let source: Box<dyn Error + 'static> = Box::new(source);
        let source = Some(source);
        GenericError { message, backtrace: None, source }
    }
}

impl<D> fmt::Display for GenericError<D>
where
    D: fmt::Display,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&self.message, f)
    }
}

impl<D> Error for GenericError<D>
where
    D: fmt::Debug + fmt::Display,
{
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        self.source.as_deref()
    }

    fn provide<'a>(&'a self, req: &mut Request<'a>) {
        self.backtrace.as_ref().map(|bt| req.provide_ref::<Backtrace>(bt));
    }
}

#[test]
fn error_formats_single_line_with_rude_display_impl() {
    #[derive(Debug)]
    struct MyMessage;

    impl fmt::Display for MyMessage {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            f.write_str("line 1\nline 2")?;
            f.write_str("\nline 3\nline 4\n")?;
            f.write_str("line 5\nline 6")?;
            Ok(())
        }
    }

    let error = GenericError::new(MyMessage);
    let error = GenericError::new_with_source(MyMessage, error);
    let error = GenericError::new_with_source(MyMessage, error);
    let error = GenericError::new_with_source(MyMessage, error);
    let report = Report::new(error);
    let expected = "\
line 1
line 2
line 3
line 4
line 5
line 6: line 1
line 2
line 3
line 4
line 5
line 6: line 1
line 2
line 3
line 4
line 5
line 6: line 1
line 2
line 3
line 4
line 5
line 6";

    let actual = report.to_string();
    assert_eq!(expected, actual);
}

#[test]
fn error_formats_multi_line_with_rude_display_impl() {
    #[derive(Debug)]
    struct MyMessage;

    impl fmt::Display for MyMessage {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            f.write_str("line 1\nline 2")?;
            f.write_str("\nline 3\nline 4\n")?;
            f.write_str("line 5\nline 6")?;
            Ok(())
        }
    }

    let error = GenericError::new(MyMessage);
    let error = GenericError::new_with_source(MyMessage, error);
    let error = GenericError::new_with_source(MyMessage, error);
    let error = GenericError::new_with_source(MyMessage, error);
    let report = Report::new(error).pretty(true);
    let expected = "line 1
line 2
line 3
line 4
line 5
line 6

Caused by:
   0: line 1
      line 2
      line 3
      line 4
      line 5
      line 6
   1: line 1
      line 2
      line 3
      line 4
      line 5
      line 6
   2: line 1
      line 2
      line 3
      line 4
      line 5
      line 6";

    let actual = report.to_string();
    assert_eq!(expected, actual);
}

#[test]
fn errors_that_start_with_newline_formats_correctly() {
    #[derive(Debug)]
    struct MyMessage;

    impl fmt::Display for MyMessage {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            f.write_str("\nThe message\n")
        }
    }

    let error = GenericError::new(MyMessage);
    let error = GenericError::new_with_source(MyMessage, error);
    let error = GenericError::new_with_source(MyMessage, error);
    let report = Report::new(error).pretty(true);
    let expected = "
The message


Caused by:
   0: \
\n      The message
      \
\n   1: \
\n      The message
      ";

    let actual = report.to_string();
    assert_eq!(expected, actual);
}

#[test]
fn errors_with_multiple_writes_on_same_line_dont_insert_erroneous_newlines() {
    #[derive(Debug)]
    struct MyMessage;

    impl fmt::Display for MyMessage {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            f.write_str("The message")?;
            f.write_str(" goes on")?;
            f.write_str(" and on.")
        }
    }

    let error = GenericError::new(MyMessage);
    let error = GenericError::new_with_source(MyMessage, error);
    let error = GenericError::new_with_source(MyMessage, error);
    let report = Report::new(error).pretty(true);
    let expected = "\
The message goes on and on.

Caused by:
   0: The message goes on and on.
   1: The message goes on and on.";

    let actual = report.to_string();
    println!("{actual}");
    assert_eq!(expected, actual);
}

#[test]
fn errors_with_string_interpolation_formats_correctly() {
    #[derive(Debug)]
    struct MyMessage(usize);

    impl fmt::Display for MyMessage {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "Got an error code: ({}). ", self.0)?;
            write!(f, "What would you like to do in response?")
        }
    }

    let error = GenericError::new(MyMessage(10));
    let error = GenericError::new_with_source(MyMessage(20), error);
    let report = Report::new(error).pretty(true);
    let expected = "\
Got an error code: (20). What would you like to do in response?

Caused by:
      Got an error code: (10). What would you like to do in response?";
    let actual = report.to_string();
    assert_eq!(expected, actual);
}

#[test]
fn empty_lines_mid_message() {
    #[derive(Debug)]
    struct MyMessage;

    impl fmt::Display for MyMessage {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            f.write_str("line 1\n\nline 2")
        }
    }

    let error = GenericError::new(MyMessage);
    let error = GenericError::new_with_source(MyMessage, error);
    let error = GenericError::new_with_source(MyMessage, error);
    let report = Report::new(error).pretty(true);
    let expected = "\
line 1

line 2

Caused by:
   0: line 1
      \
\n      line 2
   1: line 1
      \
\n      line 2";

    let actual = report.to_string();
    assert_eq!(expected, actual);
}

#[test]
fn only_one_source() {
    #[derive(Debug)]
    struct MyMessage;

    impl fmt::Display for MyMessage {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            f.write_str("line 1\nline 2")
        }
    }

    let error = GenericError::new(MyMessage);
    let error = GenericError::new_with_source(MyMessage, error);
    let report = Report::new(error).pretty(true);
    let expected = "\
line 1
line 2

Caused by:
      line 1
      line 2";

    let actual = report.to_string();
    assert_eq!(expected, actual);
}
