/* Copyright (C) 2023 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 <android-base/file.h>
#include <android-base/test_utils.h>
#include <gtest/gtest.h>
#include <unistd.h>

#include <string>

#include "RustGen.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: Public
    access: Readonly
    legacy_prop_name: "legacy.android.test.string"
}
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: Public
    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: Public
    access: ReadWrite
    deprecated: true
}
prop {
    api_name: "el"
    type: EnumList
    enum_values: "enu|mva|lue"
    scope: Internal
    access: ReadWrite
    deprecated: true
}
)";

constexpr const char* kExpectedPublicOutput =
    R"(//! Autogenerated system property accessors.
//!
//! This is an autogenerated module. The module contains methods for typed access to
//! Android system properties.

// Generated by the sysprop generator. DO NOT EDIT!

use std::fmt;
use rustutils::system_properties::{self, error::SysPropError, parsers_formatters};

/// Returns the value of the property 'android.test_int' if set.
pub fn test_int() -> std::result::Result<Option<i32>, SysPropError> {
    let result = match system_properties::read("android.test_int") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'android.test_int', returns 'Ok' if successful.
pub fn set_test_int(v: i32) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format(&v);
    system_properties::write("android.test_int", value.as_str()).map_err(SysPropError::SetError)
}

/// Returns the value of the property 'android.test.string' if set.
pub fn test_string() -> std::result::Result<Option<String>, SysPropError> {
    let result = match system_properties::read("android.test.string") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    if result.is_ok() { return result; }
    log::debug!("Failed to fetch the original property 'android.test.string' ('{}'), falling back to the legacy one 'legacy.android.test.string'.", result.unwrap_err());
    match system_properties::read("legacy.android.test.string") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    }
}

/// Returns the value of the property 'ro.android.test.b' if set.
pub fn test_boo_lea_n() -> std::result::Result<Option<bool>, SysPropError> {
    let result = match system_properties::read("ro.android.test.b") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse_bool(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'ro.android.test.b', returns 'Ok' if successful.
pub fn set_test_boo_lea_n(v: bool) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format_bool(&v);
    system_properties::write("ro.android.test.b", value.as_str()).map_err(SysPropError::SetError)
}

/// Returns the value of the property 'android_os_test-long' if set.
pub fn android_os_test_long() -> std::result::Result<Option<i64>, SysPropError> {
    let result = match system_properties::read("android_os_test-long") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'android_os_test-long', returns 'Ok' if successful.
pub fn set_android_os_test_long(v: i64) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format(&v);
    system_properties::write("android_os_test-long", value.as_str()).map_err(SysPropError::SetError)
}

/// Returns the value of the property 'test_list_int' if set.
pub fn test_list_int() -> std::result::Result<Option<Vec<i32>>, SysPropError> {
    let result = match system_properties::read("test_list_int") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'test_list_int', returns 'Ok' if successful.
pub fn set_test_list_int(v: &[i32]) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format_list(v);
    system_properties::write("test_list_int", value.as_str()).map_err(SysPropError::SetError)
}

/// Returns the value of the property 'test_strlist' if set.
#[deprecated]
pub fn test_strlist() -> std::result::Result<Option<Vec<String>>, SysPropError> {
    let result = match system_properties::read("test_strlist") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'test_strlist', returns 'Ok' if successful.
#[deprecated]
pub fn set_test_strlist(v: &[String]) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format_list(v);
    system_properties::write("test_strlist", value.as_str()).map_err(SysPropError::SetError)
}

)";

constexpr const char* kExpectedInternalOutput =
    R"(//! Autogenerated system property accessors.
//!
//! This is an autogenerated module. The module contains methods for typed access to
//! Android system properties.

// Generated by the sysprop generator. DO NOT EDIT!

use std::fmt;
use rustutils::system_properties::{self, error::SysPropError, parsers_formatters};

/// Returns the value of the property 'android.test_double' if set.
pub fn test_double() -> std::result::Result<Option<f64>, SysPropError> {
    let result = match system_properties::read("android.test_double") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'android.test_double', returns 'Ok' if successful.
pub fn set_test_double(v: f64) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format(&v);
    system_properties::write("android.test_double", value.as_str()).map_err(SysPropError::SetError)
}

/// Returns the value of the property 'android.test_int' if set.
pub fn test_int() -> std::result::Result<Option<i32>, SysPropError> {
    let result = match system_properties::read("android.test_int") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'android.test_int', returns 'Ok' if successful.
pub fn set_test_int(v: i32) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format(&v);
    system_properties::write("android.test_int", value.as_str()).map_err(SysPropError::SetError)
}

/// Returns the value of the property 'android.test.string' if set.
pub fn test_string() -> std::result::Result<Option<String>, SysPropError> {
    let result = match system_properties::read("android.test.string") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    if result.is_ok() { return result; }
    log::debug!("Failed to fetch the original property 'android.test.string' ('{}'), falling back to the legacy one 'legacy.android.test.string'.", result.unwrap_err());
    match system_properties::read("legacy.android.test.string") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    }
}

#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash, Ord)]
pub enum TestEnumValues {
    A,
    B,
    C,
    D,
    E,
    F,
    G,
}

impl std::str::FromStr for TestEnumValues {
    type Err = String;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match s {
            "a" => Ok(TestEnumValues::A),
            "b" => Ok(TestEnumValues::B),
            "c" => Ok(TestEnumValues::C),
            "D" => Ok(TestEnumValues::D),
            "e" => Ok(TestEnumValues::E),
            "f" => Ok(TestEnumValues::F),
            "G" => Ok(TestEnumValues::G),
            _ => Err(format!("'{}' cannot be parsed for TestEnumValues", s)),
        }
    }
}

impl fmt::Display for TestEnumValues {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            TestEnumValues::A => write!(f, "a"),
            TestEnumValues::B => write!(f, "b"),
            TestEnumValues::C => write!(f, "c"),
            TestEnumValues::D => write!(f, "D"),
            TestEnumValues::E => write!(f, "e"),
            TestEnumValues::F => write!(f, "f"),
            TestEnumValues::G => write!(f, "G"),
        }
    }
}

/// Returns the value of the property 'android.test.enum' if set.
pub fn test_enum() -> std::result::Result<Option<TestEnumValues>, SysPropError> {
    let result = match system_properties::read("android.test.enum") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'android.test.enum', returns 'Ok' if successful.
pub fn set_test_enum(v: TestEnumValues) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format(&v);
    system_properties::write("android.test.enum", value.as_str()).map_err(SysPropError::SetError)
}

/// Returns the value of the property 'ro.android.test.b' if set.
pub fn test_boo_lea_n() -> std::result::Result<Option<bool>, SysPropError> {
    let result = match system_properties::read("ro.android.test.b") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse_bool(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'ro.android.test.b', returns 'Ok' if successful.
pub fn set_test_boo_lea_n(v: bool) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format_bool(&v);
    system_properties::write("ro.android.test.b", value.as_str()).map_err(SysPropError::SetError)
}

/// Returns the value of the property 'android_os_test-long' if set.
pub fn android_os_test_long() -> std::result::Result<Option<i64>, SysPropError> {
    let result = match system_properties::read("android_os_test-long") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'android_os_test-long', returns 'Ok' if successful.
pub fn set_android_os_test_long(v: i64) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format(&v);
    system_properties::write("android_os_test-long", value.as_str()).map_err(SysPropError::SetError)
}

/// Returns the value of the property 'test_double_list' if set.
pub fn test_double_list() -> std::result::Result<Option<Vec<f64>>, SysPropError> {
    let result = match system_properties::read("test_double_list") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'test_double_list', returns 'Ok' if successful.
pub fn set_test_double_list(v: &[f64]) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format_list(v);
    system_properties::write("test_double_list", value.as_str()).map_err(SysPropError::SetError)
}

/// Returns the value of the property 'test_list_int' if set.
pub fn test_list_int() -> std::result::Result<Option<Vec<i32>>, SysPropError> {
    let result = match system_properties::read("test_list_int") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'test_list_int', returns 'Ok' if successful.
pub fn set_test_list_int(v: &[i32]) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format_list(v);
    system_properties::write("test_list_int", value.as_str()).map_err(SysPropError::SetError)
}

/// Returns the value of the property 'test_strlist' if set.
#[deprecated]
pub fn test_strlist() -> std::result::Result<Option<Vec<String>>, SysPropError> {
    let result = match system_properties::read("test_strlist") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'test_strlist', returns 'Ok' if successful.
#[deprecated]
pub fn set_test_strlist(v: &[String]) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format_list(v);
    system_properties::write("test_strlist", value.as_str()).map_err(SysPropError::SetError)
}

#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Hash, Ord)]
pub enum ElValues {
    Enu,
    Mva,
    Lue,
}

impl std::str::FromStr for ElValues {
    type Err = String;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match s {
            "enu" => Ok(ElValues::Enu),
            "mva" => Ok(ElValues::Mva),
            "lue" => Ok(ElValues::Lue),
            _ => Err(format!("'{}' cannot be parsed for ElValues", s)),
        }
    }
}

impl fmt::Display for ElValues {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ElValues::Enu => write!(f, "enu"),
            ElValues::Mva => write!(f, "mva"),
            ElValues::Lue => write!(f, "lue"),
        }
    }
}

/// Returns the value of the property 'el' if set.
#[deprecated]
pub fn el() -> std::result::Result<Option<Vec<ElValues>>, SysPropError> {
    let result = match system_properties::read("el") {
        Err(e) => Err(SysPropError::FetchError(e)),
        Ok(Some(val)) => parsers_formatters::parse_list(val.as_str()).map_err(SysPropError::ParseError).map(Some),
        Ok(None) => Ok(None),
    };
    result
}

/// Sets the value of the property 'el', returns 'Ok' if successful.
#[deprecated]
pub fn set_el(v: &[ElValues]) -> std::result::Result<(), SysPropError> {
    let value = parsers_formatters::format_list(v);
    system_properties::write("el", value.as_str()).map_err(SysPropError::SetError)
}

)";

}  // namespace

using namespace std::string_literals;

TEST(SyspropTest, RustGenTest) {
  TemporaryFile temp_file;

  // strlen is optimized for constants, so don't worry about it.
  ASSERT_EQ(write(temp_file.fd, kTestSyspropFile, strlen(kTestSyspropFile)),
            strlen(kTestSyspropFile));
  close(temp_file.fd);
  temp_file.fd = -1;

  TemporaryDir temp_dir;

  std::pair<sysprop::Scope, const char*> tests[] = {
      {sysprop::Scope::Internal, kExpectedInternalOutput},
      {sysprop::Scope::Public, kExpectedPublicOutput},
  };

  for (auto [scope, expected_output] : tests) {
    std::string rust_output_path = temp_dir.path + "/mod.rs"s;

    ASSERT_RESULT_OK(GenerateRustLibrary(temp_file.path, scope, temp_dir.path));

    std::string rust_output;
    ASSERT_TRUE(
        android::base::ReadFileToString(rust_output_path, &rust_output, true));
    EXPECT_EQ(rust_output, expected_output);

    unlink(rust_output.c_str());
    rmdir((temp_dir.path + "/com/somecompany"s).c_str());
    rmdir((temp_dir.path + "/com"s).c_str());
  }
}