use std::collections::VecDeque;
use std::net::{ToSocketAddrs, UdpSocket};
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use std::time::Instant;

use crate::raw_socket::RawSender;

const QUEUE_LEN: usize = 1000;
/// Max pixels per packet. Constrained by MTU 1500:
/// 1500 (IP payload max) - 20 (IP hdr) - 8 (UDP hdr) - 2 (pixel hdr) = 1470
/// 1470 / 7 bytes per pixel = 210 pixels.
const PIXELS_PER_PACKET: usize = 210;
const MSG_PAYLOAD_SIZE: usize = 7 * PIXELS_PER_PACKET;
const MSGSIZE: usize = 2 + MSG_PAYLOAD_SIZE;

/// Maximum number of packets to batch into a single sendmmsg call.
const SEND_BATCH_SIZE: usize = 26;

/// A filled buffer ready to be sent, carrying its valid data length.
struct Packet {
    buf: Box<[u8; MSGSIZE]>,
    len: usize,
}

/// Shared state between all pixel writers and the sender thread.
struct SharedState {
    /// Filled packets waiting to be sent.
    pending: Mutex<VecDeque<Packet>>,
    /// Wakes the sender when packets are available.
    condvar: Condvar,
    /// Pool of empty, reusable buffers.
    pool: Mutex<Vec<Box<[u8; MSGSIZE]>>>,
}

/// Allocates a fresh buffer with the protocol header bytes set.
fn alloc_buf() -> Box<[u8; MSGSIZE]> {
    let mut buf = Box::new([0u8; MSGSIZE]);
    buf[0] = 0x00;
    buf[1] = 0x01;
    buf
}

/// A pixel writer that fills buffers and submits them for sending.
///
/// Multiple writers can exist concurrently, each with its own buffer,
/// all sharing the same pending queue and buffer pool. This enables
/// multi-threaded drawing.
///
/// Writers never block. If the pool is empty, they steal the oldest
/// unsent packet or allocate a fresh buffer.
pub struct Display {
    shared: Arc<SharedState>,
    /// The buffer currently being filled with pixel data.
    current_buf: Box<[u8; MSGSIZE]>,
    /// How many pixels have been written into `current_buf`.
    pos_in_buf: usize,
}

impl Display {
    /// Creates a new Display and spawns the sender thread.
    ///
    /// The returned Display can create additional writers via
    /// `create_writer()` for multi-threaded drawing.
    pub fn new(host: &str, port: u16, interface: Option<&str>) -> Self {
        let shared = Arc::new(SharedState {
            pending: Mutex::new(VecDeque::with_capacity(QUEUE_LEN)),
            condvar: Condvar::new(),
            pool: Mutex::new(Vec::with_capacity(QUEUE_LEN)),
        });

        // Pre-allocate the buffer pool. Keep one out as the initial current_buf.
        {
            let mut pool = shared.pool.lock().unwrap();
            for _ in 1..QUEUE_LEN {
                pool.push(alloc_buf());
            }
        }

        for _ in [1,2,3,4,5] {
        // Spawn the sender thread.
        let shared_sender = Arc::clone(&shared);
        match RawSender::new(host, port, 0, interface) {
            Ok(raw_sender) => {
                eprintln!("[display] Using raw AF_PACKET sender");
                thread::Builder::new()
                .name("sender-raw".into())
                .spawn(move || {
                    sender_loop_raw(raw_sender, shared_sender);
                }).unwrap();
            }
            Err(e) => {
                eprintln!("[display] Raw AF_PACKET unavailable ({}), falling back to UDP", e);
                let remote_addr = (host, port)
                    .to_socket_addrs()
                    .expect("Invalid remote address")
                    .next()
                    .expect("Could not resolve host");
                let socket = UdpSocket::bind("0.0.0.0:0").expect("Could not bind");
                socket.connect(remote_addr).expect("Could not connect");
                thread::Builder::new()
                .name("sender-raw".into())
                .spawn(move || {
                    sender_loop_udp(socket, shared_sender);
                }).unwrap();
            }
        };
        }


        Display {
            shared,
            current_buf: alloc_buf(),
            pos_in_buf: 0,
        }
    }

    /// Grabs an empty buffer: try pool first, steal oldest pending, or allocate.
    fn grab_buf(&self) -> Box<[u8; MSGSIZE]> {
        // 1. Try the pool (fast path).
        {
            let mut pool = self.shared.pool.lock().unwrap();
            if let Some(buf) = pool.pop() {
                return buf;
            }
        }
        // 2. Pool empty -- steal the oldest unsent packet.
        {
            let mut pending = self.shared.pending.lock().unwrap();
            if let Some(oldest) = pending.pop_front() {
                return oldest.buf;
            }
        }
        // 3. Everything is in-flight. Allocate a fresh buffer.
        alloc_buf()
    }

    /// Flushes the current buffer if it contains pixel data.
    ///
    /// This method **never blocks**.
    pub fn flush_frame(&mut self) {
        if self.pos_in_buf == 0 {
            return;
        }

        let len = 2 + self.pos_in_buf * 7;
        let new_buf = self.grab_buf();
        let filled_buf = std::mem::replace(&mut self.current_buf, new_buf);

        {
            let mut pending = self.shared.pending.lock().unwrap();
            pending.push_back(Packet {
                buf: filled_buf,
                len,
            });
        }
        self.shared.condvar.notify_one();
        self.pos_in_buf = 0;
    }

