//! Raw in-memory LZMA streams.
//!
//! The [`Stream`] type in this module is the primary type which performs
//! encoding/decoding of LZMA streams. Each [`Stream`] is either an encoder or
//! decoder and processes data in a streaming fashion.

use std::collections::LinkedList;
use std::error;
use std::fmt;
use std::io;
use std::mem;

/// Representation of an in-memory LZMA encoding or decoding stream.
///
/// Wraps the raw underlying `lzma_stream` type and provides the ability to
/// create streams which can either decode or encode various LZMA-based formats.
pub struct Stream {
    raw: liblzma_sys::lzma_stream,
}

unsafe impl Send for Stream {}
unsafe impl Sync for Stream {}

/// Options that can be used to configure how LZMA encoding happens.
///
/// This builder is consumed by a number of other methods.
pub struct LzmaOptions {
    raw: liblzma_sys::lzma_options_lzma,
}

/// Builder to create a multithreaded stream encoder.
#[cfg(feature = "parallel")]
pub struct MtStreamBuilder {
    raw: liblzma_sys::lzma_mt,
    filters: Option<Filters>,
}

/// A custom chain of filters to configure an encoding stream.
pub struct Filters {
    inner: Vec<liblzma_sys::lzma_filter>,
    lzma_opts: LinkedList<liblzma_sys::lzma_options_lzma>,
}

/// The `action` argument for [`Stream::process`],
#[derive(Debug, Copy, Clone)]
pub enum Action {
    /// Continue processing
    ///
    /// When encoding, encode as much input as possible. Some internal buffering
    /// will probably be done (depends on the filter chain in use), which causes
    /// latency: the input used won't usually be decodeable from the output of
    /// the same [`Stream::process`] call.
    ///
    /// When decoding, decode as much input as possible and produce as much
    /// output as possible.
    Run = liblzma_sys::LZMA_RUN as isize,

    /// Make all the input available at output
    ///
    /// Normally the encoder introduces some latency. `SyncFlush` forces all the
    /// buffered data to be available at output without resetting the internal
    /// state of the encoder. This way it is possible to use compressed stream
    /// for example for communication over network.
    ///
    /// Only some filters support `SyncFlush`. Trying to use `SyncFlush` with
    /// filters that don't support it will make [`Stream::process`] return
    /// `Error::Options`. For example, LZMA1 doesn't support `SyncFlush` but
    /// LZMA2 does.
    ///
    /// Using `SyncFlush` very often can dramatically reduce the compression
    /// ratio. With some filters (for example, LZMA2), fine-tuning the
    /// compression options may help mitigate this problem significantly (for
    /// example, match finder with LZMA2).
    ///
    /// Decoders don't support `SyncFlush`.
    SyncFlush = liblzma_sys::LZMA_SYNC_FLUSH as isize,

    /// Finish encoding of the current block.
    ///
    /// All the input data going to the current block must have been given to
    /// the encoder. Call [`Stream::process`] with `FullFlush` until it returns
    /// `Status::StreamEnd`. Then continue normally with `Run` or finish the
    /// Stream with `Finish`.
    ///
    /// This action is currently supported only by stream encoder and easy
    /// encoder (which uses stream encoder). If there is no unfinished block, no
    /// empty block is created.
    FullFlush = liblzma_sys::LZMA_FULL_FLUSH as isize,

    /// Finish encoding of the current block.
    ///
    /// This is like `FullFlush` except that this doesn't necessarily wait until
    /// all the input has been made available via the output buffer. That is,
    /// [`Stream::process`] might return `Status::StreamEnd` as soon as all the input has
    /// been consumed.
    ///
    /// `FullBarrier` is useful with a threaded encoder if one wants to split
    /// the .xz Stream into blocks at specific offsets but doesn't care if the
    /// output isn't flushed immediately. Using `FullBarrier` allows keeping the
    /// threads busy while `FullFlush` would make [`Stream::process`] wait until all the
    /// threads have finished until more data could be passed to the encoder.
    ///
    /// With a `Stream` initialized with the single-threaded
    /// `new_stream_encoder` or `new_easy_encoder`, `FullBarrier` is an alias
    /// for `FullFlush`.
    FullBarrier = liblzma_sys::LZMA_FULL_BARRIER as isize,

    /// Finish the current operation
    ///
    /// All the input data must have been given to the encoder (the last bytes
    /// can still be pending in next_in). Call [`Stream::process`] with `Finish` until it
    /// returns `Status::StreamEnd`. Once `Finish` has been used, the amount of
    /// input must no longer be changed by the application.
    ///
    /// When decoding, using `Finish` is optional unless the concatenated flag
    /// was used when the decoder was initialized. When concatenated was not
    /// used, the only effect of `Finish` is that the amount of input must not
    /// be changed just like in the encoder.
    Finish = liblzma_sys::LZMA_FINISH as isize,
}

/// Return value of a [`Stream::process`] operation.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Status {
    /// Operation completed successfully.
    Ok,

    /// End of stream was reached.
    ///
    /// When encoding, this means that a sync/full flush or `Finish` was
    /// completed. When decoding, this indicates that all data was decoded
    /// successfully.
    StreamEnd,

    /// If the TELL_ANY_CHECK flags is specified when constructing a decoder,
    /// this informs that the `check` method will now return the underlying
    /// integrity check algorithm.
    GetCheck,

    /// An error has not been encountered, but no progress is possible.
    ///
    /// Processing can be continued normally by providing more input and/or more
    /// output space, if possible.
    ///
    /// Typically the first call to [`Stream::process`] that can do no progress returns
    /// `Ok` instead of `MemNeeded`. Only the second consecutive call doing no
    /// progress will return `MemNeeded`.
    MemNeeded,
}

