use super::byte::is_ascii_ws;
use super::error::ERR_INVALID_HEADER_NAME;

pub struct HeaderLineScan {
    pub end: usize,
    pub colon: Option<usize>,
    pub value_start: usize,
    pub value_end: usize,
}

pub trait HeadInput {
    fn len(&self) -> usize;
    fn is_empty(&self) -> bool {
        self.len() == 0
    }
    fn find_crlf_from(&self, start: usize) -> Option<usize>;
    fn slice_range(&self, range: std::ops::Range<usize>) -> Option<&[u8]>;
    fn copy_range_local(
        &self,
        range: std::ops::Range<usize>,
    ) -> Option<crate::http::LocalFrameBytes>;
    fn copy_range_into(&self, range: std::ops::Range<usize>, out: &mut [u8]);
    fn for_each_slice<F>(&self, range: std::ops::Range<usize>, f: F)
    where
        F: FnMut(&[u8]);
}

impl HeadInput for [u8] {
    fn len(&self) -> usize {
        <[u8]>::len(self)
    }

    fn find_crlf_from(&self, start: usize) -> Option<usize> {
        BytesScan::find_crlf_from(self, start)
    }

    fn slice_range(&self, range: std::ops::Range<usize>) -> Option<&[u8]> {
        self.get(range)
    }

    fn copy_range_into(&self, range: std::ops::Range<usize>, out: &mut [u8]) {
        assert!(
            range.start <= range.end && range.end <= self.len(),
            "head input copy range invariant: range out of bounds",
        );
        let need = range.end - range.start;
        assert!(
            out.len() >= need,
            "head input copy range invariant: output too small",
        );
        out[..need].copy_from_slice(&self[range]);
    }

    fn copy_range_local(
        &self,
        range: std::ops::Range<usize>,
    ) -> Option<crate::http::LocalFrameBytes> {
        self.get(range)
            .map(o3::buffer::Shared::copy_from_slice)
            .map(crate::http::LocalFrameBytes::from_shared)
    }

    fn for_each_slice<F>(&self, range: std::ops::Range<usize>, mut f: F)
    where
        F: FnMut(&[u8]),
    {
        if let Some(slice) = self.get(range) {
            f(slice);
        }
    }
}

pub struct BytesScan;

impl BytesScan {
    pub fn find_byte_from(bytes: &[u8], start: usize, needle: u8) -> Option<usize> {
        if start >= bytes.len() {
            return None;
        }
        memchr::memchr(needle, &bytes[start..]).map(|i| start + i)
    }

    pub fn find_crlf_from(bytes: &[u8], start: usize) -> Option<usize> {
        if bytes.len() < 2 || start >= bytes.len().saturating_sub(1) {
            return None;
        }
        let mut seek = start;
        while let Some(cr_idx) = Self::find_byte_from(bytes, seek, b'\r') {
            if cr_idx + 1 >= bytes.len() {
                return None;
            }
            if bytes[cr_idx + 1] == b'\n' {
                return Some(cr_idx);
            }
            seek = cr_idx + 1;
        }
        None
    }

    pub fn find_name_end_valid(
        bytes: &[u8],
        start: usize,
    ) -> Result<Option<(usize, u8)>, crate::error::Error> {
        match crate::simd::scan_header_name(bytes, start) {
            crate::simd::HeaderNameOutcome::Found { pos, byte } => Ok(Some((pos, byte))),
            crate::simd::HeaderNameOutcome::Invalid => Err(crate::error::Error::BadRequest(
                ERR_INVALID_HEADER_NAME.into(),
            )),
            crate::simd::HeaderNameOutcome::None => Ok(None),
        }
    }

    pub fn find_header_line_from(bytes: &[u8], start: usize) -> Option<HeaderLineScan> {
        if start >= bytes.len() {
            return None;
        }

        let mut search = start;
        let (colon_pos, value_segment_start) = loop {
            let rel = memchr::memchr2(b':', b'\r', &bytes[search..])?;
            let pos = search + rel;
            if bytes[pos] == b':' {
                break (pos, pos + 1);
            }
            if pos + 1 >= bytes.len() {
                return None;
            }
            if bytes[pos + 1] == b'\n' {
                return Some(HeaderLineScan {
                    end: pos,
                    colon: None,
                    value_start: 0,
                    value_end: 0,
                });
            }
            search = pos + 1;
        };

        let mut search = value_segment_start;
        let cr = loop {
            let rel = memchr::memchr(b'\r', &bytes[search..])?;
            let pos = search + rel;
            if pos + 1 >= bytes.len() {
                return None;
            }
            if bytes[pos + 1] == b'\n' {
                break pos;
            }
            search = pos + 1;
        };

        let segment = &bytes[value_segment_start..cr];
        let leading = segment.iter().take_while(|&&b| is_ascii_ws(b)).count();
        let trailing = segment[leading..]
            .iter()
            .rev()
            .take_while(|&&b| is_ascii_ws(b))
            .count();
        Some(HeaderLineScan {
            end: cr,
            colon: Some(colon_pos),
            value_start: value_segment_start + leading,
            value_end: cr - trailing,
        })
    }
}
