chromium/chromeos/components/quick_answers/quick_answers_model.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/components/quick_answers/quick_answers_model.h"

#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chromeos/components/quick_answers/utils/quick_answers_utils.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "ui/base/l10n/l10n_util.h"

namespace {

double MaybeGetRatio(double value1, double value2) {
  if (value1 == quick_answers::kInvalidRateTermValue ||
      value2 == quick_answers::kInvalidRateTermValue) {
    return quick_answers::kInvalidRateTermValue;
  }

  return std::max(value1, value2) / std::min(value1, value2);
}

int GetFormulaMessageId(bool is_multiply, bool is_approximate) {
  if (is_multiply) {
    if (is_approximate) {
      return IDS_RICH_ANSWERS_VIEW_UNIT_CONVERSION_APPROXIMATE_MULTIPLICATION_FORMULA_TEXT;
    }

    return IDS_RICH_ANSWERS_VIEW_UNIT_CONVERSION_EXACT_MULTIPLICATION_FORMULA_TEXT;
  }

  if (is_approximate) {
    return IDS_RICH_ANSWERS_VIEW_UNIT_CONVERSION_APPROXIMATE_DIVISION_FORMULA_TEXT;
  }

  return IDS_RICH_ANSWERS_VIEW_UNIT_CONVERSION_EXACT_DIVISION_FORMULA_TEXT;
}

}  // namespace

