%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2003-2022. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

-module(zlib).

-export([open/0,close/1,set_controlling_process/2,
         deflateInit/1,deflateInit/2,deflateInit/6,
         deflateSetDictionary/2,deflateReset/1,deflateParams/3,
         deflate/2,deflate/3,deflateEnd/1,
         inflateInit/1,inflateInit/2,inflateInit/3,
         inflateSetDictionary/2,inflateGetDictionary/1, inflateReset/1,
         inflate/2,inflate/3,inflateEnd/1,
         inflateChunk/2,inflateChunk/1,
         safeInflate/2,
         setBufSize/2,getBufSize/1,
         crc32/1,crc32/2,crc32/3,adler32/2,adler32/3,
         crc32_combine/4,adler32_combine/4,
         compress/1,uncompress/1,zip/1,unzip/1,
         gzip/1,gunzip/1]).

-export([on_load/0]).

-deprecated([{inflateChunk, 1, "use safeInflate/2 instead"},
             {inflateChunk, 2, "use safeInflate/2 instead"},
             {getBufSize, 1, "this function will be removed in a future release"},
             {setBufSize, 2, "this function will be removed in a future release"},
             {crc32, 1, "use erlang:crc32/1 on the uncompressed data instead"},
             {crc32, 2, "use erlang:crc32/1 instead"},
             {crc32, 3, "use erlang:crc32/2 instead"},
             {adler32, 2, "use erlang:adler32/1 instead"},
             {adler32, 3, "use erlang:adler32/2 instead"},
             {crc32_combine, 4, "use erlang:crc32_combine/3 instead"},
             {adler32_combine, 4, "use erlang:adler_combine/3 instead"}]).

-export_type([zstream/0, zflush/0, zlevel/0, zwindowbits/0, zmemlevel/0,
              zstrategy/0]).

-nifs([close_nif/1, open_nif/0, set_controller_nif/2, deflateInit_nif/6,
       deflateSetDictionary_nif/2, deflateReset_nif/1, deflateEnd_nif/1,
       deflateParams_nif/3, deflate_nif/4, inflateInit_nif/3,
       inflateSetDictionary_nif/2, inflateGetDictionary_nif/1,
       inflateReset_nif/1, inflateEnd_nif/1, inflate_nif/4, crc32_nif/1,
       getStash_nif/1, clearStash_nif/1, setStash_nif/2, getBufSize_nif/1,
       setBufSize_nif/2, enqueue_nif/2]).

%% flush argument encoding
-define(Z_NO_FLUSH,      0).
-define(Z_SYNC_FLUSH,    2).
-define(Z_FULL_FLUSH,    3).
-define(Z_FINISH,        4).

%% compression level
-define(Z_NO_COMPRESSION,         0).
-define(Z_BEST_SPEED,             1).
-define(Z_BEST_COMPRESSION,       9).
-define(Z_DEFAULT_COMPRESSION,  (-1)).

%% compression strategy
-define(Z_FILTERED,            1).
-define(Z_HUFFMAN_ONLY,        2).
-define(Z_RLE,                 3).
-define(Z_DEFAULT_STRATEGY,    0).

%% deflate compression method
-define(Z_DEFLATED,  8).

-define(MAX_WBITS, 15).

-define(DEFAULT_MEMLEVEL, 8).
-define(DEFAULT_WBITS, 15).

-define(EOS_BEHAVIOR_ERROR, 0).
-define(EOS_BEHAVIOR_RESET, 1).
-define(EOS_BEHAVIOR_CUT, 2).

%% Chunk sizes are hardcoded on account of them screwing with the
%% predictability of the system. zlib is incapable of trapping so we need to
%% ensure that it never operates on any significant amount of data.
-define(DEFLATE_IN_CHUNKSIZE, 8 bsl 10).
-define(DEFLATE_OUT_CHUNKSIZE, 8 bsl 10).
-define(INFLATE_IN_CHUNKSIZE, 8 bsl 10).
-define(INFLATE_OUT_CHUNKSIZE, 16 bsl 10).

%%------------------------------------------------------------------------

%% Public data types.
-type zstream() :: reference().
-type zflush() :: 'none' | 'sync' | 'full' | 'finish'.

-type zlevel() ::
    'none' | 'default' | 'best_compression' | 'best_speed' | 0..9.
-type zstrategy() :: 'default' | 'filtered' | 'huffman_only' | 'rle'.

-type zmemlevel() :: 1..9.
-type zwindowbits() :: -15..-8 | 8..47.

