/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */

/**
 * @internal
 * @file
 * @~English
 *
 * @brief Functions for creating KTX-format files from a set of images.
 *
 * @author Mark Callow, HI Corporation
 */

/*
 * Copyright 2018-2020 Mark Callow.
 * SPDX-License-Identifier: Apache-2.0
 */

#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <assert.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(__GNUC__)
#include <strings.h>  // For strncasecmp on GNU/Linux
#endif
#include <zstd.h>
#include <zstd_errors.h>
#include <KHR/khr_df.h>

#include "ktx.h"
#include "ktxint.h"
#include "filestream.h"
#include "memstream.h"
#include "texture2.h"

#include "dfdutils/dfd.h"
#include "vkformat_enum.h"
#include "vk_format.h"

#include "version.h"

#if defined(_MSC_VER)
#define strncasecmp _strnicmp
#endif

/**
 * @defgroup writer Writer
 * @brief Write KTX-formatted data.
 * @{
 */

#if defined(_WIN32) || defined(linux) || defined(__linux) || defined(__linux__) || defined(__EMSCRIPTEN__)
/** @internal
 * @~English
 * @brief strnstr for Windows, Linux and Emscripten.
 *
 * strnstr is available in <apple>OS and BSD distributions. To use in Linux
 * requires linking an additional library, libbsd. It is simpler to use ours.
 *
 * @param[in] haystack   pointer to string to search.
 * @param[in] needle     pointer to string to search for.
 * @param[in] len        length of @p haystack string. Also used as limit to
 *                       length of @p needle string.
 *
 * @return    @p haystack, if @p needle is an empty string otherwise NULL, if
 *            @p needle does not occur in @p haystack, or a pointer to the
 *            first character of the first occurrence of @p needle.
 */
static char*
strnstr(const char *haystack, const char *needle, size_t len)
{
    size_t i;
    size_t needleLen;
    const char* needleEnd;

    // strnlen is not part of the C standard and does not compile on some platforms, 
    // use case is covered by memchr.
    needleEnd = (char *)memchr(needle, 0, len);
    if (needleEnd == needle)
        return (char *)haystack;

    needleLen = len;
    if (needleEnd != NULL)
        needleLen = needleEnd - needle;

    for (i = 0; i <= len - needleLen; i++)
    {
        if (haystack[0] == needle[0]
            && strncmp(haystack, needle, needleLen) == 0)
            return (char *)haystack;
        haystack++;
    }
    return NULL;
}
#endif

/** @internal
 * @~English
 * @brief Append the library's id to the KTXwriter value.
 *
 * @param[in] head         pointer to the head of the hash list.
 * @param[in] writerEntry  pointer to an existing KTXwriter entry.
 *
 * @return    KTX_SUCCESS on success, other KTX_* enum values on error.
 *
 * @exception KTX_OUT_OF_MEMORY  not enough memory for temporary strings.
 * @exception KTX_INVALID_OPERATION
 *                               the length of the value of writerEntry and the
 *                               lib id being added is greater than the
 *                               maximum allowed.
 */
KTX_error_code
appendLibId(ktxHashList* head, ktxHashListEntry* writerEntry)
{
    KTX_error_code result;
    const char* id;
    const char* libVer;
    const char libIdIntro[] = " / libktx ";
    size_t idLen, libIdLen;

    if (writerEntry) {
        ktx_uint32_t len;
        result = ktxHashListEntry_GetValue(writerEntry, &len, (void**)&id);
        idLen = len;
    } else {
        id = "Unidentified app";
        idLen = 17;
    }

    // strnstr needed because KTXwriter values may not be NUL terminated.
    if (strnstr(id, "__default__", idLen) != NULL) {
        libVer = STR(LIBKTX_DEFAULT_VERSION);
    } else {
        libVer = STR(LIBKTX_VERSION);
    }
    // sizeof(libIdIntro) includes space for its terminating NUL which we will
    // overwrite so no need for +1 after strlen.
    libIdLen = sizeof(libIdIntro) + (ktx_uint32_t)strlen(libVer);
    char* libId = malloc(libIdLen);
    if (!libId)
        return KTX_OUT_OF_MEMORY;
    strncpy(libId, libIdIntro, libIdLen);
    strncpy(&libId[sizeof(libIdIntro)-1], libVer,
            libIdLen-(sizeof(libIdIntro)-1));

    if (strnstr(id, libId, idLen) != NULL) {
        // This lib id is already in the writer value.
        return KTX_SUCCESS;
    }

    const char* libVerPos = strnstr(id, libIdIntro, idLen);
    if (libVerPos != NULL) {
        // There is a libktx version but not the current version.
        idLen = libVerPos - id;
    } else if (id[idLen-1] == '\0') {
        idLen--;
    }

    size_t fullIdLen = idLen + strlen(libId) + 1;
    if (fullIdLen > UINT_MAX)
        return KTX_INVALID_OPERATION;
    char* fullId = malloc(fullIdLen);
    if (!fullId)
        return KTX_OUT_OF_MEMORY;
    strncpy(fullId, id, idLen);
    strncpy(&fullId[idLen], libId, libIdLen);
    assert(fullId[fullIdLen-1] == '\0');

    ktxHashList_DeleteEntry(head, writerEntry);
    result = ktxHashList_AddKVPair(head, KTX_WRITER_KEY,
                                   (ktx_uint32_t)fullIdLen, fullId);
    free(libId);
    free(fullId);
    return result;
}