/// Possible error codes that can be returned from a processing operation.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum Error {
    /// The underlying data was corrupt.
    Data,

    /// Invalid or unsupported options were specified.
    Options,

    /// File format wasn't recognized.
    Format,

    /// Memory usage limit was reached.
    ///
    /// The memory limit can be increased with `set_memlimit`
    MemLimit,

    /// Memory couldn't be allocated.
    Mem,

    /// A programming error was encountered.
    Program,

    /// The `TELL_NO_CHECK` flag was specified and no integrity check was
    /// available for this stream.
    NoCheck,

    /// The `TELL_UNSUPPORTED_CHECK` flag was specified and no integrity check
    /// isn't implemented in this build of liblzma for this stream.
    UnsupportedCheck,
}

/// Possible integrity checks that can be part of a .xz stream.
#[allow(missing_docs)] // self-explanatory mostly
#[derive(Debug, Copy, Clone)]
pub enum Check {
    None = liblzma_sys::LZMA_CHECK_NONE as isize,
    Crc32 = liblzma_sys::LZMA_CHECK_CRC32 as isize,
    Crc64 = liblzma_sys::LZMA_CHECK_CRC64 as isize,
    Sha256 = liblzma_sys::LZMA_CHECK_SHA256 as isize,
}

/// Compression modes
///
/// This selects the function used to analyze the data produced by the match
/// finder.
#[derive(Debug, Copy, Clone)]
pub enum Mode {
    /// Fast compression.
    ///
    /// Fast mode is usually at its best when combined with a hash chain match
    /// finder.
    Fast = liblzma_sys::LZMA_MODE_FAST as isize,

    /// Normal compression.
    ///
    /// This is usually notably slower than fast mode. Use this together with
    /// binary tree match finders to expose the full potential of the LZMA1 or
    /// LZMA2 encoder.
    Normal = liblzma_sys::LZMA_MODE_NORMAL as isize,
}

/// Match finders
///
/// Match finder has major effect on both speed and compression ratio. Usually
/// hash chains are faster than binary trees.
///
/// If you will use `SyncFlush` often, the hash chains may be a better choice,
/// because binary trees get much higher compression ratio penalty with
/// `SyncFlush`.
///
/// The memory usage formulas are only rough estimates, which are closest to
/// reality when dict_size is a power of two. The formulas are  more complex in
/// reality, and can also change a little between liblzma versions.
#[derive(Debug, Copy, Clone)]
pub enum MatchFinder {
    /// Hash Chain with 2- and 3-byte hashing
    HashChain3 = liblzma_sys::LZMA_MF_HC3 as isize,
    /// Hash Chain with 2-, 3-, and 4-byte hashing
    HashChain4 = liblzma_sys::LZMA_MF_HC4 as isize,

    /// Binary Tree with 2-byte hashing
    BinaryTree2 = liblzma_sys::LZMA_MF_BT2 as isize,
    /// Binary Tree with 2- and 3-byte hashing
    BinaryTree3 = liblzma_sys::LZMA_MF_BT3 as isize,
    /// Binary Tree with 2-, 3-, and 4-byte hashing
    BinaryTree4 = liblzma_sys::LZMA_MF_BT4 as isize,
}

/// A flag passed when initializing a decoder, causes [`Stream::process`] to return
/// [`Status::GetCheck`] as soon as the integrity check is known.
pub const TELL_ANY_CHECK: u32 = liblzma_sys::LZMA_TELL_ANY_CHECK;

/// A flag passed when initializing a decoder, causes [`Stream::process`] to return
/// [`Error::NoCheck`] if the stream being decoded has no integrity check.
pub const TELL_NO_CHECK: u32 = liblzma_sys::LZMA_TELL_NO_CHECK;

/// A flag passed when initializing a decoder, causes [`Stream::process`] to return
/// [`Error::UnsupportedCheck`] if the stream being decoded has an integrity check
/// that cannot be verified by this build of liblzma.
pub const TELL_UNSUPPORTED_CHECK: u32 = liblzma_sys::LZMA_TELL_UNSUPPORTED_CHECK;

/// A flag passed when initializing a decoder, causes the decoder to ignore any
/// integrity checks listed.
pub const IGNORE_CHECK: u32 = liblzma_sys::LZMA_TELL_UNSUPPORTED_CHECK;

/// A flag passed when initializing a decoder, indicates that the stream may be
/// multiple concatenated xz files.
pub const CONCATENATED: u32 = liblzma_sys::LZMA_CONCATENATED;

/// Default compression preset level.
pub const PRESET_DEFAULT: u32 = liblzma_sys::LZMA_PRESET_DEFAULT;

/// Mask for extracting the preset level bits from a preset value.
pub const PRESET_LEVEL_MASK: u32 = liblzma_sys::LZMA_PRESET_LEVEL_MASK;

/// Flag to request the slower "extreme" variant of a preset.
///
/// Combine this with a preset level using bitwise-OR.
/// For example: `6 | PRESET_EXTREME`.
pub const PRESET_EXTREME: u32 = liblzma_sys::LZMA_PRESET_EXTREME;

/// Encoder-related functions
impl Stream {
    /// Initialize .xz stream encoder using a preset number
    ///
    /// This is intended to be used by most for encoding data. The `preset`
    /// argument is usually a level in the range 0-9 with 6 being a good
    /// default. You may also bitwise-OR a level with [`PRESET_EXTREME`] to use
    /// the slower extreme variant, for example `6 | PRESET_EXTREME`.
    ///
    /// The `check` argument is the integrity check to insert at the end of the
    /// stream. The default of `Crc64` is typically appropriate.
    #[inline]
    pub fn new_easy_encoder(preset: u32, check: Check) -> Result<Stream, Error> {
        let mut init = unsafe { Stream::zeroed() };
        cvt(unsafe {
            liblzma_sys::lzma_easy_encoder(&mut init.raw, preset, check as liblzma_sys::lzma_check)
        })?;
        Ok(init)
    }

