///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// DxilCompression.cpp                                                       //
// Copyright (C) Microsoft Corporation. All rights reserved.                 //
// This file is distributed under the University of Illinois Open Source     //
// License. See LICENSE.TXT for details.                                     //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
//
// Helper wrapper functions for zlib deflate and inflate. Entirely
// self-contained, only depends on IMalloc interface.
//

#include "dxc/DxilCompression/DxilCompression.h"
#include "dxc/Support/Global.h"
#include "dxc/Support/WinIncludes.h"

#include "miniz.h"
typedef size_t ZlibSize_t;
typedef const Bytef ZlibInputBytesf;

namespace {
//
// A resource managment class for a zlib stream that calls the appropriate init
// and end routines.
//
class Zlib
{
public:
    enum Operation {INFLATE, DEFLATE};
    Zlib(Operation Op, IMalloc *pAllocator)
        : m_Stream{}
        , m_Op(Op)
        , m_Initalized(false)
    {
        m_Stream = {};

        if (pAllocator)
        {
            m_Stream.zalloc = ZAlloc;
            m_Stream.zfree = ZFree;
            m_Stream.opaque = pAllocator;
        }

        int ret = Z_ERRNO;
        if (Op == INFLATE)
        {
            ret = inflateInit(&m_Stream);
        }
        else
        {
            ret = deflateInit(&m_Stream, Z_DEFAULT_COMPRESSION);
        }

        if (ret != Z_OK)
        {
            m_InitializationResult = TranslateZlibResult(ret);
            return;
        }

        m_Initalized = true;
    }

    ~Zlib()
    {
        if (m_Initalized)
        {
            if (m_Op == INFLATE)
            {
                inflateEnd(&m_Stream);
            }
            else
            {
                deflateEnd(&m_Stream);
            }
        }
    }

    z_stream *GetStream()
    {
        if (m_Initalized)
            return &m_Stream;
        return nullptr;
    }

    hlsl::ZlibResult GetInitializationResult() const {
        return m_InitializationResult;
    }

    static hlsl::ZlibResult TranslateZlibResult(int zlibResult) {
        switch (zlibResult) {
        default:
            return hlsl::ZlibResult::InvalidData;
        case Z_MEM_ERROR:
        case Z_BUF_ERROR:
            return hlsl::ZlibResult::OutOfMemory;
        }
    }

private:
    z_stream m_Stream;
    Operation m_Op;
    bool m_Initalized;
    hlsl::ZlibResult m_InitializationResult = hlsl::ZlibResult::Success;

    static
    void *ZAlloc(void *context, ZlibSize_t items, ZlibSize_t size)
    {
        IMalloc *mallocif = (IMalloc *)context;
        return mallocif->Alloc(items * size);
    }

    static
    void ZFree(void *context, void *pointer)
    {
        IMalloc *mallocif = (IMalloc *)context;
        mallocif->Free(pointer);
    }
};

} // namespace

hlsl::ZlibResult hlsl::ZlibDecompress(IMalloc *pMalloc, const void *pCompressedBuffer, size_t BufferSizeInBytes, void *pUncompressedBuffer, size_t UncompressedBufferSize) {
  Zlib zlib(Zlib::INFLATE, pMalloc);
  z_stream *pStream = zlib.GetStream();
  if (!pStream)
    return zlib.GetInitializationResult();

  pStream->avail_in = BufferSizeInBytes;
  pStream->next_in = (ZlibInputBytesf *)pCompressedBuffer;
  pStream->next_out = (Byte*)pUncompressedBuffer;
  pStream->avail_out = UncompressedBufferSize;

  // Compression should finish in one call because of the call to deflateBound.
  int status = inflate(pStream, Z_FINISH);
  if (status != Z_STREAM_END) {
    return Zlib::TranslateZlibResult(status);
  }

  return ZlibResult::Success;
}

hlsl::ZlibResult hlsl::ZlibCompress(IMalloc *pMalloc,
  const void *pData, size_t pDataSize,
  void *pUserData,
  ZlibCallbackFn *Callback,
  size_t *pOutCompressedSize)
{
  Zlib zlib(Zlib::DEFLATE, pMalloc);
  z_stream *pStream = zlib.GetStream();
  if (!pStream)
    return zlib.GetInitializationResult();

  const size_t UpperBound = deflateBound(pStream, pDataSize);
  void *pDestBuffer = Callback(pUserData, UpperBound);
  if (!pDestBuffer)
    return ZlibResult::OutOfMemory;

  pStream->next_in = (ZlibInputBytesf *)pData;
  pStream->avail_in = pDataSize;
  pStream->next_out = (Byte *)pDestBuffer;
  pStream->avail_out = UpperBound;

  int status = deflate(pStream, Z_FINISH);
  if (status != Z_STREAM_END) {
    return Zlib::TranslateZlibResult(status);
  }

  *pOutCompressedSize = pStream->total_out;
  return ZlibResult::Success;
}

