/// <summary>
        /// Create multi-threaded compressing XZStream. Requires more memory than single-threaded mode.
        /// </summary>
        public unsafe XZStream(Stream baseStream, XZCompressOptions compOpts, XZThreadedCompressOptions threadOpts)
        {
            XZInit.Manager.EnsureLoaded();

            BaseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
            _mode      = Mode.Compress;
            _disposed  = false;

            // Check and set XZStreamOptions
            _leaveOpen  = compOpts.LeaveOpen;
            _bufferSize = CheckBufferSize(compOpts.BufferSize);
            _workBuf    = new byte[_bufferSize];

            // Prepare LzmaStream and buffers
            _lzmaStream    = new LzmaStream();
            _lzmaStreamPin = GCHandle.Alloc(_lzmaStream, GCHandleType.Pinned);

            // Check LzmaMt instance
            LzmaMt mt = compOpts.ToLzmaMt(threadOpts);

            CheckPreset(mt.Preset);

            // Initialize the encoder
            LzmaRet ret = XZInit.Lib.LzmaStreamEncoderMt(_lzmaStream, mt);

            XZException.CheckReturnValue(ret);

            // Set possible max memory usage.
            MaxMemUsage = XZInit.Lib.LzmaStreamEncoderMtMemUsage(mt);
        }
        /// <summary>
        /// Create single-threaded compressing XZStream.
        /// </summary>
        public unsafe XZStream(Stream baseStream, XZCompressOptions compOpts)
        {
            XZInit.Manager.EnsureLoaded();

            BaseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
            _mode      = Mode.Compress;
            _disposed  = false;

            // Check and set compress options
            _leaveOpen  = compOpts.LeaveOpen;
            _bufferSize = CheckBufferSize(compOpts.BufferSize);
            _workBuf    = new byte[_bufferSize];

            // Prepare LzmaStream
            _lzmaStream    = new LzmaStream();
            _lzmaStreamPin = GCHandle.Alloc(_lzmaStream, GCHandleType.Pinned);

            // Check preset
            uint preset = compOpts.Preset;

            CheckPreset(preset);

            // Initialize the encoder
            LzmaRet ret = XZInit.Lib.LzmaEasyEncoder(_lzmaStream, preset, compOpts.Check);

            XZException.CheckReturnValue(ret);

            // Set possible max memory usage.
            MaxMemUsage = XZInit.Lib.LzmaEasyEncoderMemUsage(preset);
        }
        public unsafe void Write(ReadOnlySpan <byte> span)