    /// Initialize .lzma encoder (legacy file format)
    ///
    /// The .lzma format is sometimes called the LZMA_Alone format, which is the
    /// reason for the name of this function. The .lzma format supports only the
    /// LZMA1 filter. There is no support for integrity checks like CRC32.
    ///
    /// Use this function if and only if you need to create files readable by
    /// legacy LZMA tools such as LZMA Utils 4.32.x. Moving to the .xz format
    /// (the `new_easy_encoder` function) is strongly recommended.
    ///
    /// The valid action values for [`Stream::process`] are [`Action::Run`] and [`Action::Finish`].
    /// No flushing is supported, because the file format doesn't support it.
    #[inline]
    pub fn new_lzma_encoder(options: &LzmaOptions) -> Result<Stream, Error> {
        let mut init = unsafe { Stream::zeroed() };
        cvt(unsafe { liblzma_sys::lzma_alone_encoder(&mut init.raw, &options.raw) })?;
        Ok(init)
    }

    /// Initialize .xz Stream encoder using a custom filter chain
    ///
    /// This function is similar to `new_easy_encoder` but a custom filter chain
    /// is specified.
    #[inline]
    pub fn new_stream_encoder(filters: &Filters, check: Check) -> Result<Stream, Error> {
        let mut init = unsafe { Stream::zeroed() };
        cvt(unsafe {
            liblzma_sys::lzma_stream_encoder(
                &mut init.raw,
                filters.inner.as_ptr(),
                check as liblzma_sys::lzma_check,
            )
        })?;
        Ok(init)
    }

    /// Initialize an encoder stream using a custom filter chain.
    #[inline]
    pub fn new_raw_encoder(filters: &Filters) -> Result<Stream, Error> {
        let mut init = unsafe { Self::zeroed() };
        cvt(unsafe { liblzma_sys::lzma_raw_encoder(&mut init.raw, filters.inner.as_ptr()) })?;
        Ok(init)
    }
}

/// Decoder-related functions
impl Stream {
    /// Initialize a decoder which will choose a stream/lzma formats depending
    /// on the input stream.
    #[inline]
    pub fn new_auto_decoder(memlimit: u64, flags: u32) -> Result<Stream, Error> {
        let mut init = unsafe { Self::zeroed() };
        cvt(unsafe { liblzma_sys::lzma_auto_decoder(&mut init.raw, memlimit, flags) })?;
        Ok(init)
    }

    /// Initialize a .xz stream decoder.
    ///
    /// The maximum memory usage can be specified along with flags such as
    /// [`TELL_ANY_CHECK`], [`TELL_NO_CHECK`], [`TELL_UNSUPPORTED_CHECK`],
    /// [`IGNORE_CHECK`], or [`CONCATENATED`].
    #[inline]
    pub fn new_stream_decoder(memlimit: u64, flags: u32) -> Result<Stream, Error> {
        let mut init = unsafe { Self::zeroed() };
        cvt(unsafe { liblzma_sys::lzma_stream_decoder(&mut init.raw, memlimit, flags) })?;
        Ok(init)
    }

    /// Initialize a .lzma stream decoder.
    ///
    /// The maximum memory usage can also be specified.
    #[inline]
    pub fn new_lzma_decoder(memlimit: u64) -> Result<Stream, Error> {
        let mut init = unsafe { Self::zeroed() };
        cvt(unsafe { liblzma_sys::lzma_alone_decoder(&mut init.raw, memlimit) })?;
        Ok(init)
    }

    /// Initialize a .lz stream decoder.
    #[inline]
    pub fn new_lzip_decoder(memlimit: u64, flags: u32) -> Result<Self, Error> {
        let mut init = unsafe { Self::zeroed() };
        cvt(unsafe { liblzma_sys::lzma_lzip_decoder(&mut init.raw, memlimit, flags) })?;
        Ok(init)
    }

    /// Initialize a decoder stream using a custom filter chain.
    #[inline]
    pub fn new_raw_decoder(filters: &Filters) -> Result<Stream, Error> {
        let mut init = unsafe { Self::zeroed() };
        cvt(unsafe { liblzma_sys::lzma_raw_decoder(&mut init.raw, filters.inner.as_ptr()) })?;
        Ok(init)
    }
}

/// Generic functions
impl Stream {
    #[inline]
    unsafe fn zeroed() -> Self {
        Self {
            raw: unsafe { mem::zeroed() },
        }
    }

    #[inline]
    unsafe fn process_inner(
        &mut self,
        input: &[u8],
        output_ptr: *mut u8,
        output_len: usize,
        action: Action,
    ) -> Result<Status, Error> {
        self.raw.next_in = input.as_ptr();
        self.raw.avail_in = input.len();
        self.raw.next_out = output_ptr;
        self.raw.avail_out = output_len;
        let action = action as liblzma_sys::lzma_action;
        unsafe { cvt(liblzma_sys::lzma_code(&mut self.raw, action)) }
    }

    /// Processes some data from input into an output buffer.
    ///
    /// This will perform the appropriate encoding or decoding operation
    /// depending on the kind of underlying stream. See [`Action`] for the
    /// possible actions that can be taken.
    ///
    /// After the first use of [`Action::SyncFlush`], [`Action::FullFlush`],
    /// [`Action::FullBarrier`], or [`Action::Finish`], the same [`Action`]
    /// must be used until this returns [`Status::StreamEnd`]. Not doing so
    /// will result in a [`Error::Program`].
    ///
    /// The amount of input must not be modified by the application until
    /// this returns [`Status::StreamEnd`], otherwise [`Error::Program`] will
    /// be returned.
    #[inline]
    pub fn process(
        &mut self,
        input: &[u8],
        output: &mut [u8],
        action: Action,
    ) -> Result<Status, Error> {
        unsafe { self.process_inner(input, output.as_mut_ptr(), output.len(), action) }
    }

