use crate::{QUOTED_PAIR_TABLE, QUOTED_STRING_TABLE, TOKEN_TABLE, URL_TABLE};

pub enum MatchResult {
  Continue,
  Suspend,
  Stop,
}

pub enum HeaderLineScanResult {
  Cr(usize),
  Invalid(usize),
  Incomplete,
}

#[inline(always)]
pub fn ensure_valid_line(data: &[u8], cr: usize, available: usize) -> MatchResult {
  if cr + 1 == available {
    MatchResult::Suspend
  } else if data[cr + 1] != b'\n' {
    MatchResult::Stop
  } else {
    MatchResult::Continue
  }
}

#[inline(always)]
pub fn find_cr(data: &[u8], available: usize) -> Option<usize> {
  if available == 0 {
    None
  } else {
    find_char(data, 0, available - 1, b'\r')
  }
}

#[inline(always)]
pub fn find_char(buf: &[u8], start: usize, end: usize, needle: u8) -> Option<usize> {
  if start > end || end >= buf.len() {
    return None;
  }

  memchr::memchr(needle, &buf[start..=end]).map(|i| start + i)
}

#[inline(always)]
pub fn find_char2(buf: &[u8], start: usize, end: usize, needle1: u8, needle2: u8) -> Option<usize> {
  if start > end || end >= buf.len() {
    return None;
  }

  memchr::memchr2(needle1, needle2, &buf[start..=end]).map(|i| start + i)
}

#[inline(always)]
pub fn is_digit(byte: u8) -> bool { byte.wrapping_sub(b'0') <= 9 }

#[inline(always)]
pub fn is_ws(byte: u8) -> bool { byte == b' ' || byte == b'\t' }

#[inline(always)]
pub fn validate_token(data: &[u8], start: usize, end: usize) -> bool {
  if start == end {
    return false;
  }

  let mut i = start;
  while i < end {
    if !TOKEN_TABLE[data[i] as usize] {
      return false;
    }

    i += 1;
  }

  true
}

#[inline(always)]
pub fn validate_quoted_string(data: &[u8], start: usize, end: usize) -> bool {
  let mut i = start;
  while i < end {
    let byte = data[i];
    if byte == b'\\' {
      i += 1;
      if i == end || !QUOTED_PAIR_TABLE[data[i] as usize] {
        return false;
      }
    } else if !QUOTED_STRING_TABLE[byte as usize] {
      return false;
    }

    i += 1;
  }

  true
}

#[inline(always)]
pub fn validate_url(data: &[u8], start: usize, end: usize) -> bool {
  if start == end {
    return false;
  }

  let mut i = start;
  while i < end {
    if !URL_TABLE[data[i] as usize] {
      return false;
    }

    i += 1;
  }

  true
}

#[inline(always)]
pub fn strip_ows_fast(data: &[u8], start_ref: &mut usize, end_ref: &mut usize, allow_empty: bool) -> bool {
  let start = *start_ref;
  let end = *end_ref;

  if start < end && data[start] == b' ' && !is_ws(data[end - 1]) {
    *start_ref = start + 1;
    return true;
  }

  strip_ows(data, start_ref, end_ref, allow_empty)
}

#[inline(always)]
pub fn strip_ows(data: &[u8], start_ref: &mut usize, end_ref: &mut usize, allow_empty: bool) -> bool {
  let mut start = *start_ref;
  let mut end = *end_ref;

  while start < end && is_ws(data[start]) {
    start += 1;
  }

  while end > start && is_ws(data[end - 1]) {
    end -= 1;
  }

  if start == end && !allow_empty {
    return false;
  }

  *start_ref = start;
  *end_ref = end;
  true
}

#[inline(always)]
fn scan_header_line_scalar(ptr: *const u8, mut i: usize, end: usize) -> HeaderLineScanResult {
  while i < end {
    let byte = unsafe { *ptr.add(i) };

    if byte == b'\r' {
      return HeaderLineScanResult::Cr(i);
    }

    if (byte < 0x20 && byte != b'\t') || byte == 0x7f {
      return HeaderLineScanResult::Invalid(i);
    }

    i += 1;
  }

  HeaderLineScanResult::Incomplete
}

