/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "zip_ffi.h"

#include <fcntl.h>
#include <string>
#include <vector>

#include "app_log_wrapper.h"
#include "bundle_error.h"
#include "cj_common_ffi.h"
#include "cj_zip.h"
#include "common_func.h"
#include "directory_ex.h"
#include "file_path.h"
#include "zip_reader.h"
#include "zip_utils.h"
#include "zlib.h"

using namespace OHOS::CJSystemapi::BundleManager;

namespace OHOS {
namespace AppExecFwk {
namespace LIBZIP {
static inline ErrCode ZlibBusinessError(int32_t errCode)
{
    if (errCode == Z_STREAM_ERROR) {
        return ERR_ZLIB_ZSTREAM_ERROR;
    } else if (errCode == Z_MEM_ERROR) {
        return ERR_ZLIB_MEMORY_ALLOC_FAILED;
    } else if (errCode == Z_BUF_ERROR) {
        return ERR_ZLIB_BUFFER_ERROR;
    } else {
        return ERR_ZLIB_INTERNAL_STRUCT_ERROR;
    }
}

struct CjZipEntity {
    int32_t Compress(ZipCompressParam* param);
    int32_t Compress2(ZipCompressParam* param);
    int32_t CompressBound(int32_t sourceLen);
    int32_t UnCompress(ZipCompressParam* param);

    void GetZStream(CZStream* cStrm, int32_t* errCode);
    int32_t Deflate(CZStream* cStrm, int32_t flush, int32_t* errCode);
    int32_t DeflateEnd(CZStream* cStrm, int32_t* errCode);
    int32_t DeflateInit2(CZStream* cStrm, DeflateInit2Param* param, int32_t* errCode);

    int32_t Inflate(CZStream* cStrm, int32_t flush, int32_t* errCode);
    int32_t InflateEnd(CZStream* cStrm, int32_t* errCode);
    int32_t InflateInit(CZStream* cStrm, int32_t* errCode);
    int32_t InflateInit2(CZStream* cStrm, int32_t windowBits, int32_t* errCode);

    void InitialZStream(CZStream* cStrm);
    void SetZStreamValue(CZStream* cStrm);

