use std::ops::{Deref, DerefMut};
use std::sync::Mutex;
use std::time::SystemTime;

use crate::{proxy::copy::Proxy, serialize::Serialize, write::LogWrite, Item, Severity};

pub trait Logger: Sync + Send {
    fn log(&self, item: Item);
}

pub fn logger_new_from_proxy<L, P>(original: L, proxy: P) -> impl Logger
where
    L: Logger,
    P: Proxy + Sync + Send,
{
    logger_new_from_fn(move |old: Item| {
        let neo: Item = proxy.get_item(old);
        original.log(neo)
    })
}

struct FnLogger<L> {
    internal: L,
}

impl<L> Logger for FnLogger<L>
where
    L: Fn(Item) + Sync + Send,
{
    fn log(&self, item: Item) {
        (self.internal)(item)
    }
}

pub fn logger_new_from_fn<L>(internal: L) -> impl Logger
where
    L: Fn(Item) + Sync + Send,
{
    FnLogger { internal }
}

struct WriteSerialized<S, W> {
    serialize: S,
    write: W,
}

impl<S, W> Logger for WriteSerialized<S, W>
where
    S: Serialize,
    W: LogWrite,
{
    fn log(&self, item: Item) {
        let mut buf: String = String::new();
        self.serialize.serialize(&item, &mut buf);
        self.write.write(buf.as_str(), item.severity)
    }
}

pub fn logger_new<S, W>(serialize: S, write: W) -> impl Logger
where
    S: Serialize,
    W: LogWrite,
{
    WriteSerialized { serialize, write }
}

static _LOGGER: Mutex<Option<&dyn Logger>> = Mutex::new(None);

impl Logger for Option<&dyn Logger> {
    fn log(&self, item: Item) {
        match self {
            None => {}
            Some(l) => l.log(item),
        }
    }
}

impl Logger for Mutex<Option<&dyn Logger>> {
    fn log(&self, item: Item) {
        match self.lock() {
            Err(_) => {}
            Ok(g) => {
                let ro: &Option<_> = g.deref();
                let o: Option<&dyn Logger> = ro.as_deref();
                o.log(item)
            }
        }
    }
}

impl Logger for Box<dyn Logger> {
    fn log(&self, item: Item) {
        let r = self.as_ref();
        r.log(item)
    }
}

fn _log(mut item: Item) {
    item.timestamp = SystemTime::now();
    _LOGGER.log(item)
}

pub fn log_trace(mut item: Item) {
    item.severity = Severity::Trace;
    _log(item)
}

pub fn log_debug(mut item: Item) {
    item.severity = Severity::Debug;
    _log(item)
}

pub fn log_info(mut item: Item) {
    item.severity = Severity::Info;
    _log(item)
}

pub fn log_warn(mut item: Item) {
    item.severity = Severity::Warn;
    _log(item)
}

pub fn log_error(mut item: Item) {
    item.severity = Severity::Error;
    _log(item)
}

pub fn log_fatal(mut item: Item) {
    item.severity = Severity::Fatal;
    _log(item)
}

pub fn set(neo: &'static dyn Logger) {
    match _LOGGER.try_lock() {
        Err(_) => {}
        Ok(mut g) => {
            let mo: &mut Option<_> = g.deref_mut();
            mo.replace(neo);
        }
    }
}

pub fn set_boxed(neo: Box<dyn Logger>) {
    set(Box::leak(neo))
}