#[cfg(target_arch = "aarch64")]
#[inline(always)]
pub fn find_header_line_end(ptr: *const u8, len: usize) -> HeaderLineScanResult {
  use core::arch::aarch64::*;

  if len < 16 {
    return scan_header_line_scalar(ptr, 0, len);
  }

  let mut i = 0;
  let v_20 = unsafe { vdupq_n_u8(0x20) };
  let v_7f = unsafe { vdupq_n_u8(0x7f) };
  let v_tab = unsafe { vdupq_n_u8(b'\t') };
  let v_cr = unsafe { vdupq_n_u8(b'\r') };

  while i + 16 <= len {
    let x = unsafe { vld1q_u8(ptr.add(i)) };
    let lt_20 = unsafe { vcltq_u8(x, v_20) };
    let eq_tab = unsafe { vceqq_u8(x, v_tab) };
    let eq_cr = unsafe { vceqq_u8(x, v_cr) };
    let eq_7f = unsafe { vceqq_u8(x, v_7f) };
    let ctrl = unsafe { vbicq_u8(lt_20, eq_tab) };
    let invalid = unsafe { vbicq_u8(vorrq_u8(ctrl, eq_7f), eq_cr) };
    let found = unsafe { vorrq_u8(eq_cr, invalid) };

    if unsafe { vmaxvq_u8(found) } != 0 {
      return scan_header_line_scalar(ptr, i, i + 16);
    }

    i += 16;
  }

  scan_header_line_scalar(ptr, i, len)
}