/**
 * @memberof ktxTexture2 @private
 * @~English
 * @brief Set image for level, layer, faceSlice from a ktxStream source.
 *
 * @param[in] This      pointer to the target ktxTexture object.
 * @param[in] level     mip level of the image to set.
 * @param[in] layer     array layer of the image to set.
 * @param[in] faceSlice cube map face or depth slice of the image to set or
 *                      KTX_FACESLICE_WHOLE_LEVEL to set the entire level.
 * @param[in] src       ktxStream pointer to the source.
 * @param[in] srcSize   size of the source image in bytes.
 *
 * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
 *
 * @exception KTX_INVALID_VALUE @p This or @p src is NULL.
 * @exception KTX_INVALID_VALUE @p srcSize != the expected image size for the
 *                              specified level, layer & faceSlice.
 * @exception KTX_INVALID_OPERATION
 *                              No storage was allocated when the texture was
 *                              created.
 */
KTX_error_code
ktxTexture2_setImageFromStream(ktxTexture2* This, ktx_uint32_t level,
                               ktx_uint32_t layer, ktx_uint32_t faceSlice,
                               ktxStream* src, ktx_size_t srcSize)
{
    ktx_size_t imageByteLength;
    ktx_size_t imageByteOffset;
    ktx_error_code_e result;

    if (!This || !src)
        return KTX_INVALID_VALUE;

    if (!This->pData)
        return KTX_INVALID_OPERATION;

    if (faceSlice == KTX_FACESLICE_WHOLE_LEVEL) {
        result = ktxTexture_GetImageOffset(ktxTexture(This), level, layer, 0, &imageByteOffset);
        if (result != KTX_SUCCESS) {
            return result;
        }
        imageByteLength = ktxTexture_calcLevelSize(ktxTexture(This), level, KTX_FORMAT_VERSION_TWO);
    } else {
        result = ktxTexture_GetImageOffset(ktxTexture(This), level, layer, faceSlice, &imageByteOffset);
        if (result != KTX_SUCCESS) {
            return result;
        }
        imageByteLength = ktxTexture_GetImageSize(ktxTexture(This), level);
    }

    if (srcSize != imageByteLength)
        return KTX_INVALID_OPERATION;
    // The above will catch a flagrantly invalid srcSize. This is an
    // additional check of the internal calculations.
    assert (imageByteOffset + srcSize <= This->dataSize);

    /* Can copy whole image at once */
    src->read(src, This->pData + imageByteOffset, srcSize);
    return KTX_SUCCESS;
}

/**
 * @memberof ktxTexture2
 * @~English
 * @brief Set image for level, layer, faceSlice from a stdio stream source.
 *
 * Uncompressed images read from the stream are expected to have their rows
 * tightly packed as is the norm for most image file formats. KTX 2 also requires
 * tight packing this function does not add any padding.
 *
 * Level, layer, faceSlice rather than offset are specified to enable some
 * validation.
 *
 * @param[in] This      pointer to the target ktxTexture object.
 * @param[in] level     mip level of the image to set.
 * @param[in] layer     array layer of the image to set.
 * @param[in] faceSlice cube map face or depth slice of the image to set or
 *                      KTX_FACESLICE_WHOLE_LEVEL to set the entire level.
 * @param[in] src       stdio stream pointer to the source.
 * @param[in] srcSize   size of the source image in bytes.
 *
 * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
 *
 * @exception KTX_INVALID_VALUE @p This or @p src is NULL.
 * @exception KTX_INVALID_VALUE @p srcSize != the expected image size for the
 *                              specified level, layer & faceSlice.
 * @exception KTX_INVALID_OPERATION
 *                              No storage was allocated when the texture was
 *                              created.
 */
