/**********************************************************************
 * Project:  CPL - Common Portability Library
 * Purpose:  Registry of compression/decompression functions
 * Author:   Even Rouault <even.rouault at spatialys.com>
 *
 **********************************************************************
 * Copyright (c) 2021, Even Rouault <even.rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "cpl_compressor.h"
#include "cpl_error.h"
#include "cpl_multiproc.h"
#include "cpl_string.h"
#include "cpl_conv.h"  // CPLZLibInflate()

#ifdef HAVE_BLOSC
#include <blosc.h>
#endif

#ifdef HAVE_LIBDEFLATE
#include "libdeflate.h"
#else
#include "zlib.h"
#endif

#ifdef HAVE_LZMA
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdocumentation"
#endif
#include <lzma.h>
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#endif

#ifdef HAVE_ZSTD
#include <zstd.h>
#endif

#ifdef HAVE_LZ4
#include <lz4.h>
#endif

#include <limits>
#include <mutex>
#include <type_traits>
#include <vector>

static std::mutex gMutex;
static std::vector<CPLCompressor *> *gpCompressors = nullptr;
static std::vector<CPLCompressor *> *gpDecompressors = nullptr;

#ifdef HAVE_BLOSC
static bool CPLBloscCompressor(const void *input_data, size_t input_size,
                               void **output_data, size_t *output_size,
                               CSLConstList options,
                               void * /* compressor_user_data */)
{
    if (output_data != nullptr && *output_data != nullptr &&
        output_size != nullptr && *output_size != 0)
    {
        const int clevel = atoi(CSLFetchNameValueDef(options, "CLEVEL", "5"));
        const char *pszShuffle =
            CSLFetchNameValueDef(options, "SHUFFLE", "BYTE");
        const int shuffle =
            (EQUAL(pszShuffle, "BYTE") || EQUAL(pszShuffle, "1"))
                ? BLOSC_SHUFFLE
            : (EQUAL(pszShuffle, "BIT") || EQUAL(pszShuffle, "2"))
                ? BLOSC_BITSHUFFLE
                : BLOSC_NOSHUFFLE;
        const int typesize =
            atoi(CSLFetchNameValueDef(options, "TYPESIZE", "1"));
        const char *compressor =
            CSLFetchNameValueDef(options, "CNAME", BLOSC_LZ4_COMPNAME);
        const int blocksize =
            atoi(CSLFetchNameValueDef(options, "BLOCKSIZE", "0"));
        if (blocksize < 0)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid BLOCKSIZE");
            return false;
        }
        const char *pszNumThreads =
            CSLFetchNameValueDef(options, "NUM_THREADS", "1");
        const int numthreads = EQUAL(pszNumThreads, "ALL_CPUS")
                                   ? CPLGetNumCPUs()
                                   : atoi(pszNumThreads);
        int ret = blosc_compress_ctx(clevel, shuffle, typesize, input_size,
                                     input_data, *output_data, *output_size,
                                     compressor, blocksize, numthreads);
        if (ret < 0)
        {
            *output_size = 0;
            return false;
        }
        if (ret == 0)
        {
            *output_size = input_size + BLOSC_MAX_OVERHEAD;
            return false;
        }
        *output_size = ret;
        return true;
    }

    if (output_data == nullptr && output_size != nullptr)
    {
        *output_size = input_size + BLOSC_MAX_OVERHEAD;
        return true;
    }

    if (output_data != nullptr && *output_data == nullptr &&
        output_size != nullptr)
    {
        size_t nSafeSize = input_size + BLOSC_MAX_OVERHEAD;
        *output_data = VSI_MALLOC_VERBOSE(nSafeSize);
        *output_size = nSafeSize;
        if (*output_data == nullptr)
            return false;
        bool ret = CPLBloscCompressor(input_data, input_size, output_data,
                                      output_size, options, nullptr);
        if (!ret)
        {
            VSIFree(*output_data);
            *output_data = nullptr;
        }
        return ret;
    }

    CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
    return false;
}

static bool CPLBloscDecompressor(const void *input_data, size_t input_size,
                                 void **output_data, size_t *output_size,
                                 CSLConstList options,
                                 void * /* compressor_user_data */)
{
    size_t nSafeSize = 0;
    if (blosc_cbuffer_validate(input_data, input_size, &nSafeSize) < 0)
    {
        *output_size = 0;
        return false;
    }

    if (output_data != nullptr && *output_data != nullptr &&
        output_size != nullptr && *output_size != 0)
    {
        if (nSafeSize < *output_size)
        {
            *output_size = nSafeSize;
            return false;
        }

        const char *pszNumThreads =
            CSLFetchNameValueDef(options, "NUM_THREADS", "1");
        const int numthreads = EQUAL(pszNumThreads, "ALL_CPUS")
                                   ? CPLGetNumCPUs()
                                   : atoi(pszNumThreads);
        if (blosc_decompress_ctx(input_data, *output_data, *output_size,
                                 numthreads) <= 0)
        {
            *output_size = 0;
            return false;
        }

        *output_size = nSafeSize;
        return true;
    }

    if (output_data == nullptr && output_size != nullptr)
    {
        *output_size = nSafeSize;
        return true;
    }

    if (output_data != nullptr && *output_data == nullptr &&
        output_size != nullptr)
    {
        *output_data = VSI_MALLOC_VERBOSE(nSafeSize);
        *output_size = nSafeSize;
        if (*output_data == nullptr)
            return false;
        bool ret = CPLBloscDecompressor(input_data, input_size, output_data,
                                        output_size, options, nullptr);
        if (!ret)
        {
            VSIFree(*output_data);
            *output_data = nullptr;
        }
        return ret;
    }

    CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
    return false;
}

#endif

#ifdef HAVE_LZMA
static bool CPLLZMACompressor(const void *input_data, size_t input_size,
                              void **output_data, size_t *output_size,
                              CSLConstList options,
                              void * /* compressor_user_data */)
{
    if (output_data != nullptr && *output_data != nullptr &&
        output_size != nullptr && *output_size != 0)
    {
        const int preset = atoi(CSLFetchNameValueDef(options, "PRESET", "6"));
        const int delta = atoi(CSLFetchNameValueDef(options, "DELTA", "1"));

        lzma_filter filters[3];
        lzma_options_delta opt_delta;
        lzma_options_lzma opt_lzma;

        opt_delta.type = LZMA_DELTA_TYPE_BYTE;
        opt_delta.dist = delta;
        filters[0].id = LZMA_FILTER_DELTA;
        filters[0].options = &opt_delta;

        lzma_lzma_preset(&opt_lzma, preset);
        filters[1].id = LZMA_FILTER_LZMA2;
        filters[1].options = &opt_lzma;

        filters[2].id = LZMA_VLI_UNKNOWN;
        filters[2].options = nullptr;

        size_t out_pos = 0;
        lzma_ret ret = lzma_stream_buffer_encode(
            filters, LZMA_CHECK_NONE,
            nullptr,  // allocator,
            static_cast<const uint8_t *>(input_data), input_size,
            static_cast<uint8_t *>(*output_data), &out_pos, *output_size);
        if (ret != LZMA_OK)
        {
            *output_size = 0;
            return false;
        }
        *output_size = out_pos;
        return true;
    }

    if (output_data == nullptr && output_size != nullptr)
    {
        *output_size = lzma_stream_buffer_bound(input_size);
        return true;
    }

    if (output_data != nullptr && *output_data == nullptr &&
        output_size != nullptr)
    {
        size_t nSafeSize = lzma_stream_buffer_bound(input_size);
        *output_data = VSI_MALLOC_VERBOSE(nSafeSize);
        *output_size = nSafeSize;
        if (*output_data == nullptr)
            return false;
        bool ret = CPLLZMACompressor(input_data, input_size, output_data,
                                     output_size, options, nullptr);
        if (!ret)
        {
            VSIFree(*output_data);
            *output_data = nullptr;
        }
        return ret;
    }

    CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
    return false;
}

