use bytes::BytesMut;
use crossbeam::queue::ArrayQueue;
use std::sync::Arc;

pub const BLOCK_SIZE: usize = 16384;
pub const BUFFER_POOL_BLOCKS: usize = 1024;
pub const BUFFER_POOL_PIECES: usize = 64;
pub const DEFAULT_PIECE_SIZE: usize = 2 * 1024 * 1024;

pub struct BufferPool {
    block_buffers: ArrayQueue<BytesMut>,
    piece_buffers: ArrayQueue<BytesMut>,
}

impl BufferPool {
    pub fn new() -> Arc<Self> {
        let pool = Arc::new(Self {
            block_buffers: ArrayQueue::new(BUFFER_POOL_BLOCKS),
            piece_buffers: ArrayQueue::new(BUFFER_POOL_PIECES),
        });

        for _ in 0..BUFFER_POOL_BLOCKS {
            let _ = pool.block_buffers.push(BytesMut::with_capacity(BLOCK_SIZE));
        }

        for _ in 0..BUFFER_POOL_PIECES {
            let _ = pool
                .piece_buffers
                .push(BytesMut::with_capacity(DEFAULT_PIECE_SIZE));
        }

        pool
    }

    pub fn get_block_buffer(&self) -> BytesMut {
        self.block_buffers
            .pop()
            .unwrap_or_else(|| BytesMut::with_capacity(BLOCK_SIZE))
    }

    pub fn return_block_buffer(&self, mut buf: BytesMut) {
        buf.clear();
        let _ = self.block_buffers.push(buf);
    }

    pub fn get_piece_buffer(&self, size: usize) -> BytesMut {
        if let Some(mut buf) = self.piece_buffers.pop() {
            if buf.capacity() >= size {
                return buf;
            }
            buf.reserve(size - buf.capacity());
            return buf;
        }
        BytesMut::with_capacity(size)
    }

    pub fn return_piece_buffer(&self, mut buf: BytesMut) {
        buf.clear();
        let _ = self.piece_buffers.push(buf);
    }

    pub fn block_buffers_available(&self) -> usize {
        self.block_buffers.len()
    }

    pub fn piece_buffers_available(&self) -> usize {
        self.piece_buffers.len()
    }
}

impl Default for BufferPool {
    fn default() -> Self {
        Self {
            block_buffers: ArrayQueue::new(BUFFER_POOL_BLOCKS),
            piece_buffers: ArrayQueue::new(BUFFER_POOL_PIECES),
        }
    }
}
