//! Logger client (IPC sender)

use crate::errors::{FalcornError, Result};
use crate::logging::ipc::LogEvent;
use crossbeam_channel::{Receiver, Sender, bounded};
use std::io::Write;
use std::os::unix::net::UnixStream;
use std::thread;
use std::time::Duration;

pub fn start_logger_client(socket_path: &str) -> Sender<LogEvent> {
    let (tx, rx) = bounded::<LogEvent>(4096);
    let path = socket_path.to_string();

    thread::spawn(move || {
        let mut reconnect_backoff = Duration::from_millis(50);
        const MAX_BACKOFF: Duration = Duration::from_millis(1000);

        loop {
            let mut stream = match UnixStream::connect(&path) {
                Ok(stream) => {
                    reconnect_backoff = Duration::from_millis(50);
                    stream
                }
                Err(_) => {
                    thread::sleep(reconnect_backoff);
                    reconnect_backoff = (reconnect_backoff * 2).min(MAX_BACKOFF);
                    continue;
                }
            };

            let _ = stream.set_nonblocking(false);

            if pump_logs(&mut stream, &rx).is_err() {
                // Logger backend temporarily unavailable:
                // reconnect in background and keep workers unaffected.
            }
        }
    });

    tx
}

fn pump_logs(stream: &mut UnixStream, rx: &Receiver<LogEvent>) -> Result<()> {
    for evt in rx.iter() {
        let payload = bincode::serialize(&evt)
            .map_err(|e| FalcornError::runtime(format!("bincode serialize: {}", e)))?;

        let len = payload.len() as u32;
        let mut frame = Vec::with_capacity(4 + payload.len());

        frame.extend_from_slice(&len.to_le_bytes());
        frame.extend_from_slice(&payload);

        if stream.write_all(&frame).is_err() {
            // drop on write error
            break;
        }
    }
    Ok(())
}