static bool CPLLZMADecompressor(const void *input_data, size_t input_size,
                                void **output_data, size_t *output_size,
                                CSLConstList options,
                                void * /* compressor_user_data */)
{
    if (output_data != nullptr && *output_data != nullptr &&
        output_size != nullptr && *output_size != 0)
    {
        size_t in_pos = 0;
        size_t out_pos = 0;
        uint64_t memlimit = 100 * 1024 * 1024;
        lzma_ret ret = lzma_stream_buffer_decode(
            &memlimit,
            0,        // flags
            nullptr,  // allocator,
            static_cast<const uint8_t *>(input_data), &in_pos, input_size,
            static_cast<uint8_t *>(*output_data), &out_pos, *output_size);
        if (ret != LZMA_OK)
        {
            *output_size = 0;
            return false;
        }
        *output_size = out_pos;
        return true;
    }

    if (output_data == nullptr && output_size != nullptr)
    {
        // inefficient !
        void *tmpBuffer = nullptr;
        bool ret = CPLLZMADecompressor(input_data, input_size, &tmpBuffer,
                                       output_size, options, nullptr);
        VSIFree(tmpBuffer);
        return ret;
    }

    if (output_data != nullptr && *output_data == nullptr &&
        output_size != nullptr)
    {
        size_t nOutSize = input_size < std::numeric_limits<size_t>::max() / 2
                              ? input_size * 2
                              : input_size;
        *output_data = VSI_MALLOC_VERBOSE(nOutSize);
        if (*output_data == nullptr)
        {
            *output_size = 0;
            return false;
        }

        while (true)
        {
            size_t in_pos = 0;
            size_t out_pos = 0;
            uint64_t memlimit = 100 * 1024 * 1024;
            lzma_ret ret = lzma_stream_buffer_decode(
                &memlimit,
                0,        // flags
                nullptr,  // allocator,
                static_cast<const uint8_t *>(input_data), &in_pos, input_size,
                static_cast<uint8_t *>(*output_data), &out_pos, nOutSize);
            if (ret == LZMA_OK)
            {
                *output_size = out_pos;
                return true;
            }
            else if (ret == LZMA_BUF_ERROR &&
                     nOutSize < std::numeric_limits<size_t>::max() / 2)
            {
                nOutSize *= 2;
                void *tmpBuffer = VSI_REALLOC_VERBOSE(*output_data, nOutSize);
                if (tmpBuffer == nullptr)
                {
                    VSIFree(*output_data);
                    *output_data = nullptr;
                    *output_size = 0;
                    return false;
                }
                *output_data = tmpBuffer;
            }
            else
            {
                VSIFree(*output_data);
                *output_data = nullptr;
                *output_size = 0;
                return false;
            }
        }
    }

    CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
    return false;
}

#endif  // HAVE_LZMA

#ifdef HAVE_ZSTD
static bool CPLZSTDCompressor(const void *input_data, size_t input_size,
                              void **output_data, size_t *output_size,
                              CSLConstList options,
                              void * /* compressor_user_data */)
{
    if (output_data != nullptr && *output_data != nullptr &&
        output_size != nullptr && *output_size != 0)
    {
        const int level = atoi(CSLFetchNameValueDef(options, "LEVEL", "13"));
        ZSTD_CCtx *ctx = ZSTD_createCCtx();
        if (ctx == nullptr)
        {
            *output_size = 0;
            return false;
        }

        size_t ret = ZSTD_compressCCtx(ctx, *output_data, *output_size,
                                       input_data, input_size, level);
        ZSTD_freeCCtx(ctx);
        if (ZSTD_isError(ret))
        {
            *output_size = 0;
            return false;
        }

        *output_size = ret;
        return true;
    }

    if (output_data == nullptr && output_size != nullptr)
    {
        *output_size = ZSTD_compressBound(input_size);
        return true;
    }

    if (output_data != nullptr && *output_data == nullptr &&
        output_size != nullptr)
    {
        size_t nSafeSize = ZSTD_compressBound(input_size);
        *output_data = VSI_MALLOC_VERBOSE(nSafeSize);
        *output_size = nSafeSize;
        if (*output_data == nullptr)
            return false;
        bool ret = CPLZSTDCompressor(input_data, input_size, output_data,
                                     output_size, options, nullptr);
        if (!ret)
        {
            VSIFree(*output_data);
            *output_data = nullptr;
        }
        return ret;
    }

    CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
    return false;
}

static bool CPLZSTDDecompressor(const void *input_data, size_t input_size,
                                void **output_data, size_t *output_size,
                                CSLConstList /* options */,
                                void * /* compressor_user_data */)
{
    if (output_data != nullptr && *output_data != nullptr &&
        output_size != nullptr && *output_size != 0)
    {
        size_t ret =
            ZSTD_decompress(*output_data, *output_size, input_data, input_size);
        if (ZSTD_isError(ret))
        {
            *output_size = static_cast<size_t>(
                ZSTD_getDecompressedSize(input_data, input_size));
            return false;
        }

        *output_size = ret;
        return true;
    }

    if (output_data == nullptr && output_size != nullptr)
    {
        *output_size = static_cast<size_t>(
            ZSTD_getDecompressedSize(input_data, input_size));
        return *output_size != 0;
    }

    if (output_data != nullptr && *output_data == nullptr &&
        output_size != nullptr)
    {
        size_t nOutSize = static_cast<size_t>(
            ZSTD_getDecompressedSize(input_data, input_size));
        *output_data = VSI_MALLOC_VERBOSE(nOutSize);
        if (*output_data == nullptr)
        {
            *output_size = 0;
            return false;
        }

        size_t ret =
            ZSTD_decompress(*output_data, nOutSize, input_data, input_size);
        if (ZSTD_isError(ret))
        {
            *output_size = 0;
            VSIFree(*output_data);
            *output_data = nullptr;
            return false;
        }

        *output_size = ret;
        return true;
    }

    CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
    return false;
}