%% Private data types.

-type zmethod()     :: 'deflated'.

-record(zlib_opts, {
        stream :: zstream() | 'undefined',
        method :: function(),
        input_chunk_size :: pos_integer(),
        output_chunk_size :: pos_integer(),
        flush :: non_neg_integer()
    }).

%%------------------------------------------------------------------------

on_load() ->
    case erlang:load_nif(atom_to_list(?MODULE), 0) of
        ok -> ok
    end.

-spec open() -> zstream().
open() ->
    open_nif().
open_nif() ->
    erlang:nif_error(undef).

-spec close(Z) -> 'ok' when
      Z :: zstream().
close(Z) ->
    close_nif(Z).
close_nif(_Z) ->
    erlang:nif_error(undef).

-spec set_controlling_process(Z, Pid) -> 'ok' when
      Z :: zstream(),
      Pid :: pid().
set_controlling_process(Z, Pid) ->
    set_controller_nif(Z, Pid).
set_controller_nif(_Z, _Pid) ->
    erlang:nif_error(undef).

-spec deflateInit(Z) -> 'ok' when
      Z :: zstream().
deflateInit(Z) ->
    deflateInit(Z, default).

-spec deflateInit(Z, Level) -> 'ok' when
      Z :: zstream(),
      Level :: zlevel().
deflateInit(Z, Level) ->
    deflateInit(Z, Level, deflated, ?DEFAULT_WBITS, ?DEFAULT_MEMLEVEL, default).

-spec deflateInit(Z, Level, Method, WindowBits, MemLevel, Strategy) -> 'ok' when
      Z :: zstream(),
      Level :: zlevel(),
      Method :: zmethod(),
      WindowBits :: zwindowbits(),
      MemLevel :: zmemlevel(),
      Strategy :: zstrategy().
deflateInit(Z, Level, Method, WindowBits, MemLevel, Strategy) ->
    deflateInit_nif(Z,
                    arg_level(Level),
                    arg_method(Method),
                    arg_bitsz(WindowBits),
                    arg_mem(MemLevel),
                    arg_strategy(Strategy)).
deflateInit_nif(_Z, _Level, _Method, _WindowBits, _MemLevel, _Strategy) ->
    erlang:nif_error(undef).

-spec deflateSetDictionary(Z, Dictionary) -> Adler32 when
      Z :: zstream(),
      Dictionary :: iodata(),
      Adler32 :: non_neg_integer().
deflateSetDictionary(Z, Dictionary) ->
    deflateSetDictionary_nif(Z, Dictionary).
deflateSetDictionary_nif(_Z, _Dictionary) ->
    erlang:nif_error(undef).

-spec deflateReset(Z) -> 'ok' when
      Z :: zstream().
deflateReset(Z) ->
    deflateReset_nif(Z).
deflateReset_nif(_Z) ->
    erlang:nif_error(undef).

-spec deflateParams(Z, Level, Strategy) -> ok when
      Z :: zstream(),
      Level :: zlevel(),
      Strategy :: zstrategy().
deflateParams(Z, Level0, Strategy0) ->
    Level = arg_level(Level0),
    Strategy = arg_strategy(Strategy0),
    Progress = deflate(Z, <<>>, sync),
    case deflateParams_nif(Z, Level, Strategy) of
        ok ->
            save_progress(Z, deflate, Progress),
            ok;
        Other ->
            Other
    end.
deflateParams_nif(_Z, _Level, _Strategy) ->
    erlang:nif_error(undef).

-spec deflate(Z, Data) -> Compressed when
      Z :: zstream(),
      Data :: iodata(),
      Compressed :: iolist().
deflate(Z, Data) ->
    deflate(Z, Data, none).

-spec deflate(Z, Data, Flush) -> Compressed when
      Z :: zstream(),
      Data :: iodata(),
      Flush :: zflush(),
      Compressed :: iolist().
deflate(Z, Data, Flush) ->
    Progress = restore_progress(Z, deflate),
    enqueue_input(Z, Data),
    append_iolist(Progress, dequeue_all_chunks(Z, deflate_opts(Flush))).

deflate_opts(Flush) ->
    #zlib_opts{
        method = fun deflate_nif/4,
        input_chunk_size = ?DEFLATE_IN_CHUNKSIZE,
        output_chunk_size = ?DEFLATE_OUT_CHUNKSIZE,
        flush = arg_flush(Flush)
    }.

deflate_nif(_Z, _InputChSize, _OutputChSize, _Flush) ->
    erlang:nif_error(undef).