#[cfg(target_arch = "x86_64")]
#[inline(always)]
pub fn find_header_line_end(ptr: *const u8, len: usize) -> HeaderLineScanResult {
  use core::arch::x86_64::*;

  if len < 16 {
    return scan_header_line_scalar(ptr, 0, len);
  }

  let mut i = 0;
  let v_20 = unsafe { _mm_set1_epi8(0x20u8 as i8) };
  let v_7f = unsafe { _mm_set1_epi8(0x7fu8 as i8) };
  let v_tab = unsafe { _mm_set1_epi8(b'\t' as i8) };
  let v_cr = unsafe { _mm_set1_epi8(b'\r' as i8) };
  let v_zero = unsafe { _mm_setzero_si128() };
  let v_ones = unsafe { _mm_cmpeq_epi8(v_zero, v_zero) };

  while i + 32 <= len {
    let x = unsafe { _mm_loadu_si128(ptr.add(i) as *const __m128i) };
    let lt_20 = unsafe { _mm_andnot_si128(_mm_cmpeq_epi8(_mm_subs_epu8(v_20, x), v_zero), v_ones) };
    let eq_tab = unsafe { _mm_cmpeq_epi8(x, v_tab) };
    let eq_cr = unsafe { _mm_cmpeq_epi8(x, v_cr) };
    let eq_7f = unsafe { _mm_cmpeq_epi8(x, v_7f) };
    let ctrl = unsafe { _mm_andnot_si128(eq_tab, lt_20) };
    let invalid = unsafe { _mm_andnot_si128(eq_cr, _mm_or_si128(ctrl, eq_7f)) };
    let cr_mask = unsafe { _mm_movemask_epi8(eq_cr) };
    let invalid_mask = unsafe { _mm_movemask_epi8(invalid) };
    let found_mask = cr_mask | invalid_mask;

    if found_mask != 0 {
      let index = i + found_mask.trailing_zeros() as usize;
      return if cr_mask & (1 << (index - i)) != 0 {
        HeaderLineScanResult::Cr(index)
      } else {
        HeaderLineScanResult::Invalid(index)
      };
    }

    let x = unsafe { _mm_loadu_si128(ptr.add(i + 16) as *const __m128i) };
    let lt_20 = unsafe { _mm_andnot_si128(_mm_cmpeq_epi8(_mm_subs_epu8(v_20, x), v_zero), v_ones) };
    let eq_tab = unsafe { _mm_cmpeq_epi8(x, v_tab) };
    let eq_cr = unsafe { _mm_cmpeq_epi8(x, v_cr) };
    let eq_7f = unsafe { _mm_cmpeq_epi8(x, v_7f) };
    let ctrl = unsafe { _mm_andnot_si128(eq_tab, lt_20) };
    let invalid = unsafe { _mm_andnot_si128(eq_cr, _mm_or_si128(ctrl, eq_7f)) };
    let cr_mask = unsafe { _mm_movemask_epi8(eq_cr) };
    let invalid_mask = unsafe { _mm_movemask_epi8(invalid) };
    let found_mask = cr_mask | invalid_mask;

    if found_mask != 0 {
      let lane = found_mask.trailing_zeros() as usize;
      let index = i + 16 + lane;
      return if cr_mask & (1 << lane) != 0 {
        HeaderLineScanResult::Cr(index)
      } else {
        HeaderLineScanResult::Invalid(index)
      };
    }

    i += 32;
  }

  while i + 16 <= len {
    let x = unsafe { _mm_loadu_si128(ptr.add(i) as *const __m128i) };
    let lt_20 = unsafe { _mm_andnot_si128(_mm_cmpeq_epi8(_mm_subs_epu8(v_20, x), v_zero), v_ones) };
    let eq_tab = unsafe { _mm_cmpeq_epi8(x, v_tab) };
    let eq_cr = unsafe { _mm_cmpeq_epi8(x, v_cr) };
    let eq_7f = unsafe { _mm_cmpeq_epi8(x, v_7f) };
    let ctrl = unsafe { _mm_andnot_si128(eq_tab, lt_20) };
    let invalid = unsafe { _mm_andnot_si128(eq_cr, _mm_or_si128(ctrl, eq_7f)) };
    let cr_mask = unsafe { _mm_movemask_epi8(eq_cr) };
    let invalid_mask = unsafe { _mm_movemask_epi8(invalid) };
    let found_mask = cr_mask | invalid_mask;

    if found_mask != 0 {
      let lane = found_mask.trailing_zeros() as usize;
      let index = i + lane;
      return if cr_mask & (1 << lane) != 0 {
        HeaderLineScanResult::Cr(index)
      } else {
        HeaderLineScanResult::Invalid(index)
      };
    }

    i += 16;
  }

  scan_header_line_scalar(ptr, i, len)
}

#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[target_feature(enable = "simd128")]
pub fn find_header_line_end(ptr: *const u8, len: usize) -> HeaderLineScanResult {
  use core::arch::wasm32::*;

  if len < 16 {
    return scan_header_line_scalar(ptr, 0, len);
  }

  let mut i = 0;
  let v_20 = u8x16_splat(0x20);
  let v_7f = u8x16_splat(0x7f);
  let v_tab = u8x16_splat(b'\t');
  let v_cr = u8x16_splat(b'\r');

  while i + 16 <= len {
    let x = unsafe { v128_load(ptr.add(i) as *const v128) };
    let lt_20 = u8x16_lt(x, v_20);
    let eq_tab = u8x16_eq(x, v_tab);
    let eq_cr = u8x16_eq(x, v_cr);
    let eq_7f = u8x16_eq(x, v_7f);
    let ctrl = v128_andnot(eq_tab, lt_20);
    let invalid = v128_andnot(eq_cr, v128_or(ctrl, eq_7f));
    let found = v128_or(eq_cr, invalid);

    if v128_any_true(found) {
      return scan_header_line_scalar(ptr, i, i + 16);
    }

    i += 16;
  }

  scan_header_line_scalar(ptr, i, len)
}

#[cfg(not(any(
  target_arch = "aarch64",
  target_arch = "x86_64",
  all(target_arch = "wasm32", target_feature = "simd128")
)))]
#[inline(always)]
pub fn find_header_line_end(ptr: *const u8, len: usize) -> HeaderLineScanResult { scan_header_line_scalar(ptr, 0, len) }