#endif  // HAVE_ZSTD

#ifdef HAVE_LZ4
static bool CPLLZ4Compressor(const void *input_data, size_t input_size,
                             void **output_data, size_t *output_size,
                             CSLConstList options,
                             void * /* compressor_user_data */)
{
    if (input_size > static_cast<size_t>(std::numeric_limits<int>::max()))
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Too large input buffer. "
                 "Max supported is INT_MAX");
        *output_size = 0;
        return false;
    }

    const bool bHeader =
        CPLTestBool(CSLFetchNameValueDef(options, "HEADER", "YES"));
    const int header_size = bHeader ? static_cast<int>(sizeof(int32_t)) : 0;

    if (output_data != nullptr && *output_data != nullptr &&
        output_size != nullptr && *output_size != 0)
    {
        const int acceleration =
            atoi(CSLFetchNameValueDef(options, "ACCELERATION", "1"));
        if (*output_size >
            static_cast<size_t>(std::numeric_limits<int>::max() - 4))
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Too large output buffer. "
                     "Max supported is INT_MAX");
            *output_size = 0;
            return false;
        }

        if (bHeader && static_cast<int>(*output_size) < header_size)
        {
            *output_size = 0;
            return false;
        }

        int ret = LZ4_compress_fast(
            static_cast<const char *>(input_data),
            static_cast<char *>(*output_data) + header_size,
            static_cast<int>(input_size),
            static_cast<int>(*output_size) - header_size, acceleration);
        if (ret <= 0 || ret > std::numeric_limits<int>::max() - header_size)
        {
            *output_size = 0;
            return false;
        }

        int32_t sizeLSB = CPL_LSBWORD32(static_cast<int>(input_size));
        memcpy(*output_data, &sizeLSB, sizeof(sizeLSB));

        *output_size = static_cast<size_t>(header_size + ret);
        return true;
    }

    if (output_data == nullptr && output_size != nullptr)
    {
        *output_size = static_cast<size_t>(header_size) +
                       LZ4_compressBound(static_cast<int>(input_size));
        return true;
    }

    if (output_data != nullptr && *output_data == nullptr &&
        output_size != nullptr)
    {
        size_t nSafeSize = static_cast<size_t>(header_size) +
                           LZ4_compressBound(static_cast<int>(input_size));
        *output_data = VSI_MALLOC_VERBOSE(nSafeSize);
        *output_size = nSafeSize;
        if (*output_data == nullptr)
            return false;
        bool ret = CPLLZ4Compressor(input_data, input_size, output_data,
                                    output_size, options, nullptr);
        if (!ret)
        {
            VSIFree(*output_data);
            *output_data = nullptr;
        }
        return ret;
    }

    CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
    return false;
}

static bool CPLLZ4Decompressor(const void *input_data, size_t input_size,
                               void **output_data, size_t *output_size,
                               CSLConstList options,
                               void * /* compressor_user_data */)
{
    if (input_size > static_cast<size_t>(std::numeric_limits<int>::max()))
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Too large input buffer. "
                 "Max supported is INT_MAX");
        *output_size = 0;
        return false;
    }

    const bool bHeader =
        CPLTestBool(CSLFetchNameValueDef(options, "HEADER", "YES"));
    const int header_size = bHeader ? static_cast<int>(sizeof(int32_t)) : 0;
    if (bHeader && static_cast<int>(input_size) < header_size)
    {
        *output_size = 0;
        return false;
    }

    if (output_data != nullptr && *output_data != nullptr &&
        output_size != nullptr && *output_size != 0)
    {
        if (*output_size > static_cast<size_t>(std::numeric_limits<int>::max()))
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Too large output buffer. "
                     "Max supported is INT_MAX");
            *output_size = 0;
            return false;
        }

        int ret = LZ4_decompress_safe(
            static_cast<const char *>(input_data) + header_size,
            static_cast<char *>(*output_data),
            static_cast<int>(input_size) - header_size,
            static_cast<int>(*output_size));
        if (ret <= 0)
        {
            *output_size = 0;
            return false;
        }

        *output_size = ret;
        return true;
    }

    if (output_data == nullptr && output_size != nullptr)
    {
        if (bHeader)
        {
            int nSize = CPL_LSBSINT32PTR(input_data);
            if (nSize < 0)
            {
                *output_size = 0;
                return false;
            }
            *output_size = nSize;
            return true;
        }

        // inefficient !
        void *tmpBuffer = nullptr;
        bool ret = CPLLZ4Decompressor(input_data, input_size, &tmpBuffer,
                                      output_size, options, nullptr);
        VSIFree(tmpBuffer);
        return ret;
    }

    if (output_data != nullptr && *output_data == nullptr &&
        output_size != nullptr)
    {
        if (bHeader)
        {
            int nSize = CPL_LSBSINT32PTR(input_data);
            if (nSize <= 0)
            {
                *output_size = 0;
                return false;
            }
            if (nSize > INT_MAX - 1 || /* to make Coverity scan happy */
                nSize / 10000 > static_cast<int>(input_size))
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Stored uncompressed size (%d) is much larger "
                         "than compressed size (%d)",
                         nSize, static_cast<int>(input_size));
                *output_size = nSize;
                return false;
            }
            *output_data = VSI_MALLOC_VERBOSE(nSize);
            *output_size = nSize;
            if (*output_data == nullptr)
            {
                return false;
            }
            if (!CPLLZ4Decompressor(input_data, input_size, output_data,
                                    output_size, options, nullptr))
            {
                VSIFree(*output_data);
                *output_data = nullptr;
                *output_size = 0;
                return false;
            }
            return true;
        }

        size_t nOutSize =
            static_cast<int>(input_size) < std::numeric_limits<int>::max() / 2
                ? input_size * 2
                : static_cast<size_t>(std::numeric_limits<int>::max());
        *output_data = VSI_MALLOC_VERBOSE(nOutSize);
        if (*output_data == nullptr)
        {
            *output_size = 0;
            return false;
        }

        while (true)
        {
            int ret = LZ4_decompress_safe_partial(
                static_cast<const char *>(input_data),
                static_cast<char *>(*output_data), static_cast<int>(input_size),
                static_cast<int>(nOutSize), static_cast<int>(nOutSize));
            if (ret <= 0)
            {
                VSIFree(*output_data);
                *output_data = nullptr;
                *output_size = 0;
                return false;
            }
            else if (ret < static_cast<int>(nOutSize))
            {
                *output_size = ret;
                return true;
            }
            else if (static_cast<int>(nOutSize) <
                     std::numeric_limits<int>::max() / 2)
            {
                nOutSize *= 2;
                void *tmpBuffer = VSI_REALLOC_VERBOSE(*output_data, nOutSize);
                if (tmpBuffer == nullptr)
                {
                    VSIFree(*output_data);
                    *output_data = nullptr;
                    *output_size = 0;
                    return false;
                }
                *output_data = tmpBuffer;
            }
            else
            {
                VSIFree(*output_data);
                *output_data = nullptr;
                *output_size = 0;
                return false;
            }
        }
    }

    CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
    return false;
}

