/*
 * 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 "patch/update_patch.h"
#include <memory>
#include <vector>
#include "blocks_patch.h"
#include "diffpatch.h"
#include "image_patch.h"
#include "openssl/sha.h"
#include "securec.h"

using namespace Hpackage;
namespace UpdatePatch {
int32_t UpdateApplyPatch::ApplyImagePatch(const PatchParam &param, const std::vector<uint8_t> &bonusData,
    ImageProcessor writer, const std::string& expected)
{
    if (writer == nullptr) {
        PATCH_LOGE("ApplyImagePatch : processor is null");
        return -1;
    }
    std::unique_ptr<ImagePatchWriter> patchWriter = std::make_unique<ImagePatchWriter>(writer, expected, "");
    if (patchWriter == nullptr) {
        PATCH_LOGE("ApplyImagePatch : Failed to create patch writer");
        return -1;
    }
    int32_t ret = patchWriter->Init();
    if (ret != 0) {
        PATCH_LOGE("ApplyImagePatch : Failed to init patch writer");
        return -1;
    }
    ret = ApplyImagePatch(param, patchWriter.get(), bonusData);
    if (ret != 0) {
        PATCH_LOGE("ApplyImagePatch : Failed to apply image patch");
        return -1;
    }
    return patchWriter->Finish();
}

bool UpdateApplyPatch::PreCheck(const PatchParam &param, const UpdatePatchWriterPtr writer)
{
    if (writer == nullptr) {
        PATCH_LOGE("check param fail ");
        return false;
    }
    if (param.patchSize < (std::char_traits<char>::length(PKGDIFF_MAGIC) + sizeof(int32_t))) {
        PATCH_LOGE("patch too short to contain header ");
        return false;
    }
    if (memcmp(param.patch, PKGDIFF_MAGIC, std::char_traits<char>::length(PKGDIFF_MAGIC)) != 0) {
        PATCH_LOGE("corrupt patch file header (magic number) ");
        return false;
    }
    return true;
}

int32_t UpdateApplyPatch::ApplyImagePatch(const PatchParam &param,
    UpdatePatchWriterPtr writer, const std::vector<uint8_t> &bonusData)
{
    if (!PreCheck(param, writer)) {
        return -1;
    }
    size_t offset = std::char_traits<char>::length(PKGDIFF_MAGIC);
    int32_t numChunks = ImagePatch::ReadLE<int32_t>(param.patch + offset);
    offset += sizeof(int32_t);

    std::vector<uint8_t> empty;
    for (int i = 0; i < numChunks; ++i) {
        // each chunk's header record starts with 4 bytes.
        if ((offset + sizeof(int32_t)) > param.patchSize) {
            PATCH_LOGE("Failed to read chunk record ");
            return -1;
        }
        int32_t type = ImagePatch::ReadLE<int32_t>(param.patch + offset);
        PATCH_LOGI("ApplyImagePatch numChunks[%d] type %d offset %d", i, type, offset);
        offset += sizeof(int32_t);
        std::unique_ptr<ImagePatch> imagePatch = nullptr;
        switch (type) {
            case BLOCK_NORMAL:
                imagePatch = std::make_unique<NormalImagePatch>(writer);
                break;
            case BLOCK_RAW:
                imagePatch = std::make_unique<RowImagePatch>(writer);
                break;
            case BLOCK_DEFLATE:
                imagePatch = std::make_unique<ZipImagePatch>(writer, ((i == 1) ? bonusData : empty));
                break;
            case BLOCK_LZ4:
                imagePatch = std::make_unique<Lz4ImagePatch>(writer, ((i == 1) ? bonusData : empty));
                break;
            default:
                break;
        }
        if (imagePatch == nullptr) {
            PATCH_LOGE("Failed to  creareimg patch ");
            return -1;
        }
        int32_t ret = imagePatch->ApplyImagePatch(param, offset);
        if (ret != 0) {
            PATCH_LOGE("Apply image patch fail ");
            return -1;
        }
    }
    return 0;
}

int32_t UpdateApplyPatch::ApplyBlockPatch(const PatchBuffer &patchInfo,
    const BlockBuffer &oldInfo, std::vector<uint8_t> &newData)
{
    std::unique_ptr<BlocksBufferPatch> patch = std::make_unique<BlocksBufferPatch>(patchInfo, oldInfo, newData);
    if (patch == nullptr) {
        PATCH_LOGE("Failed to  creare patch ");
        return -1;
    }
    return patch->ApplyPatch();
}

int32_t UpdateApplyPatch::ApplyBlockPatch(const PatchBuffer &patchInfo,
    const BlockBuffer &oldInfo, UpdatePatchWriterPtr writer)
{
    PkgManager* pkgManager = Hpackage::PkgManager::CreatePackageInstance();
    if (pkgManager == nullptr) {
        PATCH_LOGE("Failed to get pkg manager");
        return -1;
    }

    Hpackage::PkgManager::StreamPtr stream = nullptr;
    int32_t ret = pkgManager->CreatePkgStream(stream, "", {oldInfo.buffer, oldInfo.length});
    if (stream == nullptr || ret != PKG_SUCCESS) {
        PATCH_LOGE("Failed to create stream");
        pkgManager->ClosePkgStream(stream);
        Hpackage::PkgManager::ReleasePackageInstance(pkgManager);
        return -1;
    }

    std::unique_ptr<BlocksStreamPatch> patch = std::make_unique<BlocksStreamPatch>(patchInfo, stream, writer);
    if (patch == nullptr) {
        PATCH_LOGE("Failed to  creare patch ");
        pkgManager->ClosePkgStream(stream);
        Hpackage::PkgManager::ReleasePackageInstance(pkgManager);
        return -1;
    }
    ret = patch->ApplyPatch();
    pkgManager->ClosePkgStream(stream);
    Hpackage::PkgManager::ReleasePackageInstance(pkgManager);
    return ret;
}

int32_t UpdateApplyPatch::ApplyBlockPatch(const PatchBuffer &patchInfo,
    const BlockBuffer &oldInfo, ImageProcessor writer, const std::string& expected)
{
    if (writer == nullptr) {
        PATCH_LOGE("ApplyBlockPatch : processor is null");
        return -1;
    }
    std::unique_ptr<ImagePatchWriter> patchWriter = std::make_unique<ImagePatchWriter>(writer, expected, "");
    if (patchWriter == nullptr) {
        PATCH_LOGE("ApplyBlockPatch : Failed to create patch writer");
        return -1;
    }
    int32_t ret = patchWriter->Init();
    if (ret != 0) {
        PATCH_LOGE("ApplyBlockPatch : Failed to init patch writer");
        return -1;
    }

    PkgManager* pkgManager = Hpackage::PkgManager::CreatePackageInstance();
    if (pkgManager == nullptr) {
        PATCH_LOGE("ApplyBlockPatch ::Failed to get pkg manager");
        return -1;
    }

    Hpackage::PkgManager::StreamPtr stream = nullptr;
    ret = pkgManager->CreatePkgStream(stream, "", {oldInfo.buffer, oldInfo.length});
    if (stream == nullptr) {
        PATCH_LOGE("Failed to create stream");
        pkgManager->ClosePkgStream(stream);
        Hpackage::PkgManager::ReleasePackageInstance(pkgManager);
        return -1;
    }

    std::unique_ptr<BlocksStreamPatch> patch = std::make_unique<BlocksStreamPatch>(patchInfo,
        stream, patchWriter.get());
    if (patch == nullptr) {
        PATCH_LOGE("Failed to  creare patch ");
        pkgManager->ClosePkgStream(stream);
        Hpackage::PkgManager::ReleasePackageInstance(pkgManager);
        return -1;
    }
    ret = patch->ApplyPatch();
    pkgManager->ClosePkgStream(stream);
    Hpackage::PkgManager::ReleasePackageInstance(pkgManager);
    if (ret != 0) {
        PATCH_LOGE("Failed to applay patch ");
        return -1;
    }
    return patchWriter->Finish();
}

int32_t UpdateApplyPatch::ApplyBlockPatch(const PatchBuffer &patchInfo,
    Hpackage::PkgManager::StreamPtr stream, UpdatePatchWriterPtr writer)
{
    std::unique_ptr<BlocksStreamPatch> patch = std::make_unique<BlocksStreamPatch>(patchInfo, stream, writer);
    if (patch == nullptr) {
        PATCH_LOGE("Failed to  creare patch ");
        return -1;
    }
    return patch->ApplyPatch();
}

int32_t UpdateApplyPatch::ApplyPatch(const std::string &patchName,
    const std::string &oldName, const std::string &newName)
{
    PATCH_DEBUG("UpdatePatch::ApplyPatch : %s ", patchName.c_str());
    std::vector<uint8_t> empty;
    MemMapInfo patchData {};
    MemMapInfo oldData {};
    if (PatchMapFile(patchName, patchData) != 0) {
        PATCH_LOGE("ApplyPatch : Failed to read patch file");
        return -1;
    }
    if (PatchMapFile(oldName, oldData) != 0) {
        PATCH_LOGE("ApplyPatch : Failed to read old file");
        return -1;
    }
    PATCH_LOGI("UpdatePatch::ApplyPatch patchData %zu oldData %zu ", patchData.length, oldData.length);
    std::unique_ptr<FilePatchWriter> writer = std::make_unique<FilePatchWriter>(newName);
    if (writer == nullptr) {
        PATCH_LOGE("Failed to create writer");
        return -1;
    }
    writer->Init();

    // check if image patch
    if (patchData.length < std::char_traits<char>::length(PKGDIFF_MAGIC)) {
        PATCH_LOGE("length error");
        return -1;
    }
    if (memcmp(patchData.memory, PKGDIFF_MAGIC, std::char_traits<char>::length(PKGDIFF_MAGIC)) == 0) {
        PatchParam param {};
        param.patch = patchData.memory;
        param.patchSize = patchData.length;
        param.oldBuff = oldData.memory;
        param.oldSize = oldData.length;
        if (UpdatePatch::UpdateApplyPatch::ApplyImagePatch(param, writer.get(), empty) != 0) {
            PATCH_LOGE("Failed to apply image patch file");
            return -1;
        }
    } else if (memcmp(patchData.memory, BSDIFF_MAGIC, std::char_traits<char>::length(BSDIFF_MAGIC)) == 0) { // bsdiff
        PatchBuffer patchInfo = {patchData.memory, 0, patchData.length};
        BlockBuffer oldInfo = {oldData.memory, oldData.length};
        if (ApplyBlockPatch(patchInfo, oldInfo, writer.get()) != 0) {
            PATCH_LOGE("Failed to apply block patch");
            return -1;
        }
    } else {
        PATCH_LOGE("Invalid patch file");
        return -1;
    }
    writer->Finish();
    return 0;
}

int32_t ImagePatchWriter::Init()
{
    if (init_) {
        PATCH_LOGE("Has beed init");
        return -1;
    }
    if (writer_ == nullptr) {
        PATCH_LOGE("Writer is null");
        return -1;
    }
    SHA256_Init(&sha256Ctx_);
    init_ = true;
    return 0;
}

int32_t ImagePatchWriter::Write(size_t start, const BlockBuffer &buffer, size_t len)
{
    if (!init_) {
        PATCH_LOGE("Failed to check init");
        return -1;
    }
    if (len == 0) {
        return 0;
    }
    SHA256_Update(&sha256Ctx_, buffer.buffer, len);
    return writer_(start, buffer, len);
}

int32_t ImagePatchWriter::Finish()
{
    if (!init_) {
        PATCH_LOGE("Failed to check init");
        return -1;
    }
    std::vector<uint8_t> digest(SHA256_DIGEST_LENGTH);
    SHA256_Final(digest.data(), &sha256Ctx_);
    BlockBuffer data = {  digest.data(), digest.size() };
    std::string hexDigest = ConvertSha256Hex(data);
    init_ = false;
    int32_t ret = hexDigest.compare(expected_);
    if (ret != 0) {
        PATCH_LOGE("VerifySha256 SHA256 : %s expected SHA256 : %s", hexDigest.c_str(), expected_.c_str());
        return ret;
    }
    return 0;
}

int32_t FilePatchWriter::Init()
{
    if (init_) {
        PATCH_LOGE("Has beed init");
        return -1;
    }
    if (!stream_.is_open()) {
        stream_.open(newFileName_, std::ios::out | std::ios::binary);
        if (stream_.fail()) {
            PATCH_LOGE("Failed to open %s", newFileName_.c_str());
            return -1;
        }
    }
    init_ = true;
    return 0;
}

int32_t FilePatchWriter::Write(size_t start, const BlockBuffer &buffer, size_t len)
{
    if (!init_) {
        PATCH_LOGE("Failed to check init");
        return -1;
    }
    if (len == 0) {
        return 0;
    }
    if (!stream_.is_open()) {
        stream_.open(newFileName_, std::ios::out | std::ios::binary);
        if (stream_.fail()) {
            PATCH_LOGE("Failed to open %s", newFileName_.c_str());
            return -1;
        }
    }
    stream_.write(reinterpret_cast<const char*>(buffer.buffer), len);
    return 0;
}

int32_t FilePatchWriter::Finish()
{
    if (!init_) {
        PATCH_LOGE("Failed to check init");
        return -1;
    }
    PATCH_LOGI("FilePatchWriter %zu", static_cast<size_t>(stream_.tellp()));
    stream_.close();
    init_ = false;
    return 0;
}
} // namespace UpdatePatch