/*
 * Copyright (C) 2018 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 <unistd.h>
#include <string>

#include <android-base/file.h>
#include <android-base/scopeguard.h>
#include <android-base/test_utils.h>
#include <gtest/gtest.h>

#include "CppGen.h"

namespace {

constexpr const char* kTestSyspropFile =
    R"(owner: Platform
module: "android.sysprop.PlatformProperties"

prop {
    api_name: "test_double"
    type: Double
    prop_name: "android.test_double"
    scope: Internal
    access: ReadWrite
}
prop {
    api_name: "test_int"
    type: Integer
    prop_name: "android.test_int"
    scope: Public
    access: ReadWrite
}
prop {
    api_name: "test.string"
    type: String
    prop_name: "android.test.string"
    scope: System
    access: ReadWrite
}

prop {
    api_name: "test.enum"
    type: Enum
    prop_name: "android.test.enum"
    enum_values: "a|b|c|D|e|f|G"
    scope: Internal
    access: ReadWrite
}
prop {
    api_name: "test_BOOLeaN"
    type: Boolean
    prop_name: "ro.android.test.b"
    scope: Public
    access: Writeonce
}
prop {
    api_name: "android.os_test-long"
    type: Long
    scope: System
    access: ReadWrite
}

prop {
    api_name: "test_double_list"
    type: DoubleList
    scope: Internal
    access: ReadWrite
}
prop {
    api_name: "test_list_int"
    type: IntegerList
    scope: Public
    access: ReadWrite
}
prop {
    api_name: "test.strlist"
    type: StringList
    scope: System
    access: ReadWrite
}

prop {
    api_name: "el"
    type: EnumList
    enum_values: "enu|mva|lue"
    scope: Internal
    access: ReadWrite
}
)";

constexpr const char* kExpectedHeaderOutput =
    R"(// Generated by the sysprop generator. DO NOT EDIT!

#pragma once

#include <cstdint>
#include <optional>
#include <string>
#include <vector>

namespace android::sysprop::PlatformProperties {

std::optional<double> test_double();
bool test_double(const std::optional<double>& value);

std::optional<std::int32_t> test_int();
bool test_int(const std::optional<std::int32_t>& value);

std::optional<std::string> test_string();
bool test_string(const std::optional<std::string>& value);

enum class test_enum_values {
    A,
    B,
    C,
    D,
    E,
    F,
    G,
};

std::optional<test_enum_values> test_enum();
bool test_enum(const std::optional<test_enum_values>& value);

std::optional<bool> test_BOOLeaN();
bool test_BOOLeaN(const std::optional<bool>& value);

std::optional<std::int64_t> android_os_test_long();
bool android_os_test_long(const std::optional<std::int64_t>& value);

std::vector<std::optional<double>> test_double_list();
bool test_double_list(const std::vector<std::optional<double>>& value);

std::vector<std::optional<std::int32_t>> test_list_int();
bool test_list_int(const std::vector<std::optional<std::int32_t>>& value);

std::vector<std::optional<std::string>> test_strlist();
bool test_strlist(const std::vector<std::optional<std::string>>& value);

enum class el_values {
    ENU,
    MVA,
    LUE,
};

std::vector<std::optional<el_values>> el();
bool el(const std::vector<std::optional<el_values>>& value);

}  // namespace android::sysprop::PlatformProperties
)";

constexpr const char* kExpectedSystemHeaderOutput =
    R"(// Generated by the sysprop generator. DO NOT EDIT!

#pragma once

#include <cstdint>
#include <optional>
#include <string>
#include <vector>

namespace android::sysprop::PlatformProperties {

std::optional<std::int32_t> test_int();

std::optional<std::string> test_string();

std::optional<bool> test_BOOLeaN();

std::optional<std::int64_t> android_os_test_long();

std::vector<std::optional<std::int32_t>> test_list_int();

std::vector<std::optional<std::string>> test_strlist();

}  // namespace android::sysprop::PlatformProperties
)";

constexpr const char* kExpectedSourceOutput =
    R"(// Generated by the sysprop generator. DO NOT EDIT!

#include <properties/PlatformProperties.sysprop.h>

#include <cctype>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <limits>
#include <utility>

#include <strings.h>
#include <sys/system_properties.h>

#include <android-base/parseint.h>
#include <log/log.h>

namespace {

using namespace android::sysprop::PlatformProperties;

template <typename T> T DoParse(const char* str);

constexpr const std::pair<const char*, test_enum_values> test_enum_list[] = {
    {"a", test_enum_values::A},
    {"b", test_enum_values::B},
    {"c", test_enum_values::C},
    {"D", test_enum_values::D},
    {"e", test_enum_values::E},
    {"f", test_enum_values::F},
    {"G", test_enum_values::G},
};

template <>
std::optional<test_enum_values> DoParse(const char* str) {
    for (auto [name, val] : test_enum_list) {
        if (strcmp(str, name) == 0) {
            return val;
        }
    }
    return std::nullopt;
}

std::string FormatValue(std::optional<test_enum_values> value) {
    if (!value) return "";
    for (auto [name, val] : test_enum_list) {
        if (val == *value) {
            return name;
        }
    }
    LOG_ALWAYS_FATAL("Invalid value %d for property android.test.enum", static_cast<std::int32_t>(*value));
    __builtin_unreachable();
}

constexpr const std::pair<const char*, el_values> el_list[] = {
    {"enu", el_values::ENU},
    {"mva", el_values::MVA},
    {"lue", el_values::LUE},
};

template <>
std::optional<el_values> DoParse(const char* str) {
    for (auto [name, val] : el_list) {
        if (strcmp(str, name) == 0) {
            return val;
        }
    }
    return std::nullopt;
}

std::string FormatValue(std::optional<el_values> value) {
    if (!value) return "";
    for (auto [name, val] : el_list) {
        if (val == *value) {
            return name;
        }
    }
    LOG_ALWAYS_FATAL("Invalid value %d for property el", static_cast<std::int32_t>(*value));
    __builtin_unreachable();
}

template <typename T> constexpr bool is_vector = false;

template <typename T> constexpr bool is_vector<std::vector<T>> = true;

template <> [[maybe_unused]] std::optional<bool> DoParse(const char* str) {
    static constexpr const char* kYes[] = {"1", "true"};
    static constexpr const char* kNo[] = {"0", "false"};

    for (const char* yes : kYes) {
        if (strcasecmp(yes, str) == 0) return std::make_optional(true);
    }

    for (const char* no : kNo) {
        if (strcasecmp(no, str) == 0) return std::make_optional(false);
    }

    return std::nullopt;
}

template <> [[maybe_unused]] std::optional<std::int32_t> DoParse(const char* str) {
    std::int32_t ret;
    return android::base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
}

template <> [[maybe_unused]] std::optional<std::int64_t> DoParse(const char* str) {
    std::int64_t ret;
    return android::base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
}

template <> [[maybe_unused]] std::optional<double> DoParse(const char* str) {
    int old_errno = errno;
    errno = 0;
    char* end;
    double ret = std::strtod(str, &end);
    if (errno != 0) {
        return std::nullopt;
    }
    if (str == end || *end != '\0') {
        errno = EINVAL;
        return std::nullopt;
    }
    errno = old_errno;
    return std::make_optional(ret);
}

template <> [[maybe_unused]] std::optional<std::string> DoParse(const char* str) {
    return *str == '\0' ? std::nullopt : std::make_optional(str);
}

template <typename Vec> [[maybe_unused]] Vec DoParseList(const char* str) {
    Vec ret;
    const char* p = str;
    for (;;) {
        const char* found = p;
        while (*found != '\0' && *found != ',') {
            ++found;
        }
        std::string value(p, found);
        ret.emplace_back(DoParse<typename Vec::value_type>(value.c_str()));
        if (*found == '\0') break;
        p = found + 1;
    }
    return ret;
}

template <typename T> inline T TryParse(const char* str) {
    if constexpr(is_vector<T>) {
        return DoParseList<T>(str);
    } else {
        return DoParse<T>(str);
    }
}

[[maybe_unused]] std::string FormatValue(const std::optional<std::int32_t>& value) {
    return value ? std::to_string(*value) : "";
}

[[maybe_unused]] std::string FormatValue(const std::optional<std::int64_t>& value) {
    return value ? std::to_string(*value) : "";
}

[[maybe_unused]] std::string FormatValue(const std::optional<double>& value) {
    if (!value) return "";
    char buf[1024];
    std::sprintf(buf, "%.*g", std::numeric_limits<double>::max_digits10, *value);
    return buf;
}

[[maybe_unused]] std::string FormatValue(const std::optional<bool>& value) {
    return value ? (*value ? "true" : "false") : "";
}

template <typename T>
[[maybe_unused]] std::string FormatValue(const std::vector<T>& value) {
    if (value.empty()) return "";

    std::string ret;
    bool first = true;

    for (auto&& element : value) {
        if (!first) ret += ",";
        else first = false;
        if constexpr(std::is_same_v<T, std::optional<std::string>>) {
            if (element) ret += *element;
        } else {
            ret += FormatValue(element);
        }
    }

    return ret;
}

template <typename T>
T GetProp(const char* key) {
    T ret;
    auto pi = __system_property_find(key);
    if (pi != nullptr) {
        __system_property_read_callback(pi, [](void* cookie, const char*, const char* value, std::uint32_t) {
            *static_cast<T*>(cookie) = TryParse<T>(value);
        }, &ret);
    }
    return ret;
}

}  // namespace

namespace android::sysprop::PlatformProperties {

std::optional<double> test_double() {
    return GetProp<std::optional<double>>("android.test_double");
}

bool test_double(const std::optional<double>& value) {
    return __system_property_set("android.test_double", FormatValue(value).c_str()) == 0;
}

std::optional<std::int32_t> test_int() {
    return GetProp<std::optional<std::int32_t>>("android.test_int");
}

bool test_int(const std::optional<std::int32_t>& value) {
    return __system_property_set("android.test_int", FormatValue(value).c_str()) == 0;
}

std::optional<std::string> test_string() {
    return GetProp<std::optional<std::string>>("android.test.string");
}

bool test_string(const std::optional<std::string>& value) {
    return __system_property_set("android.test.string", value ? value->c_str() : "") == 0;
}

std::optional<test_enum_values> test_enum() {
    return GetProp<std::optional<test_enum_values>>("android.test.enum");
}

bool test_enum(const std::optional<test_enum_values>& value) {
    return __system_property_set("android.test.enum", FormatValue(value).c_str()) == 0;
}

std::optional<bool> test_BOOLeaN() {
    return GetProp<std::optional<bool>>("ro.android.test.b");
}

bool test_BOOLeaN(const std::optional<bool>& value) {
    return __system_property_set("ro.android.test.b", FormatValue(value).c_str()) == 0;
}

std::optional<std::int64_t> android_os_test_long() {
    return GetProp<std::optional<std::int64_t>>("android.os_test-long");
}

bool android_os_test_long(const std::optional<std::int64_t>& value) {
    return __system_property_set("android.os_test-long", FormatValue(value).c_str()) == 0;
}

std::vector<std::optional<double>> test_double_list() {
    return GetProp<std::vector<std::optional<double>>>("test_double_list");
}

bool test_double_list(const std::vector<std::optional<double>>& value) {
    return __system_property_set("test_double_list", FormatValue(value).c_str()) == 0;
}

std::vector<std::optional<std::int32_t>> test_list_int() {
    return GetProp<std::vector<std::optional<std::int32_t>>>("test_list_int");
}

bool test_list_int(const std::vector<std::optional<std::int32_t>>& value) {
    return __system_property_set("test_list_int", FormatValue(value).c_str()) == 0;
}

std::vector<std::optional<std::string>> test_strlist() {
    return GetProp<std::vector<std::optional<std::string>>>("test.strlist");
}

bool test_strlist(const std::vector<std::optional<std::string>>& value) {
    return __system_property_set("test.strlist", FormatValue(value).c_str()) == 0;
}

std::vector<std::optional<el_values>> el() {
    return GetProp<std::vector<std::optional<el_values>>>("el");
}

bool el(const std::vector<std::optional<el_values>>& value) {
    return __system_property_set("el", FormatValue(value).c_str()) == 0;
}

}  // namespace android::sysprop::PlatformProperties
)";

}  // namespace

using namespace std::string_literals;

TEST(SyspropTest, CppGenTest) {
  TemporaryDir temp_dir;

  std::string temp_sysprop_path = temp_dir.path + "/PlatformProperties.sysprop"s;
  ASSERT_TRUE(
      android::base::WriteStringToFile(kTestSyspropFile, temp_sysprop_path));

  auto sysprop_deleter = android::base::make_scope_guard(
      [&] { unlink(temp_sysprop_path.c_str()); });

  std::string err;
  ASSERT_TRUE(GenerateCppFiles(
      temp_sysprop_path, temp_dir.path, temp_dir.path + "/system"s,
      temp_dir.path, "properties/PlatformProperties.sysprop.h", &err));
  ASSERT_TRUE(err.empty());

  std::string header_output_path =
      temp_dir.path + "/PlatformProperties.sysprop.h"s;
  std::string system_header_output_path =
      temp_dir.path + "/system/PlatformProperties.sysprop.h"s;
  std::string source_output_path =
      temp_dir.path + "/PlatformProperties.sysprop.cpp"s;

  auto generated_file_deleter = android::base::make_scope_guard([&] {
    unlink(header_output_path.c_str());
    unlink(system_header_output_path.c_str());
    unlink(source_output_path.c_str());
  });

  std::string header_output;
  ASSERT_TRUE(android::base::ReadFileToString(header_output_path,
                                              &header_output, true));
  EXPECT_EQ(header_output, kExpectedHeaderOutput);

  std::string system_header_output;
  ASSERT_TRUE(android::base::ReadFileToString(system_header_output_path,
                                              &system_header_output, true));
  EXPECT_EQ(system_header_output, kExpectedSystemHeaderOutput);

  std::string source_output;
  ASSERT_TRUE(android::base::ReadFileToString(source_output_path,
                                              &source_output, true));
  EXPECT_EQ(source_output, kExpectedSourceOutput);
}