-spec deflateEnd(Z) -> 'ok' when
      Z :: zstream().
deflateEnd(Z) ->
    deflateEnd_nif(Z).
deflateEnd_nif(_Z) ->
    erlang:nif_error(undef).

-spec inflateInit(Z) -> 'ok' when
      Z :: zstream().
inflateInit(Z) ->
    inflateInit(Z, ?DEFAULT_WBITS).

-spec inflateInit(Z, WindowBits) -> 'ok' when
      Z :: zstream(),
      WindowBits :: zwindowbits().
inflateInit(Z, WindowBits) ->
    inflateInit(Z, WindowBits, cut).

-spec inflateInit(Z, WindowBits, EoSBehavior) -> 'ok' when
      Z :: zstream(),
      WindowBits :: zwindowbits(),
      EoSBehavior :: error | reset | cut.
inflateInit(Z, WindowBits, EoSBehavior) ->
    inflateInit_nif(Z, arg_bitsz(WindowBits), arg_eos_behavior(EoSBehavior)).
inflateInit_nif(_Z, _WindowBits, _EoSBehavior) ->
    erlang:nif_error(undef).

-spec inflateSetDictionary(Z, Dictionary) -> 'ok' when
      Z :: zstream(),
      Dictionary :: iodata().
inflateSetDictionary(Z, Dictionary) ->
    inflateSetDictionary_nif(Z, Dictionary).
inflateSetDictionary_nif(_Z, _Dictionary) ->
    erlang:nif_error(undef).

-spec inflateGetDictionary(Z) -> Dictionary when
      Z :: zstream(),
      Dictionary :: binary().
inflateGetDictionary(Z) ->
    case inflateGetDictionary_nif(Z) of
        Dictionary when is_binary(Dictionary) ->
            Dictionary;
        not_supported ->
            erlang:error(enotsup)
    end.
inflateGetDictionary_nif(_Z) ->
    erlang:nif_error(undef).

-spec inflateReset(Z) -> 'ok' when
      Z :: zstream().
inflateReset(Z) ->
    inflateReset_nif(Z).
inflateReset_nif(_Z) ->
    erlang:nif_error(undef).

-spec inflate(Z, Data) -> Decompressed when
      Z :: zstream(),
      Data :: iodata(),
      Decompressed :: iolist().
inflate(Z, Data) ->
    inflate(Z, Data, []).

-spec inflate(Z, Data, Options) -> Decompressed when
      Z :: zstream(),
      Data :: iodata(),
      Options :: list({exception_on_need_dict, boolean()}),
      Decompressed :: iolist() |
                      {need_dictionary,
                       Adler32 :: non_neg_integer(),
                       Output :: iolist()}.
inflate(Z, Data, Options) ->
    enqueue_input(Z, Data),
    Result = dequeue_all_chunks(Z, inflate_opts()),
    case proplist_get_value(Options, exception_on_need_dict, true) of
        true -> exception_on_need_dict(Z, Result);
        false -> Result
    end.

inflate_nif(_Z, _InputChSize, _OutputChSize, _Flush) ->
    erlang:nif_error(undef).

inflate_opts() ->
    #zlib_opts{
        method = fun inflate_nif/4,
        input_chunk_size = ?INFLATE_IN_CHUNKSIZE,
        output_chunk_size = ?INFLATE_OUT_CHUNKSIZE,
        flush = arg_flush(none)
    }.

-spec inflateChunk(Z, Data) -> Decompressed | {more, Decompressed} when
      Z :: zstream(),
      Data :: iodata(),
      Decompressed :: iolist().
inflateChunk(Z, Data) ->
    enqueue_input(Z, Data),
    inflateChunk(Z).

-spec inflateChunk(Z) -> Decompressed | {more, Decompressed} when
      Z :: zstream(),
      Decompressed :: iolist().
inflateChunk(Z) ->
    Opts0 = inflate_opts(),
    Opts = Opts0#zlib_opts { output_chunk_size = getBufSize(Z) },

    Result0 = dequeue_next_chunk(Z, Opts),
    Result1 = exception_on_need_dict(Z, Result0),
    yield_inflateChunk(Z, Result1).

yield_inflateChunk(_Z, {continue, Output}) ->
    {more, lists:flatten(Output)};
yield_inflateChunk(_Z, {finished, Output}) ->
    lists:flatten(Output).

