// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause

//! Contains a generic implementation of `BitmapSlice`.

use std::fmt::{self, Debug};
use std::ops::Deref;
use std::sync::Arc;

use crate::bitmap::{Bitmap, BitmapSlice, WithBitmapSlice};

/// Represents a slice into a `Bitmap` object, starting at `base_offset`.
#[derive(Clone, Copy)]
pub struct BaseSlice<B> {
    inner: B,
    base_offset: usize,
}

impl<B> BaseSlice<B> {
    /// Create a new `BitmapSlice`, starting at the specified `offset`.
    pub fn new(inner: B, offset: usize) -> Self {
        BaseSlice {
            inner,
            base_offset: offset,
        }
    }
}

impl<'a, B> WithBitmapSlice<'a> for BaseSlice<B>
where
    B: Clone + Deref,
    B::Target: Bitmap,
{
    type S = Self;
}

impl<B> BitmapSlice for BaseSlice<B>
where
    B: Clone + Deref,
    B::Target: Bitmap,
{
}

impl<B> Bitmap for BaseSlice<B>
where
    B: Clone + Deref,
    B::Target: Bitmap,
{
    /// Mark the memory range specified by the given `offset` (relative to the base offset of
    /// the slice) and `len` as dirtied.
    fn mark_dirty(&self, offset: usize, len: usize) {
        // The `Bitmap` operations are supposed to accompany guest memory accesses defined by the
        // same parameters (i.e. offset & length), so we use simple wrapping arithmetic instead of
        // performing additional checks. If an overflow would occur, we simply end up marking some
        // other region as dirty (which is just a false positive) instead of a region that could
        // not have been accessed to begin with.
        self.inner
            .mark_dirty(self.base_offset.wrapping_add(offset), len)
    }

    fn dirty_at(&self, offset: usize) -> bool {
        self.inner.dirty_at(self.base_offset.wrapping_add(offset))
    }

    /// Create a new `BitmapSlice` starting from the specified `offset` into the current slice.
    fn slice_at(&self, offset: usize) -> Self {
        BaseSlice {
            inner: self.inner.clone(),
            base_offset: self.base_offset.wrapping_add(offset),
        }
    }
}

impl<B> Debug for BaseSlice<B> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // Dummy impl for now.
        write!(f, "(bitmap slice)")
    }
}

impl<B: Default> Default for BaseSlice<B> {
    fn default() -> Self {
        BaseSlice {
            inner: B::default(),
            base_offset: 0,
        }
    }
}

/// A `BitmapSlice` implementation that wraps a reference to a `Bitmap` object.
pub type RefSlice<'a, B> = BaseSlice<&'a B>;

/// A `BitmapSlice` implementation that uses an `Arc` handle to a `Bitmap` object.
pub type ArcSlice<B> = BaseSlice<Arc<B>>;

#[cfg(test)]
mod tests {
    use super::*;

    use crate::bitmap::tests::{range_is_clean, range_is_dirty, test_bitmap};
    use crate::bitmap::AtomicBitmap;
    use std::num::NonZeroUsize;

    #[test]
    fn test_slice() {
        let bitmap_size = 0x1_0000;
        let dirty_offset = 0x1000;
        let dirty_len = 0x100;

        {
            let bitmap = AtomicBitmap::new(bitmap_size, NonZeroUsize::MIN);
            let slice1 = bitmap.slice_at(0);
            let slice2 = bitmap.slice_at(dirty_offset);

            assert!(range_is_clean(&slice1, 0, bitmap_size));
            assert!(range_is_clean(&slice2, 0, dirty_len));

            bitmap.mark_dirty(dirty_offset, dirty_len);

            assert!(range_is_dirty(&slice1, dirty_offset, dirty_len));
            assert!(range_is_dirty(&slice2, 0, dirty_len));
        }

        {
            let bitmap = AtomicBitmap::new(bitmap_size, NonZeroUsize::MIN);
            let slice = bitmap.slice_at(0);
            test_bitmap(&slice);
        }
    }
}
