module By = Digestif_by
module Bi = Digestif_bi

module Int32 = struct
  include Int32

  let ( lsl ) = Int32.shift_left

  let ( lsr ) = Int32.shift_right_logical

  let ( asr ) = Int32.shift_right

  let ( lor ) = Int32.logor

  let ( lxor ) = Int32.logxor

  let ( land ) = Int32.logand

  let ( + ) = Int32.add

  let rol32 a n = (a lsl n) lor (a lsr (32 - n))
end

module Int64 = struct
  include Int64

  let ( land ) = Int64.logand

  let ( lsl ) = Int64.shift_left
end

module type S = sig
  type ctx

  type kind = [ `SHA1 ]

  val init : unit -> ctx

  val unsafe_feed_bytes : ctx -> By.t -> int -> int -> unit

  val unsafe_feed_bigstring : ctx -> Bi.t -> int -> int -> unit

  val unsafe_get : ctx -> By.t

  val dup : ctx -> ctx
end

module Unsafe : S = struct
  type kind = [ `SHA1 ]

  type ctx = { mutable size : int64; b : Bytes.t; h : int32 array }

  let dup ctx = { size = ctx.size; b = By.copy ctx.b; h = Array.copy ctx.h }

  let init () =
    let b = By.make 64 '\x00' in
    {
      size = 0L;
      b;
      h = [| 0x67452301l; 0xefcdab89l; 0x98badcfel; 0x10325476l; 0xc3d2e1f0l |];
    }

  let f1 x y z = Int32.(z lxor (x land (y lxor z)))

  let f2 x y z = Int32.(x lxor y lxor z)

  let f3 x y z = Int32.((x land y) + (z land (x lxor y)))

  let f4 = f2

  let k1 = 0x5a827999l

  let k2 = 0x6ed9eba1l

  let k3 = 0x8f1bbcdcl

  let k4 = 0xca62c1d6l

  let sha1_do_chunk :
      type a. be32_to_cpu:(a -> int -> int32) -> ctx -> a -> int -> unit =
   fun ~be32_to_cpu ctx buf off ->
    let a = ref ctx.h.(0) in
    let b = ref ctx.h.(1) in
    let c = ref ctx.h.(2) in
    let d = ref ctx.h.(3) in
    let e = ref ctx.h.(4) in
    let w = Array.make 16 0l in
    let m i =
      let ( && ) a b = a land b in
      let ( -- ) a b = a - b in
      let v =
        Int32.(
          rol32
            (w.(i && 0x0F)
            lxor w.((i -- 14) && 0x0F)
            lxor w.((i -- 8) && 0x0F)
            lxor w.((i -- 3) && 0x0F))
            1) in
      w.(i land 0x0F) <- v ;
      w.(i land 0x0F) in
    let round a b c d e f k w =
      (e := Int32.(!e + rol32 !a 5 + f !b !c !d + k + w)) ;
      b := Int32.(rol32 !b 30) in
    for i = 0 to 15 do
      w.(i) <- be32_to_cpu buf (off + (i * 4))
    done ;
    round a b c d e f1 k1 w.(0) ;
    round e a b c d f1 k1 w.(1) ;
    round d e a b c f1 k1 w.(2) ;
    round c d e a b f1 k1 w.(3) ;
    round b c d e a f1 k1 w.(4) ;
    round a b c d e f1 k1 w.(5) ;
    round e a b c d f1 k1 w.(6) ;
    round d e a b c f1 k1 w.(7) ;
    round c d e a b f1 k1 w.(8) ;
    round b c d e a f1 k1 w.(9) ;
    round a b c d e f1 k1 w.(10) ;
    round e a b c d f1 k1 w.(11) ;
    round d e a b c f1 k1 w.(12) ;
    round c d e a b f1 k1 w.(13) ;
    round b c d e a f1 k1 w.(14) ;
    round a b c d e f1 k1 w.(15) ;
    round e a b c d f1 k1 (m 16) ;
    round d e a b c f1 k1 (m 17) ;
    round c d e a b f1 k1 (m 18) ;
    round b c d e a f1 k1 (m 19) ;
    round a b c d e f2 k2 (m 20) ;
    round e a b c d f2 k2 (m 21) ;
    round d e a b c f2 k2 (m 22) ;
    round c d e a b f2 k2 (m 23) ;
    round b c d e a f2 k2 (m 24) ;
    round a b c d e f2 k2 (m 25) ;
    round e a b c d f2 k2 (m 26) ;
    round d e a b c f2 k2 (m 27) ;
    round c d e a b f2 k2 (m 28) ;
    round b c d e a f2 k2 (m 29) ;
    round a b c d e f2 k2 (m 30) ;
    round e a b c d f2 k2 (m 31) ;
    round d e a b c f2 k2 (m 32) ;
    round c d e a b f2 k2 (m 33) ;
    round b c d e a f2 k2 (m 34) ;
    round a b c d e f2 k2 (m 35) ;
    round e a b c d f2 k2 (m 36) ;
    round d e a b c f2 k2 (m 37) ;
    round c d e a b f2 k2 (m 38) ;
    round b c d e a f2 k2 (m 39) ;
    round a b c d e f3 k3 (m 40) ;
    round e a b c d f3 k3 (m 41) ;
    round d e a b c f3 k3 (m 42) ;
    round c d e a b f3 k3 (m 43) ;
    round b c d e a f3 k3 (m 44) ;
    round a b c d e f3 k3 (m 45) ;
    round e a b c d f3 k3 (m 46) ;
    round d e a b c f3 k3 (m 47) ;
    round c d e a b f3 k3 (m 48) ;
    round b c d e a f3 k3 (m 49) ;
    round a b c d e f3 k3 (m 50) ;
    round e a b c d f3 k3 (m 51) ;
    round d e a b c f3 k3 (m 52) ;
    round c d e a b f3 k3 (m 53) ;
    round b c d e a f3 k3 (m 54) ;
    round a b c d e f3 k3 (m 55) ;
    round e a b c d f3 k3 (m 56) ;
    round d e a b c f3 k3 (m 57) ;
    round c d e a b f3 k3 (m 58) ;
    round b c d e a f3 k3 (m 59) ;
    round a b c d e f4 k4 (m 60) ;
    round e a b c d f4 k4 (m 61) ;
    round d e a b c f4 k4 (m 62) ;
    round c d e a b f4 k4 (m 63) ;
    round b c d e a f4 k4 (m 64) ;
    round a b c d e f4 k4 (m 65) ;
    round e a b c d f4 k4 (m 66) ;
    round d e a b c f4 k4 (m 67) ;
    round c d e a b f4 k4 (m 68) ;
    round b c d e a f4 k4 (m 69) ;
    round a b c d e f4 k4 (m 70) ;
    round e a b c d f4 k4 (m 71) ;
    round d e a b c f4 k4 (m 72) ;
    round c d e a b f4 k4 (m 73) ;
    round b c d e a f4 k4 (m 74) ;
    round a b c d e f4 k4 (m 75) ;
    round e a b c d f4 k4 (m 76) ;
    round d e a b c f4 k4 (m 77) ;
    round c d e a b f4 k4 (m 78) ;
    round b c d e a f4 k4 (m 79) ;
    ctx.h.(0) <- Int32.add ctx.h.(0) !a ;
    ctx.h.(1) <- Int32.add ctx.h.(1) !b ;
    ctx.h.(2) <- Int32.add ctx.h.(2) !c ;
    ctx.h.(3) <- Int32.add ctx.h.(3) !d ;
    ctx.h.(4) <- Int32.add ctx.h.(4) !e ;
    ()

  let feed :
      type a.
      blit:(a -> int -> By.t -> int -> int -> unit) ->
      be32_to_cpu:(a -> int -> int32) ->
      ctx ->
      a ->
      int ->
      int ->
      unit =
   fun ~blit ~be32_to_cpu ctx buf off len ->
    let idx = ref Int64.(to_int (ctx.size land 0x3FL)) in
    let len = ref len in
    let off = ref off in
    let to_fill = 64 - !idx in
    ctx.size <- Int64.add ctx.size (Int64.of_int !len) ;
    if !idx <> 0 && !len >= to_fill
    then (
      blit buf !off ctx.b !idx to_fill ;
      sha1_do_chunk ~be32_to_cpu:By.be32_to_cpu ctx ctx.b 0 ;
      len := !len - to_fill ;
      off := !off + to_fill ;
      idx := 0) ;
    while !len >= 64 do
      sha1_do_chunk ~be32_to_cpu ctx buf !off ;
      len := !len - 64 ;
      off := !off + 64
    done ;
    if !len <> 0 then blit buf !off ctx.b !idx !len ;
    ()

  let unsafe_feed_bytes = feed ~blit:By.blit ~be32_to_cpu:By.be32_to_cpu

  let unsafe_feed_bigstring =
    feed ~blit:By.blit_from_bigstring ~be32_to_cpu:Bi.be32_to_cpu

  let unsafe_get ctx =
    let index = Int64.(to_int (ctx.size land 0x3FL)) in
    let padlen = if index < 56 then 56 - index else 64 + 56 - index in
    let padding = By.init padlen (function 0 -> '\x80' | _ -> '\x00') in
    let bits = By.create 8 in
    By.cpu_to_be64 bits 0 Int64.(ctx.size lsl 3) ;
    unsafe_feed_bytes ctx padding 0 padlen ;
    unsafe_feed_bytes ctx bits 0 8 ;
    let res = By.create (5 * 4) in
    for i = 0 to 4 do
      By.cpu_to_be32 res (i * 4) ctx.h.(i)
    done ;
    res
end