exception_on_need_dict(Z, {need_dictionary, Adler, Output}) ->
    Progress = restore_progress(Z, inflate),
    save_progress(Z, inflate, append_iolist(Progress, Output)),
    erlang:error({need_dictionary, Adler});
exception_on_need_dict(Z, {Mark, Output}) ->
    Progress = restore_progress(Z, inflate),
    {Mark, append_iolist(Progress, Output)};
exception_on_need_dict(Z, Output) when is_list(Output); is_binary(Output) ->
    Progress = restore_progress(Z, inflate),
    append_iolist(Progress, Output).

-spec safeInflate(Z, Data) -> Result when
      Z :: zstream(),
      Data :: iodata(),
      Result :: {continue, Output :: iolist()} |
                {finished, Output :: iolist()} |
                {need_dictionary,
                 Adler32 :: non_neg_integer(),
                 Output :: iolist()}.
safeInflate(Z, Data) ->
    enqueue_input(Z, Data),
    dequeue_next_chunk(Z, inflate_opts()).

-spec inflateEnd(Z) -> 'ok' when
      Z :: zstream().
inflateEnd(Z) ->
    inflateEnd_nif(Z).
inflateEnd_nif(_Z) ->
    erlang:nif_error(undef).

-spec setBufSize(Z, Size) -> 'ok' when
      Z :: zstream(),
      Size :: non_neg_integer().
setBufSize(Z, Size) when is_integer(Size), Size > 16, Size < (1 bsl 24) ->
    setBufSize_nif(Z, Size);
setBufSize(_Z, _Size) ->
    erlang:error(badarg).
setBufSize_nif(_Z, _Size) ->
    erlang:nif_error(undef).

-spec getBufSize(Z) -> non_neg_integer() when
      Z :: zstream().
getBufSize(Z) ->
    getBufSize_nif(Z).
getBufSize_nif(_Z) ->
    erlang:nif_error(undef).

-spec crc32(Z) -> CRC when
      Z :: zstream(),
      CRC :: non_neg_integer().
crc32(Z) ->
    crc32_nif(Z).
crc32_nif(_Z) ->
    erlang:nif_error(undef).

-spec crc32(Z, Data) -> CRC when
      Z :: zstream(),
      Data :: iodata(),
      CRC :: non_neg_integer().
crc32(Z, Data) when is_reference(Z) ->
    erlang:crc32(Data);
crc32(_Z, _Data) ->
    erlang:error(badarg).

-spec crc32(Z, PrevCRC, Data) -> CRC when
      Z :: zstream(),
      PrevCRC :: non_neg_integer(),
      Data :: iodata(),
      CRC :: non_neg_integer().
crc32(Z, CRC, Data) when is_reference(Z) ->
    erlang:crc32(CRC, Data);
crc32(_Z, _CRC, _Data) ->
    erlang:error(badarg).

-spec crc32_combine(Z, CRC1, CRC2, Size2) -> CRC when
      Z :: zstream(),
      CRC :: non_neg_integer(),
      CRC1 :: non_neg_integer(),
      CRC2 :: non_neg_integer(),
      Size2 :: non_neg_integer().
crc32_combine(Z, CRC1, CRC2, Size2) when is_reference(Z) ->
    erlang:crc32_combine(CRC1, CRC2, Size2);
crc32_combine(_Z, _CRC1, _CRC2, _Size2) ->
    erlang:error(badarg).

-spec adler32(Z, Data) -> CheckSum when
      Z :: zstream(),
      Data :: iodata(),
      CheckSum :: non_neg_integer().
adler32(Z, Data) when is_reference(Z) ->
    erlang:adler32(Data);
adler32(_Z, _Data) ->
    erlang:error(badarg).

-spec adler32(Z, PrevAdler, Data) -> CheckSum when
      Z :: zstream(),
      PrevAdler :: non_neg_integer(),
      Data :: iodata(),
      CheckSum :: non_neg_integer().
adler32(Z, Adler, Data) when is_reference(Z) ->
    erlang:adler32(Adler, Data);
adler32(_Z, _Adler, _Data) ->
    erlang:error(badarg).

-spec adler32_combine(Z, Adler1, Adler2, Size2) -> Adler when
      Z :: zstream(),
      Adler :: non_neg_integer(),
      Adler1 :: non_neg_integer(),
      Adler2 :: non_neg_integer(),
      Size2 :: non_neg_integer().
adler32_combine(Z, Adler1, Adler2, Size2) when is_reference(Z) ->
    erlang:adler32_combine(Adler1, Adler2, Size2);
