/*
 * 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.
 */

#define LOG_TAG "sysprop_rust_gen"

#include "RustGen.h"

#include <android-base/file.h>

#include <regex>
#include <string>

#include "CodeWriter.h"
#include "Common.h"
#include "sysprop.pb.h"

using android::base::Result;

namespace {

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

constexpr const char* kRustFileImports = R"(use std::fmt;
use rustutils::system_properties::{self, error::SysPropError, parsers_formatters};)";

constexpr const char* kIndent = "    ";

constexpr const char* kDeprecated = "#[deprecated]";

std::string GetRustEnumType(const sysprop::Property& prop) {
  std::string result = ApiNameToIdentifier(prop.api_name());
  return SnakeCaseToCamelCase(result) + "Values";
}

std::string GetRustReturnType(const sysprop::Property& prop) {
  switch (prop.type()) {
    case sysprop::Boolean:
      return "bool";
    case sysprop::Integer:
      return "i32";
    case sysprop::UInt:
      return "u32";
    case sysprop::Long:
      return "i64";
    case sysprop::ULong:
      return "u64";
    case sysprop::Double:
      return "f64";
    case sysprop::String:
      return "String";
    case sysprop::Enum:
      return GetRustEnumType(prop);
    case sysprop::BooleanList:
      return "Vec<bool>";
    case sysprop::IntegerList:
      return "Vec<i32>";
    case sysprop::UIntList:
      return "Vec<u32>";
    case sysprop::LongList:
      return "Vec<i64>";
    case sysprop::ULongList:
      return "Vec<u64>";
    case sysprop::DoubleList:
      return "Vec<f64>";
    case sysprop::StringList:
      return "Vec<String>";
    case sysprop::EnumList:
      return "Vec<" + GetRustEnumType(prop) + ">";
    default:
      __builtin_unreachable();
  }
}

std::string GetRustAcceptType(const sysprop::Property& prop) {
  switch (prop.type()) {
    case sysprop::Boolean:
      return "bool";
    case sysprop::Integer:
      return "i32";
    case sysprop::UInt:
      return "u32";
    case sysprop::Long:
      return "i64";
    case sysprop::ULong:
      return "u64";
    case sysprop::Double:
      return "f64";
    case sysprop::String:
      return "&str";
    case sysprop::Enum:
      return GetRustEnumType(prop);
    case sysprop::BooleanList:
      return "&[bool]";
    case sysprop::IntegerList:
      return "&[i32]";
    case sysprop::UIntList:
      return "&[u32]";
    case sysprop::LongList:
      return "&[i64]";
    case sysprop::ULongList:
      return "&[u64]";
    case sysprop::DoubleList:
      return "&[f64]";
    case sysprop::StringList:
      return "&[String]";
    case sysprop::EnumList:
      return "&[" + GetRustEnumType(prop) + "]";
    default:
      __builtin_unreachable();
  }
}

std::string GetTypeParser(const sysprop::Property& prop) {
  switch (prop.type()) {
    case sysprop::Boolean:
      return "parsers_formatters::parse_bool";
    case sysprop::Integer:
    case sysprop::UInt:
    case sysprop::Long:
    case sysprop::ULong:
    case sysprop::Double:
    case sysprop::String:
    case sysprop::Enum:
      return "parsers_formatters::parse";
    case sysprop::BooleanList:
      return "parsers_formatters::parse_bool_list";
    case sysprop::IntegerList:
    case sysprop::UIntList:
    case sysprop::LongList:
    case sysprop::ULongList:
    case sysprop::DoubleList:
    case sysprop::StringList:
    case sysprop::EnumList:
      return "parsers_formatters::parse_list";
    default:
      __builtin_unreachable();
  }
}