    #[inline]
    pub fn write_raw_pixels(&mut self, pixels: &[[u8; 7]]) {
        let mut remaining = pixels;
        while !remaining.is_empty() {
            let space = PIXELS_PER_PACKET - self.pos_in_buf;
            let n = remaining.len().min(space);
            let dst_offset = 2 + self.pos_in_buf * 7;

            // SAFETY: [u8; 7] has align 1 and no padding, so &[[u8; 7]]
            // is a contiguous block of bytes we can copy in one shot.
            let src = unsafe {
                std::slice::from_raw_parts(remaining.as_ptr() as *const u8, n * 7)
            };
            self.current_buf[dst_offset..dst_offset + n * 7].copy_from_slice(src);

            self.pos_in_buf += n;
            remaining = &remaining[n..];

            if self.pos_in_buf == PIXELS_PER_PACKET {
                self.flush_frame();
            }
        }
    }

}

// ---------------------------------------------------------------------------
// Sender threads
// ---------------------------------------------------------------------------

/// Raw AF_PACKET sender loop.
fn sender_loop_raw(raw_sender: RawSender, shared: Arc<SharedState>) {
    let mut batch: Vec<Packet> = Vec::with_capacity(SEND_BATCH_SIZE);
    let mut frame_buf: Vec<u8> = Vec::with_capacity(SEND_BATCH_SIZE * (MSGSIZE + 42));

    let mut stats_sent: u64 = 0;
    let mut stats_bytes: u64 = 0;
    let mut stats_errors: u64 = 0;
    let mut stats_send_us: u128 = 0;
    let mut stats_send_calls: u64 = 0;
    let mut stats_last_report = Instant::now();

    loop {
        {
            let mut pending = shared.pending.lock().unwrap();
            while pending.is_empty() {
                pending = shared.condvar.wait(pending).unwrap();
            }
            let n = pending.len().min(SEND_BATCH_SIZE);
            batch.extend(pending.drain(..n));
        }

        let payload_ptrs: Vec<&[u8]> = batch.iter().map(|p| &p.buf[..p.len]).collect();

        let send_start = Instant::now();
        match raw_sender.send_batch(&payload_ptrs, &mut frame_buf) {
            Ok(n) => {
                stats_sent += n as u64;
                for packet in batch.iter().take(n) {
                    stats_bytes += (packet.len + raw_sender.header_size()) as u64;
                }
            }
            Err(e) => {
                if stats_errors == 0 {
                    eprintln!("sendmmsg (raw) failed: {}", e);
                }
                stats_errors += batch.len() as u64;
            }
        }
        stats_send_us += send_start.elapsed().as_micros();
        stats_send_calls += 1;

        // Return buffers to the pool.
        {
            let mut pool = shared.pool.lock().unwrap();
            for packet in batch.drain(..) {
                pool.push(packet.buf);
            }
        }

        let now = Instant::now();
        let elapsed = (now - stats_last_report).as_secs_f64();
        if elapsed >= 5.0 {
            let pps = stats_sent as f64 / elapsed;
            let mbps = stats_bytes as f64 * 8.0 / elapsed / 1_000_000.0;
            eprintln!(
                "[sender]    {:.0} pkt/s, {:.1} Mbit/s sent | errors: {} | send: {:.0}us avg over {} calls",
                pps, mbps, stats_errors,
                stats_send_us as f64 / stats_send_calls.max(1) as f64,
                stats_send_calls,
            );
            stats_sent = 0;
            stats_bytes = 0;
            stats_errors = 0;
            stats_send_us = 0;
            stats_send_calls = 0;
            stats_last_report = now;
        }
    }
}

/// Fallback UDP sender loop.
fn sender_loop_udp(socket: UdpSocket, shared: Arc<SharedState>) {
    let mut batch: Vec<Packet> = Vec::with_capacity(SEND_BATCH_SIZE);
    let mut stats_sent: u64 = 0;
    let mut stats_bytes: u64 = 0;
    let mut stats_last_report = Instant::now();

    loop {
        {
            let mut pending = shared.pending.lock().unwrap();
            while pending.is_empty() {
                pending = shared.condvar.wait(pending).unwrap();
            }
            let n = pending.len().min(SEND_BATCH_SIZE);
            batch.extend(pending.drain(..n));
        }

        for packet in &batch {
            if let Ok(n) = socket.send(&packet.buf[..packet.len]) {
                stats_sent += 1;
                stats_bytes += n as u64;
            }
        }

        {
            let mut pool = shared.pool.lock().unwrap();
            for packet in batch.drain(..) {
                pool.push(packet.buf);
            }
        }

        let now = Instant::now();
        let elapsed = (now - stats_last_report).as_secs_f64();
        if elapsed >= 5.0 {
            let pps = stats_sent as f64 / elapsed;
            let mbps = stats_bytes as f64 * 8.0 / elapsed / 1_000_000.0;
            eprintln!("[sender]    {:.0} pkt/s, {:.1} Mbit/s (UDP fallback)", pps, mbps);
            stats_sent = 0;
            stats_bytes = 0;
            stats_last_report = now;
        }
    }
}