KTX_error_code
ktxTexture2_SetImageFromStdioStream(ktxTexture2* This, ktx_uint32_t level,
                                    ktx_uint32_t layer, ktx_uint32_t faceSlice,
                                    FILE* src, ktx_size_t srcSize)
{
    ktxStream srcstr;
    KTX_error_code result;

    result = ktxFileStream_construct(&srcstr, src, KTX_FALSE);
    if (result != KTX_SUCCESS)
        return result;
    result = ktxTexture2_setImageFromStream(This, level, layer, faceSlice,
                                            &srcstr, srcSize);
    ktxFileStream_destruct(&srcstr);
    return result;
}

/**
 * @memberof ktxTexture2
 * @~English
 * @brief Set image for level, layer, faceSlice from an image in memory.
 *
 * Uncompressed images in memory are expected to have their rows tightly packed
 * as is the norm for most image file formats.  KTX 2 also requires
 * tight packing this function does not add any padding.
 *
 * Level, layer, faceSlice rather than offset are specified to enable some
 * validation.
 *
 * @note The caller is responsible for freeing the original image memory
 *       referred to by @p src.
 *
 * @param[in] This      pointer to the target ktxTexture object.
 * @param[in] level     mip level of the image to set.
 * @param[in] layer     array layer of the image to set.
 * @param[in] faceSlice cube map face or depth slice of the image to set or
 *                      KTX_FACESLICE_WHOLE_LEVEL to set the entire level.
 * @param[in] src       pointer to the image source in memory.
 * @param[in] srcSize   size of the source image in bytes.
 *
 * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
 *
 * @exception KTX_INVALID_VALUE @p This or @p src is NULL.
 * @exception KTX_INVALID_VALUE @p srcSize != the expected image size for the
 *                              specified level, layer & faceSlice.
 * @exception KTX_INVALID_OPERATION
 *                              No storage was allocated when the texture was
 *                              created.
 */
KTX_error_code
ktxTexture2_SetImageFromMemory(ktxTexture2* This, ktx_uint32_t level,
                               ktx_uint32_t layer, ktx_uint32_t faceSlice,
                               const ktx_uint8_t* src, ktx_size_t srcSize)
{
    ktxStream srcstr;
    KTX_error_code result;

    result = ktxMemStream_construct_ro(&srcstr, src, srcSize);
    if (result != KTX_SUCCESS)
        return result;
    result = ktxTexture2_setImageFromStream(This, level, layer, faceSlice,
                                            &srcstr, srcSize);
    ktxMemStream_destruct(&srcstr);
    return result;
}

#if defined(TestNoMetadata)
// Only so texturetests can test loading of files without any metadata.
ktx_bool_t __disableWriterMetadata__ = KTX_FALSE;
#endif

/**
 * @memberof ktxTexture2
 * @~English
 * @brief Write a ktxTexture object to a ktxStream in KTX format.
 *
 * @param[in] This      pointer to the target ktxTexture object.
 * @param[in] dststr    destination ktxStream.
 *
 * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
 *
 * @exception KTX_INVALID_VALUE @p This or @p dststr is NULL.
 * @exception KTX_INVALID_OPERATION
 *                              The ktxTexture does not contain any image data.
 * @exception KTX_INVALID_OPERATION
 *                              Both kvDataHead and kvData are set in the
 *                              ktxTexture
 * @exception KTX_INVALID_OPERATION
 *                              The length of the already set writerId metadata
 *                              plus the library's version id exceeds the
 *                              maximum allowed.
 * @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
 *                              the system.
 * @exception KTX_FILE_WRITE_ERROR
 *                              An error occurred while writing the file.
 */