namespace quick_answers {

std::optional<quick_answers::Intent> ToIntent(IntentType intent_type) {
  switch (intent_type) {
    case IntentType::kDictionary:
      return quick_answers::Intent::kDefinition;
    case IntentType::kTranslation:
      return quick_answers::Intent::kTranslation;
    case IntentType::kUnit:
      return quick_answers::Intent::kUnitConversion;
    case IntentType::kUnknown:
      return std::nullopt;
  }

  CHECK(false) << "Invalid intent type enum value provided";
}

PhoneticsInfo::PhoneticsInfo() = default;
PhoneticsInfo::PhoneticsInfo(const PhoneticsInfo&) = default;
PhoneticsInfo::~PhoneticsInfo() = default;

bool PhoneticsInfo::PhoneticsInfoAvailable() const {
  return AudioUrlAvailable() || TtsAudioAvailable();
}

bool PhoneticsInfo::AudioUrlAvailable() const {
  return !phonetics_audio.is_empty();
}

bool PhoneticsInfo::TtsAudioAvailable() const {
  if (!tts_audio_enabled) {
    return false;
  }

  return !query_text.empty() && !locale.empty();
}

QuickAnswer::QuickAnswer() = default;
QuickAnswer::~QuickAnswer() = default;

IntentInfo::IntentInfo() = default;
IntentInfo::IntentInfo(const IntentInfo& other) = default;
IntentInfo::IntentInfo(const std::string& intent_text,
                       IntentType intent_type,
                       const std::string& device_language,
                       const std::string& source_language) {
  this->intent_text = intent_text;
  this->intent_type = intent_type;
  this->device_language = device_language;
  this->source_language = source_language;
}
IntentInfo::~IntentInfo() = default;

PreprocessedOutput::PreprocessedOutput() = default;
PreprocessedOutput::PreprocessedOutput(const PreprocessedOutput& other) =
    default;
PreprocessedOutput::~PreprocessedOutput() = default;

QuickAnswersRequest::QuickAnswersRequest() = default;
QuickAnswersRequest::QuickAnswersRequest(const QuickAnswersRequest& other) =
    default;
QuickAnswersRequest::~QuickAnswersRequest() = default;

Sense::Sense() = default;
Sense::Sense(const Sense& other) = default;
Sense& Sense::Sense::operator=(const Sense& other) = default;
Sense::~Sense() = default;

DefinitionResult::DefinitionResult() = default;
DefinitionResult::DefinitionResult(const DefinitionResult& other) = default;
DefinitionResult& DefinitionResult::DefinitionResult::operator=(
    const DefinitionResult& other) = default;
DefinitionResult::~DefinitionResult() = default;

TranslationResult::TranslationResult() = default;
TranslationResult::TranslationResult(const TranslationResult& other) = default;
TranslationResult& TranslationResult::TranslationResult::operator=(
    const TranslationResult& other) = default;
TranslationResult::~TranslationResult() = default;

ConversionRule::ConversionRule(const std::string& category,
                               const std::string& unit_name,
                               double term_a,
                               double term_b,
                               double term_c)
    : category_(category),
      unit_name_(unit_name),
      term_a_(term_a),
      term_b_(term_b),
      term_c_(term_c) {}
ConversionRule::ConversionRule(const ConversionRule& other) = default;
ConversionRule& ConversionRule::ConversionRule::operator=(
    const ConversionRule& other) = default;
ConversionRule::~ConversionRule() = default;
std::optional<ConversionRule> ConversionRule::Create(
    const std::string& category,
    const std::string& unit_name,
    const std::optional<double>& term_a,
    const std::optional<double>& term_b,
    const std::optional<double>& term_c) {
  if (category.empty() || unit_name.empty()) {
    return std::nullopt;
  }

  // If neither |term_a| nor |term_c| is valid, there is no valid conversion
  // rule.
  if ((!term_a || term_a.value() == kInvalidRateTermValue) &&
      (!term_c || term_c.value() == kInvalidRateTermValue)) {
    return std::nullopt;
  }

  // Neither |term_a| nor |term_c| should be negative. Return nullopt for this
  // unexpected case.
  if ((term_a && term_a.value() < 0) || (term_c && term_c.value() < 0)) {
    return std::nullopt;
  }

  double term_a_value = term_a.value_or(kInvalidRateTermValue);
  double term_b_value = term_b.value_or(kInvalidRateTermValue);
  double term_c_value = term_c.value_or(kInvalidRateTermValue);
  return ConversionRule(category, unit_name, term_a_value, term_b_value,
                        term_c_value);
}
double ConversionRule::ConvertAmountToSi(double unit_amount) const {
  return (term_a_ != kInvalidRateTermValue) ? (term_a_ * unit_amount + term_b_)
                                            : (term_c_ / unit_amount);
}
double ConversionRule::ConvertAmountFromSi(double si_amount) const {
  return (term_a_ != kInvalidRateTermValue) ? ((si_amount - term_b_) / term_a_)
                                            : (term_c_ / si_amount);
}
bool ConversionRule::IsSingleVariableLinearConversion() const {
  return (term_a_ != kInvalidRateTermValue) &&
         (term_b_ == kInvalidRateTermValue) &&
         (term_c_ == kInvalidRateTermValue);
}

UnitConversion::UnitConversion(const ConversionRule& source_rule,
                               const ConversionRule& dest_rule)
    : source_rule_(source_rule), dest_rule_(dest_rule) {}
UnitConversion::UnitConversion(const UnitConversion& other) = default;
UnitConversion& UnitConversion::UnitConversion::operator=(
    const UnitConversion& other) = default;
UnitConversion::~UnitConversion() = default;
std::optional<UnitConversion> UnitConversion::Create(
    const ConversionRule& source_rule,
    const ConversionRule& dest_rule) {
  if (source_rule.category() != dest_rule.category()) {
    return std::nullopt;
  }

  return UnitConversion(source_rule, dest_rule);
}
bool UnitConversion::operator<(const UnitConversion& other) const {
  double linear_term_ratio =
      MaybeGetRatio(source_rule_.term_a(), dest_rule_.term_a());
  if (linear_term_ratio == kInvalidRateTermValue) {
    return false;
  }

  double other_linear_term_ratio =
      MaybeGetRatio(other.source_rule_.term_a(), other.dest_rule_.term_a());
  if (other_linear_term_ratio == kInvalidRateTermValue) {
    return true;
  }

  return linear_term_ratio < other_linear_term_ratio;
}
double UnitConversion::ConvertSourceAmountToDestAmount(
    double source_amount) const {
  return dest_rule_.ConvertAmountFromSi(
      source_rule_.ConvertAmountToSi(source_amount));
}
std::optional<std::string> UnitConversion::GetConversionFormulaText() const {
  // We only return formula description texts for conversions between two units
  // whose `ConversionRule` only involves |term_a_| values i.e. formula form is:
  // a * source = target.
  if (!source_rule_.IsSingleVariableLinearConversion() ||
      !dest_rule_.IsSingleVariableLinearConversion()) {
    return std::nullopt;
  }

  // Don't return a formula description text if the conversion rate is
  // exactly 1.
  if (source_rule_.term_a() == dest_rule_.term_a()) {
    return std::nullopt;
  }

  // Get the greater ratio (i.e. >= 1) of the two linear |term_a_| values.
  // The conversion formula will be in the form: source * (a1/a2) = target
  // The actual ratio will determine whether the conversion operator used for
  // the formula description is multiplication (when the source unit |term_a|
  // value is the numerator) or division (when the target unit |term_a| value
  // is the numerator).
  double conversion_term_a =
      MaybeGetRatio(source_rule_.term_a(), dest_rule_.term_a());

  if (conversion_term_a == kInvalidRateTermValue) {
    return std::nullopt;
  }

  // Check if the conversion term is a decimal number. If it is, an
  // approximation qualifier (i.e. "for an approximate result, ...") will be
  // appended at the beginning of the formula text.
  double int_part = 0.0;
  bool is_approximate_formula = std::modf(conversion_term_a, &int_part) != 0.0;

  bool is_multiply_formula = source_rule_.term_a() > dest_rule_.term_a();
  int formula_message_id =
      GetFormulaMessageId(is_multiply_formula, is_approximate_formula);

  return l10n_util::GetStringFUTF8(
      formula_message_id,
      base::UTF8ToUTF16(base::ToLowerASCII(source_rule_.category())),
      base::UTF8ToUTF16(BuildRoundedUnitAmountDisplayText(conversion_term_a)));
}

UnitConversionResult::UnitConversionResult() = default;
UnitConversionResult::UnitConversionResult(const UnitConversionResult& other) =
    default;
UnitConversionResult& UnitConversionResult::UnitConversionResult::operator=(
    const UnitConversionResult& other) = default;
UnitConversionResult::~UnitConversionResult() = default;

StructuredResult::StructuredResult() = default;
StructuredResult::~StructuredResult() = default;
ResultType StructuredResult::GetResultType() const {
  if (translation_result) {
    return ResultType::kTranslationResult;
  }
  if (definition_result) {
    return ResultType::kDefinitionResult;
  }
  if (unit_conversion_result) {
    return ResultType::kUnitConversionResult;
  }
  return ResultType::kNoResult;
}

QuickAnswersSession::QuickAnswersSession() = default;
QuickAnswersSession::~QuickAnswersSession() = default;

}  // namespace quick_answers