    /// Same as [`Self::process`] but accepts uninitialized buffer.
    ///
    /// To retrieve bytes written into the `output`, please call [`Self::total_out()`] before
    /// and after the call to [`Self::process_uninit`] and the diff of `total_out` would be
    /// the bytes written to the `output`.
    #[inline]
    pub fn process_uninit(
        &mut self,
        input: &[u8],
        output: &mut [mem::MaybeUninit<u8>],
        action: Action,
    ) -> Result<Status, Error> {
        unsafe { self.process_inner(input, output.as_mut_ptr() as *mut _, output.len(), action) }
    }

    /// Performs the same data as [`Stream::process`], but places output data in a [`Vec`].
    ///
    /// This function will use the extra capacity of `output` as a destination
    /// for bytes to be placed. The length of `output` will automatically get
    /// updated after the operation has completed.
    ///
    /// See [`Stream::process`] for the other arguments.
    #[inline]
    pub fn process_vec(
        &mut self,
        input: &[u8],
        output: &mut Vec<u8>,
        action: Action,
    ) -> Result<Status, Error> {
        let len = output.len();

        unsafe {
            let before = self.total_out();
            let ret = self.process_uninit(input, output.spare_capacity_mut(), action);
            output.set_len((self.total_out() - before) as usize + len);
            ret
        }
    }

    /// Returns the total amount of input bytes consumed by this stream.
    #[inline]
    pub fn total_in(&self) -> u64 {
        self.raw.total_in
    }

    /// Returns the total amount of bytes produced by this stream.
    #[inline]
    pub fn total_out(&self) -> u64 {
        self.raw.total_out
    }

    /// Get the current memory usage limit.
    ///
    /// This is only supported if the underlying stream supports a memlimit.
    #[inline]
    pub fn memlimit(&self) -> u64 {
        unsafe { liblzma_sys::lzma_memlimit_get(&self.raw) }
    }

    /// Set the current memory usage limit.
    ///
    /// This can return [`Error::MemLimit`] if the new limit is too small or
    /// [`Error::Program`] if this stream doesn't take a memory limit.
    #[inline]
    pub fn set_memlimit(&mut self, limit: u64) -> Result<(), Error> {
        cvt(unsafe { liblzma_sys::lzma_memlimit_set(&mut self.raw, limit) }).map(|_| ())
    }
}

impl LzmaOptions {
    /// Creates a new blank set of options.
    #[inline]
    pub fn new() -> LzmaOptions {
        LzmaOptions {
            raw: unsafe { mem::zeroed() },
        }
    }

    /// Creates a new blank set of options for encoding.
    ///
    /// The `preset` argument is usually a level in the range 0-9. You may also
    /// bitwise-OR a level with [`PRESET_EXTREME`] to use the slower extreme
    /// variant, for example `6 | PRESET_EXTREME`.
    #[inline]
    pub fn new_preset(preset: u32) -> Result<LzmaOptions, Error> {
        unsafe {
            let mut options = Self::new();
            let ret = liblzma_sys::lzma_lzma_preset(&mut options.raw, preset);
            if ret != 0 {
                Err(Error::Program)
            } else {
                Ok(options)
            }
        }
    }

    /// Configures the dictionary size, in bytes
    ///
    /// Dictionary size indicates how many bytes of the recently processed
    /// uncompressed data is kept in memory.
    ///
    /// The minimum dictionary size is 4096 bytes and the default is 2^23 = 8MB.
    #[inline]
    pub fn dict_size(&mut self, size: u32) -> &mut LzmaOptions {
        self.raw.dict_size = size;
        self
    }

    /// Configures the number of literal context bits.
    ///
    /// How many of the highest bits of the previous uncompressed eight-bit byte
    /// (also known as `literal') are taken into account when predicting the
    /// bits of the next literal.
    ///
    /// The maximum value to this is 4 and the default is 3. It is not currently
    /// supported if this plus [`LzmaOptions::literal_position_bits`] is greater than 4.
    #[inline]
    pub fn literal_context_bits(&mut self, bits: u32) -> &mut LzmaOptions {
        self.raw.lc = bits;
        self
    }

    /// Configures the number of literal position bits.
    ///
    /// This affects what kind of alignment in the uncompressed data is assumed
    /// when encoding literals. A literal is a single 8-bit byte. See
    /// [`LzmaOptions::position_bits`] for more information about alignment.
    ///
    /// The default for this is 0.
    #[inline]
    pub fn literal_position_bits(&mut self, bits: u32) -> &mut LzmaOptions {
        self.raw.lp = bits;
        self
    }

    /// Configures the number of position bits.
    ///
    /// Position bits affects what kind of alignment in the uncompressed data is
    /// assumed in general. The default of 2 means four-byte alignment (2^pb
    /// = 2^2 = 4), which is often a good choice when there's no better guess.
    ///
    /// When the alignment is known, setting pb accordingly may reduce the file
    /// size a little. E.g. with text files having one-byte alignment (US-ASCII,
    /// ISO-8859-*, UTF-8), setting pb=0 can improve compression slightly. For
    /// UTF-16 text, pb=1 is a good choice. If the alignment is an odd number
    /// like 3 bytes, pb=0 might be the best choice.
    ///
    /// Even though the assumed alignment can be adjusted with pb and lp, LZMA1
    /// and LZMA2 still slightly favor 16-byte alignment. It might be worth
    /// taking into account when designing file formats that are likely to be
    /// often compressed with LZMA1 or LZMA2.
    #[inline]
    pub fn position_bits(&mut self, bits: u32) -> &mut LzmaOptions {
        self.raw.pb = bits;
        self
    }

    /// Configures the compression mode.
    #[inline]
    pub fn mode(&mut self, mode: Mode) -> &mut LzmaOptions {
        self.raw.mode = mode as liblzma_sys::lzma_mode;
        self
    }