adler32_combine(_Z, _Adler1, _Adler2, _Size2) ->
    erlang:error(badarg).

%% compress/uncompress zlib with header
-spec compress(Data) -> Compressed when
      Data :: iodata(),
      Compressed :: binary().
compress(Data) ->
    Z = open(),
    Bs = try
             deflateInit(Z, default),
             B = deflate(Z, Data, finish),
             deflateEnd(Z),
             B
         after
             close(Z)
         end,
    iolist_to_binary(Bs).

-spec uncompress(Data) -> Decompressed when
      Data  :: iodata(),
      Decompressed :: binary().
uncompress(Data) ->
    try iolist_size(Data) of
        Size ->
            if
                Size >= 8 ->
                    Z = open(),
                    Bs = try
                             inflateInit(Z),
                             B = inflate(Z, Data),
                             inflateEnd(Z),
                             B
                         after
                             close(Z)
                         end,
                    iolist_to_binary(Bs);
                true ->
                    erlang:error(data_error)
            end
    catch
        _:_ ->
            erlang:error(badarg)
    end.

%% unzip/zip zlib without header (zip members)
-spec zip(Data) -> Compressed when
      Data :: iodata(),
      Compressed :: binary().
zip(Data) ->
    Z = open(),
    Bs = try
             deflateInit(Z, default, deflated, -?MAX_WBITS, 8, default),
             B = deflate(Z, Data, finish),
             deflateEnd(Z),
             B
         after
             close(Z)
         end,
    iolist_to_binary(Bs).

-spec unzip(Data) -> Decompressed when
      Data :: iodata(),
      Decompressed :: binary().
unzip(Data) ->
    Z = open(),
    Bs = try
             inflateInit(Z, -?MAX_WBITS),
             B = inflate(Z, Data),
             inflateEnd(Z),
             B
         after
             close(Z)
         end,
    iolist_to_binary(Bs).

-spec gzip(Data) -> Compressed when
      Data :: iodata(),
      Compressed :: binary().
gzip(Data) ->
    Z = open(),
    Bs = try
             deflateInit(Z, default, deflated, 16+?MAX_WBITS, 8, default),
             B = deflate(Z, Data, finish),
             deflateEnd(Z),
             B
         after
             close(Z)
         end,
    iolist_to_binary(Bs).

-spec gunzip(Data) -> Decompressed when
      Data :: iodata(),
      Decompressed :: binary().
gunzip(Data) ->
    Z = open(),
    Bs = try
             inflateInit(Z, 16+?MAX_WBITS, reset),
             B = inflate(Z, Data),
             inflateEnd(Z),
             B
         after
             close(Z)
         end,
    iolist_to_binary(Bs).

-spec dequeue_all_chunks(Z, Opts) -> Result when
    Z :: zstream(),
    Opts :: #zlib_opts{},
    Result :: {need_dictionary, integer(), iolist()} |
              iolist().
dequeue_all_chunks(Z, Opts) ->
    dequeue_all_chunks_1(Z, Opts, []).
dequeue_all_chunks_1(Z, Opts, Output) ->
    case dequeue_next_chunk(Z, Opts) of
        {need_dictionary, _, _} = NeedDict ->
            NeedDict;
        {continue, Chunk} ->
            dequeue_all_chunks_1(Z, Opts, append_iolist(Output, Chunk));
        {finished, Chunk} ->
            append_iolist(Output, Chunk)
    end.

-spec dequeue_next_chunk(Z, Opts) -> Result when
    Z :: zstream(),
    Opts :: #zlib_opts{},
    Result :: {need_dictionary, integer(), iolist()} |
              {continue, iolist()} |
              {finished, iolist()}.
dequeue_next_chunk(Z, Opts) ->
    Method = Opts#zlib_opts.method,
    IChSz = Opts#zlib_opts.input_chunk_size,
    OChSz = Opts#zlib_opts.output_chunk_size,
    Flush = Opts#zlib_opts.flush,
    Method(Z, IChSz, OChSz, Flush).

-spec append_iolist(IO, D) -> iolist() when
    IO :: iodata(),
    D :: iodata().
append_iolist([], D) when is_list(D) -> D;
append_iolist([], D) -> [D];
append_iolist(IO, []) -> IO;
append_iolist(IO, [D]) -> [IO, D];
append_iolist(IO, D) -> [IO, D].

