use serde_derive::Serialize;
use std::cell::RefCell;

use crate::TestStatus;
use crate::report::item::Item as ReportItem;

#[derive(Serialize)]
enum Status {
    Init,
    Skip,
    Todo,
    Pass,
    Degraded,
    Fail,
    Panic,
    Abort,
}

#[derive(Serialize)]
#[serde(untagged)]
enum Item<'a>
{
    Plan(Box<Plan<'a>>),
    Case(Box<Case<'a>>),
    Comment(&'a String),
}

#[derive(Serialize)]
struct Generic<'a>
{
    name:		&'a str,
    #[serde(skip_serializing_if = "Option::is_none")]
    description:	Option<&'a str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    comment:		Option<&'a str>,
    status:		Status,
}

#[derive(Serialize)]
struct Counter
{
    num_total:		u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    num_planned:	Option<u32>,
    num_skip:		u32,
    num_pass:		u32,
    num_degraded:	u32,
    num_fail:		u32,
    num_panic:		u32,
    num_abort:		u32,
}

#[derive(Serialize)]
struct Plan<'a>
{
    #[serde(flatten)]
    generic:	Generic<'a>,

    items:	Vec<Item<'a>>,

    statistics: Counter,
}

#[derive(Serialize)]
struct Case<'a>
{
    #[serde(flatten)]
    generic:		Generic<'a>,

    #[serde(skip_serializing_if = "Option::is_none")]
    value_expect:	Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    value_got:		Option<String>,
}

fn to_counter(counter: &crate::TestCounter,
	      num_planned: Option<u32>) -> Counter
{
    Counter {
	num_total:	counter.get_total(),
	num_planned:	num_planned,
	num_skip:	counter.num_skip,
	num_pass:	counter.num_pass,
	num_degraded:	counter.num_degraded,
	num_fail:	counter.num_fail,
	num_panic:	counter.num_panic,
	num_abort:	counter.num_abort,
    }
}

fn to_status(status: TestStatus) -> Status
{
    match status {
	TestStatus::Init	=> Status::Init,
	TestStatus::Skip	=> Status::Skip,
	TestStatus::Todo	=> Status::Todo,
	TestStatus::Pass	=> Status::Pass,
	TestStatus::Degraded	=> Status::Degraded,
	TestStatus::Fail	=> Status::Fail,
	TestStatus::Panic	=> Status::Panic,
	TestStatus::Abort	=> Status::Abort,
    }
}

fn to_generic(generic: &crate::report::generic::Generic,
	      status: TestStatus) -> Generic<'_>
{
    Generic {
	name:		&generic.name,
	description:	generic.description.as_deref(),
	comment:	generic.comment.as_deref(),
	status:		to_status(status),
    }
}

fn to_plan(plan: &crate::ReportPlan) -> Plan<'_>
{
    Plan {
	generic:	to_generic(&plan.generic, plan.status),
	items:		to_item_list(&plan.elems),
	statistics:	to_counter(&plan.counter, plan.num_planned),
    }
}

fn to_case(case: &crate::ReportCase) -> Case<'_>
{
    Case {
	generic:	to_generic(&case.generic, case.status),
	value_expect:	case.val_expect.clone(),
	value_got:	case.val_got.clone(),
    }
}

fn to_item(item: &ReportItem) -> Item<'_>
{
    match item {
	ReportItem::Plan(p) => Item::Plan(Box::new(to_plan(p))),
	ReportItem::Case(c) => Item::Case(Box::new(to_case(c))),
	ReportItem::Comment(c) => Item::Comment(c),
    }
}

fn to_item_list(item: &[ReportItem]) -> Vec<Item<'_>>
{
    item.iter()
	.map(to_item)
	.collect()
}

pub(crate) struct JsonFormatter<W>
where
    W: std::io::Write
{
    writer: RefCell<W>,
}

impl <W> JsonFormatter<W>
where
	W: std::io::Write
{
    fn new(w: W) -> Self {
	Self {
	    writer: RefCell::new(w),
	}
    }
}

impl <W> super::PlanFormatter for JsonFormatter<W>
where
    W: std::io::Write + Clone,
{
    fn serialize(&self, plan: &crate::ReportPlan) -> Result<(), ()>
    {
	let writer = self.writer.borrow();

	serde_json::to_writer_pretty(writer.clone(), &to_plan(plan)).unwrap();

	Ok(())
    }
}

pub(crate) struct JsonFormatterBuilder<W>
{
    _p: std::marker::PhantomData<fn() -> W>,
}

impl <W> JsonFormatterBuilder<W>
{
    pub fn new() -> Self {
	Self {
	    _p: std::marker::PhantomData
	}
    }
}

impl <W> super::PlanFormatterBuilder<W> for JsonFormatterBuilder<W>
where
    W: std::io::Write + Clone
{
    type Target = JsonFormatter<W>;

    fn from_writer(&self, w: W) -> Self::Target {
	Self::Target::new(w)
    }
}
