use std::cell::RefCell;
use std::sync::atomic::AtomicU64;

use crate::{ TestStatus };

use crate::engine::PlanRc;

use super::opts::Opts;
use super::report::{ BoxedReport };

static ID: AtomicU64 = AtomicU64::new(0);

fn next_id() -> u64 {
    ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}

thread_local! {
    static ACTUAL_TEST_CASE: RefCell<Option<Case>> = RefCell::new(None);
}

#[derive(Debug)]
pub struct Case {
    id:			u64,
    plan:		PlanRc,
    opts:		Opts,

    stime:		Option<std::time::Instant>,

    pub(crate) report:	BoxedReport,
}

impl Case {
    fn new(plan: PlanRc, opts: Opts) -> Self {
	let mut report = BoxedReport::new();

	report.generic.name        = opts.name.clone();
	report.generic.description = opts.description.clone();
	report.generic.comment     = opts.comment.clone();

	Self {
	    id:		next_id(),
	    plan:	plan,
	    opts:	opts,
	    report:	report,
	    stime:	None,
	}
    }

    pub(crate) fn start(&mut self) {
	self.stime = Some(std::time::Instant::now());
    }

    pub(crate) fn finish(&mut self, status: Result<TestStatus, Box<dyn std::any::Any + Send + 'static>>)
    {
	use super::opts::Expect;

	let do_degrade = self.opts.do_degrade;

	let degraded_or  = |f: TestStatus| match (do_degrade, f) {
	    (false, v) => v,
	    (true,  v) => v.when_degraded(),
	};

	if let Some(t) = self.stime {
	    self.report.record_duration(std::time::Instant::now() - t);
	}

	//eprintln!("{:?}, status={:?}, expect={:?}", self.report.generic.name, status, self.opts.expect);

	let my_status = match (status, &self.opts.expect) {
	    (Ok(TestStatus::Skip),  _) => TestStatus::Skip,

	    (Ok(TestStatus::Init),  _) => {
		panic!("Unsupported testcase result");
	    },

	    (_, &Expect::Ignore) |
	    (Ok(TestStatus::Pass), &Expect::Pass) |
	    (Ok(TestStatus::Fail), &Expect::Fail) |
	    (Ok(TestStatus::Fail), &Expect::FailOrPanic) |
	    (Err(_), &Expect::Panic) |
	    (Err(_), &Expect::FailOrPanic) => TestStatus::Pass,

	    (Err(e), _) => {
		if let Some(s) = e.downcast_ref::<&str>() {
		    self.report.set_reason(s);
		} else if let Some(s) = e.downcast_ref::<&String>() {
		    self.report.set_reason(s);
		} else if let Some(s) = e.downcast_ref::<&std::panic::PanicInfo>() {
		    eprintln!("unknown error XXX: {:?}", s);
		    // self.report.set_reason(s);
		} else {
		    eprintln!("unknown error: {:?}", e);
		    // std::panic::resume_unwind(e);
		}

		degraded_or(TestStatus::Panic)
	    },

	    (Ok(_), &Expect::FailOrPanic) |
	    (Ok(_), &Expect::Panic) |
	    (Ok(_), &Expect::Fail) => degraded_or(TestStatus::Fail),

	    (Ok(v), _) => degraded_or(v),
	};

	self.report.status = my_status;
	self.plan.record_result(my_status);
    }

    pub fn get_status(&self) -> TestStatus {
	self.report.status
    }

    pub fn get_stderr() -> std::io::Result<std::fs::File>
    {
	Self::with_mut(|c| c.report.get_stderr())
    }

    pub fn get_stdout() -> std::io::Result<std::fs::File>
    {
	Self::with_mut(|c| c.report.get_stdout())
    }

    pub(super) fn create(plan: PlanRc, opts: Opts)
    {
	let new_testcase = Self::new(plan, opts);

	let old = ACTUAL_TEST_CASE.with(|c| {
	    c.replace(Some(new_testcase))
	});

	assert!(old.is_none());
    }

    pub(crate) fn close() -> Self {
	ACTUAL_TEST_CASE.with(|c| {
	    c.replace(None)
	})
	    .expect("no actual testcase")
    }

    pub fn with_mut<F, R>(f: F) -> R
    where
	F: FnOnce(&mut Self) -> R,
    {
	ACTUAL_TEST_CASE.with(|c| {
	    let mut testcase_ref = c.borrow_mut();
	    let mut testcase = testcase_ref
		.as_mut()
		.expect("actual testcase not set");

	    f(&mut testcase)
	})
    }
}

impl Drop for Case {
    fn drop(&mut self) {
	self.plan.append_report(self.report.take())
    }
}
