use std::mem::size_of;
use std::ops::Range;

use crate::Alignment;

/// A single field definition within a fixed-width record.
#[derive(Debug, Clone)]
pub struct FwReaderField {
    range: Range<usize>,
    padding: u8,
    alignment: Alignment,
}

impl FwReaderField {
    /// Creates a new [`FwReaderFieldBuilder`] for a field spanning the given byte range.
    #[inline]
    #[must_use]
    pub fn builder(range: Range<usize>) -> FwReaderFieldBuilder {
        FwReaderFieldBuilder::new(range)
    }

    /// Returns the byte range of this field within a record.
    #[must_use]
    #[inline]
    pub fn range(&self) -> &Range<usize> {
        &self.range
    }

    /// Returns the padding byte used for this field.
    #[must_use]
    #[inline]
    pub fn padding(&self) -> u8 {
        self.padding
    }

    /// Returns the alignment of the value within this field.
    #[must_use]
    #[inline]
    pub fn alignment(&self) -> Alignment {
        self.alignment
    }

    /// Returns the byte range of the value within a record buffer, after
    /// stripping padding from the appropriate side based on alignment.
    ///
    /// - [`Alignment::Left`]: strips trailing padding (value is on the left).
    /// - [`Alignment::Right`]: strips leading padding (value is on the right).
    pub(crate) fn stripped_range(&self, buffer: &[u8]) -> Range<usize> {
        let data = &buffer[self.range.start..self.range.end];
        match self.alignment {
            Alignment::Left => {
                let end = trim_end_padding(data, self.padding);
                self.range.start..(self.range.start + end)
            }
            Alignment::Right => {
                let start = trim_start_padding(data, self.padding);
                (self.range.start + start)..self.range.end
            }
        }
    }
}

/// Returns the length of `data` after trimming trailing `padding` bytes,
/// scanning backwards in `usize`-width chunks before falling back to
/// byte-at-a-time for the remainder.
fn trim_end_padding(data: &[u8], padding: u8) -> usize {
    let pad_word = usize::from_ne_bytes([padding; size_of::<usize>()]);
    let (remainder, chunks) = data.as_rchunks::<{ size_of::<usize>() }>();

    let mut trimmed_chunks = chunks.len();
    for &chunk in chunks.iter().rev() {
        if usize::from_ne_bytes(chunk) != pad_word {
            break;
        }
        trimmed_chunks -= 1;
    }

    let mut end = remainder.len() + trimmed_chunks * size_of::<usize>();
    while end > 0 && data[end - 1] == padding {
        end -= 1;
    }
    end
}

/// Returns the index of the first non-`padding` byte in `data`,
/// scanning forwards in `usize`-width chunks before falling back to
/// byte-at-a-time for the remainder.
fn trim_start_padding(data: &[u8], padding: u8) -> usize {
    let pad_word = usize::from_ne_bytes([padding; size_of::<usize>()]);
    let (chunks, _remainder) = data.as_chunks::<{ size_of::<usize>() }>();

    let mut trimmed_chunks = 0;
    for &chunk in chunks {
        if usize::from_ne_bytes(chunk) != pad_word {
            break;
        }
        trimmed_chunks += 1;
    }

    let mut start = trimmed_chunks * size_of::<usize>();
    while start < data.len() && data[start] == padding {
        start += 1;
    }
    start
}

/// Builder for constructing a [`FwReaderField`].
#[derive(Debug)]
pub struct FwReaderFieldBuilder {
    range: Range<usize>,
    padding: u8,
    alignment: Alignment,
}

impl FwReaderFieldBuilder {
    /// Creates a new builder for a field spanning the given byte range.
    pub(crate) fn new(range: Range<usize>) -> Self {
        Self {
            range,
            padding: b' ',
            alignment: Alignment::default(),
        }
    }

    /// Sets the padding byte. Defaults to `b' '` (space).
    #[must_use]
    #[inline]
    pub fn padding(mut self, padding: u8) -> Self {
        self.padding = padding;
        self
    }

    /// Sets the field alignment. Defaults to [`Alignment::Left`].
    #[must_use]
    #[inline]
    pub fn alignment(mut self, alignment: Alignment) -> Self {
        self.alignment = alignment;
        self
    }

    /// Builds the [`FwReaderField`].
    #[must_use]
    #[inline]
    pub fn build(self) -> FwReaderField {
        FwReaderField {
            range: self.range,
            padding: self.padding,
            alignment: self.alignment,
        }
    }
}

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

    #[test]
    fn builder_defaults() {
        let field = FwReaderField::builder(0..10).build();
        assert_eq!(field.range(), &(0..10));
        assert_eq!(field.padding(), b' ');
        assert_eq!(field.alignment(), Alignment::Left);
    }

    #[test]
    fn builder_custom_padding() {
        let field = FwReaderField::builder(5..15).padding(b'0').build();
        assert_eq!(field.range(), &(5..15));
        assert_eq!(field.padding(), b'0');
        assert_eq!(field.alignment(), Alignment::Left);
    }

    #[test]
    fn builder_custom_alignment() {
        let field = FwReaderField::builder(0..8)
            .alignment(Alignment::Right)
            .build();
        assert_eq!(field.alignment(), Alignment::Right);
    }

    #[test]
    fn builder_all_custom() {
        let field = FwReaderField::builder(2..6)
            .padding(b'*')
            .alignment(Alignment::Right)
            .build();
        assert_eq!(field.range(), &(2..6));
        assert_eq!(field.padding(), b'*');
        assert_eq!(field.alignment(), Alignment::Right);
    }

    #[test]
    fn stripped_range_left_aligned() {
        let field = FwReaderField::builder(0..10).build();
        let buf = b"hello     ";
        assert_eq!(&buf[field.stripped_range(buf)], b"hello");
    }

    #[test]
    fn stripped_range_right_aligned() {
        let field = FwReaderField::builder(0..10)
            .alignment(Alignment::Right)
            .build();
        let buf = b"     hello";
        assert_eq!(&buf[field.stripped_range(buf)], b"hello");
    }

    #[test]
    fn stripped_range_custom_byte() {
        let field = FwReaderField::builder(0..6).padding(b'0').build();
        let buf = b"420000";
        assert_eq!(&buf[field.stripped_range(buf)], b"42");
    }

    #[test]
    fn stripped_range_all_padding() {
        let field = FwReaderField::builder(0..5).build();
        let buf = b"     ";
        assert_eq!(&buf[field.stripped_range(buf)], b"");
    }

    #[test]
    fn stripped_range_no_padding() {
        let field = FwReaderField::builder(0..5).build();
        let buf = b"hello";
        assert_eq!(&buf[field.stripped_range(buf)], b"hello");
    }
}