KTX_error_code
ktxTexture2_WriteToStream(ktxTexture2* This, ktxStream* dststr)
{
    DECLARE_PRIVATE(ktxTexture2);
    KTX_header2 header = { .identifier = KTX2_IDENTIFIER_REF };
    KTX_error_code result;
    ktx_uint32_t kvdLen;
    ktx_uint8_t* pKvd;
    ktx_uint32_t align8PadLen = 0;
    ktx_uint64_t sgdLen;
    ktx_uint32_t initialLevelPadLen;
    ktx_uint32_t levelIndexSize;
    ktx_uint64_t baseOffset;

    if (!dststr) {
        return KTX_INVALID_VALUE;
    }

    if (This->pData == NULL)
        return KTX_INVALID_OPERATION;

    header.vkFormat = This->vkFormat;
    header.typeSize = This->_protected->_typeSize;
    header.pixelWidth = This->baseWidth;
    header.pixelHeight = This->numDimensions > 1 ? This->baseHeight : 0;
    header.pixelDepth = This->numDimensions > 2 ? This->baseDepth : 0;
    header.layerCount = This->isArray ? This->numLayers : 0;
    assert (This->isCubemap ? This->numFaces == 6 : This->numFaces == 1);
    header.faceCount = This->numFaces;
    assert (This->generateMipmaps? This->numLevels == 1 : This->numLevels >= 1);
    header.levelCount = This->generateMipmaps ? 0 : This->numLevels;
    header.supercompressionScheme = This->supercompressionScheme;

    levelIndexSize = sizeof(ktxLevelIndexEntry) * This->numLevels;

    baseOffset = sizeof(header) + levelIndexSize;

    header.dataFormatDescriptor.byteOffset = (uint32_t)baseOffset;
    header.dataFormatDescriptor.byteLength = *This->pDfd;
    baseOffset += header.dataFormatDescriptor.byteLength;

    ktxHashListEntry* pEntry;
    // Check for invalid metadata.
    for (pEntry = This->kvDataHead; pEntry != NULL; pEntry = ktxHashList_Next(pEntry)) {
        unsigned int keyLen;
        char* key;

        ktxHashListEntry_GetKey(pEntry, &keyLen, &key);
        if (strncasecmp(key, "KTX", 3) == 0) {
            ktx_uint32_t i;
            const char* knownKeys[] = {
              "KTXcubemapIncomplete",
              "KTXorientation",
              "KTXglFormat",
              "KTXdxgiFormat__",
              "KTXmetalPixelFormat",
              "KTXswizzle",
              "KTXwriter",
              "KTXwriterScParams",
              "KTXastcDecodeMode",
              "KTXanimData"
            };
            if (strncmp(key, "ktx", 3) == 0)
                return KTX_INVALID_OPERATION;
            // Check for unrecognized KTX keys.
            for (i = 0; i < sizeof(knownKeys)/sizeof(char*); i++) {
                if (strcmp(key, knownKeys[i]) == 0)
                    break;
            }
            if (i == sizeof(knownKeys)/sizeof(char*))
                return KTX_INVALID_OPERATION;
        }
    }

#if defined(TestNoMetadata)
    if (!__disableWriterMetadata__) {
#endif
        pEntry = NULL;
        result = ktxHashList_FindEntry(&This->kvDataHead, KTX_WRITER_KEY,
                                       &pEntry);
        result = appendLibId(&This->kvDataHead, pEntry);
        if (result != KTX_SUCCESS)
            return result;
#if defined(TestNoMetadata)
    }
#endif

    ktxHashList_Sort(&This->kvDataHead); // KTX2 requires sorted metadata.
    ktxHashList_Serialize(&This->kvDataHead, &kvdLen, &pKvd);
    header.keyValueData.byteOffset = kvdLen != 0 ? (uint32_t)baseOffset : 0;
    header.keyValueData.byteLength = kvdLen;
    baseOffset += kvdLen;

    sgdLen = private->_sgdByteLength;
    if (sgdLen) {
        align8PadLen = _KTX_PAD8_LEN(baseOffset);
        baseOffset += align8PadLen;
    }

    header.supercompressionGlobalData.byteOffset = sgdLen != 0 ? baseOffset : 0;
    header.supercompressionGlobalData.byteLength = sgdLen;
    baseOffset += sgdLen;

    initialLevelPadLen = _KTX_PADN_LEN(This->_private->_requiredLevelAlignment,
                                       baseOffset);
    baseOffset += initialLevelPadLen;

    // write header and indices
    result = dststr->write(dststr, &header, sizeof(header), 1);
    if (result != KTX_SUCCESS)
        return result;

    // Create a copy of the level index with file-adjusted offsets and write it.
    ktxLevelIndexEntry* levelIndex
                            = (ktxLevelIndexEntry*)malloc(levelIndexSize);
    if (!levelIndex)
        return KTX_OUT_OF_MEMORY;
    for (ktx_uint32_t level = 0; level < This->numLevels; level++) {
        levelIndex[level].byteLength = private->_levelIndex[level].byteLength;
        levelIndex[level].uncompressedByteLength
                         = private->_levelIndex[level].uncompressedByteLength;
        levelIndex[level].byteOffset = private->_levelIndex[level].byteOffset;
        levelIndex[level].byteOffset += baseOffset;
    }
    result = dststr->write(dststr, levelIndex, levelIndexSize, 1);
    free(levelIndex);
    if (result != KTX_SUCCESS)
        return result;

   // write data format descriptor
   result = dststr->write(dststr, This->pDfd, 1, *This->pDfd);

   // write keyValueData
    if (kvdLen != 0) {
        assert(pKvd != NULL);

        result = dststr->write(dststr, pKvd, 1, kvdLen);
        free(pKvd);
        if (result != KTX_SUCCESS) {
             return result;
        }
    }

    char padding[32] = { 0 };
    // write supercompressionGlobalData & sgdPadding
    if (private->_sgdByteLength != 0) {
        if (align8PadLen) {
            result = dststr->write(dststr, padding, 1, align8PadLen);
            if (result != KTX_SUCCESS) {
                 return result;
            }
        }

        result = dststr->write(dststr, private->_supercompressionGlobalData,
                               1, private->_sgdByteLength);
        if (result != KTX_SUCCESS) {
            return result;
        }
    }

    if (initialLevelPadLen) {
        result = dststr->write(dststr, padding, 1, initialLevelPadLen);
        if (result != KTX_SUCCESS) {
             return result;
        }
    }

    // write the image data
    for (ktx_int32_t level = This->numLevels-1; level >= 0 && result == KTX_SUCCESS; --level)
    {
        ktx_uint64_t srcLevelOffset, levelSize;
#define DUMP_IMAGE 0
#if defined(DEBUG) || DUMP_IMAGE
        ktx_size_t pos;
#endif

#if defined(DEBUG)
        result = dststr->getpos(dststr, (ktx_off_t*)&pos);
        // Could fail if stdout is a pipe
        if (result == KTX_SUCCESS)
            assert(pos == private->_levelIndex[level].byteOffset + baseOffset);
        else
            assert(result == KTX_FILE_ISPIPE);
#endif

        srcLevelOffset = ktxTexture2_levelDataOffset(This, level);
        levelSize = private->_levelIndex[level].byteLength;

#if DUMP_IMAGE
        if (!This->isCompressed) {
            for (layer = 0; layer < This->numLayers; layer++) {
                ktx_uint32_t faceSlice;
                for (faceSlice = 0; faceSlice < numImages; faceSlice++) {
                    dststr->getsize(dststr, &pos);
                    fprintf(stdout, "Writing level %d, layer %d, faceSlice %d to baseOffset %#zx\n",
                            level, layer, faceSlice, pos);
                    for (uint32_t y = 0; y < (This->baseHeight >> level); y++) {
                        for (uint32_t x = 0; x < rowBytes; x++) {
                            fprintf(stdout, "%#x, ",
                                    *(This->pData + srcOffset + y * rowBytes + x));
                        }
                        fprintf(stdout, "\n");
                    }
                }
            }
        }
        fprintf(stdout, "\n");
#endif
        // Write entire level.
        result = dststr->write(dststr, This->pData + srcLevelOffset,
                               levelSize, 1);
        if (result == KTX_SUCCESS && level > 0) { // No padding at end.
            ktx_uint32_t levelPadLen
                       = _KTX_PADN_LEN(This->_private->_requiredLevelAlignment,
                                       levelSize);
            if (levelPadLen != 0)
              result = dststr->write(dststr, padding, 1, levelPadLen);
        }
    }

    return result;
}

