// 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::*;

#[ext]
pub impl<T> T where T: Debug {
  fn to_debug(&self) -> String { format!("{:?}", self) }
}

#[ext]
pub impl<T,E> Result<T,E> where AE: From<E> {
  fn dcontext<D:Debug>(self, d: D) -> anyhow::Result<T> {
    self.map_err(|e| AE::from(e)).with_context(|| d.to_debug())
  }
}

#[derive(Error,Debug)]
pub enum ReadLimitedError<H: std::error::Error + 'static> {
  #[error("maximum size {limit} exceeded")]
  Truncated { sofar: Box<[u8]>, limit: usize },

  #[error("HTTP error {0}")]
  Http(#[from] H),
}

impl ReadLimitedError<hyper::Error> {
  pub fn discard_data(&mut self) { match self {
    ReadLimitedError::Truncated { sofar,.. } => { mem::take(sofar); },
    _ => { },
  } }
}
#[ext]
pub impl<T> Result<T,ReadLimitedError<hyper::Error>> {
  fn discard_data(self) -> Self {
    self.map_err(|mut e| { e.discard_data(); e })
  }
}

// Works around the lack of ErrorKind::IsADirectory
// #![feature(io_error_more)]
// https://github.com/rust-lang/rust/issues/86442
#[ext]
pub impl io::Error {
  fn is_is_a_directory(&self) -> bool {
    self.raw_os_error()
      .unwrap_or_else(|| panic!(
 "trying to tell whether Kind is IsADirectory for non-OS error io::Error {}",
        self))
      == libc::EISDIR
  }
}

#[throws(ReadLimitedError<H>)]
pub async fn read_limited_bytes<S, H>(limit: usize, initial: Box<[u8]>,
                                   capacity: usize,
                                   mut stream: Pin<&mut S>) -> Box<[u8]>
where S: futures::Stream<Item=Result<hyper::body::Bytes, H>>,
      H: std::error::Error + 'static,
      // we also require that the Stream is cancellation-safe
{
  let mut accum = initial.into_vec();
  let capacity = min(limit, capacity);
  if capacity > accum.len() { accum.reserve(capacity - accum.len()); }
  while let Some(item) = stream.next().await {
    let b = item?;
    accum.extend(b);
    if accum.len() > limit {
      throw!(ReadLimitedError::Truncated { limit, sofar: accum.into() })
    }
  }
  accum.into()
}

pub fn time_t_now() -> u64 {
  SystemTime::now()
    .duration_since(UNIX_EPOCH)
    .unwrap_or_else(|_| Duration::default()) // clock is being weird
    .as_secs()
}

use sha2::Digest as _;
use sha2::digest::Update as _;

pub type HmacH = sha2::Sha256;
pub const HMAC_B: usize = 64;
pub const HMAC_L: usize = 32;

pub fn token_hmac(key: &[u8], message: &[u8]) -> [u8; HMAC_L] {
  let key = {
    let mut padded = [0; HMAC_B];
    if key.len() > padded.len() {
      let digest: [u8; HMAC_L] = HmacH::digest(key).into();
      padded[0..HMAC_L].copy_from_slice(&digest);
    } else {
      padded[0.. key.len()].copy_from_slice(key);
    }
    padded
  };
  let mut ikey = key;  for k in &mut ikey { *k ^= 0x36; }
  let mut okey = key;  for k in &mut okey { *k ^= 0x5C; }

  //dbg!(DumpHex(&key), DumpHex(message), DumpHex(&ikey), DumpHex(&okey));

  let h1 = HmacH::new()
    .chain(ikey)
    .chain(message)
    .finalize();
  let h2 = HmacH::new()
    .chain(okey)
    .chain(h1)
    .finalize();
  h2.into()
}