#endif  // HAVE_LZ4

static void *CPLGZipCompress(const void *ptr, size_t nBytes, int nLevel,
                             void *outptr, size_t nOutAvailableBytes,
                             size_t *pnOutBytes)
{
    if (pnOutBytes != nullptr)
        *pnOutBytes = 0;

    size_t nTmpSize = 0;
    void *pTmp;
#ifdef HAVE_LIBDEFLATE
    struct libdeflate_compressor *enc =
        libdeflate_alloc_compressor(nLevel < 0 ? 7 : nLevel);
    if (enc == nullptr)
    {
        return nullptr;
    }
#endif
    if (outptr == nullptr)
    {
#ifdef HAVE_LIBDEFLATE
        nTmpSize = libdeflate_gzip_compress_bound(enc, nBytes);
#else
        nTmpSize = 32 + nBytes * 2;
#endif
        pTmp = VSIMalloc(nTmpSize);
        if (pTmp == nullptr)
        {
#ifdef HAVE_LIBDEFLATE
            libdeflate_free_compressor(enc);
#endif
            return nullptr;
        }
    }
    else
    {
        pTmp = outptr;
        nTmpSize = nOutAvailableBytes;
    }

#ifdef HAVE_LIBDEFLATE
    size_t nCompressedBytes =
        libdeflate_gzip_compress(enc, ptr, nBytes, pTmp, nTmpSize);
    libdeflate_free_compressor(enc);
    if (nCompressedBytes == 0)
    {
        if (pTmp != outptr)
            VSIFree(pTmp);
        return nullptr;
    }
    if (pnOutBytes != nullptr)
        *pnOutBytes = nCompressedBytes;
#else
    z_stream strm;
    strm.zalloc = nullptr;
    strm.zfree = nullptr;
    strm.opaque = nullptr;
    constexpr int windowsBits = 15;
    constexpr int gzipEncoding = 16;
    int ret = deflateInit2(&strm, nLevel < 0 ? Z_DEFAULT_COMPRESSION : nLevel,
                           Z_DEFLATED, windowsBits + gzipEncoding, 8,
                           Z_DEFAULT_STRATEGY);
    if (ret != Z_OK)
    {
        if (pTmp != outptr)
            VSIFree(pTmp);
        return nullptr;
    }

    strm.avail_in = static_cast<uInt>(nBytes);
    strm.next_in = reinterpret_cast<Bytef *>(const_cast<void *>(ptr));
    strm.avail_out = static_cast<uInt>(nTmpSize);
    strm.next_out = reinterpret_cast<Bytef *>(pTmp);
    ret = deflate(&strm, Z_FINISH);
    if (ret != Z_STREAM_END)
    {
        if (pTmp != outptr)
            VSIFree(pTmp);
        return nullptr;
    }
    if (pnOutBytes != nullptr)
        *pnOutBytes = nTmpSize - strm.avail_out;
    deflateEnd(&strm);
#endif

    return pTmp;
}

static bool CPLZlibCompressor(const void *input_data, size_t input_size,
                              void **output_data, size_t *output_size,
                              CSLConstList options, void *compressor_user_data)
{
    const char *alg = static_cast<const char *>(compressor_user_data);
    const auto pfnCompress =
        strcmp(alg, "zlib") == 0 ? CPLZLibDeflate : CPLGZipCompress;
    const int clevel = atoi(CSLFetchNameValueDef(options, "LEVEL",
#if HAVE_LIBDEFLATE
                                                 "7"
#else
                                                 "6"
#endif
                                                 ));

    if (output_data != nullptr && *output_data != nullptr &&
        output_size != nullptr && *output_size != 0)
    {
        size_t nOutBytes = 0;
        if (nullptr == pfnCompress(input_data, input_size, clevel, *output_data,
                                   *output_size, &nOutBytes))
        {
            *output_size = 0;
            return false;
        }

        *output_size = nOutBytes;
        return true;
    }

    if (output_data == nullptr && output_size != nullptr)
    {
#if HAVE_LIBDEFLATE
        struct libdeflate_compressor *enc = libdeflate_alloc_compressor(clevel);
        if (enc == nullptr)
        {
            *output_size = 0;
            return false;
        }
        if (strcmp(alg, "zlib") == 0)
            *output_size = libdeflate_zlib_compress_bound(enc, input_size);
        else
            *output_size = libdeflate_gzip_compress_bound(enc, input_size);
        libdeflate_free_compressor(enc);
#else
        // Really inefficient !
        size_t nOutSize = 0;
        void *outbuffer =
            pfnCompress(input_data, input_size, clevel, nullptr, 0, &nOutSize);
        if (outbuffer == nullptr)
        {
            *output_size = 0;
            return false;
        }
        VSIFree(outbuffer);
        *output_size = nOutSize;
#endif
        return true;
    }

    if (output_data != nullptr && *output_data == nullptr &&
        output_size != nullptr)
    {
        size_t nOutSize = 0;
        *output_data =
            pfnCompress(input_data, input_size, clevel, nullptr, 0, &nOutSize);
        if (*output_data == nullptr)
        {
            *output_size = 0;
            return false;
        }
        *output_size = nOutSize;
        return true;
    }

    CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
    return false;
}

namespace
{
template <class T> inline T swap(T x)
{
    return x;
}
template <> inline uint16_t swap<uint16_t>(uint16_t x)
{
    return CPL_SWAP16(x);
}
template <> inline int16_t swap<int16_t>(int16_t x)
{
    return CPL_SWAP16(x);
}
template <> inline uint32_t swap<uint32_t>(uint32_t x)
{
    return CPL_SWAP32(x);
}
template <> inline int32_t swap<int32_t>(int32_t x)
{
    return CPL_SWAP32(x);
}
template <> inline uint64_t swap<uint64_t>(uint64_t x)
{
    return CPL_SWAP64(x);
}
template <> inline int64_t swap<int64_t>(int64_t x)
{
    return CPL_SWAP64(x);
}
template <> inline float swap<float>(float x)
{
    float ret = x;
    CPL_SWAP32PTR(&ret);
    return ret;
}
template <> inline double swap<double>(double x)
{
    double ret = x;
    CPL_SWAP64PTR(&ret);
    return ret;
}
}  // namespace

namespace
{
// Workaround -ftrapv
template <class T>
CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW inline T SubNoOverflow(T left, T right)
{
    typedef typename std::make_unsigned<T>::type U;
    U leftU = static_cast<U>(left);
    U rightU = static_cast<U>(right);
    leftU = static_cast<U>(leftU - rightU);
    T ret;
    memcpy(&ret, &leftU, sizeof(ret));
    return leftU;
}
template <> inline float SubNoOverflow<float>(float x, float y)
{
    return x - y;
}
template <> inline double SubNoOverflow<double>(double x, double y)
{
    return x - y;
}
}  // namespace