/**
 * @memberof ktxTexture2
 * @~English
 * @brief Write a ktxTexture object to a stdio stream in KTX format.
 *
 * Callers are strongly urged to include a KTXwriter item in the texture's
 * metadata. It can be added by code, similar to the following, prior to
 * calling this function.
 * @code
 *     char writer[100];
 *     snprintf(writer, sizeof(writer), "%s version %s", appName, appVer);
 *     ktxHashList_AddKVPair(&texture->kvDataHead, KTX_WRITER_KEY,
 *                           (ktx_uint32_t)strlen(writer) + 1,
 *                           writer);
 * @endcode
 *
 * @param[in] This      pointer to the target ktxTexture object.
 * @param[in] dstsstr   destination stdio stream.
 *
 * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
 *
 * @exception KTX_INVALID_VALUE @p This or @p dstsstr is NULL.
 * @exception KTX_INVALID_OPERATION
 *                              The ktxTexture does not contain any image data.
 * @exception KTX_INVALID_OPERATION
 *                              Both kvDataHead and kvData are set in the
 *                              ktxTexture
 * @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
 *                              the system.
 * @exception KTX_FILE_WRITE_ERROR
 *                              An error occurred while writing the file.
 */
KTX_error_code
ktxTexture2_WriteToStdioStream(ktxTexture2* This, FILE* dstsstr)
{
    ktxStream stream;
    KTX_error_code result = KTX_SUCCESS;

    if (!This)
        return KTX_INVALID_VALUE;

    result = ktxFileStream_construct(&stream, dstsstr, KTX_FALSE);
    if (result != KTX_SUCCESS)
        return result;

    return ktxTexture2_WriteToStream(This, &stream);
}

