use crate::NEWLINE;
use chrono::{
    format::{DelayedFormat, Fixed, Item},
    DateTime, Local,
};
use log::{Level, Record};
use log4rs::encode::{Encode, Write};
use serde::ser::{self, Serialize, SerializeMap};
use serde_json;
use std::{fmt, option, thread};

/// An `Encode`r which writes a JSON object.
#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
pub struct JsonEncoder(Option<String>);

impl JsonEncoder {
    /// Returns a new `JsonEncoder` with a default configuration.
    pub fn new(target: Option<String>) -> Self {
        JsonEncoder(target)
    }
}

impl JsonEncoder {
    fn encode_inner(
        &self,
        w: &mut dyn Write,
        time: DateTime<Local>,
        record: &Record,
    ) -> anyhow::Result<()> {
        let tmp;
        let target = if let Some(t) = self.0.clone() {
            tmp = t;
            &tmp
        } else {
            record.target()
        };
        let thread = thread::current();
        let message = Message {
            time: time.format_with_items(Some(Item::Fixed(Fixed::RFC3339)).into_iter()),
            message: record.args(),
            level: record.level(),
            module_path: record.module_path(),
            file: record.file(),
            line: record.line(),
            target: target,
            thread: thread.name(),
            thread_id: thread_id::get(),
        };
        message.serialize(&mut serde_json::Serializer::new(&mut *w))?;
        w.write_all(NEWLINE.as_bytes())?;
        Ok(())
    }
}

impl Encode for JsonEncoder {
    fn encode(&self, w: &mut dyn Write, record: &Record) -> anyhow::Result<()> {
        self.encode_inner(w, Local::now(), record)
    }
}

#[derive(serde::Serialize)]
struct Message<'a> {
    #[serde(serialize_with = "ser_display")]
    time: DelayedFormat<option::IntoIter<Item<'a>>>,
    #[serde(serialize_with = "ser_display")]
    message: &'a fmt::Arguments<'a>,
    #[serde(skip_serializing_if = "Option::is_none")]
    module_path: Option<&'a str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    file: Option<&'a str>,
    #[serde(skip_serializing_if = "Option::is_none", serialize_with = "ser_option_line")]
    line: Option<u32>,
    #[serde(serialize_with = "ser_display")]
    level: Level,
    target: &'a str,
    #[serde(skip_serializing_if = "Option::is_none")]
    thread: Option<&'a str>,
    #[serde(serialize_with = "ser_display")]
    thread_id: usize,
}

fn ser_display<T, S>(v: &T, s: S) -> Result<S::Ok, S::Error>
where
    T: fmt::Display,
    S: ser::Serializer,
{
    s.collect_str(v)
}

/// 序列化 Option<u32> 为字符串
fn ser_option_line<S>(v: &Option<u32>, s: S) -> Result<S::Ok, S::Error>
where
    S: ser::Serializer,
{
    match v {
        Some(line) => s.serialize_str(&line.to_string()),
        None => s.serialize_none(),
    }
}
