// Copyright 2021-2022 Ian Jackson and contributors to Hippotat
// SPDX-License-Identifier: GPL-3.0-or-later WITH LicenseRef-Hippotat-OpenSSL-Exception
// There is NO WARRANTY.

use crate::prelude::*;

#[derive(Debug)]
pub struct Component<'b> {
  pub name: PartName,
  pub payload: &'b [u8],
}

#[derive(Debug)]
#[derive(Eq,PartialEq,Ord,PartialOrd,Hash)]
#[allow(non_camel_case_types)]
pub enum PartName { m, d, Other }

pub type BoundaryFinder = memchr::memmem::Finder<'static>;

#[throws(AE)]
/// Processes the start of a component (or terminating boundary).
///
/// Returned payload is only the start of the payload; the next
/// boundary has not been identified.
pub fn process_boundary<'b>(warnings: &mut Warnings,
                            after_leader: &'b [u8], expected: PartName)
                            -> Option<Component<'b>> {
  let rhs = after_leader;
  let mut rhs =
       if let Some(rhs) = rhs.strip_prefix(b"\r\n") { rhs         }
  else if let Some(_  ) = rhs.strip_prefix(b"--"  ) { return None }
  else if let Some(rhs) = rhs.strip_prefix(b"\n"  ) { rhs         }
  else { throw!(anyhow!("invalid multipart delimiter")) };

  let mut part_name = None;

  loop {
    // RHS points to the start of a header line
    let nl = memchr::memchr(b'\n', rhs)
      .ok_or_else(|| anyhow!("part headers truncated"))?;
    let l = &rhs[0..nl]; rhs = &rhs[nl+1..];
    if l == b"\r" || l == b"" { break } // end of headers
    if l.starts_with(b"--") { throw!(anyhow!("boundary in part headers")) }

    match (||{
      let l = str::from_utf8(l).context("interpret part headers as utf-8")?;

      let (_, disposition) = if let Some(y) =
        regex_captures!(r#"^Content-Disposition[ \t]*:[ \t]*(.*)$"#i, l) { y }
        else { return Ok(()) };

      let disposition = disposition.trim_end();
      if disposition.len() >= 100 { throw!(anyhow!(
        "Content-Disposition value implausibly long"
      )) }

      // todo: replace with mailparse?
      // (not in side, dep on charset not in sid)
      // also seems to box for all the bits

      // This let's us pretend it's a mime type, so we can use mime::Mime
      let disposition = format!("dummy/{}", disposition);

      let disposition: mime::Mime = disposition.parse()
        .context("parse Content-Disposition")?;
      let name = disposition.get_param("name")
        .ok_or_else(|| anyhow!(r#"find "name" in Content-Disposition"#))?;

      let name = match name.as_ref() {
        "m" => PartName::m,
        "d" => PartName::d,
        _   => PartName::Other,
      };

      if let Some(_) = mem::replace(&mut part_name, Some(name)) {
        throw!(anyhow!(r#"multiple "name"s in Content-Disposition(s)"#))
      }
      Ok::<_,AE>(())
    })() {
      Err(e) => warnings.add(&e)?,
      Ok(()) => { },
    };
  }

  //dbg!(DumpHex(rhs));

  Some(Component { name: part_name.unwrap_or(expected), payload: rhs })
}

pub struct ComponentIterator<'b> {
  at_boundary: &'b [u8],
  boundary_finder: BoundaryFinder,
}

#[derive(Error,Debug)]
#[error("missing mime multipart boundary")]
pub struct MissingBoundary;

impl<'b> ComponentIterator<'b> {
  #[throws(MissingBoundary)]
  pub fn resume_mid_component(buf: &'b [u8], boundary_finder: BoundaryFinder)
                              -> (&'b [u8], Self) {
    let next_boundary = boundary_finder.find(buf).ok_or(MissingBoundary)?;
    let part = &buf[0..next_boundary];
    let part = Self::payload_trim(part);

    //dbg!(DumpHex(part));

    (part, ComponentIterator {
      at_boundary: &buf[next_boundary..],
      boundary_finder,
    })
  }

  fn payload_trim(payload: &[u8]) -> &[u8] {
    payload.strip_suffix(b"\r").unwrap_or(payload)
  }

  #[throws(AE)]
  pub fn next(&mut self, warnings: &mut Warnings, expected: PartName)
              -> Option<Component<'b>> {
    if self.at_boundary.is_empty() { return None }

    let mut comp = match {
      //dbg!(DumpHex(self.boundary_finder.needle()));
      let boundary_len = self.boundary_finder.needle().len();
      //dbg!(boundary_len);
      process_boundary(warnings,
                       &self.at_boundary[boundary_len..],
                       expected)?
    } {
      None => {
        self.at_boundary = &self.at_boundary[0..0];
        return None;
      },
      Some(c) => c,
    };

    let next_boundary = self.boundary_finder.find(comp.payload)
      .ok_or(MissingBoundary)?;

    self.at_boundary = &comp.payload[next_boundary..];
    comp.payload = Self::payload_trim(&comp.payload[0..next_boundary]);

    //dbg!(DumpHex(comp.payload));
    //dbg!(DumpHex(&self.at_boundary[0..5]));

    Some(comp)
  }
}

pub struct MetadataFieldIterator<'b> {
  buf: &'b [u8],
  last: Option<usize>,
  iter: memchr::Memchr<'b>,
}

impl<'b> MetadataFieldIterator<'b> {
  pub fn new(buf: &'b [u8]) -> Self { Self {
    buf,
    last: Some(0),
    iter: memchr::Memchr::new(b'\n', buf),
  } }

  #[throws(AE)]
  pub fn need_next(&mut self) -> &'b str
  {
    self.next().ok_or_else(|| anyhow!("missing"))??
  }

  #[throws(AE)]
  pub fn need_parse<T>(&mut self) -> T
  where T: FromStr,
        AE: From<T::Err>,
  {
    self.parse()?.ok_or_else(|| anyhow!("missing"))?
  }

  #[throws(AE)]
  pub fn parse<T>(&mut self) -> Option<T>
  where T: FromStr,
        AE: From<T::Err>,
  {
    let s = if let Some(r) = self.next() { r? } else { return None };
    Some(s.parse()?)
  }

  pub fn remaining_bytes_len(&self) -> usize {
    if let Some(last) = self.last {
      self.buf.len() - last
    } else {
      0
    }
  }
}
                                      
impl<'b> Iterator for MetadataFieldIterator<'b> {
  type Item = Result<&'b str, std::str::Utf8Error>;
  fn next(&mut self) -> Option<Result<&'b str, std::str::Utf8Error>> {
    let last = self.last?;
    let (s, last) = match self.iter.next() {
      Some(nl) => (&self.buf[last..nl], Some(nl+1)),
      None     => (&self.buf[last..],   None),
    };
    self.last = last;
    let s = str::from_utf8(s).map(|s| s.trim());
    Some(s)
  }
}
impl<'b> std::iter::FusedIterator for MetadataFieldIterator<'b> { }
