use std::panic::AssertUnwindSafe;

use crate::{ TestCase, TestStatus, TestResult };

use crate::engine::PlanRc as TestPlanRc;
use crate::engine::Plan   as TestPlan;
use crate::engine::PlanOpts;
use crate::engine::case::opts::{ Opts as CaseOpts, Expect };

use crate::util::status::AsStatus;

use super::desc::Desc;

//#[derive(Debug)]
struct TmpResult<T>(T, TestStatus);

impl <T> AsStatus for TmpResult<T>
{
    fn as_status(&self) -> TestStatus {
	self.1
    }
}

pub struct Opts<'a> {
    plan: &'a Plan,
    opts: PlanOpts,
}

impl <'a> Opts<'a> {
    pub fn set_total_count(mut self, count: u32) -> Self {
	self.opts.set_total_count(count);
	self
    }

    pub fn set_description(mut self, desc: &str) -> Self {
	self.opts.set_description(desc);
	self
    }

    pub fn set_comment(mut self, comment: &str) -> Self {
	self.opts.set_comment(comment);
	self
    }

    pub fn ignore_abort(mut self) -> Self {
	self.opts.ignore_abort();
	self
    }

    pub fn run<F>(self, f: F) -> TestStatus
    where
	F: FnOnce(&Plan),
    {
	self.opts.run(&self.plan.0, f)
    }
}

pub struct Plan(TestPlanRc);

pub struct CaseProxy<'a> {
    plan:	&'a Plan,
    opts:	CaseOpts,
}

impl <'a> CaseProxy<'a>
{
    pub fn set_description(mut self, desc: &str) -> Self {
	self.opts.replace_description(desc);
	self
    }

    pub fn set_comment(mut self, comment: &str) -> Self {
	self.opts.replace_comment(comment);
	self
    }

    pub fn set_expect(mut self, expect: Expect, do_degrade: bool) -> Self {
	self.opts.replace_expect(expect, do_degrade);
	self
    }

    pub fn set_skip(mut self, do_skip: bool) -> Self {
	self.opts.replace_skip(do_skip);
	self
    }

    pub fn run<F,R>(self, f: F) -> TestResult<R>
    where
	F: FnOnce() -> R,
	R: AsStatus ,
    {
	self.plan.run_case(self.opts, f)
    }

    pub fn eq<T,F,R>(self, exp: T, f: F) -> TestResult<()>
    where
	T: std::fmt::Debug,
	F: FnOnce() -> R,
	R: PartialEq<T>,
	R: std::fmt::Debug,
	R: Clone,
    {
	self.plan.eq(self.opts, exp, f)
    }

    pub fn ne<T,F,R>(self, exp: T, f: F) -> TestResult<()>
    where
	T: std::fmt::Debug,
	F: FnOnce() -> R,
	R: PartialEq<T>,
	R: std::fmt::Debug,
	R: Clone,
    {
	self.plan.ne(self.opts, exp, f)
    }
}

impl Plan {
    pub fn new() -> Self {
	Self(TestPlanRc::new_root())
    }

    pub(crate) fn from_plan(plan: &TestPlanRc) -> Self {
	Self(plan.clone())
    }

    pub fn new_case(&self, name: &str) -> CaseProxy {
	CaseProxy {
	    plan:	self,
	    opts:	CaseOpts::new(name),
	}
    }

    fn run_case<F,R>(&self, opts: CaseOpts, f: F) -> TestResult<R>
    where
	F: FnOnce() -> R,
	R: AsStatus ,
    {
	let mut do_skip = opts.do_skip;

	opts.create(self.0.clone());

	if !do_skip && self.0.is_aborted() {
	    TestCase::with_mut(|c| c.report.set_reason("skipped because plan aborted"));
	    do_skip = true;
	}

	let res = if do_skip {
	    TestCase::with_mut(|c| c.finish(Ok(TestStatus::Skip)));
	    None
	} else {
	    TestCase::with_mut(|c| c.start());
	    let res = std::panic::catch_unwind(AssertUnwindSafe(|| {
		f()
	    }));

	    let (res_val, res_err) = match res {
		Ok(v)  => (Some(v), None),
		Err(e) => (None,    Some(e)),
	    };
	    if let Some(v) = &res_val {
		if let Some(r) = v.reason() {
		    TestCase::with_mut(|c| c.report.set_reason(&r));
		}
	    }
	    TestCase::with_mut(|c| c.finish(
		if let Some(e) = res_err {
		    Err(e)
		} else if let Some(v) = &res_val {
		    Ok(v.as_status())
		} else {
		    panic!("impossible branch");
		}
	    ));

	    res_val
	};

	TestResult::new(res, TestCase::close())
    }

