use std::borrow::Cow;
use std::ops::{Index, Range};

/// A record of raw byte slices, one per field defined in the schema.
///
/// When borrowed, each field is a zero-copy view into the reader's
/// internal buffer. Call [`into_owned`](ByteRecord::into_owned) to produce a
/// `ByteRecord<'static>` that owns its data.
#[derive(Debug, Clone)]
pub struct ByteRecord<'a> {
    buffer: Cow<'a, [u8]>,
    ranges: Cow<'a, [Range<usize>]>,
}

impl<'a> ByteRecord<'a> {
    /// Creates a new borrowed `ByteRecord` referencing the given buffer and
    /// field ranges within it.
    pub(crate) fn borrowed(buffer: &'a [u8], ranges: &'a [Range<usize>]) -> Self {
        Self {
            buffer: Cow::Borrowed(buffer),
            ranges: Cow::Borrowed(ranges),
        }
    }

    /// Returns the field at the given index, or `None` if out of bounds.
    #[must_use]
    #[inline]
    pub fn get(&self, index: usize) -> Option<&[u8]> {
        let range = self.ranges.get(index)?;
        Some(&self.buffer[range.start..range.end])
    }

    /// Returns the number of fields in this record.
    #[must_use]
    #[inline]
    pub fn len(&self) -> usize {
        self.ranges.len()
    }

    /// Returns `true` if this record has no fields.
    #[must_use]
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.ranges.is_empty()
    }

    /// Converts this record into an owned `ByteRecord<'static>` by copying
    /// any borrowed data into a single contiguous buffer.
    #[must_use]
    pub fn into_owned(self) -> ByteRecord<'static> {
        let total_bytes: usize = self.ranges.iter().map(|r| r.end - r.start).sum();
        let mut owned_buffer = Vec::with_capacity(total_bytes);
        let mut owned_ranges = Vec::with_capacity(self.ranges.len());
        for range in self.ranges.iter() {
            let start = owned_buffer.len();
            owned_buffer.extend_from_slice(&self.buffer[range.start..range.end]);
            owned_ranges.push(start..owned_buffer.len());
        }
        ByteRecord {
            buffer: Cow::Owned(owned_buffer),
            ranges: Cow::Owned(owned_ranges),
        }
    }

    /// Creates an owned `ByteRecord<'static>` by packing fields from the
    /// given buffer and ranges into a single contiguous allocation.
    pub(crate) fn packed(buffer: &[u8], ranges: &[Range<usize>]) -> ByteRecord<'static> {
        let total_bytes: usize = ranges.iter().map(|r| r.end - r.start).sum();
        let mut owned_buffer = Vec::with_capacity(total_bytes);
        let mut owned_ranges = Vec::with_capacity(ranges.len());
        for range in ranges {
            let start = owned_buffer.len();
            owned_buffer.extend_from_slice(&buffer[range.start..range.end]);
            owned_ranges.push(start..owned_buffer.len());
        }
        ByteRecord {
            buffer: Cow::Owned(owned_buffer),
            ranges: Cow::Owned(owned_ranges),
        }
    }
}

impl Index<usize> for ByteRecord<'_> {
    type Output = [u8];

    #[inline]
    fn index(&self, index: usize) -> &[u8] {
        let range = &self.ranges[index];
        &self.buffer[range.start..range.end]
    }
}

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

    fn borrowed_record() -> ByteRecord<'static> {
        let buffer: &[u8] = b"Alice30";
        let ranges: &[Range<usize>] = &[0..5, 5..7];
        ByteRecord::borrowed(buffer, ranges)
    }

    fn owned_record() -> ByteRecord<'static> {
        borrowed_record().into_owned()
    }

    // -- Borrowed record --

    #[test]
    fn borrowed_get() {
        let rec = borrowed_record();
        assert_eq!(rec.get(0), Some(b"Alice".as_slice()));
        assert_eq!(rec.get(1), Some(b"30".as_slice()));
        assert_eq!(rec.get(2), None);
    }

    #[test]
    fn borrowed_index() {
        let rec = borrowed_record();
        assert_eq!(&rec[0], b"Alice");
        assert_eq!(&rec[1], b"30");
    }

    #[test]
    fn borrowed_len() {
        let rec = borrowed_record();
        assert_eq!(rec.len(), 2);
        assert!(!rec.is_empty());
    }

    #[test]
    fn borrowed_empty() {
        let rec = ByteRecord::borrowed(b"", &[]);
        assert_eq!(rec.len(), 0);
        assert!(rec.is_empty());
    }

    // -- Owned record (via into_owned) --

    #[test]
    fn owned_get() {
        let rec = owned_record();
        assert_eq!(rec.get(0), Some(b"Alice".as_slice()));
        assert_eq!(rec.get(1), Some(b"30".as_slice()));
        assert_eq!(rec.get(2), None);
    }

    #[test]
    fn owned_index() {
        let rec = owned_record();
        assert_eq!(&rec[0], b"Alice");
        assert_eq!(&rec[1], b"30");
    }

    #[test]
    fn owned_len() {
        let rec = owned_record();
        assert_eq!(rec.len(), 2);
        assert!(!rec.is_empty());
    }

    #[test]
    fn into_owned_already_owned() {
        let rec = owned_record().into_owned();
        assert_eq!(&rec[0], b"Alice");
        assert_eq!(&rec[1], b"30");
    }

    // -- Packed record --

    #[test]
    fn packed_get() {
        let rec = ByteRecord::packed(b"Alice30", &[0..5, 5..7]);
        assert_eq!(rec.get(0), Some(b"Alice".as_slice()));
        assert_eq!(rec.get(1), Some(b"30".as_slice()));
        assert_eq!(rec.get(2), None);
    }

    #[test]
    fn packed_index() {
        let rec = ByteRecord::packed(b"Alice30", &[0..5, 5..7]);
        assert_eq!(&rec[0], b"Alice");
        assert_eq!(&rec[1], b"30");
    }
}