/**
 * @memberof ktxTexture2
 * @~English
 * @brief Write a ktxTexture object to a named file in KTX format.
 *
 * The file name must be encoded in utf-8. On Windows convert unicode names
 * to utf-8 with @c WideCharToMultiByte(CP_UTF8, ...) before calling.
 *
 * Callers are strongly urged to include a KTXwriter item in the texture's
 * metadata. It can be added by code, similar to the following, prior to
 * calling this function.
 * @code
 *     char writer[100];
 *     snprintf(writer, sizeof(writer), "%s version %s", appName, appVer);
 *     ktxHashList_AddKVPair(&texture->kvDataHead, KTX_WRITER_KEY,
 *                           (ktx_uint32_t)strlen(writer) + 1,
 *                           writer);
 * @endcode
 *
 * @param[in] This      pointer to the target ktxTexture object.
 * @param[in] dstname   destination file name.
 *
 * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
 *
 * @exception KTX_INVALID_VALUE @p This or @p dstname is NULL.
 * @exception KTX_INVALID_OPERATION
 *                              The ktxTexture does not contain any image data.
 * @exception KTX_INVALID_OPERATION
 *                              Both kvDataHead and kvData are set in the
 *                              ktxTexture
 * @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
 *                              the system.
 * @exception KTX_FILE_WRITE_ERROR
 *                              An error occurred while writing the file.
 */
KTX_error_code
ktxTexture2_WriteToNamedFile(ktxTexture2* This, const char* const dstname)
{
    KTX_error_code result;
    FILE* dst;

    if (!This)
        return KTX_INVALID_VALUE;

    dst = ktxFOpenUTF8(dstname, "wb");
    if (dst) {
        result = ktxTexture2_WriteToStdioStream(This, dst);
        fclose(dst);
    } else
        result = KTX_FILE_OPEN_FAILED;

    return result;
}

/**
 * @memberof ktxTexture2
 * @~English
 * @brief Write a ktxTexture object to block of memory in KTX format.
 *
 * Memory is allocated by the function and the caller is responsible for
 * freeing it.
 *
 * Callers are strongly urged to include a KTXwriter item in the texture's
 * metadata. It can be added by code, similar to the following, prior to
 * calling this function.
 * @code
 *     char writer[100];
 *     snprintf(writer, sizeof(writer), "%s version %s", appName, appVer);
 *     ktxHashList_AddKVPair(&texture->kvDataHead, KTX_WRITER_KEY,
 *                           (ktx_uint32_t)strlen(writer) + 1,
 *                           writer);
 * @endcode
 *
 * @param[in]     This       pointer to the target ktxTexture object.
 * @param[in,out] ppDstBytes pointer to location to write the address of
 *                           the destination memory. The Application is
 *                           responsible for freeing this memory.
 * @param[in,out] pSize      pointer to location to write the size in bytes of
 *                           the KTX data.
 *
 * @return      KTX_SUCCESS on success, other KTX_* enum values on error.
 *
 * @exception KTX_INVALID_VALUE @p This, @p ppDstBytes or @p pSize is NULL.
 * @exception KTX_INVALID_OPERATION
 *                              The ktxTexture does not contain any image data.
 * @exception KTX_INVALID_OPERATION
 *                              Both kvDataHead and kvData are set in the
 *                              ktxTexture
 * @exception KTX_FILE_OVERFLOW The file exceeded the maximum size supported by
 *                              the system.
 * @exception KTX_FILE_WRITE_ERROR
 *                              An error occurred while writing the file.
 */
KTX_error_code
ktxTexture2_WriteToMemory(ktxTexture2* This,
                          ktx_uint8_t** ppDstBytes, ktx_size_t* pSize)
{
    struct ktxStream dststr;
    KTX_error_code result;
    ktx_size_t strSize;

    if (!This || !ppDstBytes || !pSize)
        return KTX_INVALID_VALUE;

    *ppDstBytes = NULL;

    result = ktxMemStream_construct(&dststr, KTX_FALSE);
    if (result != KTX_SUCCESS)
        return result;

    result = ktxTexture2_WriteToStream(This, &dststr);
    if(result != KTX_SUCCESS)
    {
        ktxMemStream_destruct(&dststr);
        return result;
    }

    ktxMemStream_getdata(&dststr, ppDstBytes);
    dststr.getsize(&dststr, &strSize);
    *pSize = (GLsizei)strSize;
    /* This function does not free the memory pointed at by the
     * value obtained from ktxMemStream_getdata() thanks to the
     * KTX_FALSE passed to the constructor above.
     */
    ktxMemStream_destruct(&dststr);
    return KTX_SUCCESS;

}

