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

#include "ash/accessibility/accessibility_focus_ring_controller_impl.h"

#include <stddef.h>

#include <algorithm>
#include <memory>
#include <set>
#include <utility>
#include <vector>

#include "ash/accessibility/accessibility_cursor_ring_layer.h"
#include "ash/accessibility/accessibility_focus_ring_group.h"
#include "ash/accessibility/accessibility_focus_ring_layer.h"
#include "ash/accessibility/accessibility_highlight_layer.h"
#include "ash/accessibility/focus_ring_layer.h"
#include "ash/accessibility/layer_animation_info.h"
#include "base/check.h"

namespace ash {

namespace {

// Cursor constants.
constexpr int kCursorFadeInTimeMilliseconds = 400;
constexpr int kCursorFadeOutTimeMilliseconds = 1200;
constexpr int kCursorRingColorRed = 255;
constexpr int kCursorRingColorGreen = 51;
constexpr int kCursorRingColorBlue = 51;

// Caret constants.
constexpr int kCaretFadeInTimeMilliseconds = 100;
constexpr int kCaretFadeOutTimeMilliseconds = 1600;
constexpr int kCaretRingColorRed = 51;
constexpr int kCaretRingColorGreen = 51;
constexpr int kCaretRingColorBlue = 255;

// Highlight constants.
constexpr float kHighlightOpacity = 0.3f;

}  // namespace

AccessibilityFocusRingControllerImpl::AccessibilityFocusRingControllerImpl() {
  cursor_animation_info_.fade_in_time =
      base::TimeDelta::FromMilliseconds(kCursorFadeInTimeMilliseconds);
  cursor_animation_info_.fade_out_time =
      base::TimeDelta::FromMilliseconds(kCursorFadeOutTimeMilliseconds);
  caret_animation_info_.fade_in_time =
      base::TimeDelta::FromMilliseconds(kCaretFadeInTimeMilliseconds);
  caret_animation_info_.fade_out_time =
      base::TimeDelta::FromMilliseconds(kCaretFadeOutTimeMilliseconds);
}

AccessibilityFocusRingControllerImpl::~AccessibilityFocusRingControllerImpl() =
    default;

void AccessibilityFocusRingControllerImpl::SetFocusRing(
    const std::string& focus_ring_id,
    std::unique_ptr<AccessibilityFocusRingInfo> focus_ring) {
  // This code assumes |focus_ring| is always non-null.
  DCHECK(focus_ring);
  AccessibilityFocusRingGroup* focus_ring_group =
      GetFocusRingGroupForId(focus_ring_id, true /* Create if missing */);
  if (focus_ring_group->UpdateFocusRing(std::move(focus_ring), this))
    OnLayerChange(focus_ring_group->focus_animation_info());
}

void AccessibilityFocusRingControllerImpl::HideFocusRing(
    const std::string& focus_ring_id) {
  AccessibilityFocusRingGroup* focus_ring_group =
      GetFocusRingGroupForId(focus_ring_id, false /* Do not create */);
  if (!focus_ring_group)
    return;
  focus_ring_group->ClearFocusRects(this);
  OnLayerChange(focus_ring_group->focus_animation_info());
}

void AccessibilityFocusRingControllerImpl::SetHighlights(
    const std::vector<gfx::Rect>& rects,
    SkColor color) {
  highlight_rects_ = rects;
  GetColorAndOpacityFromColor(color, kHighlightOpacity, &highlight_color_,
                              &highlight_opacity_);
  UpdateHighlightFromHighlightRects();
}

void AccessibilityFocusRingControllerImpl::HideHighlights() {
  highlight_rects_.clear();
  UpdateHighlightFromHighlightRects();
}

void AccessibilityFocusRingControllerImpl::UpdateHighlightFromHighlightRects() {
  if (!highlight_layer_)
    highlight_layer_ = std::make_unique<AccessibilityHighlightLayer>(this);
  highlight_layer_->Set(highlight_rects_, highlight_color_);
  highlight_layer_->SetOpacity(highlight_opacity_);
}

void AccessibilityFocusRingControllerImpl::OnLayerChange(
    LayerAnimationInfo* animation_info) {
  animation_info->change_time = base::TimeTicks::Now();
  if (animation_info->opacity == 0)
    animation_info->start_time = animation_info->change_time;
}

void AccessibilityFocusRingControllerImpl::SetCursorRing(
    const gfx::Point& location) {
  cursor_location_ = location;
  if (!cursor_layer_) {
    cursor_layer_.reset(new AccessibilityCursorRingLayer(
        this, kCursorRingColorRed, kCursorRingColorGreen,
        kCursorRingColorBlue));
  }
  cursor_layer_->Set(location);
  OnLayerChange(&cursor_animation_info_);
}

void AccessibilityFocusRingControllerImpl::HideCursorRing() {
  cursor_layer_.reset();
}

void AccessibilityFocusRingControllerImpl::SetCaretRing(
    const gfx::Point& location) {
  caret_location_ = location;

  if (!caret_layer_) {
    caret_layer_.reset(new AccessibilityCursorRingLayer(
        this, kCaretRingColorRed, kCaretRingColorGreen, kCaretRingColorBlue));
  }

  caret_layer_->Set(location);
  OnLayerChange(&caret_animation_info_);
}

void AccessibilityFocusRingControllerImpl::HideCaretRing() {
  caret_layer_.reset();
}

void AccessibilityFocusRingControllerImpl::SetNoFadeForTesting() {
  no_fade_for_testing_ = true;
  for (auto iter = focus_ring_groups_.begin(); iter != focus_ring_groups_.end();
       ++iter) {
    iter->second->set_no_fade_for_testing();
    iter->second->focus_animation_info()->fade_in_time = base::TimeDelta();
    iter->second->focus_animation_info()->fade_out_time =
        base::TimeDelta::FromHours(1);
  }
  cursor_animation_info_.fade_in_time = base::TimeDelta();
  cursor_animation_info_.fade_out_time = base::TimeDelta::FromHours(1);
  caret_animation_info_.fade_in_time = base::TimeDelta();
  caret_animation_info_.fade_out_time = base::TimeDelta::FromHours(1);
}

const AccessibilityFocusRingGroup*
AccessibilityFocusRingControllerImpl::GetFocusRingGroupForTesting(
    const std::string& focus_ring_id) {
  return GetFocusRingGroupForId(focus_ring_id, false /* create if missing */);
}

void AccessibilityFocusRingControllerImpl::GetColorAndOpacityFromColor(
    SkColor color,
    float default_opacity,
    SkColor* result_color,
    float* result_opacity) {
  int alpha = SkColorGetA(color);
  if (alpha == 0xFF) {
    *result_opacity = default_opacity;
  } else {
    *result_opacity = SkColor4f::FromColor(color).fA;
  }
  *result_color = SkColorSetA(color, 0xFF);
}

void AccessibilityFocusRingControllerImpl::OnDeviceScaleFactorChanged() {
  for (auto iter = focus_ring_groups_.begin(); iter != focus_ring_groups_.end();
       ++iter)
    iter->second->UpdateFocusRingsFromInfo(this);
}

void AccessibilityFocusRingControllerImpl::OnAnimationStep(
    base::TimeTicks timestamp) {
  for (auto iter = focus_ring_groups_.begin(); iter != focus_ring_groups_.end();
       ++iter) {
    if (iter->second->CanAnimate())
      iter->second->AnimateFocusRings(timestamp);
  }

  if (cursor_layer_ && cursor_layer_->CanAnimate())
    AnimateCursorRing(timestamp);

  if (caret_layer_ && caret_layer_->CanAnimate())
    AnimateCaretRing(timestamp);
}

void AccessibilityFocusRingControllerImpl::AnimateCursorRing(
    base::TimeTicks timestamp) {
  CHECK(cursor_layer_);

  ComputeOpacity(&cursor_animation_info_, timestamp);
  if (cursor_animation_info_.opacity == 0.0) {
    cursor_layer_.reset();
    return;
  }
  cursor_layer_->SetOpacity(cursor_animation_info_.opacity);
}

void AccessibilityFocusRingControllerImpl::AnimateCaretRing(
    base::TimeTicks timestamp) {
  CHECK(caret_layer_);

  ComputeOpacity(&caret_animation_info_, timestamp);
  if (caret_animation_info_.opacity == 0.0) {
    caret_layer_.reset();
    return;
  }
  caret_layer_->SetOpacity(caret_animation_info_.opacity);
}

AccessibilityFocusRingGroup*
AccessibilityFocusRingControllerImpl::GetFocusRingGroupForId(
    const std::string& focus_ring_id,
    bool create) {
  auto iter = focus_ring_groups_.find(focus_ring_id);
  if (iter != focus_ring_groups_.end())
    return iter->second.get();

  if (!create)
    return nullptr;

  // Add it and then return it.
  focus_ring_groups_[focus_ring_id] =
      std::make_unique<AccessibilityFocusRingGroup>();
  if (no_fade_for_testing_)
    SetNoFadeForTesting();

  return focus_ring_groups_[focus_ring_id].get();
}

}  // namespace ash