std::string GetTypeFormatter(const sysprop::Property& prop) {
  switch (prop.type()) {
    case sysprop::Boolean:
      if (prop.integer_as_bool()) {
        return "parsers_formatters::format_bool_as_int";
      }
      return "parsers_formatters::format_bool";
    case sysprop::String:
    case sysprop::Integer:
    case sysprop::UInt:
    case sysprop::Long:
    case sysprop::ULong:
    case sysprop::Double:
    case sysprop::Enum:
      return "parsers_formatters::format";
    case sysprop::BooleanList:
      if (prop.integer_as_bool()) {
        return "parsers_formatters::format_bool_list_as_int";
      }
      return "parsers_formatters::format_bool_list";
    case sysprop::IntegerList:
    case sysprop::UIntList:
    case sysprop::LongList:
    case sysprop::ULongList:
    case sysprop::DoubleList:
    case sysprop::StringList:
    case sysprop::EnumList:
      return "parsers_formatters::format_list";
    default:
      __builtin_unreachable();
  }
}

std::string GenerateRustSource(sysprop::Properties props, sysprop::Scope scope) {
  CodeWriter writer(kIndent);
  writer.Write("%s\n\n", kDocs);
  writer.Write("%s", kGeneratedFileFooterComments);
  writer.Write("%s\n\n", kRustFileImports);

  for (int i = 0; i < props.prop_size(); ++i) {
    const sysprop::Property& prop = props.prop(i);
    if (prop.scope() > scope) continue;

    std::string prop_id =
        CamelCaseToSnakeCase(ApiNameToIdentifier(prop.api_name()));

    // Create enum.
    if (prop.type() == sysprop::Enum || prop.type() == sysprop::EnumList) {
      auto enum_type = GetRustEnumType(prop);
      auto values = ParseEnumValues(prop.enum_values());

      writer.Write("#[allow(missing_docs)]\n");
      writer.Write(
          "#[derive(Copy, Clone, Debug, Eq, "
          "PartialEq, PartialOrd, Hash, Ord)]\n");
      writer.Write("pub enum %s {\n", enum_type.c_str());
      writer.Indent();
      for (const std::string& value : values) {
        writer.Write("%s,\n", SnakeCaseToCamelCase(value).c_str());
      }
      writer.Dedent();
      writer.Write("}\n\n");

      // Enum parser.
      writer.Write("impl std::str::FromStr for %s {\n", enum_type.c_str());
      writer.Indent();
      writer.Write("type Err = String;\n\n");
      writer.Write(
          "fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {\n");
      writer.Indent();
      writer.Write("match s {\n");
      writer.Indent();
      for (const std::string& value : values) {
        writer.Write("\"%s\" => Ok(%s::%s),\n", value.c_str(),
                     enum_type.c_str(), SnakeCaseToCamelCase(value).c_str());
      }
      writer.Write("_ => Err(format!(\"'{}' cannot be parsed for %s\", s)),\n",
                   enum_type.c_str());
      writer.Dedent();
      writer.Write("}\n");
      writer.Dedent();
      writer.Write("}\n");
      writer.Dedent();
      writer.Write("}\n\n");

      // Enum formatter.
      writer.Write("impl fmt::Display for %s {\n", enum_type.c_str());
      writer.Indent();
      writer.Write(
          "fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n");
      writer.Indent();
      writer.Write("match self {\n");
      writer.Indent();
      for (const std::string& value : values) {
        writer.Write("%s::%s => write!(f, \"%s\"),\n", enum_type.c_str(),
                     SnakeCaseToCamelCase(value).c_str(), value.c_str());
      }
      writer.Dedent();
      writer.Write("}\n");
      writer.Dedent();
      writer.Write("}\n");
      writer.Dedent();
      writer.Write("}\n\n");
    }

    // Write getter.
    std::string prop_return_type = GetRustReturnType(prop);
    std::string parser = GetTypeParser(prop);
    writer.Write("/// Returns the value of the property '%s' if set.\n",
                 prop.prop_name().c_str());
    if (prop.deprecated()) writer.Write("%s\n", kDeprecated);
    // Escape prop id if it is similar to `type` keyword.
    std::string identifier = (prop_id == "type") ? "r#" + prop_id : prop_id;
    writer.Write(
        "pub fn %s() -> std::result::Result<Option<%s>, SysPropError> {\n",
        identifier.c_str(), prop_return_type.c_str());
    writer.Indent();
    // Try original property.
    writer.Write("let result = match system_properties::read(\"%s\") {\n",
                 prop.prop_name().c_str());
    writer.Indent();
    writer.Write("Err(e) => Err(SysPropError::FetchError(e)),\n");
    writer.Write(
        "Ok(Some(val)) => "
        "%s(val.as_str()).map_err(SysPropError::ParseError).map(Some),\n",
        parser.c_str());
    writer.Write("Ok(None) => Ok(None),\n");
    writer.Dedent();
    writer.Write("};\n");
    // Try legacy property
    if (!prop.legacy_prop_name().empty()) {
      writer.Write("if result.is_ok() { return result; }\n");
      // Avoid omitting the error when fallback to legacy.
      writer.Write(
          "log::debug!(\"Failed to fetch the original property '%s' ('{}'), "
          "falling back to the legacy one '%s'.\", result.unwrap_err());\n",
          prop.prop_name().c_str(), prop.legacy_prop_name().c_str());
      writer.Write("match system_properties::read(\"%s\") {\n",
                   prop.legacy_prop_name().c_str());
      writer.Indent();
      writer.Write("Err(e) => Err(SysPropError::FetchError(e)),\n");
      writer.Write(
          "Ok(Some(val)) => "
          "%s(val.as_str()).map_err(SysPropError::ParseError).map(Some),\n",
          parser.c_str());
      writer.Write("Ok(None) => Ok(None),\n");
      writer.Dedent();
      writer.Write("}\n");
    } else {
      writer.Write("result\n");
    }
    writer.Dedent();
    writer.Write("}\n\n");

    // Write setter.
    if (prop.access() == sysprop::Readonly) continue;
    std::string prop_accept_type = GetRustAcceptType(prop);
    std::string formatter = GetTypeFormatter(prop);
    writer.Write(
        "/// Sets the value of the property '%s', "
        "returns 'Ok' if successful.\n",
        prop.prop_name().c_str());
    if (prop.deprecated()) writer.Write("%s\n", kDeprecated);
    writer.Write(
        "pub fn set_%s(v: %s) -> std::result::Result<(), SysPropError> {\n",
        prop_id.c_str(), prop_accept_type.c_str());
    writer.Indent();
    std::string write_arg;
    if (prop.type() == sysprop::String) {
      write_arg = "v";
    } else {
      // We need to borrow single values.
      std::string format_arg = prop.type() >= 20 ? "v" : "&v";
      writer.Write("let value = %s(%s);\n", formatter.c_str(),
                   format_arg.c_str());
      write_arg = "value.as_str()";
    }
    writer.Write(
        "system_properties::write(\"%s\", "
        "%s).map_err(SysPropError::SetError)\n",
        prop.prop_name().c_str(), write_arg.c_str());
    writer.Dedent();
    writer.Write("}\n\n");
  }
  return writer.Code();
}

};  // namespace

Result<void> GenerateRustLibrary(const std::string& input_file_path,
                                 sysprop::Scope scope,
                                 const std::string& rust_output_dir) {
  sysprop::Properties props;

  if (auto res = ParseProps(input_file_path); res.ok()) {
    props = std::move(*res);
  } else {
    return res.error();
  }

  std::string lib_path = rust_output_dir + "/mod.rs";
  std::string lib_result = GenerateRustSource(props, scope);
  if (!android::base::WriteStringToFile(lib_result, lib_path)) {
    return ErrnoErrorf("Writing generated rust lib to {} failed", lib_path);
  }

  return {};
}
