/*
 * Copyright (c) 2021 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 "update_diff.h"
#include <cstdlib>
#include <memory>
#include "image_diff.h"
#include "pkg_manager.h"

using namespace Hpackage;

namespace UpdatePatch {
#ifdef __WIN32
#undef ERROR
#endif

ImageParser::~ImageParser()
{
    Hpackage::PkgManager::ReleasePackageInstance(pkgManager_);
    pkgManager_ = nullptr;
}

int32_t ImageParser::GetPkgBuffer(BlockBuffer &buffer) const
{
    int32_t ret = -1;
    Hpackage::PkgBuffer pkgBuffer {};
    if (stream_ != nullptr) {
        ret = stream_->GetBuffer(pkgBuffer);
        buffer.buffer = pkgBuffer.buffer;
        buffer.length = pkgBuffer.length;
    }
    return ret;
}

const Hpackage::FileInfo *ImageParser::GetFileInfo(const std::string &fileName) const
{
    if (pkgManager_ != nullptr) {
        return pkgManager_->GetFileInfo(fileName);
    }
    return nullptr;
}

int32_t ImageParser::Parse(const std::string &packageName)
{
    pkgManager_ = Hpackage::PkgManager::CreatePackageInstance();
    if (pkgManager_ == nullptr) {
        PATCH_LOGE("Failed to get pkg manager");
        return PATCH_INVALID_PARAM;
    }
    int32_t ret = PatchMapFile(packageName, memMap_);
    if (ret != 0) {
        PATCH_LOGE("Failed to read file");
        return -1;
    }

    PkgBuffer buffer {memMap_.memory, memMap_.length};
    ret = pkgManager_->CreatePkgStream(stream_, packageName, buffer);
    if (ret != 0) {
        PATCH_LOGE("Failed to create pkg stream");
        return -1;
    }

    // parse img and get image type
    type_ = PKG_PACK_TYPE_ZIP;
    ret = pkgManager_->ParsePackage(stream_, fileIds_, type_);
    if (ret == 0) {
        return 0;
    }
    type_ = PKG_PACK_TYPE_LZ4;
    ret = pkgManager_->ParsePackage(stream_, fileIds_, type_);
    if (ret == 0) {
        return 0;
    }
    type_ = PKG_PACK_TYPE_GZIP;
    ret = pkgManager_->ParsePackage(stream_, fileIds_, type_);
    if (ret == 0) {
        return 0;
    }
    type_ = PKG_PACK_TYPE_NONE;
    return 0;
}

int32_t ImageParser::Extract(const std::string &fileName, std::vector<uint8_t> &buffer)
{
    PATCH_DEBUG("ImageParser::Extract %s", fileName.c_str());
    if (pkgManager_ == nullptr) {
        PATCH_LOGE("Failed to get pkg manager");
        return PATCH_INVALID_PARAM;
    }
    size_t bufferSize = 0;
    Hpackage::PkgManager::StreamPtr outStream = nullptr;
    int32_t ret = pkgManager_->CreatePkgStream(outStream, fileName,
        [&buffer, &bufferSize](const PkgBuffer &data, size_t size,
            size_t start, bool isFinish, const void *context) ->int {
            if (isFinish) {
                return 0;
            }
            bufferSize += size;
            if ((start + bufferSize) > buffer.size()) {
                buffer.resize(IGMDIFF_LIMIT_UNIT * ((start + bufferSize) / IGMDIFF_LIMIT_UNIT + 1));
            }
            return memcpy_s(buffer.data() + start, buffer.size(), data.buffer, size);
        }, nullptr);
    if (ret != 0) {
        PATCH_LOGE("Failed to extract data");
        return -1;
    }

    ret = pkgManager_->ExtractFile(fileName, outStream);
    pkgManager_->ClosePkgStream(outStream);

    const FileInfo *fileInfo = pkgManager_->GetFileInfo(fileName);
    if (fileInfo == nullptr) {
        PATCH_LOGE("Failed to get file info");
        return -1;
    }
    if (fileInfo->unpackedSize != bufferSize) {
        PATCH_LOGE("Failed to check uncompress data size %zu %zu", fileInfo->unpackedSize, bufferSize);
        return -1;
    }
    return ret;
}

int32_t UpdateDiff::MakePatch(const std::string &oldFileName,
    const std::string &newFileName, const std::string &patchFileName)
{
    if (blockDiff_) {
        return BlocksDiff::MakePatch(oldFileName, newFileName, patchFileName);
    }

    newParser_.reset(new ImageParser());
    oldParser_.reset(new ImageParser());
    if (newParser_ == nullptr || oldParser_ == nullptr) {
        PATCH_LOGE("Failed to create parser");
        return -1;
    }

    if (newParser_->Parse(newFileName) != 0 || oldParser_->Parse(oldFileName) != 0) {
        PATCH_LOGE("Failed to parse image");
        return -1;
    }
    std::unique_ptr<ImageDiff> imageDiff = nullptr;
    PATCH_DEBUG("UpdateDiff::MakePatch type: %d %d", newParser_->GetType(), oldParser_->GetType());
    if (newParser_->GetType() != oldParser_->GetType()) {
        imageDiff.reset(new ImageDiff(limit_, newParser_.get(), oldParser_.get()));
        if (imageDiff == nullptr) {
            PATCH_LOGE("Failed to diff file");
            return -1;
        }
        return imageDiff->MakePatch(patchFileName);
    }

    switch (newParser_->GetType()) {
        case PKG_PACK_TYPE_ZIP:
            imageDiff.reset(new ZipImageDiff(limit_, newParser_.get(), oldParser_.get()));
            break;
        case PKG_PACK_TYPE_LZ4:
            imageDiff.reset(new Lz4ImageDiff(limit_, newParser_.get(), oldParser_.get()));
            break;
        case PKG_PACK_TYPE_GZIP:
            imageDiff.reset(new GZipImageDiff(limit_, newParser_.get(), oldParser_.get()));
            break;
        default:
            imageDiff.reset(new ImageDiff(limit_, newParser_.get(), oldParser_.get()));
            break;
    }
    if (imageDiff == nullptr) {
        PATCH_LOGE("Failed to diff file");
        return -1;
    }
    return imageDiff->MakePatch(patchFileName);
}

int32_t UpdateDiff::DiffImage(size_t limit, const std::string &oldFileName,
    const std::string &newFileName, const std::string &patchFileName)
{
    auto updateDiff = std::make_unique<UpdateDiff>(limit, false);
    if (updateDiff == nullptr) {
        PATCH_LOGE("Failed to create update diff");
        return -1;
    }
    return updateDiff->MakePatch(oldFileName, newFileName, patchFileName);
}

int32_t UpdateDiff::DiffBlock(const std::string &oldFileName,
    const std::string &newFileName, const std::string &patchFileName)
{
    auto updateDiff = std::make_unique<UpdateDiff>(0, true);
    if (updateDiff == nullptr) {
        PATCH_LOGE("Failed to create update diff");
        return -1;
    }
    return updateDiff->MakePatch(oldFileName, newFileName, patchFileName);
}
} // namespace UpdatePatch
