/*
 * Copyright (C) 2020 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 "property_monitor.h"

#include <stdlib.h>
#include <unistd.h>

#include <chrono>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>

#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/thread_annotations.h>
#include <gtest/gtest.h>

using namespace std::chrono_literals;

struct PropertyChanges {
    std::unordered_map<std::string, std::vector<std::string>> changes GUARDED_BY(mutex);
    std::mutex mutex;
};

static std::string ManglePropertyName(std::string name) {
    name.push_back('.');
    name.append(std::to_string(gettid()));
    name.append(std::to_string(rand()));
    return name;
}

static std::thread SpawnThread(PropertyMonitor* pm) {
    return std::thread([pm]() {
        pm->Run();
    });
}

static std::function<void()> RegisterExitCallback(PropertyMonitor* pm) {
    std::string prop_name = ManglePropertyName("debug.property_monitor_test.exit");
    android::base::SetProperty(prop_name, "0");
    pm->Add(prop_name, [](std::string value) { return value != "1"; });
    return [prop_name]() {
        android::base::SetProperty(prop_name, "1");
    };
}

static void RegisterCallback(PropertyMonitor* pm, PropertyChanges* output,
                             std::string property_name) {
    pm->Add(property_name, [output, property_name](std::string value) {
        std::lock_guard<std::mutex> lock(output->mutex);
        LOG(INFO) << property_name << " = " << value;
        output->changes[property_name].emplace_back(std::move(value));
        return true;
    });
}

TEST(PropertyMonitorTest, initial) {
    PropertyMonitor pm;
    PropertyChanges output;

    auto exit_fn = RegisterExitCallback(&pm);

    std::string foo = ManglePropertyName("debug.property_monitor_test.initial");
    std::string never_set = ManglePropertyName("debug.property_monitor_test.never_set");
    RegisterCallback(&pm, &output, foo);
    android::base::SetProperty(foo, "foo");

    RegisterCallback(&pm, &output, never_set);

    auto thread = SpawnThread(&pm);

    exit_fn();
    thread.join();

    std::lock_guard<std::mutex> lock(output.mutex);
    ASSERT_EQ(2UL, output.changes.size());
    ASSERT_EQ(2UL, output.changes[foo].size());
    ASSERT_EQ("", output.changes[foo][0]);
    ASSERT_EQ("foo", output.changes[foo][1]);
    ASSERT_EQ("", output.changes[never_set][0]);
}

// test case for b/478026377
TEST(PropertyMonitorTest, initial_empty) {
    PropertyMonitor pm;
    PropertyChanges output;

    auto exit_fn = RegisterExitCallback(&pm);

    std::string foo = ManglePropertyName("debug.property_monitor_test.initial");
    std::string never_set = ManglePropertyName("debug.property_monitor_test.never_set");
    RegisterCallback(&pm, &output, foo);
    android::base::SetProperty(foo, "");

    RegisterCallback(&pm, &output, never_set);

    auto thread = SpawnThread(&pm);

    exit_fn();
    thread.join();

    std::lock_guard<std::mutex> lock(output.mutex);
    ASSERT_EQ(2UL, output.changes.size());
    ASSERT_EQ(2UL, output.changes[foo].size());
    ASSERT_EQ("", output.changes[foo][0]);
    ASSERT_EQ("", output.changes[foo][1]);
    ASSERT_EQ("", output.changes[never_set][0]);
}

TEST(PropertyMonitorTest, change) {
    PropertyMonitor pm;
    PropertyChanges output;

    auto exit_fn = RegisterExitCallback(&pm);

    std::string foo = ManglePropertyName("debug.property_monitor_test.foo");

    RegisterCallback(&pm, &output, foo);
    android::base::SetProperty(foo, "foo");

    auto thread = SpawnThread(&pm);
    std::this_thread::sleep_for(100ms);

    {
        std::lock_guard<std::mutex> lock(output.mutex);
        ASSERT_EQ(1UL, output.changes.size());
        ASSERT_EQ(2UL, output.changes[foo].size());
        ASSERT_EQ("", output.changes[foo][0]);
        ASSERT_EQ("foo", output.changes[foo][1]);
    }

    android::base::SetProperty(foo, "oh");
    std::this_thread::sleep_for(100ms);

    {
        std::lock_guard<std::mutex> lock(output.mutex);
        ASSERT_EQ(1UL, output.changes.size());
        ASSERT_EQ(3UL, output.changes[foo].size());
        ASSERT_EQ("", output.changes[foo][0]);
        ASSERT_EQ("foo", output.changes[foo][1]);
        ASSERT_EQ("oh", output.changes[foo][2]);
    }

    exit_fn();
    thread.join();
}

TEST(PropertyMonitorTest, multiple) {
    PropertyMonitor pm;
    PropertyChanges output;

    auto exit_fn = RegisterExitCallback(&pm);

    std::string foo = ManglePropertyName("debug.property_monitor_test.foo");
    std::string bar = ManglePropertyName("debug.property_monitor_test.bar");

    RegisterCallback(&pm, &output, foo);
    RegisterCallback(&pm, &output, bar);

    android::base::SetProperty(foo, "foo");
    android::base::SetProperty(bar, "bar");

    auto thread = SpawnThread(&pm);
    std::this_thread::sleep_for(100ms);

    {
        std::lock_guard<std::mutex> lock(output.mutex);
        ASSERT_EQ(2UL, output.changes.size());

        ASSERT_EQ(2UL, output.changes[foo].size());
        ASSERT_EQ("", output.changes[foo][0]);
        ASSERT_EQ("foo", output.changes[foo][1]);

        ASSERT_EQ(2UL, output.changes[bar].size());
        ASSERT_EQ("", output.changes[bar][0]);
        ASSERT_EQ("bar", output.changes[bar][1]);
    }

    android::base::SetProperty(foo, "bar");
    android::base::SetProperty(bar, "foo");
    std::this_thread::sleep_for(100ms);

    {
        std::lock_guard<std::mutex> lock(output.mutex);
        ASSERT_EQ(2UL, output.changes.size());

        ASSERT_EQ(3UL, output.changes[foo].size());
        ASSERT_EQ("", output.changes[foo][0]);
        ASSERT_EQ("foo", output.changes[foo][1]);
        ASSERT_EQ("bar", output.changes[foo][2]);

        ASSERT_EQ(3UL, output.changes[bar].size());
        ASSERT_EQ("", output.changes[foo][0]);
        ASSERT_EQ("bar", output.changes[bar][1]);
        ASSERT_EQ("foo", output.changes[bar][2]);
    }

    exit_fn();
    thread.join();
}
