(***********************************************************************)
(*                                                                     *)
(*                         The CamlZip library                         *)
(*                                                                     *)
(*            Xavier Leroy, projet Cristal, INRIA Rocquencourt         *)
(*                  adapted to Extc lib by Caue Waneck                 *)
(*                                                                     *)
(*  Copyright 2001 Institut National de Recherche en Informatique et   *)
(*  en Automatique.  All rights reserved.  This file is distributed    *)
(*  under the terms of the GNU Lesser General Public License, with     *)
(*  the special exception on linking described in file LICENSE.        *)
(*                                                                     *)
(***********************************************************************)

(* $Id: zlib.ml,v 1.4 2008/12/07 09:23:08 xleroy Exp $ *)

open Extc;;

let buffer_size = 1024

let polynom = 0xedb88320l

let crc_table = Array.init 256 (fun n ->
  let crc = ref (Int32.of_int n) in
  for j = 0 to 7 do
    crc := if Int32.to_int (Int32.logand (!crc) 1l) <> 0 then
      Int32.logxor (Int32.shift_right_logical (!crc) 1) polynom
    else
      Int32.shift_right_logical (!crc) 1;
  done;
  !crc) 

let max_wbits = 15

let compress ?(level = 6) ?(header = true) refill flush =
  let inbuf = Bytes.create buffer_size
  and outbuf = Bytes.create buffer_size in
  let zs = Extc.zlib_deflate_init2 level (if header then max_wbits else -max_wbits) in
  let rec compr inpos inavail =
    if inavail = 0 then begin
      let incount = refill inbuf in
      if incount = 0 then compr_finish() else compr 0 incount
    end else begin
      let res = Extc.zlib_deflate zs ~src:(Bytes.to_string inbuf) ~spos:inpos ~slen:inavail ~dst:outbuf ~dpos:0 ~dlen:buffer_size Z_NO_FLUSH in
      let used_in, used_out = res.z_read, res.z_wrote in
      flush outbuf used_out;
      compr (inpos + used_in) (inavail - used_in)
    end
  and compr_finish () =
    let ret = Extc.zlib_deflate zs ~src:(Bytes.to_string inbuf) ~spos:0 ~slen:0 ~dst:outbuf ~dpos:0 ~dlen:buffer_size Z_FINISH in
    let (finished, _, used_out) = ret.z_finish, ret.z_read, ret.z_wrote in
    flush outbuf used_out;
    if not finished then compr_finish()
  in
    compr 0 0;
    Extc.zlib_deflate_end zs

let compress_direct  ?(level = 6) ?(header = true) flush =
  let outbuf = Bytes.create buffer_size in
  let zs = Extc.zlib_deflate_init2 level (if header then max_wbits else -max_wbits) in
  let rec compr inbuf inpos inavail =
    if inavail = 0 then ()
    else begin
      let res = Extc.zlib_deflate zs ~src:(Bytes.to_string inbuf) ~spos:inpos ~slen:inavail ~dst:outbuf ~dpos:0 ~dlen:buffer_size Z_NO_FLUSH in
      let used_in, used_out = res.z_read, res.z_wrote in
      flush outbuf used_out;
      compr inbuf (inpos + used_in) (inavail - used_in)
    end
  and compr_finish () =
    let ret = Extc.zlib_deflate zs ~src:"" ~spos:0 ~slen:0 ~dst:outbuf ~dpos:0 ~dlen:buffer_size Z_FINISH in
    let (finished, _, used_out) = ret.z_finish, (), ret.z_wrote in
    flush outbuf used_out;
    if not finished then compr_finish()
  in
  compr, compr_finish

let uncompress ?(header = true) refill flush =
  let inbuf = Bytes.create buffer_size
  and outbuf = Bytes.create buffer_size in
  let zs = Extc.zlib_inflate_init2 (if header then max_wbits else -max_wbits) in
  let rec uncompr inpos inavail =
    if inavail = 0 then begin
      let incount = refill inbuf in
      if incount = 0 then uncompr_finish true else uncompr 0 incount
    end else begin
      let ret = Extc.zlib_inflate zs ~src:(Bytes.to_string inbuf) ~spos: inpos ~slen:inavail ~dst:outbuf ~dpos:0 ~dlen:buffer_size Z_SYNC_FLUSH in
      let (finished, used_in, used_out) = ret.z_finish, ret.z_read, ret.z_wrote in
      flush outbuf used_out;
      if not finished then uncompr (inpos + used_in) (inavail - used_in)
    end
  and uncompr_finish first_finish =
    (* Gotcha: if there is no header, inflate requires an extra "dummy" byte
       after the compressed stream in order to complete decompression
       and return finished = true. *)
    let dummy_byte = if first_finish && not header then 1 else 0 in
    let ret = Extc.zlib_inflate zs ~src:(Bytes.to_string inbuf) ~spos:0 ~slen:dummy_byte ~dst:outbuf ~dpos:0 ~dlen:buffer_size Z_SYNC_FLUSH in
    let (finished, _, used_out) = ret.z_finish, ret.z_read, ret.z_wrote in
    flush outbuf used_out;
    if not finished then uncompr_finish false
  in
    uncompr 0 0;
    Extc.zlib_inflate_end zs

let update_crc crc buf pos len =
  let c = ref (Int32.lognot crc) in
  for i = pos to (len + pos - 1) do
    let b = Int32.of_int (int_of_char (Bytes.get buf i)) in
    c := Int32.logxor (Array.get crc_table (Int32.to_int (Int32.logand (Int32.logxor !c b) 0xFFl))) (Int32.shift_right_logical !c 8);
  done;
  let ret = Int32.lognot !c in
  ret