use std::sync::atomic::AtomicU64;
use super::plan_rc::PlanRc;
use super::opts::Opts;
use super::info_ref::InfoRef;

use crate::TestStatus;
use crate::PlanRunner;

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

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

#[derive(Debug)]
pub struct Plan {
    id:			u64,
    parent:		Option<PlanRc>,
    pub(super) info:	InfoRef,
    ignore_abort:	bool,
}

impl Plan {
    #[allow(clippy::new_ret_no_self)]
    pub fn new() -> PlanRc {
	PlanRc::new_root()
    }

    pub fn from_parent(parent: PlanRc, opts: Opts) -> PlanRc
    {
	PlanRc::new(Self {
	    id:		next_id(),
	    parent:	Some(parent.clone()),
	    info:	InfoRef::create(&parent, &opts),
	    ignore_abort: opts.ignore_abort,
	})
    }

    pub fn with_opts(name: &str) -> Opts
    {
	Opts::new(name)
    }

    pub(super) fn new_root() -> Self {
	Self {
	    id:		next_id(),
	    parent:	None,
	    info:	InfoRef::default(),
	    ignore_abort: false,
	}
    }

    fn finish(&self, duration: std::time::Duration)
    {
	self.info.with_mut(|i| i.record_duration(duration));
    }

    fn close(&self, mut report: Box<crate::ReportPlan>)
    {
	report.close();

	match self.parent.as_ref() {
	    Some(p) => {
		p.record_result(report.status);
		p.info.with_mut(|i| i.append_report(report));
	    },

	    None    => panic!("no parent"),
	}
    }

    pub(crate) fn set_status(&self, status: TestStatus)
    {
	let info = self.info.lock().unwrap();

	info.borrow_mut().report.status.update(status);
    }

    pub(crate) fn set_reason(&self, reason: &str)
    {
	let info = self.info.lock().unwrap();

	if info.borrow().report.reason.is_none() {
	    info.borrow_mut().report.reason = Some(reason.to_string());
	}
    }

    pub fn get_status(&self) -> TestStatus {
	self.info.with(|i| i.report.status)
    }

    pub fn is_aborted(&self) -> bool {
	self.get_status() == TestStatus::Abort && !self.ignore_abort
    }

    pub fn override_status(&self, status: TestStatus) -> TestStatus
    {
	let info = self.info.lock().unwrap();
	let old = info.borrow().report.status;

	info.borrow_mut().report.status = status;
	old
    }

    pub fn record_result(&self, status: TestStatus)
    {
	let info = self.info.lock().unwrap();

	info.borrow_mut().report.status.update(status);
	info.borrow_mut().report.counter.inc_from_status(status);
    }

    fn take_report(&self) -> Box<crate::ReportPlan>
    {
	let info_lock = self.info.lock().unwrap();
	let mut info = info_lock.borrow_mut();

	info.report.take()
    }

    pub(crate) fn append_report(&self, report: Box<crate::ReportCase>)
    {
	let info_lock = self.info.lock().unwrap();
	let mut info = info_lock.borrow_mut();

	info.report.append_case(report)
    }

    pub(crate) fn append_comment(&self, s: &str)
    {
	let info_lock = self.info.lock().unwrap();
	let mut info = info_lock.borrow_mut();

	info.report.append_comment(s)
    }

    pub(super) fn run_internal<F>(parent: PlanRc, opts: Opts, f: F) -> TestStatus
    where
	F: FnOnce(&PlanRunner),
    {
	let new_plan = Self::from_parent(parent, opts);

	new_plan.set_status(TestStatus::Pass);

	let stime = std::time::Instant::now();
	f(&PlanRunner::from_plan(&new_plan));
	let etime = std::time::Instant::now();

	new_plan.finish(etime - stime);

	let mut report = new_plan.take_report();

	if let Some(v) = &report.num_planned {
	    if *v != report.counter.get_total() {
		report.status.update(TestStatus::Fail);
		if report.reason.is_none() {
		    report.reason = Some("testcase number mismatch".to_string());
		}
	    }
	}

	let status = report.status;

	new_plan.close(report);

	status
    }

    fn emit(&self)
    {
	self.info.with(|i| i.emit_report());
    }
}

impl Drop for Plan {
    fn drop(&mut self) {
	if self.parent.is_none() {
	    self.emit();

	    assert!(self.get_status().is_ok());
	}
    }
}