#[test]
fn hmac_test_vectors(){
  // C&P from RFC 4231
  let vectors = r#"
   Key =          0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b
                  0b0b0b0b                          (20 bytes)
   Data =         4869205468657265                  ("Hi There")

   HMAC-SHA-256 = b0344c61d8db38535ca8afceaf0bf12b
                  881dc200c9833da726e9376c2e32cff7

    
   Key =          4a656665                          ("Jefe")
   Data =         7768617420646f2079612077616e7420  ("what do ya want ")
                  666f72206e6f7468696e673f          ("for nothing?")

   HMAC-SHA-256 = 5bdcc146bf60754e6a042426089575c7
                  5a003f089d2739839dec58b964ec3843


   Key =          aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaa                          (20 bytes)
   Data =         dddddddddddddddddddddddddddddddd
                  dddddddddddddddddddddddddddddddd
                  dddddddddddddddddddddddddddddddd
                  dddd                              (50 bytes)

   HMAC-SHA-256 = 773ea91e36800e46854db8ebd09181a7
                  2959098b3ef8c122d9635514ced565fe


   Key =          0102030405060708090a0b0c0d0e0f10
                  111213141516171819                (25 bytes)
   Data =         cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd
                  cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd
                  cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd
                  cdcd                              (50 bytes)

   HMAC-SHA-256 = 82558a389a443c0ea4cc819899f2083a
                  85f0faa3e578f8077a2e3ff46729665b



   Key =          aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaa                            (131 bytes)
   Data =         54657374205573696e67204c61726765  ("Test Using Large")
                  72205468616e20426c6f636b2d53697a  ("r Than Block-Siz")
                  65204b6579202d2048617368204b6579  ("e Key - Hash Key")
                  204669727374                      (" First")

   HMAC-SHA-256 = 60e431591ee0b67f0d8a26aacbf5b77f
                  8e0bc6213728c5140546040f0ee37f54

   Key =          aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
                  aaaaaa                            (131 bytes)
   Data =         54686973206973206120746573742075  ("This is a test u")
                  73696e672061206c6172676572207468  ("sing a larger th")
                  616e20626c6f636b2d73697a65206b65  ("an block-size ke")
                  7920616e642061206c61726765722074  ("y and a larger t")
                  68616e20626c6f636b2d73697a652064  ("han block-size d")
                  6174612e20546865206b6579206e6565  ("ata. The key nee")
                  647320746f2062652068617368656420  ("ds to be hashed ")
                  6265666f7265206265696e6720757365  ("before being use")
                  642062792074686520484d414320616c  ("d by the HMAC al")
                  676f726974686d2e                  ("gorithm.")

   HMAC-SHA-256 = 9b09ffa71b942fcb27635fbcd5b0e944
                  bfdc63644f0713938a7f51535c3a35e2
"#;
  let vectors = regex_replace_all!{
    r#"\(.*\)"#,
    vectors.trim_end(),
    |_| "",
  };
  let vectors = regex_replace_all!{
    r#" *\n                  "#,
    &vectors,
    |_| "",
  };
  let vectors = regex_replace_all!{
    r#"\s*\n"#,
    &vectors,
    |_| "\n",
  };
  let mut lines = vectors.split('\n');
  assert_eq!( lines.next().unwrap(), "" );
  let mut get = |prefix| {
    let l = lines.next()?;
    dbg!(l);
    let b = l.strip_prefix(prefix).unwrap().as_bytes().chunks(2)
      .map(|s| str::from_utf8(s).unwrap())
      .map(|s| { assert_eq!(s.len(), 2); u8::from_str_radix(s,16).unwrap() })
      .collect::<Vec<u8>>();
    Some(b)
  };
  while let Some(key) = get("   Key =          ") {
    let data = get("   Data =         ").unwrap();
    let exp = get("   HMAC-SHA-256 = ").unwrap();
    let got = token_hmac(&key, &data);
    assert_eq!(&got[..], &exp);
  }
}

pub fn verify_cli<F: clap::CommandFactory>() {
  let app: clap::Command = F::command();
  app.debug_assert();
}