    /// Configures the nice length of a match.
    ///
    /// This determines how many bytes the encoder compares from the match
    /// candidates when looking for the best match. Once a match of at least
    /// `len` bytes long is found, the encoder stops looking for better
    /// candidates and encodes the match. (Naturally, if the found match is
    /// actually longer than `len`, the actual length is encoded; it's not
    /// truncated to `len`.)
    ///
    /// Bigger values usually increase the compression ratio and compression
    /// time. For most files, 32 to 128 is a good value, which gives very good
    /// compression ratio at good speed.
    ///
    /// The exact minimum value depends on the match finder. The maximum is 273,
    /// which is the maximum length of a match that LZMA1 and LZMA2 can encode.
    #[inline]
    pub fn nice_len(&mut self, len: u32) -> &mut LzmaOptions {
        self.raw.nice_len = len;
        self
    }

    /// Configures the match finder ID.
    #[inline]
    pub fn match_finder(&mut self, mf: MatchFinder) -> &mut LzmaOptions {
        self.raw.mf = mf as liblzma_sys::lzma_match_finder;
        self
    }

    /// Maximum search depth in the match finder.
    ///
    /// For every input byte, match finder searches through the hash chain or
    /// binary tree in a loop, each iteration going one step deeper in the chain
    /// or tree. The searching stops if
    ///
    ///  - a match of at least [`LzmaOptions::nice_len`] bytes long is found;
    ///  - all match candidates from the hash chain or binary tree have
    ///    been checked; or
    ///  - maximum search depth is reached.
    ///
    /// Maximum search depth is needed to prevent the match finder from wasting
    /// too much time in case there are lots of short match candidates. On the
    /// other hand, stopping the search before all candidates have been checked
    /// can reduce compression ratio.
    ///
    /// Setting depth to zero tells liblzma to use an automatic default value,
    /// that depends on the selected match finder and [`LzmaOptions::nice_len`].
    /// The default is in the range [4, 200] or so (it may vary between liblzma
    /// versions).
    ///
    /// Using a bigger depth value than the default can increase compression
    /// ratio in some cases. There is no strict maximum value, but high values
    /// (thousands or millions) should be used with care: the encoder could
    /// remain fast enough with typical input, but malicious input could cause
    /// the match finder to slow down dramatically, possibly creating a denial
    /// of service attack.
    #[inline]
    pub fn depth(&mut self, depth: u32) -> &mut LzmaOptions {
        self.raw.depth = depth;
        self
    }
}

impl Check {
    /// Test if this check is supported in this build of liblzma.
    #[inline]
    pub fn is_supported(&self) -> bool {
        let ret = unsafe { liblzma_sys::lzma_check_is_supported(*self as liblzma_sys::lzma_check) };
        ret != 0
    }
}

impl MatchFinder {
    /// Test if this match finder is supported in this build of liblzma.
    #[inline]
    pub fn is_supported(&self) -> bool {
        let ret =
            unsafe { liblzma_sys::lzma_mf_is_supported(*self as liblzma_sys::lzma_match_finder) };
        ret != 0
    }
}

impl Filters {
    /// Creates a new filter chain with no filters.
    #[inline]
    pub fn new() -> Filters {
        Filters {
            inner: vec![liblzma_sys::lzma_filter {
                id: liblzma_sys::LZMA_VLI_UNKNOWN,
                options: std::ptr::null_mut(),
            }],
            lzma_opts: LinkedList::new(),
        }
    }

