// 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 "components/live_caption/views/caption_bubble_model.h"

#include "base/feature_list.h"
#include "base/logging.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "components/live_caption/caption_bubble_context.h"
#include "components/live_caption/views/caption_bubble.h"
#include "media/base/media_switches.h"

namespace {

// Non-scrollable caption bubble contains 2 lines of text in its normal size and
// 8 lines in its expanded size, so the maximum number of lines before
// truncating is 9.
// For scrollable caption bubble the number of lines in text is limited by
// `kLiveCaptionScrollableMaxLines` feature parameter (see below).
constexpr int kMaxLines = 9;

// Returns the length of the longest common prefix between two strings.
int GetLongestCommonPrefixLength(const std::string& str1,
                                 const std::string& str2) {
  int length = 0;
  for (unsigned long i = 0, j = 0; i < str1.length() && j < str2.length();
       i++, j++, length++) {
    if (str1[i] != str2[j]) {
      break;
    }
  }

  return length;
}

}  // namespace

namespace captions {

BASE_FEATURE(kLiveCaptionScrollable, base::FEATURE_DISABLED_BY_DEFAULT);

BASE_FEATURE_PARAM(size_t,
                   kLiveCaptionScrollableMaxLines,
                   &kLiveCaptionScrollable,
                   "live_caption_scrollable_max_lines",
                   250);

CaptionBubbleModel::CaptionBubbleModel(CaptionBubbleContext* context,
                                       OnCaptionBubbleClosedCallback callback)
    : unique_id_(GetNextId()),
      caption_bubble_closed_callback_(callback),
      context_(context) {
  DCHECK(context_);
}

CaptionBubbleModel::~CaptionBubbleModel() {
  if (base::FeatureList::IsEnabled(media::kLiveCaptionLogFlickerRate)) {
    // Log the number of erasures per partial result.
    double flicker_rate = (partial_result_count_ > 0)
                              ? erasure_count_ / partial_result_count_
                              : 0;
    LOG(WARNING) << "Live caption flicker rate:" << flicker_rate
                 << ". (not a warning)";
  }

  if (observer_)
    observer_->SetModel(nullptr);
}

void CaptionBubbleModel::SetObserver(CaptionBubble* observer) {
  if (observer_)
    return;
  observer_ = observer;
  if (observer_) {
    observer_->OnTextChanged();
    observer_->OnErrorChanged(
        CaptionBubbleErrorType::kGeneric, base::RepeatingClosure(),
        base::BindRepeating(
            [](CaptionBubbleErrorType error_type, bool checked) {}));
  }
}

void CaptionBubbleModel::RemoveObserver() {
  observer_ = nullptr;
}

void CaptionBubbleModel::OnTextChanged() {
  if (observer_) {
    observer_->OnTextChanged();
  }
}

void CaptionBubbleModel::OnAutoDetectedLanguageChanged() {
  if (observer_) {
    observer_->OnAutoDetectedLanguageChanged();
  }
}

void CaptionBubbleModel::SetPartialText(const std::string& partial_text) {
  if (base::FeatureList::IsEnabled(media::kLiveCaptionLogFlickerRate)) {
    erasure_count_ +=
        partial_text_.size() -
        GetLongestCommonPrefixLength(partial_text, partial_text_);
    partial_result_count_++;
  }

  partial_text_ = partial_text;
  OnTextChanged();
  if (has_error_) {
    has_error_ = false;
    if (observer_)
      observer_->OnErrorChanged(
          CaptionBubbleErrorType::kGeneric, base::RepeatingClosure(),
          base::BindRepeating(
              [](CaptionBubbleErrorType error_type, bool checked) {}));
  }
}

void CaptionBubbleModel::SetDownloadProgressText(
    const std::u16string& download_progress_text) {
  download_progress_text_ = download_progress_text;

  if (observer_) {
    observer_->OnDownloadProgressTextChanged();
  }
}

void CaptionBubbleModel::OnLanguagePackInstalled() {
  if (observer_) {
    observer_->OnLanguagePackInstalled();
  }
}

void CaptionBubbleModel::CloseButtonPressed() {
  caption_bubble_closed_callback_.Run(context_->GetSessionId());
  Close();
}

void CaptionBubbleModel::Close() {
  is_closed_ = true;
  ClearText();
}

std::string CaptionBubbleModel::GetFullText() const {
  // Ensure that there is a space between the final and partial texts.
  if (!final_text_.empty() && !partial_text_.empty() &&
      !std::isspace(final_text_.back()) && !std::isspace(partial_text_[0])) {
    return final_text_ + " " + partial_text_;
  }

  return final_text_ + partial_text_;
}

void CaptionBubbleModel::OnError(
    CaptionBubbleErrorType error_type,
    OnErrorClickedCallback error_clicked_callback,
    OnDoNotShowAgainClickedCallback error_silenced_callback) {
  has_error_ = true;
  error_type_ = error_type;
  if (observer_) {
    base::UmaHistogramEnumeration(
        "Accessibility.LiveCaption.CaptionBubbleError", error_type);
    observer_->OnErrorChanged(error_type, std::move(error_clicked_callback),
                              std::move(error_silenced_callback));
  }
}

void CaptionBubbleModel::ClearText() {
  partial_text_.clear();
  final_text_.clear();
  OnTextChanged();
}

void CaptionBubbleModel::CommitPartialText() {
  final_text_ = GetFullText();
  partial_text_.clear();
  if (!observer_)
    return;

  const size_t max_lines = base::FeatureList::IsEnabled(kLiveCaptionScrollable)
                               ? kLiveCaptionScrollableMaxLines.Get()
                               : kMaxLines;

  // Truncate the final text to max_lines lines long. This time, alert the
  // observer that the text has changed.
  const size_t num_lines = observer_->GetNumLinesInLabel();
  if (num_lines > max_lines) {
    const size_t truncate_index =
        observer_->GetTextIndexOfLineInLabel(num_lines - max_lines);
    final_text_.erase(0, truncate_index);
    OnTextChanged();
  }
}

void CaptionBubbleModel::SetLanguage(const std::string& language_code) {
  if (!observer_) {
    return;
  }

  auto_detected_language_code_ = language_code;
  OnAutoDetectedLanguageChanged();
}

// static
CaptionBubbleModel::Id CaptionBubbleModel::GetNextId() {
  static Id::Generator generator;
  return generator.GenerateNextId();
}

}  // namespace captions
