/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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_utils.h"

#include <android-base/file.h>
#include <gtest/gtest.h>
#include <stdio.h>
#include <stdlib.h>
#include <sstream>
#include <string>

#include "adb_io.h"
#include "sysdeps.h"

using namespace com::android::fastdeploy;

static std::string GetTestFile(const std::string& name) {
    return "fastdeploy/testdata/" + name;
}

bool FileMatchesContent(android::base::borrowed_fd input, const char* contents,
                        ssize_t contentsSize) {
    adb_lseek(input, 0, SEEK_SET);
    // Use a temp buffer larger than any test contents.
    constexpr int BUFFER_SIZE = 2048;
    char buffer[BUFFER_SIZE];
    bool result = true;
    // Validate size of files is equal.
    ssize_t readAmount = adb_read(input, buffer, BUFFER_SIZE);
    EXPECT_EQ(readAmount, contentsSize);
    result = memcmp(buffer, contents, readAmount) == 0;
    for (int i = 0; i < readAmount; i++) {
        printf("%x", buffer[i]);
    }
    printf(" == ");
    for (int i = 0; i < contentsSize; i++) {
        printf("%x", contents[i]);
    }
    printf("\n");

    return result;
}

TEST(PatchUtilsTest, SwapLongWrites) {
    TemporaryFile output;
    PatchUtils::WriteLong(0x0011223344556677, output.fd);
    adb_lseek(output.fd, 0, SEEK_SET);
    const char expected[] = {0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00};
    EXPECT_TRUE(FileMatchesContent(output.fd, expected, 8));
}

TEST(PatchUtilsTest, PipeWritesAmountToOutput) {
    std::string expected("Some Data");
    TemporaryFile input;
    TemporaryFile output;
    // Populate input file.
    WriteFdExactly(input.fd, expected);
    adb_lseek(input.fd, 0, SEEK_SET);
    // Open input file for read, and output file for write.
    PatchUtils::Pipe(input.fd, output.fd, expected.size());
    // Validate pipe worked
    EXPECT_TRUE(FileMatchesContent(output.fd, expected.c_str(), expected.size()));
}

TEST(PatchUtilsTest, SignatureConstMatches) {
    std::string apkFile = GetTestFile("rotating_cube-release.apk");
    TemporaryFile output;
    PatchUtils::WriteSignature(output.fd);
    std::string contents("FASTDEPLOY");
    EXPECT_TRUE(FileMatchesContent(output.fd, contents.c_str(), contents.size()));
}

TEST(PatchUtilsTest, GatherMetadata) {
    std::string apkFile = GetTestFile("rotating_cube-release.apk");
    APKMetaData actual = PatchUtils::GetHostAPKMetaData(apkFile.c_str());

    std::string expectedMetadata;
    android::base::ReadFileToString(GetTestFile("rotating_cube-metadata-release.data"),
                                    &expectedMetadata, true);
    APKMetaData expected;
    EXPECT_TRUE(expected.ParseFromString(expectedMetadata));

    // Test paths might vary.
    expected.set_absolute_path(actual.absolute_path());

    std::string actualMetadata;
    actual.SerializeToString(&actualMetadata);

    expected.SerializeToString(&expectedMetadata);

    EXPECT_EQ(expectedMetadata, actualMetadata);
}

static inline void sanitize(APKMetaData& metadata) {
    metadata.clear_absolute_path();
    for (auto&& entry : *metadata.mutable_entries()) {
        entry.clear_datasize();
    }
}

TEST(PatchUtilsTest, GatherDumpMetadata) {
    APKMetaData hostMetadata;
    APKMetaData deviceMetadata;

    hostMetadata = PatchUtils::GetHostAPKMetaData(GetTestFile("sample.apk").c_str());

    {
        std::string cd;
        android::base::ReadFileToString(GetTestFile("sample.cd"), &cd, true);

        APKDump dump;
        dump.set_cd(std::move(cd));

        deviceMetadata = PatchUtils::GetDeviceAPKMetaData(dump);
    }

    sanitize(hostMetadata);
    sanitize(deviceMetadata);

    std::string expectedMetadata;
    hostMetadata.SerializeToString(&expectedMetadata);

    std::string actualMetadata;
    deviceMetadata.SerializeToString(&actualMetadata);

    EXPECT_EQ(expectedMetadata, actualMetadata);
}