    /// Add an LZMA1 filter.
    ///
    /// LZMA1 is the very same thing as what was called just LZMA in LZMA Utils,
    /// 7-Zip, and LZMA SDK. It's called LZMA1 here to prevent developers from
    /// accidentally using LZMA when they actually want LZMA2.
    ///
    /// LZMA1 shouldn't be used for new applications unless you _really_ know
    /// what you are doing.  LZMA2 is almost always a better choice.
    #[inline]
    pub fn lzma1(&mut self, opts: &LzmaOptions) -> &mut Filters {
        self.lzma_opts.push_back(opts.raw);
        let ptr = self.lzma_opts.back().unwrap() as *const _ as *mut _;
        self.push(liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_LZMA1,
            options: ptr,
        })
    }

    /// Add an LZMA1 filter with properties.
    #[inline]
    pub fn lzma1_properties(&mut self, properties: &[u8]) -> Result<&mut Filters, Error> {
        let filter = liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_LZMA1,
            options: std::ptr::null_mut(),
        };
        self.push_with_properties(filter, properties)
    }

    /// Add an LZMA2 filter.
    ///
    /// Usually you want this instead of LZMA1. Compared to LZMA1, LZMA2 adds
    /// support for [`Action::SyncFlush`], uncompressed chunks (smaller expansion when
    /// trying to compress uncompressible data), possibility to change
    /// [`literal_context_bits`]/[`literal_position_bits`]/[`position_bits`] in the
    /// middle of encoding, and some other internal improvements.
    ///
    /// [`literal_context_bits`]: LzmaOptions::literal_context_bits
    /// [`literal_position_bits`]: LzmaOptions::literal_position_bits
    /// [`position_bits`]: LzmaOptions::position_bits
    #[inline]
    pub fn lzma2(&mut self, opts: &LzmaOptions) -> &mut Filters {
        self.lzma_opts.push_back(opts.raw);
        let ptr = self.lzma_opts.back().unwrap() as *const _ as *mut _;
        self.push(liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_LZMA2,
            options: ptr,
        })
    }

    /// Add an LZMA2 filter with properties.
    #[inline]
    pub fn lzma2_properties(&mut self, properties: &[u8]) -> Result<&mut Filters, Error> {
        let filter = liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_LZMA2,
            options: std::ptr::null_mut(),
        };
        self.push_with_properties(filter, properties)
    }

    /// Add a DELTA filter.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let dict_size = 0x40000;
    /// let mut opts = LzmaOptions::new_preset(6).unwrap();
    /// opts.dict_size(dict_size);
    /// let mut filters = Filters::new();
    /// filters.delta();
    /// filters.lzma2(&opts);
    /// ```
    #[inline]
    pub fn delta(&mut self) -> &mut Filters {
        self.push(liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_DELTA,
            options: std::ptr::null_mut(),
        })
    }

    /// Add a DELTA filter with properties.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let mut filters = Filters::new();
    /// filters.delta_properties(&[0x00]).unwrap();
    /// ```
    #[inline]
    pub fn delta_properties(&mut self, properties: &[u8]) -> Result<&mut Filters, Error> {
        let filter = liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_DELTA,
            options: std::ptr::null_mut(),
        };
        self.push_with_properties(filter, properties)
    }

    /// Add a filter for x86 binaries.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let dict_size = 0x40000;
    /// let mut opts = LzmaOptions::new_preset(6).unwrap();
    /// opts.dict_size(dict_size);
    /// let mut filters = Filters::new();
    /// filters.x86();
    /// filters.lzma2(&opts);
    /// ```
    #[inline]
    pub fn x86(&mut self) -> &mut Filters {
        self.push(liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_X86,
            options: std::ptr::null_mut(),
        })
    }

    /// Add a filter for x86 binaries with properties.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let mut filters = Filters::new();
    /// filters.x86_properties(&[0x00, 0x00, 0x00, 0x00]).unwrap();
    /// ```
    #[inline]
    pub fn x86_properties(&mut self, properties: &[u8]) -> Result<&mut Filters, Error> {
        let filter = liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_X86,
            options: std::ptr::null_mut(),
        };
        self.push_with_properties(filter, properties)
    }

    /// Add a filter for PowerPC binaries.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let dict_size = 0x40000;
    /// let mut opts = LzmaOptions::new_preset(6).unwrap();
    /// opts.dict_size(dict_size);
    /// let mut filters = Filters::new();
    /// filters.powerpc();
    /// filters.lzma2(&opts);
    /// ```
    #[inline]
    pub fn powerpc(&mut self) -> &mut Filters {
        self.push(liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_POWERPC,
            options: std::ptr::null_mut(),
        })
    }

    /// Add a filter for PowerPC binaries with properties.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let mut filters = Filters::new();
    /// filters.powerpc_properties(&[0x00, 0x00, 0x00, 0x00]).unwrap();
    /// ```
    #[inline]
    pub fn powerpc_properties(&mut self, properties: &[u8]) -> Result<&mut Filters, Error> {
        let filter = liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_POWERPC,
            options: std::ptr::null_mut(),
        };
        self.push_with_properties(filter, properties)
    }

    /// Add a filter for IA-64 (itanium) binaries.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let dict_size = 0x40000;
    /// let mut opts = LzmaOptions::new_preset(6).unwrap();
    /// opts.dict_size(dict_size);
    /// let mut filters = Filters::new();
    /// filters.ia64();
    /// filters.lzma2(&opts);
    /// ```
    #[inline]
    pub fn ia64(&mut self) -> &mut Filters {
        self.push(liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_IA64,
            options: std::ptr::null_mut(),
        })
    }

    /// Add a filter for IA-64 (itanium) binaries with properties.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let mut filters = Filters::new();
    /// filters.ia64_properties(&[0x00, 0x00, 0x00, 0x00]).unwrap();
    /// ```
    #[inline]
    pub fn ia64_properties(&mut self, properties: &[u8]) -> Result<&mut Filters, Error> {
        let filter = liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_IA64,
            options: std::ptr::null_mut(),
        };
        self.push_with_properties(filter, properties)
    }

    /// Add a filter for ARM binaries.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let dict_size = 0x40000;
    /// let mut opts = LzmaOptions::new_preset(6).unwrap();
    /// opts.dict_size(dict_size);
    /// let mut filters = Filters::new();
    /// filters.arm();
    /// filters.lzma2(&opts);
    /// ```
    #[inline]
    pub fn arm(&mut self) -> &mut Filters {
        self.push(liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_ARM,
            options: std::ptr::null_mut(),
        })
    }

    /// Add a filter for ARM binaries with properties.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let mut filters = Filters::new();
    /// filters.arm_properties(&[0x00, 0x00, 0x00, 0x00]).unwrap();
    /// ```
    #[inline]
    pub fn arm_properties(&mut self, properties: &[u8]) -> Result<&mut Filters, Error> {
        let filter = liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_ARM,
            options: std::ptr::null_mut(),
        };
        self.push_with_properties(filter, properties)
    }

    /// Add a filter for ARM64 binaries.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let dict_size = 0x40000;
    /// let mut opts = LzmaOptions::new_preset(6).unwrap();
    /// opts.dict_size(dict_size);
    /// let mut filters = Filters::new();
    /// filters.arm64();
    /// filters.lzma2(&opts);
    /// ```
    #[inline]
    pub fn arm64(&mut self) -> &mut Filters {
        self.push(liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_ARM64,
            options: std::ptr::null_mut(),
        })
    }

    /// Add a filter for ARM64 binaries with properties.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let mut filters = Filters::new();
    /// filters.arm64_properties(&[0x00, 0x00, 0x00, 0x00]).unwrap();
    /// ```
    #[inline]
    pub fn arm64_properties(&mut self, properties: &[u8]) -> Result<&mut Filters, Error> {
        let filter = liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_ARM64,
            options: std::ptr::null_mut(),
        };
        self.push_with_properties(filter, properties)
    }

    /// Add a filter for RISCV binaries.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let dict_size = 0x40000;
    /// let mut opts = LzmaOptions::new_preset(6).unwrap();
    /// opts.dict_size(dict_size);
    /// let mut filters = Filters::new();
    /// filters.riscv();
    /// filters.lzma2(&opts);
    /// ```
    #[inline]
    pub fn riscv(&mut self) -> &mut Filters {
        self.push(liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_RISCV,
            options: std::ptr::null_mut(),
        })
    }

    /// Add a filter for RISCV binaries with properties.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let mut filters = Filters::new();
    /// filters.riscv_properties(&[0x00, 0x00, 0x00, 0x00]).unwrap();
    /// ```
    #[inline]
    pub fn riscv_properties(&mut self, properties: &[u8]) -> Result<&mut Filters, Error> {
        let filter = liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_RISCV,
            options: std::ptr::null_mut(),
        };
        self.push_with_properties(filter, properties)
    }

    /// Add a filter for ARM-Thumb binaries.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let dict_size = 0x40000;
    /// let mut opts = LzmaOptions::new_preset(6).unwrap();
    /// opts.dict_size(dict_size);
    /// let mut filters = Filters::new();
    /// filters.arm_thumb();
    /// filters.lzma2(&opts);
    /// ```
    #[inline]
    pub fn arm_thumb(&mut self) -> &mut Filters {
        self.push(liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_ARMTHUMB,
            options: std::ptr::null_mut(),
        })
    }

    /// Add a filter for ARM-Thumb binaries with properties.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let mut filters = Filters::new();
    /// filters.arm_thumb_properties(&[0x00, 0x00, 0x00, 0x00]).unwrap();
    /// ```
    #[inline]
    pub fn arm_thumb_properties(&mut self, properties: &[u8]) -> Result<&mut Filters, Error> {
        let filter = liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_ARMTHUMB,
            options: std::ptr::null_mut(),
        };
        self.push_with_properties(filter, properties)
    }

    /// Add a filter for SPARC binaries.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let dict_size = 0x40000;
    /// let mut opts = LzmaOptions::new_preset(6).unwrap();
    /// opts.dict_size(dict_size);
    /// let mut filters = Filters::new();
    /// filters.sparc();
    /// filters.lzma2(&opts);
    /// ```
    #[inline]
    pub fn sparc(&mut self) -> &mut Filters {
        self.push(liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_SPARC,
            options: std::ptr::null_mut(),
        })
    }

    /// Add a filter for SPARC binaries with properties.
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let mut filters = Filters::new();
    /// filters.sparc_properties(&[0x00, 0x00, 0x00, 0x00]).unwrap();
    /// ```
    #[inline]
    pub fn sparc_properties(&mut self, properties: &[u8]) -> Result<&mut Filters, Error> {
        let filter = liblzma_sys::lzma_filter {
            id: liblzma_sys::LZMA_FILTER_SPARC,
            options: std::ptr::null_mut(),
        };
        self.push_with_properties(filter, properties)
    }

    #[inline]
    fn push(&mut self, filter: liblzma_sys::lzma_filter) -> &mut Filters {
        let pos = self.inner.len() - 1;
        self.inner.insert(pos, filter);
        self
    }

    #[inline]
    fn push_with_properties(
        &mut self,
        mut filter: liblzma_sys::lzma_filter,
        properties: &[u8],
    ) -> Result<&mut Filters, Error> {
        cvt(unsafe {
            liblzma_sys::lzma_properties_decode(
                &mut filter,
                std::ptr::null(),
                properties.as_ptr(),
                properties.len(),
            )
        })?;
        let pos = self.inner.len() - 1;
        self.inner.insert(pos, filter);
        Ok(self)
    }

    /// Recommend a Block size for multithreaded encoding
    ///
    /// # Examples
    /// ```
    /// use liblzma::stream::{Filters, LzmaOptions};
    ///
    /// let dict_size = 0x40000;
    /// let mut opts = LzmaOptions::new_preset(6).unwrap();
    /// opts.dict_size(dict_size);
    /// let mut filters = Filters::new();
    /// filters.lzma2(&opts);
    /// assert_eq!(filters.mt_block_size(), 1 << 20);
    /// ```
    #[cfg(feature = "parallel")]
    #[inline]
    pub fn mt_block_size(&self) -> u64 {
        unsafe { liblzma_sys::lzma_mt_block_size(self.inner.as_ptr()) }
    }
}