/**
 * @memberof ktxTexture2
 * @~English
 * @brief Deflate the data in a ktxTexture2 object using Zstandard.
 *
 * The texture's levelIndex, dataSize, DFD, data pointer, and supercompressionScheme will
 * all be updated after successful deflation to reflect the deflated data.
 *
 * @param[in] This pointer to the ktxTexture2 object of interest.
 * @param[in] compressionLevel set speed vs compression ratio trade-off. Values
 *            between 1 and 22 are accepted. The lower the level the faster. Values
 *            above 20 should be used with caution as they require more memory.
 */
KTX_error_code
ktxTexture2_DeflateZstd(ktxTexture2* This, ktx_uint32_t compressionLevel)
{
    ktx_uint32_t levelIndexByteLength =
                            This->numLevels * sizeof(ktxLevelIndexEntry);
    ktx_uint8_t* workBuf;
    ktx_uint8_t* cmpData;
    ktx_size_t dstRemainingByteLength = 0;
    ktx_size_t byteLengthCmp = 0;
    ktx_size_t levelOffset = 0;
    ktxLevelIndexEntry* cindex = This->_private->_levelIndex;
    ktxLevelIndexEntry* nindex;
    ktx_uint8_t* pCmpDst;
    ktx_error_code_e result;

    ZSTD_CCtx* cctx = ZSTD_createCCtx();
    if (cctx == NULL)
        return KTX_OUT_OF_MEMORY;

    if (This->supercompressionScheme != KTX_SS_NONE)
        return KTX_INVALID_OPERATION;

    // On rare occasions the deflated data can be a few bytes larger than
    // the source data. Calculating the dst buffer size using
    // ZSTD_compressBound provides a suitable size plus compression is said
    // to run faster when the dst buffer is >= compressBound.
    for (int32_t level = This->numLevels - 1; level >= 0; level--) {
        dstRemainingByteLength += ZSTD_compressBound(cindex[level].byteLength);
    }

    workBuf = malloc(dstRemainingByteLength + levelIndexByteLength);
    if (workBuf == NULL) {
        result = KTX_OUT_OF_MEMORY;
        goto cleanup;
    }
    nindex = (ktxLevelIndexEntry*)workBuf;
    pCmpDst = &workBuf[levelIndexByteLength];

    for (int32_t level = This->numLevels - 1; level >= 0; level--) {
        size_t levelByteLengthCmp =
            ZSTD_compressCCtx(cctx, pCmpDst + levelOffset,
                              dstRemainingByteLength,
                              &This->pData[cindex[level].byteOffset],
                              cindex[level].byteLength,
                              compressionLevel);
        if (ZSTD_isError(levelByteLengthCmp)) {
            free(workBuf);
            ZSTD_ErrorCode error = ZSTD_getErrorCode(levelByteLengthCmp);
            switch(error) {
              case ZSTD_error_parameter_outOfBound:
                result = KTX_INVALID_VALUE;
                goto cleanup;
              case ZSTD_error_dstSize_tooSmall:
#ifdef DEBUG
                assert(false && "Deflate dstSize too small.");
#else
                result = KTX_OUT_OF_MEMORY;
                goto cleanup;
#endif
              case ZSTD_error_workSpace_tooSmall:
#ifdef DEBUG
                assert(false && "Deflate workspace too small.");
#else
                result = KTX_OUT_OF_MEMORY;
                goto cleanup;
#endif
              case ZSTD_error_memory_allocation:
                result = KTX_OUT_OF_MEMORY;
                goto cleanup;
              default:
                // The remaining errors look like they should only
                // occur during decompression but just in case.
#ifdef DEBUG
                assert(true);
#else
                result = KTX_INVALID_OPERATION;
                goto cleanup;
#endif
            }
        }
        nindex[level].byteOffset = levelOffset;
        nindex[level].uncompressedByteLength = cindex[level].byteLength;
        nindex[level].byteLength = levelByteLengthCmp;
        byteLengthCmp += levelByteLengthCmp;
        levelOffset += levelByteLengthCmp;
        dstRemainingByteLength -= levelByteLengthCmp;
    }
    ZSTD_freeCCtx(cctx);

    // Move the compressed data into a correctly sized buffer.
    cmpData = malloc(byteLengthCmp);
    if (cmpData == NULL) {
        free(workBuf);
        return KTX_OUT_OF_MEMORY;
    }
    // Now modify the texture.
    memcpy(cmpData, pCmpDst, byteLengthCmp); // Copy data to sized buffer.
    memcpy(cindex, nindex, levelIndexByteLength); // Update level index
    free(workBuf);
    free(This->pData);
    This->pData = cmpData;
    This->dataSize = byteLengthCmp;
    This->supercompressionScheme = KTX_SS_ZSTD;
    This->_private->_requiredLevelAlignment = 1;
    // Clear bytesPlane to indicate we're now unsized.
    uint32_t* bdb = This->pDfd + 1;
    bdb[KHR_DF_WORD_BYTESPLANE0] = 0; /* bytesPlane3..0 = 0 */

    return KTX_SUCCESS;

cleanup:
    ZSTD_freeCCtx(cctx);
    free(workBuf);
    return result;
}