    std::unique_ptr<z_stream> zs = {};
};

int32_t CjZipEntity::Compress(ZipCompressParam* param)
{
    if (param->destBuf == nullptr || param->srcBuf == nullptr) {
        APP_LOGE("destBuf or srcBuf is nullptr");
        return ERR_ZLIB_INTERNAL_STRUCT_ERROR;
    }
    uLong destLen = static_cast<uLong>(param->destBufLen);
    int32_t status =
        compress(static_cast<Bytef*>(param->destBuf), &destLen, static_cast<Bytef*>(param->srcBuf), param->srcBufLen);
    if (status < 0) {
        APP_LOGE("compress return error: %{public}d", status);
        return ZlibBusinessError(status);
    }
    param->outStatus = status;
    param->outDestLen = static_cast<int64_t>(destLen);
    return 0;
}

int32_t CjZipEntity::Compress2(ZipCompressParam* param)
{
    if (param->destBuf == nullptr || param->srcBuf == nullptr) {
        APP_LOGE("destBuf or srcBuf is nullptr");
        return ERR_ZLIB_INTERNAL_STRUCT_ERROR;
    }
    uLong destLen = static_cast<uLong>(param->destBufLen);
    int32_t status = compress2(static_cast<Bytef*>(param->destBuf), &destLen, static_cast<Bytef*>(param->srcBuf),
        param->srcBufLen, param->level);
    if (status < 0) {
        APP_LOGE("compress2 return error: %{public}d", status);
        return ZlibBusinessError(status);
    }
    param->outStatus = status;
    param->outDestLen = static_cast<int64_t>(destLen);
    return 0;
}

int32_t CjZipEntity::CompressBound(int32_t sourceLen)
{
    return static_cast<int32_t>(compressBound(sourceLen));
}

int32_t CjZipEntity::UnCompress(ZipCompressParam* param)
{
    if (param->destBuf == nullptr || param->srcBuf == nullptr) {
        APP_LOGE("destBuf or srcBuf is nullptr");
        return ERR_ZLIB_INTERNAL_STRUCT_ERROR;
    }
    uLong destLen = static_cast<uLong>(param->destBufLen);
    int32_t status =
        uncompress(static_cast<Bytef*>(param->destBuf), &destLen, static_cast<Bytef*>(param->srcBuf), param->srcBufLen);
    if (status < 0) {
        APP_LOGE("uncompress return error: %{public}d", status);
        return ZlibBusinessError(status);
    }
    param->outStatus = status;
    param->outDestLen = static_cast<int64_t>(destLen);
    return 0;
}

void CjZipEntity::GetZStream(CZStream* cStrm, int32_t* errCode)
{
    if (zs == nullptr) {
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        APP_LOGE("GetZStream zs is nullptr");
        return;
    }
    cStrm->availableIn = zs->avail_in;
    cStrm->totalIn = zs->total_in;
    cStrm->availableOut = zs->avail_out;
    cStrm->totalOut = zs->total_out;
    cStrm->dataType = zs->data_type;
    cStrm->adler = zs->adler;
}

int32_t CjZipEntity::Deflate(CZStream* cStrm, int32_t flush, int32_t* errCode)
{
    SetZStreamValue(cStrm);
    int32_t status = deflate(zs.get(), flush);
    if (status < 0) {
        APP_LOGE("deflate return error: %{public}d", status);
        *errCode = ZlibBusinessError(status);
    }
    return status;
}

int32_t CjZipEntity::DeflateEnd(CZStream* cStrm, int32_t* errCode)
{
    SetZStreamValue(cStrm);
    int32_t status = deflateEnd(zs.get());
    if (status < 0) {
        APP_LOGE("deflateEnd return error: %{public}d", status);
        *errCode = ZlibBusinessError(status);
    }
    return status;
}

int32_t CjZipEntity::DeflateInit2(CZStream* cStrm, DeflateInit2Param* param, int32_t* errCode)
{
    zs = std::make_unique<z_stream>();
    InitialZStream(cStrm);
    int32_t status =
        deflateInit2(zs.get(), param->level, param->method, param->windowBits, param->memLevel, param->strategy);
    if (status < 0) {
        APP_LOGE("deflateInit2 return error: %{public}d", status);
        *errCode = ZlibBusinessError(status);
    }
    return status;
}

int32_t CjZipEntity::Inflate(CZStream* cStrm, int32_t flush, int32_t* errCode)
{
    SetZStreamValue(cStrm);
    int32_t status = inflate(zs.get(), flush);
    if (status < 0) {
        APP_LOGE("inflate return error: %{public}d", status);
        *errCode = ZlibBusinessError(status);
    }
    return status;
}

int32_t CjZipEntity::InflateEnd(CZStream* cStrm, int32_t* errCode)
{
    SetZStreamValue(cStrm);
    int32_t status = inflateEnd(zs.get());
    if (status < 0) {
        APP_LOGE("inflateEnd return error: %{public}d", status);
        *errCode = ZlibBusinessError(status);
    }
    return status;
}

int32_t CjZipEntity::InflateInit(CZStream* cStrm, int32_t* errCode)
{
    zs = std::make_unique<z_stream>();
    InitialZStream(cStrm);
    int32_t status = inflateInit(zs.get());
    if (status < 0) {
        APP_LOGE("inflateInit return error: %{public}d", status);
        *errCode = ZlibBusinessError(status);
    }
    return status;
}

int32_t CjZipEntity::InflateInit2(CZStream* cStrm, int32_t windowBits, int32_t* errCode)
{
    zs = std::make_unique<z_stream>();
    InitialZStream(cStrm);
    int32_t status = inflateInit2(zs.get(), windowBits);
    if (status < 0) {
        APP_LOGE("inflateInit2 return error: %{public}d", status);
        *errCode = ZlibBusinessError(status);
    }
    return status;
}

void CjZipEntity::InitialZStream(CZStream* cStrm)
{
    if (cStrm->hasNextIn) {
        zs->next_in = reinterpret_cast<Bytef*>(cStrm->nextIn);
    }
    if (cStrm->hasAvailableIn) {
        zs->avail_in = static_cast<uInt>(cStrm->availableIn);
    }
    if (cStrm->hasTotalIn) {
        zs->total_in = static_cast<uLong>(cStrm->totalIn);
    }
    if (cStrm->hasNextOut) {
        zs->next_out = reinterpret_cast<Bytef*>(cStrm->nextOut);
    }
    if (cStrm->hasAvailableOut) {
        zs->avail_out = static_cast<uInt>(cStrm->availableOut);
    }
    if (cStrm->hasTotalOut) {
        zs->total_out = static_cast<uLong>(cStrm->totalOut);
    }
    if (cStrm->hasDataType) {
        zs->data_type = cStrm->dataType;
    }
    if (cStrm->hasAdler) {
        zs->adler = static_cast<uLong>(cStrm->adler);
    }
}

void CjZipEntity::SetZStreamValue(CZStream* cStrm)
{
    if (!zs) {
        zs = std::make_unique<z_stream>();
    }
    InitialZStream(cStrm);
}

ErrCode GetOriginalSize(PlatformFile zipFd, int64_t& originalSize)
{
    ZipReader reader;
    if (!reader.OpenFromPlatformFile(zipFd)) {
        APP_LOGE("Failed to open, not ZIP format or damaged");
        return ERR_ZLIB_SRC_FILE_FORMAT_ERROR;
    }
    int64_t totalSize = 0;
    while (reader.HasMore()) {
        if (!reader.OpenCurrentEntryInZip()) {
            APP_LOGE("Failed to open the current file in zip");
            return ERR_ZLIB_SRC_FILE_FORMAT_ERROR;
        }
        const FilePath& constEntryPath = reader.CurrentEntryInfo()->GetFilePath();
        FilePath entryPath = constEntryPath;
        if (reader.CurrentEntryInfo()->IsUnsafe()) {
            APP_LOGE("Found an unsafe file in zip");
            return ERR_ZLIB_SRC_FILE_FORMAT_ERROR;
        }
        totalSize += reader.CurrentEntryInfo()->GetOriginalSize();
        if (!reader.AdvanceToNextEntry()) {
            APP_LOGE("Failed to advance to the next file");
            return ERR_ZLIB_SRC_FILE_FORMAT_ERROR;
        }
    }
    originalSize = totalSize;
    return ERR_OK;
}

bool ZipFileIsValid(const std::string& srcFile)
{
    if ((srcFile.size() == 0) || FilePath::HasRelativePathBaseOnAPIVersion(srcFile)) {
        APP_LOGE("srcFile len is 0 or ../");
        return false;
    }
    if (!FilePathCheckValid(srcFile)) {
        APP_LOGE("FilePathCheckValid return false");
        return false;
    }
    FilePath srcFileDir(srcFile);
    if (!FilePath::PathIsValid(srcFileDir)) {
        APP_LOGE("PathIsValid return false");
        return false;
    }
    if (!FilePath::PathIsReadable(srcFileDir)) {
        APP_LOGE("PathIsReadable return false");
        return false;
    }
    return true;
}

ErrCode GetOriginalSize(const std::string& srcFile, int64_t& originalSize)
{
    if (!ZipFileIsValid(srcFile)) {
        return ERR_ZLIB_SRC_FILE_DISABLED;
    }
    FILE* zipFile = fopen(srcFile.c_str(), "rb");
    if (zipFile == nullptr) {
        APP_LOGE("Failed to open file, errno: %{public}d, %{public}s", errno, strerror(errno));
        return ERR_ZLIB_SRC_FILE_DISABLED;
    }
    PlatformFile zipFd = fileno(zipFile);
    ErrCode ret = GetOriginalSize(zipFd, originalSize);
    int32_t retClose = fclose(zipFile);
    if (retClose != 0) {
        APP_LOGE("Failed to close file, errno: %{public}d, %{public}s", errno, strerror(errno));
    }
    return ret;
}

extern "C" {
FFI_EXPORT int32_t FfiBundleManagerCompressFile(const char* inFile, const char* outFile, RetOptions options)
{
    std::string strInFile(inFile);
    std::string strOutFile(outFile);
    int32_t code = ERROR_CODE_ERRNO;
    OPTIONS cOptions;
    cOptions.level = static_cast<COMPRESS_LEVEL>(options.level);
    cOptions.memLevel = static_cast<MEMORY_LEVEL>(options.memLevel);
    cOptions.strategy = static_cast<COMPRESS_STRATEGY>(options.strategy);

    code = Zip(strInFile, strOutFile, cOptions);
    int32_t err = CommonFunc::ConvertErrCode(code);

    return err;
}

FFI_EXPORT int32_t FfiBundleManagerDeCompressFileOptions(const char* inFile, const char* outFile, RetOptions options)
{
    std::string strInFile(inFile);
    std::string strOutFile(outFile);
    int32_t code = ERROR_CODE_ERRNO;
    OPTIONS cOptions;
    cOptions.level = static_cast<COMPRESS_LEVEL>(options.level);
    cOptions.memLevel = static_cast<MEMORY_LEVEL>(options.memLevel);
    cOptions.strategy = static_cast<COMPRESS_STRATEGY>(options.strategy);

    code = UnZip(strInFile, strOutFile, cOptions);
    int32_t err = CommonFunc::ConvertErrCode(code);

    return err;
}

FFI_EXPORT int32_t FfiBundleManagerDeCompressFile(const char* inFile, const char* outFile)
{
    std::string strInFile(inFile);
    std::string strOutFile(outFile);
    int32_t code = ERROR_CODE_ERRNO;
    OPTIONS cOptions;

    code = UnZip(strInFile, strOutFile, cOptions);
    int32_t err = CommonFunc::ConvertErrCode(code);

    return err;
}

FFI_EXPORT void* FfiBundleManagerZipInstCreate()
{
    return new CjZipEntity();
}

FFI_EXPORT void FfiBundleManagerZipInstDestroy(void* ffiInst)
{
    delete static_cast<CjZipEntity*>(ffiInst);
}

FFI_EXPORT void FfiBundleManagerZipCompress(void* ffiInst, ZipCompressParam* param, int32_t* errCode)
{
    *errCode = 0;
    if (ffiInst == nullptr || param == nullptr) {
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        APP_LOGE("FfiBundleManagerZipCompress param check failed");
        return;
    }
    auto entity = static_cast<CjZipEntity*>(ffiInst);
    *errCode = entity->Compress(param);
    return;
}

FFI_EXPORT void FfiBundleManagerZipCompress2(void* ffiInst, ZipCompressParam* param, int32_t* errCode)
{
    *errCode = 0;
    if (ffiInst == nullptr || param == nullptr) {
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        APP_LOGE("FfiBundleManagerZipCompress2 param check failed");
        return;
    }
    auto entity = static_cast<CjZipEntity*>(ffiInst);
    *errCode = entity->Compress2(param);
    return;
}

FFI_EXPORT int32_t FfiBundleManagerZipCompressBound(void* ffiInst, int32_t sourceLen, int32_t* errCode)
{
    *errCode = 0;
    if (ffiInst == nullptr) {
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        APP_LOGE("FfiBundleManagerZipCompressBound param check failed");
        return 0;
    }
    auto entity = static_cast<CjZipEntity*>(ffiInst);
    return entity->CompressBound(sourceLen);
}

FFI_EXPORT int32_t FfiBundleManagerZipDeflate(void* ffiInst, CZStream* cStrm, int32_t flush, int32_t* errCode)
{
    *errCode = 0;
    if (ffiInst == nullptr || cStrm == nullptr) {
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        APP_LOGE("FfiBundleManagerZipDeflate param check failed");
        return 0;
    }
    auto entity = static_cast<CjZipEntity*>(ffiInst);
    return entity->Deflate(cStrm, flush, errCode);
}

FFI_EXPORT int32_t FfiBundleManagerZipDeflateEnd(void* ffiInst, CZStream* cStrm, int32_t* errCode)
{
    *errCode = 0;
    if (ffiInst == nullptr || cStrm == nullptr) {
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        APP_LOGE("FfiBundleManagerZipDeflateEnd param check failed");
        return 0;
    }
    auto entity = static_cast<CjZipEntity*>(ffiInst);
    return entity->DeflateEnd(cStrm, errCode);
}

FFI_EXPORT int32_t FfiBundleManagerZipDeflateInit2(
    void* ffiInst, CZStream* cStrm, DeflateInit2Param* param, int32_t* errCode)
{
    *errCode = 0;
    if (ffiInst == nullptr || cStrm == nullptr || param == nullptr) {
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        APP_LOGE("FfiBundleManagerZipDeflateInit2 param check failed");
        return 0;
    }
    auto entity = static_cast<CjZipEntity*>(ffiInst);
    return entity->DeflateInit2(cStrm, param, errCode);
}

FFI_EXPORT void FfiBundleManagerZipGetZStream(void* ffiInst, CZStream* cStrm, int32_t* errCode)
{
    *errCode = 0;
    if (ffiInst == nullptr || cStrm == nullptr) {
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        APP_LOGE("FfiBundleManagerZipGetZStream param check failed");
        return;
    }
    auto entity = static_cast<CjZipEntity*>(ffiInst);
    return entity->GetZStream(cStrm, errCode);
}

FFI_EXPORT int32_t FfiBundleManagerZipInflate(void* ffiInst, CZStream* cStrm, int32_t flush, int32_t* errCode)
{
    *errCode = 0;
    if (ffiInst == nullptr || cStrm == nullptr) {
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        APP_LOGE("FfiBundleManagerZipInflate param check failed");
        return 0;
    }
    auto entity = static_cast<CjZipEntity*>(ffiInst);
    return entity->Inflate(cStrm, flush, errCode);
}

FFI_EXPORT int32_t FfiBundleManagerZipInflateEnd(void* ffiInst, CZStream* cStrm, int32_t* errCode)
{
    *errCode = 0;
    if (ffiInst == nullptr || cStrm == nullptr) {
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        APP_LOGE("FfiBundleManagerZipInflateEnd param check failed");
        return 0;
    }
    auto entity = static_cast<CjZipEntity*>(ffiInst);
    return entity->InflateEnd(cStrm, errCode);
}

FFI_EXPORT int32_t FfiBundleManagerZipInflateInit(void* ffiInst, CZStream* cStrm, int32_t* errCode)
{
    *errCode = 0;
    if (ffiInst == nullptr || cStrm == nullptr) {
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        APP_LOGE("FfiBundleManagerZipInflateInit param check failed");
        return 0;
    }
    auto entity = static_cast<CjZipEntity*>(ffiInst);
    return entity->InflateInit(cStrm, errCode);
}

FFI_EXPORT int32_t FfiBundleManagerZipInflateInit2(void* ffiInst, CZStream* cStrm, int32_t windowBits, int32_t* errCode)
{
    *errCode = 0;
    if (ffiInst == nullptr || cStrm == nullptr) {
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        APP_LOGE("FfiBundleManagerZipInflateInit2 param check failed");
        return 0;
    }
    auto entity = static_cast<CjZipEntity*>(ffiInst);
    return entity->InflateInit2(cStrm, windowBits, errCode);
}

FFI_EXPORT void FfiBundleManagerZipUnCompress(void* ffiInst, ZipCompressParam* param, int32_t* errCode)
{
    *errCode = 0;
    if (ffiInst == nullptr || param == nullptr) {
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        APP_LOGE("FfiBundleManagerZipUnCompress param check failed");
        return;
    }
    auto entity = static_cast<CjZipEntity*>(ffiInst);
    *errCode = entity->UnCompress(param);
    return;
}

FFI_EXPORT int32_t FfiBundleManagerCompressFiles(CArrString cInFiles, const char* cOutFile, RetOptions cOptions)
{
    if (cInFiles.head == nullptr || cInFiles.size <= 0 || cOutFile == nullptr) {
        APP_LOGE("FfiBundleManagerCompressFiles param check failed");
        return ERR_ZLIB_INTERNAL_STRUCT_ERROR;
    }
    std::vector<std::string> inFiles;
    inFiles.reserve(cInFiles.size);
    for (int64_t i = 0; i < cInFiles.size; i++) {
        if (cInFiles.head[i] == nullptr) {
            APP_LOGE("FfiBundleManagerCompressFiles param check failed");
            return ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        }
        inFiles.push_back(cInFiles.head[i]);
    }
    OPTIONS options;
    options.level = static_cast<COMPRESS_LEVEL>(cOptions.level);
    options.memLevel = static_cast<MEMORY_LEVEL>(cOptions.memLevel);
    options.strategy = static_cast<COMPRESS_STRATEGY>(cOptions.strategy);
    int32_t code = Zips(inFiles, cOutFile, options);
    return CommonFunc::ConvertErrCode(code);
}

FFI_EXPORT int64_t FfiBundleManagerGetOriginalSize(const char* compressedFile, int32_t* errCode)
{
    *errCode = 0;
    if (compressedFile == nullptr) {
        APP_LOGE("FfiBundleManagerGetOriginalSize param check failed");
        *errCode = ERR_ZLIB_INTERNAL_STRUCT_ERROR;
        return 0;
    }

    int64_t originalSize = 0;
    int32_t err = GetOriginalSize(compressedFile, originalSize);
    if (err != ERR_OK) {
        *errCode = CommonFunc::ConvertErrCode(err);
    }
    return originalSize;
}
}
} // LIBZIP
} // AppExecFwk
} // OHOS