#[cfg(feature = "parallel")]
impl MtStreamBuilder {
    /// Creates a new blank builder to create a multithreaded encoding [`Stream`].
    #[inline]
    pub fn new() -> Self {
        let mut init = Self {
            raw: unsafe { mem::zeroed() },
            filters: None,
        };
        init.raw.threads = 1;
        init
    }

    /// Configures the number of worker threads to use
    #[inline]
    pub fn threads(&mut self, threads: u32) -> &mut Self {
        self.raw.threads = threads;
        self
    }

    /// Configures the maximum uncompressed size of a block
    ///
    /// The encoder will start a new .xz block every `block_size` bytes.
    /// Using [`Action::FullFlush`] or [`Action::FullBarrier`] with
    /// [`Stream::process`] the caller may tell liblzma to start a new block earlier.
    ///
    /// With LZMA2, a recommended block size is 2-4 times the LZMA2 dictionary
    /// size. With very small dictionaries, it is recommended to use at least 1
    /// MiB block size for good compression ratio, even if this is more than
    /// four times the dictionary size. Note that these are only recommendations
    /// for typical use cases; feel free to use other values. Just keep in mind
    /// that using a block size less than the LZMA2 dictionary size is waste of
    /// RAM.
    ///
    /// Set this to 0 to let liblzma choose the block size depending on the
    /// compression options. For LZMA2 it will be 3*`dict_size` or 1 MiB,
    /// whichever is more.
    ///
    /// For each thread, about 3 * `block_size` bytes of memory will be
    /// allocated. This may change in later liblzma versions. If so, the memory
    /// usage will probably be reduced, not increased.
    #[inline]
    pub fn block_size(&mut self, block_size: u64) -> &mut Self {
        self.raw.block_size = block_size;
        self
    }