#endif
        { // For Compress
            if (_mode != Mode.Compress)
            {
                throw new NotSupportedException("Write() not supported on decompression");
            }

            TotalIn += span.Length;

            fixed(byte *readPtr = span)
            fixed(byte *writePtr = _workBuf)
            {
                _lzmaStream.NextIn   = readPtr;
                _lzmaStream.AvailIn  = (uint)span.Length;
                _lzmaStream.NextOut  = writePtr + _workBufPos;
                _lzmaStream.AvailOut = (uint)(_workBuf.Length - _workBufPos);

                // Return condition : _lzmaStream.AvailIn == 0
                while (_lzmaStream.AvailIn != 0)
                {
                    LzmaRet ret = XZInit.Lib.LzmaCode(_lzmaStream, LzmaAction.Run);
                    _workBufPos = (int)((ulong)_workBuf.Length - _lzmaStream.AvailOut);

                    // If the output buffer is full, write the data from the output bufffer to the output file.
                    if (_lzmaStream.AvailOut == 0)
                    {
                        // Write to _baseStream
                        BaseStream.Write(_workBuf, 0, _workBuf.Length);
                        TotalOut += _workBuf.Length;

                        // Reset NextOut and AvailOut
                        _workBufPos          = 0;
                        _lzmaStream.NextOut  = writePtr;
                        _lzmaStream.AvailOut = (uint)_workBuf.Length;
                    }

                    // Normally the return value of lzma_code() will be LZMA_OK until everything has been encoded.
                    XZException.CheckReturnValue(ret);
                }
            }
        }
        private unsafe void FinishWrite()
        {
            Debug.Assert(_mode == Mode.Compress, "FinishWrite() must not be called in decompression");

            fixed(byte *writePtr = _workBuf)
            {
                _lzmaStream.NextIn   = (byte *)0;
                _lzmaStream.AvailIn  = 0;
                _lzmaStream.NextOut  = writePtr + _workBufPos;
                _lzmaStream.AvailOut = (uint)(_workBuf.Length - _workBufPos);

                LzmaRet ret = LzmaRet.Ok;

                while (ret != LzmaRet.StreamEnd)
                {
                    ulong bakAvailOut = _lzmaStream.AvailOut;
                    ret         = XZInit.Lib.LzmaCode(_lzmaStream, LzmaAction.Finish);
                    _workBufPos = (int)(bakAvailOut - _lzmaStream.AvailOut);

                    // If the compression finished successfully,
                    // write the data from the output buffer to the output file.
                    if (_lzmaStream.AvailOut == 0 || ret == LzmaRet.StreamEnd)
                    { // Write to _baseStream
                        // When lzma_code() has returned LZMA_STREAM_END, the output buffer is likely to be only partially
                        // full. Calculate how much new data there is to be written to the output file.
                        BaseStream.Write(_workBuf, 0, _workBufPos);
                        TotalOut += _workBufPos;

                        // Reset NextOut and AvailOut
                        _workBufPos          = 0;
                        _lzmaStream.NextOut  = writePtr;
                        _lzmaStream.AvailOut = (uint)_workBuf.Length;
                    }
                    else
                    { // Once everything has been encoded successfully, the return value of lzma_code() will be LZMA_STREAM_END.
                        XZException.CheckReturnValue(ret);
                    }
                }
            }
        }
        /// <summary>
        /// Create decompressing XZStream
        /// </summary>
        public unsafe XZStream(Stream baseStream, XZDecompressOptions decompOpts)
        {
            XZInit.Manager.EnsureLoaded();

            BaseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
            _mode      = Mode.Decompress;
            _disposed  = false;

            // Check and set decompress options
            _leaveOpen  = decompOpts.LeaveOpen;
            _bufferSize = CheckBufferSize(decompOpts.BufferSize);
            _workBuf    = new byte[_bufferSize];

            // Prepare LzmaStream and buffers
            _lzmaStream    = new LzmaStream();
            _lzmaStreamPin = GCHandle.Alloc(_lzmaStream, GCHandleType.Pinned);

            // Initialize the decoder
            LzmaRet ret = XZInit.Lib.LzmaStreamDecoder(_lzmaStream, decompOpts.MemLimit, decompOpts.DecodeFlags);

            XZException.CheckReturnValue(ret);
        }
        public unsafe int Read(Span <byte> span)
#endif
        { // For Decompress
            if (_mode != Mode.Decompress)
            {
                throw new NotSupportedException("Read() not supported on compression");
            }
            if (_workBufPos == ReadDone)
            {
                return(0);
            }

            int        readSize = 0;
            LzmaAction action   = LzmaAction.Run;

            fixed(byte *readPtr = _workBuf)
            fixed(byte *writePtr = span)
            {
                _lzmaStream.NextIn   = readPtr + _workBufPos;
                _lzmaStream.NextOut  = writePtr;
                _lzmaStream.AvailOut = (uint)span.Length;

                while (_lzmaStream.AvailOut != 0)
                {
                    if (_lzmaStream.AvailIn == 0)
                    {
                        // Read from _baseStream
                        int baseReadSize = BaseStream.Read(_workBuf, 0, _workBuf.Length);
                        TotalIn += baseReadSize;

                        _workBufPos         = 0;
                        _lzmaStream.NextIn  = readPtr;
                        _lzmaStream.AvailIn = (uint)baseReadSize;

                        if (baseReadSize == 0) // End of stream
                        {
                            action = LzmaAction.Finish;
                        }
                    }

                    ulong bakAvailIn  = _lzmaStream.AvailIn;
                    ulong bakAvailOut = _lzmaStream.AvailOut;

                    LzmaRet ret = XZInit.Lib.LzmaCode(_lzmaStream, action);

                    _workBufPos += (int)(bakAvailIn - _lzmaStream.AvailIn);
                    readSize    += (int)(bakAvailOut - _lzmaStream.AvailOut);

                    // Once everything has been decoded successfully, the return value of lzma_code() will be LZMA_STREAM_END.
                    if (ret == LzmaRet.StreamEnd)
                    {
                        _workBufPos = ReadDone;
                        break;
                    }

                    // Normally the return value of lzma_code() will be LZMA_OK until everything has been encoded.
                    XZException.CheckReturnValue(ret);
                }
            }

            TotalOut += readSize;
            return(readSize);
        }