/*

 * 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));

            }

            if (memcpy_s(buffer.data() + start, buffer.size() - start, data.buffer, size) != EOK) {

                PATCH_LOGE("Failed to memcpy_s data buffer.size() = %zu size = %zu start = %zu",

                    buffer.size(), size, start);

                return -1;

            }

            return 0;

        }, 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