    /// Timeout to allow [`Stream::process`] to return early
    ///
    /// Multithreading can make liblzma to consume input and produce output in a
    /// very bursty way: it may first read a lot of input to fill internal
    /// buffers, then no input or output occurs for a while.
    ///
    /// In single-threaded mode, [`Stream::process`] won't return until it has either
    /// consumed all the input or filled the output buffer. If this is done in
    /// multithreaded mode, it may cause a call [`Stream::process`] to take even tens of
    /// seconds, which isn't acceptable in all applications.
    ///
    /// To avoid very long blocking times in [`Stream::process`], a timeout (in
    /// milliseconds) may be set here. If `process would block longer than
    /// this number of milliseconds, it will return with `Ok`. Reasonable
    /// values are 100 ms or more. The xz command line tool uses 300 ms.
    ///
    /// If long blocking times are fine for you, set timeout to a special
    /// value of 0, which will disable the timeout mechanism and will make
    /// [`Stream::process`] block until all the input is consumed or the output
    /// buffer has been filled.
    #[inline]
    pub fn timeout_ms(&mut self, timeout: u32) -> &mut Self {
        self.raw.timeout = timeout;
        self
    }

    /// Compression preset (level and possible flags)
    ///
    /// The preset is set just like with [`Stream::new_easy_encoder`], including
    /// support for [`PRESET_EXTREME`]. The preset is ignored if filters below
    /// have been specified.
    #[inline]
    pub fn preset(&mut self, preset: u32) -> &mut Self {
        self.raw.preset = preset;
        self
    }

    /// Configure a custom filter chain
    #[inline]
    pub fn filters(&mut self, filters: Filters) -> &mut Self {
        self.raw.filters = filters.inner.as_ptr();
        self.filters = Some(filters);
        self
    }

    /// Configures the integrity check type
    #[inline]
    pub fn check(&mut self, check: Check) -> &mut Self {
        self.raw.check = check as liblzma_sys::lzma_check;
        self
    }

    /// Memory usage limit to reduce the number of threads
    #[inline]
    pub fn memlimit_threading(&mut self, memlimit: u64) -> &mut Self {
        self.raw.memlimit_threading = memlimit;
        self
    }

    /// Memory usage limit that should never be exceeded
    #[inline]
    pub fn memlimit_stop(&mut self, memlimit: u64) -> &mut Self {
        self.raw.memlimit_stop = memlimit;
        self
    }

    /// Calculate approximate memory usage of multithreaded .xz encoder
    #[inline]
    pub fn memusage(&self) -> u64 {
        unsafe { liblzma_sys::lzma_stream_encoder_mt_memusage(&self.raw) }
    }

    /// Initialize multithreaded .xz stream encoder.
    #[inline]
    pub fn encoder(&self) -> Result<Stream, Error> {
        let mut init = unsafe { Stream::zeroed() };
        cvt(unsafe { liblzma_sys::lzma_stream_encoder_mt(&mut init.raw, &self.raw) })?;
        Ok(init)
    }

    /// Initialize multithreaded .xz stream decoder.
    #[inline]
    pub fn decoder(&self) -> Result<Stream, Error> {
        let mut init = unsafe { Stream::zeroed() };
        cvt(unsafe { liblzma_sys::lzma_stream_decoder_mt(&mut init.raw, &self.raw) })?;
        Ok(init)
    }
}

fn cvt(rc: liblzma_sys::lzma_ret) -> Result<Status, Error> {
    match rc {
        liblzma_sys::LZMA_OK => Ok(Status::Ok),
        liblzma_sys::LZMA_STREAM_END => Ok(Status::StreamEnd),
        liblzma_sys::LZMA_NO_CHECK => Err(Error::NoCheck),
        liblzma_sys::LZMA_UNSUPPORTED_CHECK => Err(Error::UnsupportedCheck),
        liblzma_sys::LZMA_GET_CHECK => Ok(Status::GetCheck),
        liblzma_sys::LZMA_MEM_ERROR => Err(Error::Mem),
        liblzma_sys::LZMA_MEMLIMIT_ERROR => Err(Error::MemLimit),
        liblzma_sys::LZMA_FORMAT_ERROR => Err(Error::Format),
        liblzma_sys::LZMA_OPTIONS_ERROR => Err(Error::Options),
        liblzma_sys::LZMA_DATA_ERROR => Err(Error::Data),
        liblzma_sys::LZMA_BUF_ERROR => Ok(Status::MemNeeded),
        liblzma_sys::LZMA_PROG_ERROR => Err(Error::Program),
        c => panic!("unknown return code: {}", c),
    }
}

impl From<Error> for io::Error {
    #[inline]
    fn from(e: Error) -> io::Error {
        let kind = match e {
            Error::Data => io::ErrorKind::InvalidData,
            Error::Options => io::ErrorKind::InvalidInput,
            Error::Format => io::ErrorKind::InvalidData,
            Error::MemLimit => io::ErrorKind::Other,
            Error::Mem => io::ErrorKind::Other,
            Error::Program => io::ErrorKind::Other,
            Error::NoCheck => io::ErrorKind::InvalidInput,
            Error::UnsupportedCheck => io::ErrorKind::Other,
        };

        io::Error::new(kind, e)
    }
}

impl error::Error for Error {}

impl fmt::Display for Error {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Error::Data => "lzma data error",
            Error::Options => "invalid options",
            Error::Format => "stream/file format not recognized",
            Error::MemLimit => "memory limit reached",
            Error::Mem => "can't allocate memory",
            Error::Program => "liblzma internal error",
            Error::NoCheck => "no integrity check was available",
            Error::UnsupportedCheck => "liblzma not built with check support",
        }
        .fmt(f)
    }
}

impl Drop for Stream {
    #[inline]
    fn drop(&mut self) {
        unsafe {
            liblzma_sys::lzma_end(&mut self.raw);
        }
    }
}