    pub fn abort(&self, reason: &str)
    {
	self.0.set_status(TestStatus::Abort);
	self.0.set_reason(reason);
    }

    pub fn ok<D,F,R>(&self, desc: D, f: F) -> TestResult<R>
    where
	F: FnOnce() -> R,
	R: AsStatus,
	D: Into<Desc>
    {
	self.run_case(desc.into().into_inner(), f)
    }

    pub fn fail<D,F,R>(&self, desc: D, f: F) -> TestResult<R>
    where
	F: FnOnce() -> R,
	R: AsStatus,
	D: Into<Desc>
    {
	self.run_case(desc.into().into_inner()
		      .set_expect(Expect::FailOrPanic, false),
		      f)
    }

    pub fn fail_nopanic<D,F,R>(&self, desc: D, f: F) -> TestResult<R>
    where
	F: FnOnce() -> R,
	R: AsStatus,
	D: Into<Desc>
    {
	self.run_case(desc.into().into_inner()
		      .set_expect(Expect::Fail, false),
		      f)
    }

    pub fn panic<D,F,R>(&self, desc: D, f: F) -> TestResult<R>
    where
	F: FnOnce() -> R,
	R: AsStatus,
	D: Into<Desc>
    {
	self.run_case(desc.into().into_inner()
		      .set_expect(Expect::Panic, false),
		      f)
    }

    pub fn skip<D>(&self, desc: D)
    where
	D: Into<Desc>
    {
	desc.into()
	    .into_inner()
	    .set_skip(true)
	    .create(self.0.clone());

	TestCase::with_mut(|c| c.finish(Ok(TestStatus::Skip)));

	TestCase::close();
    }

    pub fn todo<D,F,R>(&self, desc: D, f: F) -> TestResult<R>
    where
	F: FnOnce() -> R,
	R: AsStatus,
	D: Into<Desc>
    {
	self.run_case(desc.into().into_inner()
		      .set_todo(true)
		      .set_expect(Expect::Pass, true),
		      f)
    }

    pub fn comment(&self, s: &str)
    {
	self.0.append_comment(s);
    }

    pub fn cmp<D,T,F,R,C>(&self, desc: D, exp: T, f: F, cmp:C) -> TestResult<()>
    where
	T: std::fmt::Debug,
	F: FnOnce() -> R,
	R: PartialEq<T>,
	R: std::fmt::Debug,
	R: Clone,
	D: Into<Desc>,
	C: FnOnce(&R, &T) -> bool,
    {
	self.ok(desc, || {
	    let v = f();
	    let res = cmp(&v, &exp);

	    TmpResult(v, res.into())
	})
	    .record_expect(exp)
	    .record_got_fn(|v| v.0.clone())
	    .map(|_| ())
    }

    pub fn eq<D,T,F,R>(&self, desc: D, exp: T, f: F) -> TestResult<()>
    where
	T: std::fmt::Debug,
	F: FnOnce() -> R,
	R: PartialEq<T>,
	R: std::fmt::Debug,
	R: Clone,
	D: Into<Desc>,
    {
	self.cmp(desc, exp, f, |a, b| a == b)
    }

    pub fn ne<D,T,F,R>(&self, desc: D, exp: T, f: F) -> TestResult<()>
    where
	T: std::fmt::Debug,
	F: FnOnce() -> R,
	R: PartialEq<T>,
	R: std::fmt::Debug,
	R: Clone,
	D: Into<Desc>,
    {
	self.cmp(desc, exp, f, |a, b| a != b)
    }

    pub fn run<F>(&self, name: &str, f: F) -> TestStatus
    where
	F: FnOnce(&Self),
    {
	TestPlan::with_opts(name).run(&self.0, f)
    }

    pub fn new_plan<'a>(&'a self, name: &str) -> Opts<'a>
    {
	Opts {
	    plan: self,
	    opts: PlanOpts::new(name),
	}
    }

    pub unsafe fn override_status(&self, status: TestStatus) -> TestStatus
    {
	self.0.override_status(status)
    }

    pub fn is_ok(&self) -> bool
    {
	self.0.get_status().is_ok()
    }
}

impl Default for Plan {
    fn default() -> Self {
        Self::new()
    }
}
