module By = Digestif_by
module Bi = Digestif_bi

module type S = sig
  type ctx
  type kind = [ `RMD160 ]

  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 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 lnot = Int32.lognot
  let ( + ) = Int32.add
  let rol32 a n = (a lsl n) lor (a lsr (32 - n))
  let ror32 a n = (a lsr n) lor (a lsl (32 - n))
end

module Int64 = struct
  include Int64

  let ( land ) = Int64.logand
  let ( lsl ) = Int64.shift_left
end

module Unsafe : S = struct
  type kind = [ `RMD160 ]
  type ctx = { s : int32 array; mutable n : int; h : int32 array; b : Bytes.t }

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

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

  let f x y z = Int32.(x lxor y lxor z)
  let g x y z = Int32.(x land y lor (lnot x land z))
  let h x y z = Int32.(x lor lnot y lxor z)
  let i x y z = Int32.(x land z lor (y land lnot z))
  let j x y z = Int32.(x lxor (y lor lnot z))

  let ff a b c d e x s =
    let open Int32 in
    a := !a + f !b !c !d + x ;
    a := rol32 !a s + !e ;
    c := rol32 !c 10

  let gg a b c d e x s =
    let open Int32 in
    a := !a + g !b !c !d + x + 0x5a827999l ;
    a := rol32 !a s + !e ;
    c := rol32 !c 10

  let hh a b c d e x s =
    let open Int32 in
    a := !a + h !b !c !d + x + 0x6ed9eba1l ;
    a := rol32 !a s + !e ;
    c := rol32 !c 10

  let ii a b c d e x s =
    let open Int32 in
    a := !a + i !b !c !d + x + 0x8f1bbcdcl ;
    a := rol32 !a s + !e ;
    c := rol32 !c 10

  let jj a b c d e x s =
    let open Int32 in
    a := !a + j !b !c !d + x + 0xa953fd4el ;
    a := rol32 !a s + !e ;
    c := rol32 !c 10

  let fff a b c d e x s =
    let open Int32 in
    a := !a + f !b !c !d + x ;
    a := rol32 !a s + !e ;
    c := rol32 !c 10

  let ggg a b c d e x s =
    let open Int32 in
    a := !a + g !b !c !d + x + 0x7a6d76e9l ;
    a := rol32 !a s + !e ;
    c := rol32 !c 10

  let hhh a b c d e x s =
    let open Int32 in
    a := !a + h !b !c !d + x + 0x6d703ef3l ;
    a := rol32 !a s + !e ;
    c := rol32 !c 10

  let iii a b c d e x s =
    let open Int32 in
    a := !a + i !b !c !d + x + 0x5c4dd124l ;
    a := rol32 !a s + !e ;
    c := rol32 !c 10

  let jjj a b c d e x s =
    let open Int32 in
    a := !a + j !b !c !d + x + 0x50a28be6l ;
    a := rol32 !a s + !e ;
    c := rol32 !c 10

  let rmd160_do_chunk :
      type a. le32_to_cpu:(a -> int -> int32) -> ctx -> a -> int -> unit =
   fun ~le32_to_cpu ctx buff off ->
    let aa, bb, cc, dd, ee, aaa, bbb, ccc, ddd, eee =
      ( ref ctx.h.(0),
        ref ctx.h.(1),
        ref ctx.h.(2),
        ref ctx.h.(3),
        ref ctx.h.(4),
        ref ctx.h.(0),
        ref ctx.h.(1),
        ref ctx.h.(2),
        ref ctx.h.(3),
        ref ctx.h.(4) ) in
    let w = Array.make 16 0l in
    for i = 0 to 15 do
      w.(i) <- le32_to_cpu buff (off + (i * 4))
    done ;
    ff aa bb cc dd ee w.(0) 11 ;
    ff ee aa bb cc dd w.(1) 14 ;
    ff dd ee aa bb cc w.(2) 15 ;
    ff cc dd ee aa bb w.(3) 12 ;
    ff bb cc dd ee aa w.(4) 5 ;
    ff aa bb cc dd ee w.(5) 8 ;
    ff ee aa bb cc dd w.(6) 7 ;
    ff dd ee aa bb cc w.(7) 9 ;
    ff cc dd ee aa bb w.(8) 11 ;
    ff bb cc dd ee aa w.(9) 13 ;
    ff aa bb cc dd ee w.(10) 14 ;
    ff ee aa bb cc dd w.(11) 15 ;
    ff dd ee aa bb cc w.(12) 6 ;
    ff cc dd ee aa bb w.(13) 7 ;
    ff bb cc dd ee aa w.(14) 9 ;
    ff aa bb cc dd ee w.(15) 8 ;
    gg ee aa bb cc dd w.(7) 7 ;
    gg dd ee aa bb cc w.(4) 6 ;
    gg cc dd ee aa bb w.(13) 8 ;
    gg bb cc dd ee aa w.(1) 13 ;
    gg aa bb cc dd ee w.(10) 11 ;
    gg ee aa bb cc dd w.(6) 9 ;
    gg dd ee aa bb cc w.(15) 7 ;
    gg cc dd ee aa bb w.(3) 15 ;
    gg bb cc dd ee aa w.(12) 7 ;
    gg aa bb cc dd ee w.(0) 12 ;
    gg ee aa bb cc dd w.(9) 15 ;
    gg dd ee aa bb cc w.(5) 9 ;
    gg cc dd ee aa bb w.(2) 11 ;
    gg bb cc dd ee aa w.(14) 7 ;
    gg aa bb cc dd ee w.(11) 13 ;
    gg ee aa bb cc dd w.(8) 12 ;
    hh dd ee aa bb cc w.(3) 11 ;
    hh cc dd ee aa bb w.(10) 13 ;
    hh bb cc dd ee aa w.(14) 6 ;
    hh aa bb cc dd ee w.(4) 7 ;
    hh ee aa bb cc dd w.(9) 14 ;
    hh dd ee aa bb cc w.(15) 9 ;
    hh cc dd ee aa bb w.(8) 13 ;
    hh bb cc dd ee aa w.(1) 15 ;
    hh aa bb cc dd ee w.(2) 14 ;
    hh ee aa bb cc dd w.(7) 8 ;
    hh dd ee aa bb cc w.(0) 13 ;
    hh cc dd ee aa bb w.(6) 6 ;
    hh bb cc dd ee aa w.(13) 5 ;
    hh aa bb cc dd ee w.(11) 12 ;
    hh ee aa bb cc dd w.(5) 7 ;
    hh dd ee aa bb cc w.(12) 5 ;
    ii cc dd ee aa bb w.(1) 11 ;
    ii bb cc dd ee aa w.(9) 12 ;
    ii aa bb cc dd ee w.(11) 14 ;
    ii ee aa bb cc dd w.(10) 15 ;
    ii dd ee aa bb cc w.(0) 14 ;
    ii cc dd ee aa bb w.(8) 15 ;
    ii bb cc dd ee aa w.(12) 9 ;
    ii aa bb cc dd ee w.(4) 8 ;
    ii ee aa bb cc dd w.(13) 9 ;
    ii dd ee aa bb cc w.(3) 14 ;
    ii cc dd ee aa bb w.(7) 5 ;
    ii bb cc dd ee aa w.(15) 6 ;
    ii aa bb cc dd ee w.(14) 8 ;
    ii ee aa bb cc dd w.(5) 6 ;
    ii dd ee aa bb cc w.(6) 5 ;
    ii cc dd ee aa bb w.(2) 12 ;
    jj bb cc dd ee aa w.(4) 9 ;
    jj aa bb cc dd ee w.(0) 15 ;
    jj ee aa bb cc dd w.(5) 5 ;
    jj dd ee aa bb cc w.(9) 11 ;
    jj cc dd ee aa bb w.(7) 6 ;
    jj bb cc dd ee aa w.(12) 8 ;
    jj aa bb cc dd ee w.(2) 13 ;
    jj ee aa bb cc dd w.(10) 12 ;
    jj dd ee aa bb cc w.(14) 5 ;
    jj cc dd ee aa bb w.(1) 12 ;
    jj bb cc dd ee aa w.(3) 13 ;
    jj aa bb cc dd ee w.(8) 14 ;
    jj ee aa bb cc dd w.(11) 11 ;
    jj dd ee aa bb cc w.(6) 8 ;
    jj cc dd ee aa bb w.(15) 5 ;
    jj bb cc dd ee aa w.(13) 6 ;
    jjj aaa bbb ccc ddd eee w.(5) 8 ;
    jjj eee aaa bbb ccc ddd w.(14) 9 ;
    jjj ddd eee aaa bbb ccc w.(7) 9 ;
    jjj ccc ddd eee aaa bbb w.(0) 11 ;
    jjj bbb ccc ddd eee aaa w.(9) 13 ;
    jjj aaa bbb ccc ddd eee w.(2) 15 ;
    jjj eee aaa bbb ccc ddd w.(11) 15 ;
    jjj ddd eee aaa bbb ccc w.(4) 5 ;
    jjj ccc ddd eee aaa bbb w.(13) 7 ;
    jjj bbb ccc ddd eee aaa w.(6) 7 ;
    jjj aaa bbb ccc ddd eee w.(15) 8 ;
    jjj eee aaa bbb ccc ddd w.(8) 11 ;
    jjj ddd eee aaa bbb ccc w.(1) 14 ;
    jjj ccc ddd eee aaa bbb w.(10) 14 ;
    jjj bbb ccc ddd eee aaa w.(3) 12 ;
    jjj aaa bbb ccc ddd eee w.(12) 6 ;
    iii eee aaa bbb ccc ddd w.(6) 9 ;
    iii ddd eee aaa bbb ccc w.(11) 13 ;
    iii ccc ddd eee aaa bbb w.(3) 15 ;
    iii bbb ccc ddd eee aaa w.(7) 7 ;
    iii aaa bbb ccc ddd eee w.(0) 12 ;
    iii eee aaa bbb ccc ddd w.(13) 8 ;
    iii ddd eee aaa bbb ccc w.(5) 9 ;
    iii ccc ddd eee aaa bbb w.(10) 11 ;
    iii bbb ccc ddd eee aaa w.(14) 7 ;
    iii aaa bbb ccc ddd eee w.(15) 7 ;
    iii eee aaa bbb ccc ddd w.(8) 12 ;
    iii ddd eee aaa bbb ccc w.(12) 7 ;
    iii ccc ddd eee aaa bbb w.(4) 6 ;
    iii bbb ccc ddd eee aaa w.(9) 15 ;
    iii aaa bbb ccc ddd eee w.(1) 13 ;
    iii eee aaa bbb ccc ddd w.(2) 11 ;
    hhh ddd eee aaa bbb ccc w.(15) 9 ;
    hhh ccc ddd eee aaa bbb w.(5) 7 ;
    hhh bbb ccc ddd eee aaa w.(1) 15 ;
    hhh aaa bbb ccc ddd eee w.(3) 11 ;
    hhh eee aaa bbb ccc ddd w.(7) 8 ;
    hhh ddd eee aaa bbb ccc w.(14) 6 ;
    hhh ccc ddd eee aaa bbb w.(6) 6 ;
    hhh bbb ccc ddd eee aaa w.(9) 14 ;
    hhh aaa bbb ccc ddd eee w.(11) 12 ;
    hhh eee aaa bbb ccc ddd w.(8) 13 ;
    hhh ddd eee aaa bbb ccc w.(12) 5 ;
    hhh ccc ddd eee aaa bbb w.(2) 14 ;
    hhh bbb ccc ddd eee aaa w.(10) 13 ;
    hhh aaa bbb ccc ddd eee w.(0) 13 ;
    hhh eee aaa bbb ccc ddd w.(4) 7 ;
    hhh ddd eee aaa bbb ccc w.(13) 5 ;
    ggg ccc ddd eee aaa bbb w.(8) 15 ;
    ggg bbb ccc ddd eee aaa w.(6) 5 ;
    ggg aaa bbb ccc ddd eee w.(4) 8 ;
    ggg eee aaa bbb ccc ddd w.(1) 11 ;
    ggg ddd eee aaa bbb ccc w.(3) 14 ;
    ggg ccc ddd eee aaa bbb w.(11) 14 ;
    ggg bbb ccc ddd eee aaa w.(15) 6 ;
    ggg aaa bbb ccc ddd eee w.(0) 14 ;
    ggg eee aaa bbb ccc ddd w.(5) 6 ;
    ggg ddd eee aaa bbb ccc w.(12) 9 ;
    ggg ccc ddd eee aaa bbb w.(2) 12 ;
    ggg bbb ccc ddd eee aaa w.(13) 9 ;
    ggg aaa bbb ccc ddd eee w.(9) 12 ;
    ggg eee aaa bbb ccc ddd w.(7) 5 ;
    ggg ddd eee aaa bbb ccc w.(10) 15 ;
    ggg ccc ddd eee aaa bbb w.(14) 8 ;
    fff bbb ccc ddd eee aaa w.(12) 8 ;
    fff aaa bbb ccc ddd eee w.(15) 5 ;
    fff eee aaa bbb ccc ddd w.(10) 12 ;
    fff ddd eee aaa bbb ccc w.(4) 9 ;
    fff ccc ddd eee aaa bbb w.(1) 12 ;
    fff bbb ccc ddd eee aaa w.(5) 5 ;
    fff aaa bbb ccc ddd eee w.(8) 14 ;
    fff eee aaa bbb ccc ddd w.(7) 6 ;
    fff ddd eee aaa bbb ccc w.(6) 8 ;
    fff ccc ddd eee aaa bbb w.(2) 13 ;
    fff bbb ccc ddd eee aaa w.(13) 6 ;
    fff aaa bbb ccc ddd eee w.(14) 5 ;
    fff eee aaa bbb ccc ddd w.(0) 15 ;
    fff ddd eee aaa bbb ccc w.(3) 13 ;
    fff ccc ddd eee aaa bbb w.(9) 11 ;
    fff bbb ccc ddd eee aaa w.(11) 11 ;
    let open Int32 in
    ddd := !ddd + !cc + ctx.h.(1) ;
    (* final result for h[0]. *)
    ctx.h.(1) <- ctx.h.(2) + !dd + !eee ;
    ctx.h.(2) <- ctx.h.(3) + !ee + !aaa ;
    ctx.h.(3) <- ctx.h.(4) + !aa + !bbb ;
    ctx.h.(4) <- ctx.h.(0) + !bb + !ccc ;
    ctx.h.(0) <- !ddd ;
    ()

  exception Leave

  let feed :
      type a.
      le32_to_cpu:(a -> int -> int32) ->
      blit:(a -> int -> By.t -> int -> int -> unit) ->
      ctx ->
      a ->
      int ->
      int ->
      unit =
   fun ~le32_to_cpu ~blit ctx buf off len ->
    let t = ref ctx.s.(0) in
    let off = ref off in
    let len = ref len in
    ctx.s.(0) <- Int32.add !t (Int32.of_int (!len lsl 3)) ;
    if ctx.s.(0) < !t then ctx.s.(1) <- Int32.(ctx.s.(1) + 1l) ;
    ctx.s.(1) <- Int32.add ctx.s.(1) (Int32.of_int (!len lsr 29)) ;
    try
      if ctx.n <> 0
      then (
        let t = 64 - ctx.n in
        if !len < t
        then (
          blit buf !off ctx.b ctx.n !len ;
          ctx.n <- ctx.n + !len ;
          raise Leave) ;
        blit buf !off ctx.b ctx.n t ;
        rmd160_do_chunk ~le32_to_cpu:By.le32_to_cpu ctx ctx.b 0 ;
        off := !off + t ;
        len := !len - t) ;
      while !len >= 64 do
        rmd160_do_chunk ~le32_to_cpu ctx buf !off ;
        off := !off + 64 ;
        len := !len - 64
      done ;
      blit buf !off ctx.b 0 !len ;
      ctx.n <- !len
    with Leave -> ()

  let unsafe_feed_bytes ctx buf off len =
    feed ~blit:By.blit ~le32_to_cpu:By.le32_to_cpu ctx buf off len

  let unsafe_feed_bigstring ctx buf off len =
    feed ~blit:By.blit_from_bigstring ~le32_to_cpu:Bi.le32_to_cpu ctx buf off
      len

  let unsafe_get ctx =
    let i = ref (ctx.n + 1) in
    let res = By.create (5 * 4) in
    By.set ctx.b ctx.n '\x80' ;
    if !i > 56
    then (
      By.fill ctx.b !i (64 - !i) '\x00' ;
      rmd160_do_chunk ~le32_to_cpu:By.le32_to_cpu ctx ctx.b 0 ;
      i := 0) ;
    By.fill ctx.b !i (56 - !i) '\x00' ;
    By.cpu_to_le32 ctx.b 56 ctx.s.(0) ;
    By.cpu_to_le32 ctx.b 60 ctx.s.(1) ;
    rmd160_do_chunk ~le32_to_cpu:By.le32_to_cpu ctx ctx.b 0 ;
    for i = 0 to 4 do
      By.cpu_to_le32 res (i * 4) ctx.h.(i)
    done ;
    res
end