%% inflate/2 and friends are documented as throwing an error on Z_NEED_DICT
%% rather than simply returning something to that effect, and deflateParams/3
%% may flush behind the scenes. This requires us to stow away our current
%% progress in the handle and resume from that point on our next call.
%%
%% Generally speaking this is either a refc binary or nothing at all, so it's
%% pretty cheap.

-spec save_progress(Z, Kind, Output) -> ok when
      Z :: zstream(),
      Kind :: inflate | deflate,
      Output :: iolist().
save_progress(Z, Kind, Output) ->
    ok = setStash_nif(Z, {Kind, Output}).

-spec restore_progress(Z, Kind) -> iolist() when
    Z :: zstream(),
    Kind :: inflate | deflate.
restore_progress(Z, Kind) ->
    case getStash_nif(Z) of
        {ok, {Kind, Output}} ->
            ok = clearStash_nif(Z),
            Output;
        empty ->
            []
    end.

-spec clearStash_nif(Z) -> ok when
      Z :: zstream().
clearStash_nif(_Z) ->
    erlang:nif_error(undef).

-spec setStash_nif(Z, Term) -> ok when
      Z :: zstream(),
      Term :: term().
setStash_nif(_Z, _Term) ->
    erlang:nif_error(undef).

-spec getStash_nif(Z) -> {ok, term()} | empty when
      Z :: zstream().
getStash_nif(_Z) ->
    erlang:nif_error(undef).

%% The 'proplists' module isn't preloaded so we can't rely on its existence.
proplist_get_value([], _Name, DefVal) -> DefVal;
proplist_get_value([{Name, Value} | _Opts], Name, _DefVal) -> Value;
proplist_get_value([_Head | Opts], Name, DefVal) ->
    proplist_get_value(Opts, Name, DefVal).

arg_flush(none)   -> ?Z_NO_FLUSH;
%% ?Z_PARTIAL_FLUSH is deprecated in zlib -- deliberately not included.
arg_flush(sync)   -> ?Z_SYNC_FLUSH;
arg_flush(full)   -> ?Z_FULL_FLUSH;
arg_flush(finish) -> ?Z_FINISH;
arg_flush(_) -> erlang:error(bad_flush_mode).

arg_level(none)             -> ?Z_NO_COMPRESSION;
arg_level(best_speed)       -> ?Z_BEST_SPEED;
arg_level(best_compression) -> ?Z_BEST_COMPRESSION;
arg_level(default)          -> ?Z_DEFAULT_COMPRESSION;
arg_level(Level) when is_integer(Level), Level >= 0, Level =< 9 -> Level;
arg_level(_) -> erlang:error(bad_compression_level).

arg_strategy(filtered) ->     ?Z_FILTERED;
arg_strategy(huffman_only) -> ?Z_HUFFMAN_ONLY;
arg_strategy(rle) -> ?Z_RLE;
arg_strategy(default) ->      ?Z_DEFAULT_STRATEGY;
arg_strategy(_) -> erlang:error(bad_compression_strategy).

arg_method(deflated) -> ?Z_DEFLATED;
arg_method(_) -> erlang:error(bad_compression_method).

arg_eos_behavior(error) -> ?EOS_BEHAVIOR_ERROR;
arg_eos_behavior(reset) -> ?EOS_BEHAVIOR_RESET;
arg_eos_behavior(cut) -> ?EOS_BEHAVIOR_CUT;
arg_eos_behavior(_) -> erlang:error(bad_eos_behavior).

-spec arg_bitsz(zwindowbits()) -> zwindowbits().
arg_bitsz(Bits) when is_integer(Bits) andalso
                     ((8 =< Bits andalso Bits < 48) orelse
                      (-15 =< Bits andalso Bits =< -8)) ->
    Bits;
arg_bitsz(_) -> erlang:error(bad_windowbits).

-spec arg_mem(zmemlevel()) -> zmemlevel().
arg_mem(Level) when is_integer(Level), 1 =< Level, Level =< 9 -> Level;
arg_mem(_) -> erlang:error(bad_memlevel).

-spec enqueue_input(Z, IOData) -> ok when
      Z :: zstream(),
      IOData :: iodata().
enqueue_input(Z, IOData) ->
    enqueue_input_1(Z, erlang:iolist_to_iovec(IOData)).

enqueue_input_1(_Z, []) ->
    ok;
enqueue_input_1(Z, IOVec) ->
    case enqueue_nif(Z, IOVec) of
        {continue, Remainder} -> enqueue_input_1(Z, Remainder);
        ok -> ok
    end.

enqueue_nif(_Z, _IOVec) ->
    erlang:nif_error(undef).