#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
pub fn validate_token_value(ptr: *const u8, len: usize) -> bool {
  use core::arch::aarch64::*;

  let mut i = 0;

  let v_20 = vdupq_n_u8(0x20);
  let v_7f = vdupq_n_u8(0x7f);
  let v_tab = vdupq_n_u8(9);

  while i + 16 <= len {
    let x = unsafe { vld1q_u8(ptr.add(i)) };
    let lt_20 = vcltq_u8(x, v_20);
    let eq_tab = vceqq_u8(x, v_tab);
    let eq_7f = vceqq_u8(x, v_7f);
    let ctrl = vbicq_u8(lt_20, eq_tab); // lt_20 & !eq_tab
    let invalid = vorrq_u8(ctrl, eq_7f);

    if vmaxvq_u8(invalid) != 0 {
      return false;
    }

    i += 16;
  }

  while i < len {
    let b = unsafe { *ptr.add(i) };

    if (b < 0x20 && b != b'\t') || b == 0x7f {
      return false;
    }

    i += 1;
  }

  true
}

#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse2")]
pub fn validate_token_value(ptr: *const u8, len: usize) -> bool {
  use core::arch::x86_64::*;

  let mut i = 0;
  let v_20 = _mm_set1_epi8(0x20u8 as i8);
  let v_7f = _mm_set1_epi8(0x7fu8 as i8);
  let v_tab = _mm_set1_epi8(b'\t' as i8);
  let v_zero = _mm_setzero_si128();
  let v_ones = _mm_cmpeq_epi8(v_zero, v_zero);

  while i + 16 <= len {
    let x = unsafe { _mm_loadu_si128(ptr.add(i) as *const __m128i) };
    let lt_20 = _mm_andnot_si128(_mm_cmpeq_epi8(_mm_subs_epu8(v_20, x), v_zero), v_ones);
    let eq_tab = _mm_cmpeq_epi8(x, v_tab);
    let eq_7f = _mm_cmpeq_epi8(x, v_7f);
    let ctrl = _mm_andnot_si128(eq_tab, lt_20);
    let invalid = _mm_or_si128(ctrl, eq_7f);

    if _mm_movemask_epi8(invalid) != 0 {
      return false;
    }

    i += 16;
  }

  while i < len {
    let b = unsafe { *ptr.add(i) };

    if (b < 0x20 && b != b'\t') || b == 0x7f {
      return false;
    }

    i += 1;
  }

  true
}

#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[target_feature(enable = "simd128")]
pub fn validate_token_value(ptr: *const u8, len: usize) -> bool {
  use core::arch::wasm32::*;

  let mut i = 0;
  let v_20 = u8x16_splat(0x20);
  let v_7f = u8x16_splat(0x7f);
  let v_tab = u8x16_splat(b'\t');

  while i + 16 <= len {
    let x = unsafe { v128_load(ptr.add(i) as *const v128) };
    let lt_20 = u8x16_lt(x, v_20);
    let eq_tab = u8x16_eq(x, v_tab);
    let eq_7f = u8x16_eq(x, v_7f);
    let ctrl = v128_andnot(eq_tab, lt_20);
    let invalid = v128_or(ctrl, eq_7f);

    if v128_any_true(invalid) {
      return false;
    }

    i += 16;
  }

  while i < len {
    let b = unsafe { *ptr.add(i) };

    if (b < 0x20 && b != b'\t') || b == 0x7f {
      return false;
    }

    i += 1;
  }

  true
}

#[cfg(not(any(
  target_arch = "aarch64",
  target_arch = "x86_64",
  all(target_arch = "wasm32", target_feature = "simd128")
)))]
#[inline(always)]
pub fn validate_token_value(ptr: *const u8, len: usize) -> bool {
  let mut i = 0;

  while i < len {
    let b = unsafe { *ptr.add(i) };

    if (b < 0x20 && b != b'\t') || b == 0x7f {
      return false;
    }

    i += 1;
  }

  true
}