template <class T>
static bool DeltaCompressor(const void *input_data, size_t input_size,
                            const char *dtype, void *output_data)
{
    if ((input_size % sizeof(T)) != 0)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid input size");
        return false;
    }

    const size_t nElts = input_size / sizeof(T);
    const T *pSrc = static_cast<const T *>(input_data);
    T *pDst = static_cast<T *>(output_data);
#ifdef CPL_MSB
    const bool bNeedSwap = dtype[0] == '<';
#else
    const bool bNeedSwap = dtype[0] == '>';
#endif
    for (size_t i = 0; i < nElts; i++)
    {
        if (i == 0)
        {
            pDst[0] = pSrc[0];
        }
        else
        {
            if (bNeedSwap)
            {
                pDst[i] = swap(SubNoOverflow(swap(pSrc[i]), swap(pSrc[i - 1])));
            }
            else
            {
                pDst[i] = SubNoOverflow(pSrc[i], pSrc[i - 1]);
            }
        }
    }
    return true;
}

static bool CPLDeltaCompressor(const void *input_data, size_t input_size,
                               void **output_data, size_t *output_size,
                               CSLConstList options,
                               void * /* compressor_user_data */)
{
    const char *dtype = CSLFetchNameValue(options, "DTYPE");
    if (dtype == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Missing DTYPE parameter");
        if (output_size)
            *output_size = 0;
        return false;
    }
    const char *astype = CSLFetchNameValue(options, "ASTYPE");
    if (astype != nullptr && !EQUAL(astype, dtype))
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Only ASTYPE=DTYPE currently supported");
        if (output_size)
            *output_size = 0;
        return false;
    }

    if (output_data != nullptr && *output_data != nullptr &&
        output_size != nullptr && *output_size != 0)
    {
        if (*output_size < input_size)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Too small output size");
            *output_size = input_size;
            return false;
        }

        if (EQUAL(dtype, "i1"))
        {
            if (!DeltaCompressor<int8_t>(input_data, input_size, dtype,
                                         *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "u1"))
        {
            if (!DeltaCompressor<uint8_t>(input_data, input_size, dtype,
                                          *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<i2") || EQUAL(dtype, ">i2") ||
                 EQUAL(dtype, "i2"))
        {
            if (!DeltaCompressor<int16_t>(input_data, input_size, dtype,
                                          *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<u2") || EQUAL(dtype, ">u2") ||
                 EQUAL(dtype, "u2"))
        {
            if (!DeltaCompressor<uint16_t>(input_data, input_size, dtype,
                                           *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<i4") || EQUAL(dtype, ">i4") ||
                 EQUAL(dtype, "i4"))
        {
            if (!DeltaCompressor<int32_t>(input_data, input_size, dtype,
                                          *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<u4") || EQUAL(dtype, ">u4") ||
                 EQUAL(dtype, "u4"))
        {
            if (!DeltaCompressor<uint32_t>(input_data, input_size, dtype,
                                           *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<i8") || EQUAL(dtype, ">i8") ||
                 EQUAL(dtype, "i8"))
        {
            if (!DeltaCompressor<int64_t>(input_data, input_size, dtype,
                                          *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<u8") || EQUAL(dtype, ">u8") ||
                 EQUAL(dtype, "u8"))
        {
            if (!DeltaCompressor<uint64_t>(input_data, input_size, dtype,
                                           *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<f4") || EQUAL(dtype, ">f4") ||
                 EQUAL(dtype, "f4"))
        {
            if (!DeltaCompressor<float>(input_data, input_size, dtype,
                                        *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<f8") || EQUAL(dtype, ">f8") ||
                 EQUAL(dtype, "f8"))
        {
            if (!DeltaCompressor<double>(input_data, input_size, dtype,
                                         *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Unsupported dtype=%s for delta filter", dtype);
            *output_size = 0;
            return false;
        }

        *output_size = input_size;
        return true;
    }

    if (output_data == nullptr && output_size != nullptr)
    {
        *output_size = input_size;
        return true;
    }

    if (output_data != nullptr && *output_data == nullptr &&
        output_size != nullptr)
    {
        *output_data = VSI_MALLOC_VERBOSE(input_size);
        *output_size = input_size;
        if (*output_data == nullptr)
            return false;
        bool ret = CPLDeltaCompressor(input_data, input_size, output_data,
                                      output_size, options, nullptr);
        if (!ret)
        {
            VSIFree(*output_data);
            *output_data = nullptr;
        }
        return ret;
    }

    CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
    return false;
}

static void CPLAddCompressor(const CPLCompressor *compressor)
{
    CPLCompressor *copy = new CPLCompressor(*compressor);
    // cppcheck-suppress uninitdata
    copy->pszId = CPLStrdup(compressor->pszId);
    // cppcheck-suppress uninitdata
    copy->papszMetadata = CSLDuplicate(compressor->papszMetadata);
    gpCompressors->emplace_back(copy);
}

static void CPLAddBuiltinCompressors()
{
#ifdef HAVE_BLOSC
    do
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_COMPRESSOR;
        sComp.pszId = "blosc";

        const CPLStringList aosCompressors(
            CSLTokenizeString2(blosc_list_compressors(), ",", 0));
        if (aosCompressors.size() == 0)
            break;
        std::string options("OPTIONS=<Options>"
                            "  <Option name='CNAME' type='string-select' "
                            "description='Compressor name' default='");
        std::string values;
        std::string defaultCompressor;
        bool bFoundLZ4 = false;
        bool bFoundSnappy = false;
        bool bFoundZlib = false;
        for (int i = 0; i < aosCompressors.size(); i++)
        {
            values += "<Value>";
            values += aosCompressors[i];
            values += "</Value>";
            if (strcmp(aosCompressors[i], "lz4") == 0)
                bFoundLZ4 = true;
            else if (strcmp(aosCompressors[i], "snappy") == 0)
                bFoundSnappy = true;
            else if (strcmp(aosCompressors[i], "zlib") == 0)
                bFoundZlib = true;
        }
        options += bFoundLZ4      ? "lz4"
                   : bFoundSnappy ? "snappy"
                   : bFoundZlib   ? "zlib"
                                  : aosCompressors[0];
        options += "'>";
        options += values;
        options +=
            "  </Option>"
            "  <Option name='CLEVEL' type='int' description='Compression "
            "level' min='1' max='9' default='5' />"
            "  <Option name='SHUFFLE' type='string-select' description='Type "
            "of shuffle algorithm' default='BYTE'>"
            "    <Value alias='0'>NONE</Value>"
            "    <Value alias='1'>BYTE</Value>"
            "    <Value alias='2'>BIT</Value>"
            "  </Option>"
            "  <Option name='BLOCKSIZE' type='int' description='Block size' "
            "default='0' />"
            "  <Option name='TYPESIZE' type='int' description='Number of bytes "
            "for the atomic type' default='1' />"
            "  <Option name='NUM_THREADS' type='string' "
            "description='Number of worker threads for compression. Can be set "
            "to ALL_CPUS' default='1' />"
            "</Options>";

        const char *const apszMetadata[] = {
            "BLOSC_VERSION=" BLOSC_VERSION_STRING, options.c_str(), nullptr};
        sComp.papszMetadata = apszMetadata;
        sComp.pfnFunc = CPLBloscCompressor;
        sComp.user_data = nullptr;
        CPLAddCompressor(&sComp);
    } while (0);
#endif
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_COMPRESSOR;
        sComp.pszId = "zlib";
        const char *pszOptions =
            "OPTIONS=<Options>"
            "  <Option name='LEVEL' type='int' description='Compression level' "
            "min='1' max='9' default='6' />"
            "</Options>";
        const char *const apszMetadata[] = {pszOptions, nullptr};
        sComp.papszMetadata = apszMetadata;
        sComp.pfnFunc = CPLZlibCompressor;
        sComp.user_data = const_cast<char *>("zlib");
        CPLAddCompressor(&sComp);
    }
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_COMPRESSOR;
        sComp.pszId = "gzip";
        const char *pszOptions =
            "OPTIONS=<Options>"
            "  <Option name='LEVEL' type='int' description='Compression level' "
            "min='1' max='9' default='6' />"
            "</Options>";
        const char *const apszMetadata[] = {pszOptions, nullptr};
        sComp.papszMetadata = apszMetadata;
        sComp.pfnFunc = CPLZlibCompressor;
        sComp.user_data = const_cast<char *>("gzip");
        CPLAddCompressor(&sComp);
    }
#ifdef HAVE_LZMA
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_COMPRESSOR;
        sComp.pszId = "lzma";
        const char *pszOptions =
            "OPTIONS=<Options>"
            "  <Option name='PRESET' type='int' description='Compression "
            "level' min='0' max='9' default='6' />"
            "  <Option name='DELTA' type='int' description='Delta distance in "
            "byte' default='1' />"
            "</Options>";
        const char *const apszMetadata[] = {pszOptions, nullptr};
        sComp.papszMetadata = apszMetadata;
        sComp.pfnFunc = CPLLZMACompressor;
        sComp.user_data = nullptr;
        CPLAddCompressor(&sComp);
    }
#endif
#ifdef HAVE_ZSTD
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_COMPRESSOR;
        sComp.pszId = "zstd";
        const char *pszOptions =
            "OPTIONS=<Options>"
            "  <Option name='LEVEL' type='int' description='Compression level' "
            "min='1' max='22' default='13' />"
            "</Options>";
        const char *const apszMetadata[] = {pszOptions, nullptr};
        sComp.papszMetadata = apszMetadata;
        sComp.pfnFunc = CPLZSTDCompressor;
        sComp.user_data = nullptr;
        CPLAddCompressor(&sComp);
    }
#endif
#ifdef HAVE_LZ4
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_COMPRESSOR;
        sComp.pszId = "lz4";
        const char *pszOptions =
            "OPTIONS=<Options>"
            "  <Option name='ACCELERATION' type='int' "
            "description='Acceleration factor. The higher, the less "
            "compressed' min='1' default='1' />"
            "  <Option name='HEADER' type='boolean' description='Whether a "
            "header with the uncompressed size should be included (as used by "
            "Zarr)' default='YES' />"
            "</Options>";
        const char *const apszMetadata[] = {pszOptions, nullptr};
        sComp.papszMetadata = apszMetadata;
        sComp.pfnFunc = CPLLZ4Compressor;
        sComp.user_data = nullptr;
        CPLAddCompressor(&sComp);
    }
#endif
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_FILTER;
        sComp.pszId = "delta";
        const char *pszOptions =
            "OPTIONS=<Options>"
            "  <Option name='DTYPE' type='string' description='Data type "
            "following NumPy array protocol type string (typestr) format'/>"
            "</Options>";
        const char *const apszMetadata[] = {pszOptions, nullptr};
        sComp.papszMetadata = apszMetadata;
        sComp.pfnFunc = CPLDeltaCompressor;
        sComp.user_data = nullptr;
        CPLAddCompressor(&sComp);
    }
}

static bool CPLZlibDecompressor(const void *input_data, size_t input_size,
                                void **output_data, size_t *output_size,
                                CSLConstList /* options */,
                                void * /* compressor_user_data */)
{
    if (output_data != nullptr && *output_data != nullptr &&
        output_size != nullptr && *output_size != 0)
    {
        size_t nOutBytes = 0;
        if (nullptr == CPLZLibInflate(input_data, input_size, *output_data,
                                      *output_size, &nOutBytes))
        {
            *output_size = 0;
            return false;
        }

        *output_size = nOutBytes;
        return true;
    }

    if (output_data == nullptr && output_size != nullptr)
    {
        size_t nOutSize = input_size < std::numeric_limits<size_t>::max() / 4
                              ? input_size * 4
                              : input_size;
        void *tmpOutBuffer = VSIMalloc(nOutSize);
        if (tmpOutBuffer == nullptr)
        {
            *output_size = 0;
            return false;
        }
        if (nullptr == CPLZLibInflate(input_data, input_size, tmpOutBuffer,
                                      nOutSize, &nOutSize))
        {
            VSIFree(tmpOutBuffer);
            *output_size = 0;
            return false;
        }
        VSIFree(tmpOutBuffer);
        *output_size = nOutSize;
        return true;
    }

    if (output_data != nullptr && *output_data == nullptr &&
        output_size != nullptr)
    {
        size_t nOutSize = input_size < std::numeric_limits<size_t>::max() / 4
                              ? input_size * 4
                              : input_size;
        void *tmpOutBuffer = VSIMalloc(nOutSize);
        if (tmpOutBuffer == nullptr)
        {
            *output_size = 0;
            return false;
        }
        size_t nOutSizeOut = 0;
        if (nullptr == CPLZLibInflate(input_data, input_size, tmpOutBuffer,
                                      nOutSize, &nOutSizeOut))
        {
            VSIFree(tmpOutBuffer);
            *output_size = 0;
            return false;
        }
        *output_data = VSIRealloc(tmpOutBuffer, nOutSizeOut);  // cannot fail
        *output_size = nOutSizeOut;
        return true;
    }

    CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
    return false;
}

namespace
{
// Workaround -ftrapv
template <class T>
CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW inline T AddNoOverflow(T left, T right)
{
    typedef typename std::make_unsigned<T>::type U;
    U leftU = static_cast<U>(left);
    U rightU = static_cast<U>(right);
    leftU = static_cast<U>(leftU + rightU);
    T ret;
    memcpy(&ret, &leftU, sizeof(ret));
    return leftU;
}
template <> inline float AddNoOverflow<float>(float x, float y)
{
    return x + y;
}
template <> inline double AddNoOverflow<double>(double x, double y)
{
    return x + y;
}
}  // namespace

template <class T>
static bool DeltaDecompressor(const void *input_data, size_t input_size,
                              const char *dtype, void *output_data)
{
    if ((input_size % sizeof(T)) != 0)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid input size");
        return false;
    }

    const size_t nElts = input_size / sizeof(T);
    const T *pSrc = static_cast<const T *>(input_data);
    T *pDst = static_cast<T *>(output_data);
#ifdef CPL_MSB
    const bool bNeedSwap = dtype[0] == '<';
#else
    const bool bNeedSwap = dtype[0] == '>';
#endif
    for (size_t i = 0; i < nElts; i++)
    {
        if (i == 0)
        {
            pDst[0] = pSrc[0];
        }
        else
        {
            if (bNeedSwap)
            {
                pDst[i] = swap(AddNoOverflow(swap(pDst[i - 1]), swap(pSrc[i])));
            }
            else
            {
                pDst[i] = AddNoOverflow(pDst[i - 1], pSrc[i]);
            }
        }
    }
    return true;
}

static bool CPLDeltaDecompressor(const void *input_data, size_t input_size,
                                 void **output_data, size_t *output_size,
                                 CSLConstList options,
                                 void * /* compressor_user_data */)
{
    const char *dtype = CSLFetchNameValue(options, "DTYPE");
    if (dtype == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Missing DTYPE parameter");
        if (output_size)
            *output_size = 0;
        return false;
    }
    const char *astype = CSLFetchNameValue(options, "ASTYPE");
    if (astype != nullptr && !EQUAL(astype, dtype))
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Only ASTYPE=DTYPE currently supported");
        if (output_size)
            *output_size = 0;
        return false;
    }

    if (output_data != nullptr && *output_data != nullptr &&
        output_size != nullptr && *output_size != 0)
    {
        if (*output_size < input_size)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Too small output size");
            *output_size = input_size;
            return false;
        }

        if (EQUAL(dtype, "i1"))
        {
            if (!DeltaDecompressor<int8_t>(input_data, input_size, dtype,
                                           *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "u1"))
        {
            if (!DeltaDecompressor<uint8_t>(input_data, input_size, dtype,
                                            *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<i2") || EQUAL(dtype, ">i2") ||
                 EQUAL(dtype, "i2"))
        {
            if (!DeltaDecompressor<int16_t>(input_data, input_size, dtype,
                                            *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<u2") || EQUAL(dtype, ">u2") ||
                 EQUAL(dtype, "u2"))
        {
            if (!DeltaDecompressor<uint16_t>(input_data, input_size, dtype,
                                             *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<i4") || EQUAL(dtype, ">i4") ||
                 EQUAL(dtype, "i4"))
        {
            if (!DeltaDecompressor<int32_t>(input_data, input_size, dtype,
                                            *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<u4") || EQUAL(dtype, ">u4") ||
                 EQUAL(dtype, "u4"))
        {
            if (!DeltaDecompressor<uint32_t>(input_data, input_size, dtype,
                                             *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<i8") || EQUAL(dtype, ">i8") ||
                 EQUAL(dtype, "i8"))
        {
            if (!DeltaDecompressor<int64_t>(input_data, input_size, dtype,
                                            *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<u8") || EQUAL(dtype, ">u8") ||
                 EQUAL(dtype, "u8"))
        {
            if (!DeltaDecompressor<uint64_t>(input_data, input_size, dtype,
                                             *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<f4") || EQUAL(dtype, ">f4") ||
                 EQUAL(dtype, "f4"))
        {
            if (!DeltaDecompressor<float>(input_data, input_size, dtype,
                                          *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else if (EQUAL(dtype, "<f8") || EQUAL(dtype, ">f8") ||
                 EQUAL(dtype, "f8"))
        {
            if (!DeltaDecompressor<double>(input_data, input_size, dtype,
                                           *output_data))
            {
                *output_size = 0;
                return false;
            }
        }
        else
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Unsupported dtype=%s for delta filter", dtype);
            *output_size = 0;
            return false;
        }

        *output_size = input_size;
        return true;
    }

    if (output_data == nullptr && output_size != nullptr)
    {
        *output_size = input_size;
        return true;
    }

    if (output_data != nullptr && *output_data == nullptr &&
        output_size != nullptr)
    {
        *output_data = VSI_MALLOC_VERBOSE(input_size);
        *output_size = input_size;
        if (*output_data == nullptr)
            return false;
        bool ret = CPLDeltaDecompressor(input_data, input_size, output_data,
                                        output_size, options, nullptr);
        if (!ret)
        {
            VSIFree(*output_data);
            *output_data = nullptr;
        }
        return ret;
    }

    CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
    return false;
}

static void CPLAddDecompressor(const CPLCompressor *decompressor)
{
    CPLCompressor *copy = new CPLCompressor(*decompressor);
    // cppcheck-suppress uninitdata
    copy->pszId = CPLStrdup(decompressor->pszId);
    // cppcheck-suppress uninitdata
    copy->papszMetadata = CSLDuplicate(decompressor->papszMetadata);
    gpDecompressors->emplace_back(copy);
}

static void CPLAddBuiltinDecompressors()
{
#ifdef HAVE_BLOSC
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_COMPRESSOR;
        sComp.pszId = "blosc";
        const char *pszOptions =
            "OPTIONS=<Options>"
            "  <Option name='NUM_THREADS' type='string' "
            "description='Number of worker threads for decompression. Can be "
            "set to ALL_CPUS' default='1' />"
            "</Options>";
        const char *const apszMetadata[] = {
            "BLOSC_VERSION=" BLOSC_VERSION_STRING, pszOptions, nullptr};
        sComp.papszMetadata = apszMetadata;
        sComp.pfnFunc = CPLBloscDecompressor;
        sComp.user_data = nullptr;
        CPLAddDecompressor(&sComp);
    }
#endif
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_COMPRESSOR;
        sComp.pszId = "zlib";
        sComp.papszMetadata = nullptr;
        sComp.pfnFunc = CPLZlibDecompressor;
        sComp.user_data = nullptr;
        CPLAddDecompressor(&sComp);
    }
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_COMPRESSOR;
        sComp.pszId = "gzip";
        sComp.papszMetadata = nullptr;
        sComp.pfnFunc = CPLZlibDecompressor;
        sComp.user_data = nullptr;
        CPLAddDecompressor(&sComp);
    }
#ifdef HAVE_LZMA
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_COMPRESSOR;
        sComp.pszId = "lzma";
        sComp.papszMetadata = nullptr;
        sComp.pfnFunc = CPLLZMADecompressor;
        sComp.user_data = nullptr;
        CPLAddDecompressor(&sComp);
    }
#endif
#ifdef HAVE_ZSTD
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_COMPRESSOR;
        sComp.pszId = "zstd";
        sComp.papszMetadata = nullptr;
        sComp.pfnFunc = CPLZSTDDecompressor;
        sComp.user_data = nullptr;
        CPLAddDecompressor(&sComp);
    }
#endif
#ifdef HAVE_LZ4
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_COMPRESSOR;
        sComp.pszId = "lz4";
        const char *pszOptions =
            "OPTIONS=<Options>"
            "  <Option name='HEADER' type='boolean' description='Whether a "
            "header with the uncompressed size should be included (as used by "
            "Zarr)' default='YES' />"
            "</Options>";
        const char *const apszMetadata[] = {pszOptions, nullptr};
        sComp.papszMetadata = apszMetadata;
        sComp.pfnFunc = CPLLZ4Decompressor;
        sComp.user_data = nullptr;
        CPLAddDecompressor(&sComp);
    }
#endif
    {
        CPLCompressor sComp;
        sComp.nStructVersion = 1;
        sComp.eType = CCT_FILTER;
        sComp.pszId = "delta";
        const char *pszOptions =
            "OPTIONS=<Options>"
            "  <Option name='DTYPE' type='string' description='Data type "
            "following NumPy array protocol type string (typestr) format'/>"
            "</Options>";
        const char *const apszMetadata[] = {pszOptions, nullptr};
        sComp.papszMetadata = apszMetadata;
        sComp.pfnFunc = CPLDeltaDecompressor;
        sComp.user_data = nullptr;
        CPLAddDecompressor(&sComp);
    }
}

/** Register a new compressor.
 *
 * The provided structure is copied. Its pfnFunc and user_data members should
 * remain valid beyond this call however.
 *
 * @param compressor Compressor structure. Should not be null.
 * @return true if successful
 * @since GDAL 3.4
 */
bool CPLRegisterCompressor(const CPLCompressor *compressor)
{
    if (compressor->nStructVersion < 1)
        return false;
    std::lock_guard<std::mutex> lock(gMutex);
    if (gpCompressors == nullptr)
    {
        gpCompressors = new std::vector<CPLCompressor *>();
        CPLAddBuiltinCompressors();
    }
    for (size_t i = 0; i < gpCompressors->size(); ++i)
    {
        if (strcmp(compressor->pszId, (*gpCompressors)[i]->pszId) == 0)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Compressor %s already registered", compressor->pszId);
            return false;
        }
    }
    CPLAddCompressor(compressor);
    return true;
}

/** Register a new decompressor.
 *
 * The provided structure is copied. Its pfnFunc and user_data members should
 * remain valid beyond this call however.
 *
 * @param decompressor Compressor structure. Should not be null.
 * @return true if successful
 * @since GDAL 3.4
 */
bool CPLRegisterDecompressor(const CPLCompressor *decompressor)
{
    if (decompressor->nStructVersion < 1)
        return false;
    std::lock_guard<std::mutex> lock(gMutex);
    if (gpDecompressors == nullptr)
    {
        gpDecompressors = new std::vector<CPLCompressor *>();
        CPLAddBuiltinDecompressors();
    }
    for (size_t i = 0; i < gpDecompressors->size(); ++i)
    {
        if (strcmp(decompressor->pszId, (*gpDecompressors)[i]->pszId) == 0)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Decompressor %s already registered", decompressor->pszId);
            return false;
        }
    }
    CPLAddDecompressor(decompressor);
    return true;
}

/** Return the list of registered compressors.
 *
 * @return list of strings. Should be freed with CSLDestroy()
 * @since GDAL 3.4
 */
char **CPLGetCompressors(void)
{
    std::lock_guard<std::mutex> lock(gMutex);
    if (gpCompressors == nullptr)
    {
        gpCompressors = new std::vector<CPLCompressor *>();
        CPLAddBuiltinCompressors();
    }
    char **papszRet = nullptr;
    for (size_t i = 0; i < gpCompressors->size(); ++i)
    {
        papszRet = CSLAddString(papszRet, (*gpCompressors)[i]->pszId);
    }
    return papszRet;
}

/** Return the list of registered decompressors.
 *
 * @return list of strings. Should be freed with CSLDestroy()
 * @since GDAL 3.4
 */
char **CPLGetDecompressors(void)
{
    std::lock_guard<std::mutex> lock(gMutex);
    if (gpDecompressors == nullptr)
    {
        gpDecompressors = new std::vector<CPLCompressor *>();
        CPLAddBuiltinDecompressors();
    }
    char **papszRet = nullptr;
    for (size_t i = 0;
         gpDecompressors != nullptr && i < gpDecompressors->size(); ++i)
    {
        papszRet = CSLAddString(papszRet, (*gpDecompressors)[i]->pszId);
    }
    return papszRet;
}

/** Return a compressor.
 *
 * @param pszId Compressor id. Should NOT be NULL.
 * @return compressor structure, or NULL.
 * @since GDAL 3.4
 */
const CPLCompressor *CPLGetCompressor(const char *pszId)
{
    std::lock_guard<std::mutex> lock(gMutex);
    if (gpCompressors == nullptr)
    {
        gpCompressors = new std::vector<CPLCompressor *>();
        CPLAddBuiltinCompressors();
    }
    for (size_t i = 0; i < gpCompressors->size(); ++i)
    {
        if (EQUAL(pszId, (*gpCompressors)[i]->pszId))
        {
            return (*gpCompressors)[i];
        }
    }
    return nullptr;
}

/** Return a decompressor.
 *
 * @param pszId Decompressor id. Should NOT be NULL.
 * @return compressor structure, or NULL.
 * @since GDAL 3.4
 */
const CPLCompressor *CPLGetDecompressor(const char *pszId)
{
    std::lock_guard<std::mutex> lock(gMutex);
    if (gpDecompressors == nullptr)
    {
        gpDecompressors = new std::vector<CPLCompressor *>();
        CPLAddBuiltinDecompressors();
    }
    for (size_t i = 0; i < gpDecompressors->size(); ++i)
    {
        if (EQUAL(pszId, (*gpDecompressors)[i]->pszId))
        {
            return (*gpDecompressors)[i];
        }
    }
    return nullptr;
}

static void
CPLDestroyCompressorRegistryInternal(std::vector<CPLCompressor *> *&v)
{
    for (size_t i = 0; v != nullptr && i < v->size(); ++i)
    {
        CPLFree(const_cast<char *>((*v)[i]->pszId));
        CSLDestroy(const_cast<char **>((*v)[i]->papszMetadata));
        delete (*v)[i];
    }
    delete v;
    v = nullptr;
}

/*! @cond Doxygen_Suppress */
void CPLDestroyCompressorRegistry(void)
{
    std::lock_guard<std::mutex> lock(gMutex);

    CPLDestroyCompressorRegistryInternal(gpCompressors);
    CPLDestroyCompressorRegistryInternal(gpDecompressors);
}
/*! @endcond */
