//! This crates provides a sequentially locking Ring Buffer. It allows for
//! a fast and non-writer-blocking SPMC queue, where all comsumers read all
//! messages.

#![warn(missing_docs)]
#![cfg_attr(feature = "nightly", feature(const_ptr_write))]
#![cfg_attr(feature = "nightly", feature(const_mut_refs))]

use core::mem::MaybeUninit;
use core::sync::atomic::{fence, AtomicBool, AtomicUsize, Ordering};

#[derive(Debug)]
pub struct RingBuffer<T: Copy, const N: usize> {
    // what else goes here?
    // version?
    // TODO(Emil): Can we make sure this is properly aligned for cache loads?
    locked: AtomicBool,
    version: AtomicUsize,
    index: AtomicUsize,
    data: [Block<T>; N],
}

impl<T: Copy, const N: usize> RingBuffer<T, N> {
    #[cfg(feature = "nightly")]
    pub const fn new() -> RingBuffer<T, N> {
        // Initialize the array.
        let data: [Block<T>; N] = unsafe {
            let mut data: [Block<T>; N] = MaybeUninit::uninit().assume_init();
            core::ptr::write_bytes(&mut data, 0, core::mem::size_of::<Block<T>>() * N);
            data
        };

        RingBuffer {
            locked: AtomicBool::new(false),
            version: AtomicUsize::new(0),
            index: AtomicUsize::new(0),
            data,
        }
    }

    #[cfg(not(feature = "nightly"))]
    pub fn new() -> RingBuffer<T, N> {
    // Initialize the array.
        let data: [Block<T>; N] = unsafe {
            let mut data: [Block<T>; N] = MaybeUninit::uninit().assume_init();
            core::ptr::write_bytes(&mut data, 0, core::mem::size_of::<Block<T>>() * N);
            data
        };

        RingBuffer {
            locked: AtomicBool::new(false),
            version: AtomicUsize::new(0),
            index: AtomicUsize::new(0),
            data,
        }
    }

    pub fn try_lock(&self) -> Result<WriteGuard<'_, T, N>, ()> {
        if !self.locked.swap(true, Ordering::AcqRel) {
            Ok(WriteGuard { buffer: &self })
        } else {
            Err(())
        }
    }

    pub fn reader(&self) -> ReadGuard<'_, T, N> {
        ReadGuard {
            buffer: self,
            index: 0,
            version: self.version.load(Ordering::Acquire),
        }
    }
}

#[derive(Debug, Clone)]
pub struct ReadGuard<'read, T: Copy, const N: usize> {
    buffer: &'read RingBuffer<T, N>,
    index: usize,
    version: usize,
}

impl<'read, T: Copy, const N: usize> ReadGuard<'read, T, N> {
    /// Pops the next element from the front. The element is only popped for us and other threads
    /// will still need to pop this for themselves.
    pub fn pop_front(&mut self) -> Option<T> {
        // Checks if data if we are currently caught up.
        if !self.check_version() {
            return None;
        }

        let mut seq1 = self.buffer.data[self.index].seq.load(Ordering::Acquire);

        // # Safety: We verify that there was no race before returning data.
        let mut data = unsafe { self.buffer.data[self.index].message.assume_init() };

        let mut seq2 = self.buffer.data[self.index].seq.load(Ordering::Relaxed);

        while seq1 % 2 != 0 && seq1 != seq2 {
            seq1 = self.buffer.data[self.index].seq.load(Ordering::Relaxed);

            fence(Ordering::Acquire);

            // # Safety: We verify that there was no race before returning data.
            data = unsafe { self.buffer.data[self.index].message.assume_init() };

            seq2 = self.buffer.data[self.index].seq.load(Ordering::Acquire);
        }

        self.index = (self.index + 1) % N;
        Some(data)
    }

    /// Checks if we are reading data we have already consumed.
    #[inline]
    fn check_version(&mut self) -> bool {
        // The current version of the
        let seq = self.buffer.data[self.index].seq.load(Ordering::Acquire) ^ 1;

        // If we are in the beginning of the array and the version is current, that means
        if (self.index == 0 && seq == self.version) || seq < self.version {
            return false;
        }
        self.version = seq;

        true
    }
}

#[derive(Debug)]
pub struct WriteGuard<'write, T: Copy, const N: usize> {
    buffer: &'write RingBuffer<T, N>,
}

impl<'write, T: Copy, const N: usize> WriteGuard<'write, T, N> {
    pub fn push_back(&mut self, val: T) {}
}

impl<'write, T: Copy, const N: usize> Drop for WriteGuard<'write, T, N> {
    fn drop(&mut self) {
        self.buffer.locked.store(false, Ordering::Release);
    }
}

#[derive(Debug)]
#[repr(C)]
pub struct Block<T: Copy> {
    seq: AtomicUsize,
    message: MaybeUninit<T>,
}

impl<T: Copy> Block<T> {
    const fn new() -> Block<T> {
        Block {
            seq: AtomicUsize::new(0),
            message: MaybeUninit::uninit(),
        }
    }
}

impl<T: Copy> Clone for Block<T> {
    fn clone(&self) -> Self {
        Block {
            seq: AtomicUsize::new(self.seq.load(Ordering::Relaxed)),
            message: self.message,
        }
    }
}

mod test {
    use super::*;

    #[test]
    fn test_nightly() {
        if cfg!(feature = "nightly") {
            println!("nightly")
        }
    }

    #[test]
    fn test_new_buffer() {
        let buffer = RingBuffer::<u32, 32>::new();

        println!("buffer: {:?}", buffer);
    }
}
