/*
 * Copyright 2020 Bloomberg Finance LP
 *
 * 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 <buildboxcasd_digestcache.h>
#include <buildboxcasd_findmissingblobsclient.h>

#include <buildboxcommon_digestgenerator.h>
#include <buildboxcommon_protos.h>

#include <gtest/gtest.h>

using buildboxcasd::DigestCache;
using buildboxcasd::FindMissingBlobsClient;
using buildboxcommon::Digest;

namespace {
const auto digestFunctionInitializer = []() {
    buildboxcommon::DigestGenerator::init();
    return 0;
}();
} // namespace

TEST(FindMissingBlobsClientTest, TestNoCaching)
{
    const Digest presentDigest = buildboxcommon::DigestGenerator::hash("a");
    const Digest missingDigest = buildboxcommon::DigestGenerator::hash("b");

    const std::vector<Digest> request = {presentDigest, missingDigest};

    int numRequestsToRemote = 0;
    const FindMissingBlobsClient::CasClientFindMissingBlobsFunction
        findMissingBlobs = [&missingDigest, &numRequestsToRemote](
                               const std::vector<Digest> &) {
            numRequestsToRemote++;
            std::vector<Digest> res = {missingDigest};
            return res;
        };

    FindMissingBlobsClient client(findMissingBlobs, nullptr);
    ASSERT_FALSE(client.cacheEnabled());

    {
        std::vector<Digest> missing;
        client.findMissingBlobs(request, &missing);

        ASSERT_EQ(missing.size(), 1);
        ASSERT_EQ(missing[0], missingDigest);
    }

    {
        std::vector<Digest> missing;
        client.findMissingBlobs(request, &missing);

        ASSERT_EQ(missing.size(), 1);
        ASSERT_EQ(missing[0], missingDigest);
    }

    ASSERT_EQ(numRequestsToRemote, 2);
}

TEST(FindMissingBlobsClientTest, TestCaching)
{
    auto cache = std::make_shared<DigestCache>();

    const Digest presentDigest = buildboxcommon::DigestGenerator::hash("a");
    const Digest missingDigest = buildboxcommon::DigestGenerator::hash("b");

    const std::vector<Digest> request = {presentDigest, missingDigest};

    std::vector<std::vector<Digest>> requestList;
    const FindMissingBlobsClient::CasClientFindMissingBlobsFunction
        findMissingBlobs = [&missingDigest,
                            &requestList](const std::vector<Digest> &digests) {
            requestList.push_back(digests);

            std::vector<Digest> res = {missingDigest};
            return res;
        };

    FindMissingBlobsClient client(findMissingBlobs, cache.get());
    ASSERT_TRUE(client.cacheEnabled());

    {
        std::vector<Digest> missing;
        client.findMissingBlobs(request, &missing);

        ASSERT_EQ(missing.size(), 1);
        ASSERT_EQ(missing[0], missingDigest);
    }

    ASSERT_TRUE(cache->hasDigest(presentDigest));

    {
        std::vector<Digest> missing;
        client.findMissingBlobs(request, &missing);

        ASSERT_EQ(missing.size(), 1);
        ASSERT_EQ(missing[0], missingDigest);
    }

    // The client sent 2 `FindMissingBlobs()` to the remote:
    ASSERT_EQ(requestList.size(), 2);

    // The first was the original request:
    ASSERT_TRUE(std::is_permutation(request.cbegin(), request.cend(),
                                    requestList[0].cbegin()));

    // And the second only for `missingDigest`:
    ASSERT_EQ(requestList[1].size(), 1);
    ASSERT_EQ(requestList[1].front(), missingDigest);
}

TEST(FindMissingBlobsClientTest, TestEmptyRequestIsNotSentToRemote)
{
    auto cache = std::make_shared<DigestCache>();

    const Digest digestA = buildboxcommon::DigestGenerator::hash("a");
    cache->addDigest(digestA);

    int numberOfRequests = 0;
    const FindMissingBlobsClient::CasClientFindMissingBlobsFunction
        findMissingBlobs =
            [&numberOfRequests, &digestA](const std::vector<Digest> &) {
                numberOfRequests++;
                std::vector<Digest> res = {digestA};
                return res;
            };

    FindMissingBlobsClient client(findMissingBlobs, cache.get());

    const std::vector<Digest> request = {digestA};
    std::vector<Digest> missing;
    client.findMissingBlobs(request, &missing);
}
