
use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, OnceLock};
use std::time::SystemTime;

static PDO_TRACE_ENABLED: AtomicBool = AtomicBool::new(false);
static MAILBOX_TRACE_ENABLED: AtomicBool = AtomicBool::new(false);

#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LogCategory {

    Error = 0,

    Warning = 1,

    Message = 2,

    Mailbox = 3,

    PDO = 4,

    Debug = 5,

    Local = 6,
}

impl LogCategory {

    pub fn all() -> &'static [LogCategory] {
        &[
            LogCategory::Error,
            LogCategory::Warning,
            LogCategory::Message,
            LogCategory::Mailbox,
            LogCategory::PDO,
            LogCategory::Debug,
            LogCategory::Local,
        ]
    }

    pub fn default_view() -> &'static [LogCategory] {
        &[
            LogCategory::Error,
            LogCategory::Warning,
            LogCategory::Message,
        ]
    }

    pub fn from_value(v: i32) -> Option<Self> {
        match v {
            0 => Some(Self::Error),
            1 => Some(Self::Warning),
            2 => Some(Self::Message),
            3 => Some(Self::Mailbox),
            4 => Some(Self::PDO),
            5 => Some(Self::Debug),
            6 => Some(Self::Local),
            _ => None,
        }
    }
}

impl fmt::Display for LogCategory {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let name = match self {
            Self::Error => "ERROR",
            Self::Warning => "WARNING",
            Self::Message => "MESSAGE",
            Self::Mailbox => "MAILBOX",
            Self::PDO => "PDO",
            Self::Debug => "DEBUG",
            Self::Local => "LOCAL",
        };
        write!(f, "{}", name)
    }
}

#[derive(Debug, Clone)]
pub struct LogEntry {

    pub timestamp: SystemTime,

    pub category: LogCategory,

    pub message: String,
}

impl LogEntry {

    pub fn new(category: LogCategory, message: &str) -> Self {
        Self {
            timestamp: SystemTime::now(),
            category,
            message: message.to_string(),
        }
    }
}

impl fmt::Display for LogEntry {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

        let duration = self.timestamp
            .duration_since(SystemTime::UNIX_EPOCH)
            .unwrap_or_default();
        let secs = duration.as_secs();
        let millis = duration.subsec_millis();

        write!(f, "[{}.{:03}] [{}] {}", secs, millis, self.category, self.message)
    }
}

const MAX_ENTRIES: usize = 10000;

const TRIM_COUNT: usize = 2000;

pub type LogUpdatedCallback = Box<dyn Fn(&LogEntry) + Send + Sync>;

pub struct LogView {

    entries: Mutex<Vec<LogEntry>>,

    filter: Mutex<Vec<LogCategory>>,

    retain: Mutex<usize>,

    on_updated: Mutex<Option<LogUpdatedCallback>>,
}

impl LogView {

    fn new() -> Self {
        Self {
            entries: Mutex::new(Vec::new()),
            filter: Mutex::new(LogCategory::default_view().to_vec()),
            retain: Mutex::new(0),
            on_updated: Mutex::new(None),
        }
    }

    pub fn set_on_updated<F>(&self, callback: F)
    where
        F: Fn(&LogEntry) + Send + Sync + 'static,
    {
        let mut cb = self.on_updated.lock().unwrap();
        *cb = Some(Box::new(callback));
    }

    pub fn clear_on_updated(&self) {
        let mut cb = self.on_updated.lock().unwrap();
        *cb = None;
    }

    pub fn retain_count(&self) -> usize {
        *self.retain.lock().unwrap()
    }

    pub fn set_retain_count(&self, count: usize) {
        let mut r = self.retain.lock().unwrap();
        *r = count;
    }

    pub fn count(&self) -> usize {
        let entries = self.entries.lock().unwrap();
        let filter = self.filter.lock().unwrap();
        entries.iter().filter(|e| filter.contains(&e.category)).count()
    }

    pub fn get_all(&self) -> Vec<LogEntry> {
        self.entries.lock().unwrap().clone()
    }

    pub fn get_filtered(&self) -> Vec<LogEntry> {
        let entries = self.entries.lock().unwrap();
        let filter = self.filter.lock().unwrap();
        entries.iter()
            .filter(|e| filter.contains(&e.category))
            .cloned()
            .collect()
    }

    pub fn set_filter(&self, categories: &[LogCategory]) {
        let mut filter = self.filter.lock().unwrap();
        *filter = categories.to_vec();
    }

    pub fn reset_filter(&self) {
        let mut filter = self.filter.lock().unwrap();
        *filter = LogCategory::all().to_vec();
    }

    fn add(&self, entry: LogEntry) {

        if let Ok(cb) = self.on_updated.lock() {
            if let Some(ref callback) = *cb {
                callback(&entry);
            }
        }

        let mut entries = self.entries.lock().unwrap();
        entries.push(entry);

        let max = {
            let r = self.retain.lock().unwrap();
            if *r > 0 { *r } else { MAX_ENTRIES }
        };
        let trim = if max == MAX_ENTRIES { TRIM_COUNT } else { max / 5 };
        if entries.len() > max {
            entries.drain(..trim.max(1));
        }
    }

    fn clear_inner(&self) {
        self.entries.lock().unwrap().clear();
    }
}

pub struct LogManager {

    default_view: LogView,
}

static INSTANCE: OnceLock<LogManager> = OnceLock::new();

impl LogManager {

    pub fn instance() -> &'static LogManager {
        INSTANCE.get_or_init(|| LogManager {
            default_view: LogView::new(),
        })
    }

    pub fn default_view(&self) -> &LogView {
        &self.default_view
    }

    pub fn pdo_trace_enabled(&self) -> bool {
        PDO_TRACE_ENABLED.load(Ordering::Relaxed)
    }

    pub fn set_pdo_trace_enabled(&self, enabled: bool) {
        PDO_TRACE_ENABLED.store(enabled, Ordering::Relaxed);
    }

    pub fn mailbox_trace_enabled(&self) -> bool {
        MAILBOX_TRACE_ENABLED.load(Ordering::Relaxed)
    }

    pub fn set_mailbox_trace_enabled(&self, enabled: bool) {
        MAILBOX_TRACE_ENABLED.store(enabled, Ordering::Relaxed);
    }

    pub fn add_log(&self, category: LogCategory, message: &str) {

        if category == LogCategory::Debug && !cfg!(debug_assertions) {
            return;
        }

        if category == LogCategory::PDO && !PDO_TRACE_ENABLED.load(Ordering::Relaxed) {
            return;
        }
        if category == LogCategory::Mailbox && !MAILBOX_TRACE_ENABLED.load(Ordering::Relaxed) {
            return;
        }

        let entry = LogEntry::new(category, message);
        self.default_view.add(entry);
    }

    pub fn clear(&self) {
        self.default_view.clear_inner();
    }
}