/**
 * @memberof ktxTexture2
 * @~English
 * @brief Deflate the data in a ktxTexture2 object using miniz (ZLIB).
 *
 * The texture's levelIndex, dataSize, DFD, data pointer, and supercompressionScheme will
 * all be updated after successful deflation to reflect the deflated data.
 *
 * @param[in] This pointer to the ktxTexture2 object of interest.
 * @param[in] compressionLevel set speed vs compression ratio trade-off. Values
 *            between 1 and 9 are accepted. The lower the level the faster.
 */
KTX_error_code
ktxTexture2_DeflateZLIB(ktxTexture2* This, ktx_uint32_t compressionLevel)
{
    ktx_uint32_t levelIndexByteLength =
                            This->numLevels * sizeof(ktxLevelIndexEntry);
    ktx_uint8_t* workBuf;
    ktx_uint8_t* cmpData;
    ktx_size_t dstRemainingByteLength = 0;
    ktx_size_t byteLengthCmp = 0;
    ktx_size_t levelOffset = 0;
    ktxLevelIndexEntry* cindex = This->_private->_levelIndex;
    ktxLevelIndexEntry* nindex;
    ktx_uint8_t* pCmpDst;

    if (This->supercompressionScheme != KTX_SS_NONE)
        return KTX_INVALID_OPERATION;

    // On rare occasions the deflated data can be a few bytes larger than
    // the source data. Calculating the dst buffer size using
    // mz_deflateBound provides a conservative size to account for that.
    for (int32_t level = This->numLevels - 1; level >= 0; level--) {
        dstRemainingByteLength += ktxCompressZLIBBounds(cindex[level].byteLength);
    }

    workBuf = malloc(dstRemainingByteLength + levelIndexByteLength);
    if (workBuf == NULL)
        return KTX_OUT_OF_MEMORY;
    nindex = (ktxLevelIndexEntry*)workBuf;
    pCmpDst = &workBuf[levelIndexByteLength];

    for (int32_t level = This->numLevels - 1; level >= 0; level--) {
        size_t levelByteLengthCmp = dstRemainingByteLength;
        KTX_error_code result = ktxCompressZLIBInt(pCmpDst + levelOffset,
                                                    &levelByteLengthCmp,
                                                    &This->pData[cindex[level].byteOffset],
                                                    cindex[level].byteLength,
                                                    compressionLevel);
        if (result != KTX_SUCCESS) {
            free(workBuf);
            return result;
        }

        nindex[level].byteOffset = levelOffset;
        nindex[level].uncompressedByteLength = cindex[level].byteLength;
        nindex[level].byteLength = levelByteLengthCmp;
        byteLengthCmp += levelByteLengthCmp;
        levelOffset += levelByteLengthCmp;
        dstRemainingByteLength -= levelByteLengthCmp;
    }

    // Move the compressed data into a correctly sized buffer.
    cmpData = malloc(byteLengthCmp);
    if (cmpData == NULL) {
        free(workBuf);
        return KTX_OUT_OF_MEMORY;
    }
    // Now modify the texture.
    memcpy(cmpData, pCmpDst, byteLengthCmp); // Copy data to sized buffer.
    memcpy(cindex, nindex, levelIndexByteLength); // Update level index
    free(workBuf);
    free(This->pData);
    This->pData = cmpData;
    This->dataSize = byteLengthCmp;
    This->supercompressionScheme = KTX_SS_ZLIB;
    This->_private->_requiredLevelAlignment = 1;
    // Clear bytesPlane to indicate we're now unsized.
    uint32_t* bdb = This->pDfd + 1;
    bdb[KHR_DF_WORD_BYTESPLANE0] = 0; /* bytesPlane3..0 = 0 */

    return KTX_SUCCESS;
}

/** @} */