namespace media {

namespace {

template <typename EntryType>
bool Get3AEntry(const cros::mojom::CameraMetadataPtr& metadata,
                cros::mojom::CameraMetadataTag control,
                EntryType* result) {
  const auto* entry = GetMetadataEntry(metadata, control);
  if (entry) {
    *result = static_cast<EntryType>((*entry)->data[0]);
    return true;
  } else {
    return false;
  }
}

}  // namespace

Camera3AController::Camera3AController(
    const cros::mojom::CameraMetadataPtr& static_metadata,
    CaptureMetadataDispatcher* capture_metadata_dispatcher,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
    : static_metadata_(static_metadata),
      capture_metadata_dispatcher_(capture_metadata_dispatcher),
      task_runner_(std::move(task_runner)),
      af_mode_(cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF),
      af_state_(cros::mojom::AndroidControlAfState::
                    ANDROID_CONTROL_AF_STATE_INACTIVE),
      af_mode_set_(false),
      ae_mode_(cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_ON),
      ae_state_(cros::mojom::AndroidControlAeState::
                    ANDROID_CONTROL_AE_STATE_INACTIVE),
      ae_mode_set_(false),
      awb_mode_(
          cros::mojom::AndroidControlAwbMode::ANDROID_CONTROL_AWB_MODE_AUTO),
      awb_state_(cros::mojom::AndroidControlAwbState::
                     ANDROID_CONTROL_AWB_STATE_INACTIVE),
      awb_mode_set_(false),
      set_point_of_interest_running_(false),
      ae_locked_for_point_of_interest_(false) {
  DCHECK(task_runner_->BelongsToCurrentThread());

  capture_metadata_dispatcher_->AddResultMetadataObserver(this);

  auto max_regions = GetMetadataEntryAsSpan<int32_t>(
      static_metadata_,
      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_MAX_REGIONS);
  if (max_regions.empty()) {
    ae_region_supported_ = false;
    af_region_supported_ = false;
  } else {
    DCHECK_EQ(max_regions.size(), 3u);
    ae_region_supported_ = max_regions[0] > 0;
    af_region_supported_ = max_regions[2] > 0;
  }

  auto* af_modes = GetMetadataEntry(
      static_metadata_,
      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_AVAILABLE_MODES);
  if (af_modes) {
    for (const auto& m : (*af_modes)->data) {
      available_af_modes_.insert(
          static_cast<cros::mojom::AndroidControlAfMode>(m));
    }
  }
  auto* ae_modes = GetMetadataEntry(
      static_metadata_,
      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_AVAILABLE_MODES);
  if (ae_modes) {
    for (const auto& m : (*ae_modes)->data) {
      available_ae_modes_.insert(
          static_cast<cros::mojom::AndroidControlAeMode>(m));
    }
  }
  auto* awb_modes = GetMetadataEntry(
      static_metadata_,
      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_AVAILABLE_MODES);
  if (awb_modes) {
    for (const auto& m : (*awb_modes)->data) {
      available_awb_modes_.insert(
          static_cast<cros::mojom::AndroidControlAwbMode>(m));
    }
  }

  point_of_interest_supported_ = [&]() {
    // Because of line wrapping, multiple if-statements is more readable than a
    // super long boolean expression.
    auto available_modes = GetMetadataEntryAsSpan<uint8_t>(
        static_metadata_,
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AVAILABLE_MODES);
    if (available_modes.empty()) {
      return false;
    }
    if (std::find(
            available_modes.begin(), available_modes.end(),
            base::checked_cast<uint8_t>(
                cros::mojom::AndroidControlMode::ANDROID_CONTROL_MODE_AUTO)) ==
        available_modes.end()) {
      return false;
    }
    if (!available_ae_modes_.count(
            cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_ON)) {
      return false;
    }
    if (!available_af_modes_.count(
            cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_AUTO)) {
      return false;
    }
    if (!ae_region_supported_ && !af_region_supported_) {
      return false;
    }
    auto ae_lock_available = GetMetadataEntryAsSpan<uint8_t>(
        static_metadata_,
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_LOCK_AVAILABLE);
    if (ae_lock_available.empty()) {
      return false;
    }
    DCHECK_EQ(ae_lock_available.size(), 1u);
    if (ae_lock_available[0] !=
        base::checked_cast<uint8_t>(
            cros::mojom::AndroidControlAeLockAvailable::
                ANDROID_CONTROL_AE_LOCK_AVAILABLE_TRUE)) {
      return false;
    }
    return true;
  }();

  // Enable AF if supported.  MODE_AUTO is always supported on auto-focus camera
  // modules; fixed focus camera modules always has MODE_OFF.
  if (available_af_modes_.count(
          cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_AUTO)) {
    af_mode_ = cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_AUTO;
  }
  // AE should always be MODE_ON unless we enable manual sensor control.  Since
  // we don't have flash on any of our devices we don't care about the
  // flash-related AE modes.
  //
  // AWB should always be MODE_AUTO unless we enable manual sensor control.
  Set3AMode(cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
            base::checked_cast<uint8_t>(af_mode_));
  Set3AMode(cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE,
            base::checked_cast<uint8_t>(ae_mode_));
  Set3AMode(cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_MODE,
            base::checked_cast<uint8_t>(awb_mode_));

  // Enable face detection if it's available.
  auto face_modes = GetMetadataEntryAsSpan<uint8_t>(
      static_metadata, cros::mojom::CameraMetadataTag::
                           ANDROID_STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES);
  // We don't need face landmarks and ids, so using SIMPLE mode instead of FULL
  // mode should be enough.
  const auto face_mode_simple = cros::mojom::AndroidStatisticsFaceDetectMode::
      ANDROID_STATISTICS_FACE_DETECT_MODE_SIMPLE;
  if (base::Contains(face_modes,
                     base::checked_cast<uint8_t>(face_mode_simple))) {
    SetRepeatingCaptureMetadata(
        cros::mojom::CameraMetadataTag::ANDROID_STATISTICS_FACE_DETECT_MODE,
        face_mode_simple);
  }

  auto request_keys = GetMetadataEntryAsSpan<int32_t>(
      static_metadata_,
      cros::mojom::CameraMetadataTag::ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS);
  zero_shutter_lag_supported_ = base::Contains(
      request_keys,
      static_cast<int32_t>(
          cros::mojom::CameraMetadataTag::ANDROID_CONTROL_ENABLE_ZSL));
}

Camera3AController::~Camera3AController() {
  DCHECK(task_runner_->BelongsToCurrentThread());

  ClearRepeatingCaptureMetadata();
  capture_metadata_dispatcher_->RemoveResultMetadataObserver(this);
}

void Camera3AController::Stabilize3AForStillCapture(
    base::OnceClosure on_3a_stabilized_callback) {
  DCHECK(task_runner_->BelongsToCurrentThread());

  auto track = GetTraceTrack(CameraTraceEvent::kStabilize3A, request_id_);
  TRACE_EVENT_BEGIN("camera", "Stabilize3AForStillCapture", track);
  on_3a_stabilized_callback = base::BindOnce(
      [](base::OnceClosure callback, perfetto::Track track) {
        TRACE_EVENT_END("camera", std::move(track));
        std::move(callback).Run();
      },
      std::move(on_3a_stabilized_callback), std::move(track));
  ++request_id_;

  if (set_point_of_interest_running_) {
    // Use the settings from point of interest.
    if (!on_ae_locked_for_point_of_interest_callback_) {
      on_ae_locked_for_point_of_interest_callback_ =
          std::move(on_3a_stabilized_callback);
    }
    return;
  }

  if (on_3a_stabilized_callback_ || on_3a_mode_set_callback_) {
    // Already stabilizing 3A.
    return;
  }

  if (Is3AStabilized() || zero_shutter_lag_supported_) {
    std::move(on_3a_stabilized_callback).Run();
    return;
  }

  // Wait until all the 3A modes are set in the HAL; otherwise the AF trigger
  // and AE precapture trigger may be invalidated during mode transition.
  if (!af_mode_set_ || !ae_mode_set_ || !awb_mode_set_) {
    on_3a_mode_set_callback_ =
        base::BindOnce(&Camera3AController::Stabilize3AForStillCapture,
                       GetWeakPtr(), std::move(on_3a_stabilized_callback));
    return;
  }

  Set3aStabilizedCallback(std::move(on_3a_stabilized_callback),
                          base::Seconds(2));

  if (af_mode_ !=
      cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF) {
    DVLOG(1) << "Start AF trigger to lock focus";
    SetCaptureMetadata(
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
        cros::mojom::AndroidControlAfTrigger::ANDROID_CONTROL_AF_TRIGGER_START);
  }

  if (ae_mode_ !=
      cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_OFF) {
    DVLOG(1) << "Start AE precapture trigger to converge exposure";
    SetCaptureMetadata(
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
        cros::mojom::AndroidControlAePrecaptureTrigger::
            ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_START);
  }
}

void Camera3AController::OnResultMetadataAvailable(
    uint32_t frame_number,
    const cros::mojom::CameraMetadataPtr& result_metadata) {
  DCHECK(task_runner_->BelongsToCurrentThread());

  auto sensor_timestamp = GetMetadataEntryAsSpan<int64_t>(
      result_metadata,
      cros::mojom::CameraMetadataTag::ANDROID_SENSOR_TIMESTAMP);
  if (!sensor_timestamp.empty()) {
    DCHECK_EQ(sensor_timestamp.size(), 1u);
    // The sensor timestamp might not be monotonically increasing. The result
    // metadata from zero-shutter-lag request may be out of order compared to
    // previous regular requests.
    // https://developer.android.com/reference/android/hardware/camera2/CaptureResult#CONTROL_ENABLE_ZSL
    latest_sensor_timestamp_ = std::max(latest_sensor_timestamp_,
                                        base::Nanoseconds(sensor_timestamp[0]));
  }

  if (!af_mode_set_) {
    cros::mojom::AndroidControlAfMode af_mode;
    if (Get3AEntry(result_metadata,
                   cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
                   &af_mode)) {
      af_mode_set_ = (af_mode == af_mode_);
    } else {
      DVLOG(2) << "AF mode is not available in the metadata";
    }
  }

  if (!Get3AEntry(result_metadata,
                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_STATE,
                  &af_state_)) {
    DVLOG(2) << "AF state is not available in the metadata";
  }

  if (!ae_mode_set_) {
    cros::mojom::AndroidControlAeMode ae_mode;
    if (Get3AEntry(result_metadata,
                   cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE,
                   &ae_mode)) {
      ae_mode_set_ = (ae_mode == ae_mode_);
    } else {
      DVLOG(2) << "AE mode is not available in the metadata";
    }
  }
  if (!Get3AEntry(result_metadata,
                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_STATE,
                  &ae_state_)) {
    DVLOG(2) << "AE state is not available in the metadata";
  }

  if (!awb_mode_set_) {
    cros::mojom::AndroidControlAwbMode awb_mode;
    if (Get3AEntry(result_metadata,
                   cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_MODE,
                   &awb_mode)) {
      awb_mode_set_ = (awb_mode == awb_mode_);
    } else {
      DVLOG(2) << "AWB mode is not available in the metadata";
    }
  }
  if (!Get3AEntry(result_metadata,
                  cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_STATE,
                  &awb_state_)) {
    DVLOG(2) << "AWB state is not available in the metadata";
  }

  DVLOG(2) << "AF mode: " << af_mode_;
  DVLOG(2) << "AF state: " << af_state_;
  DVLOG(2) << "AE mode: " << ae_mode_;
  DVLOG(2) << "AE state: " << ae_state_;
  DVLOG(2) << "AWB mode: " << awb_mode_;
  DVLOG(2) << "AWB state: " << awb_state_;

  if (on_3a_mode_set_callback_ && af_mode_set_ && ae_mode_set_ &&
      awb_mode_set_) {
    task_runner_->PostTask(FROM_HERE, std::move(on_3a_mode_set_callback_));
  }

  bool should_run_3a_stabilized_callback = [&]() {
    if (!on_3a_stabilized_callback_) {
      return false;
    }
    if (Is3AStabilized()) {
      return true;
    }
    if (latest_sensor_timestamp_ > artificial_3a_stabilized_deadline_) {
      LOG(WARNING)
          << "Timed out stabilizing 3A, fire the callback artificially";
      return true;
    }
    return false;
  }();
  if (should_run_3a_stabilized_callback) {
    std::move(on_3a_stabilized_callback_).Run();
  }

  bool should_run_trigger_cancelled_callback = [&]() {
    if (!on_af_trigger_cancelled_callback_) {
      return false;
    }
    auto af_trigger = GetMetadataEntryAsSpan<uint8_t>(
        result_metadata,
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER);
    if (af_trigger.empty()) {
      return false;
    }
    return af_trigger[0] ==
           base::checked_cast<uint8_t>(cros::mojom::AndroidControlAfTrigger::
                                           ANDROID_CONTROL_AF_TRIGGER_CANCEL);
  }();
  if (should_run_trigger_cancelled_callback) {
    std::move(on_af_trigger_cancelled_callback_).Run();
  }
}

void Camera3AController::SetAutoFocusModeForStillCapture() {
  DCHECK(task_runner_->BelongsToCurrentThread());

  if (set_point_of_interest_running_ || ae_locked_for_point_of_interest_) {
    return;
  }

  SetCaptureMetadata(
      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
      cros::mojom::AndroidControlAfTrigger::ANDROID_CONTROL_AF_TRIGGER_CANCEL);

  if (available_af_modes_.count(
          cros::mojom::AndroidControlAfMode::
              ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
    af_mode_ = cros::mojom::AndroidControlAfMode::
        ANDROID_CONTROL_AF_MODE_CONTINUOUS_PICTURE;
  }
  Set3AMode(cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
            base::checked_cast<uint8_t>(af_mode_));
  DVLOG(1) << "Setting AF mode to: " << af_mode_;
}

void Camera3AController::SetAutoFocusModeForVideoRecording() {
  DCHECK(task_runner_->BelongsToCurrentThread());

  if (set_point_of_interest_running_ || ae_locked_for_point_of_interest_) {
    return;
  }

  SetCaptureMetadata(
      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
      cros::mojom::AndroidControlAfTrigger::ANDROID_CONTROL_AF_TRIGGER_CANCEL);

  if (available_af_modes_.count(cros::mojom::AndroidControlAfMode::
                                    ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO)) {
    af_mode_ = cros::mojom::AndroidControlAfMode::
        ANDROID_CONTROL_AF_MODE_CONTINUOUS_VIDEO;
  }
  Set3AMode(cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
            base::checked_cast<uint8_t>(af_mode_));
  DVLOG(1) << "Setting AF mode to: " << af_mode_;
}

void Camera3AController::SetAutoWhiteBalanceMode(
    cros::mojom::AndroidControlAwbMode mode) {
  DCHECK(task_runner_->BelongsToCurrentThread());

  if (!available_awb_modes_.count(mode)) {
    LOG(WARNING) << "Don't support awb mode:" << mode;
    return;
  }

  SetCaptureMetadata(
      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_LOCK,
      cros::mojom::AndroidControlAwbLock::ANDROID_CONTROL_AWB_LOCK_OFF);
  awb_mode_ = mode;
  Set3AMode(cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_MODE,
            base::checked_cast<uint8_t>(awb_mode_));
  DVLOG(1) << "Setting AWB mode to: " << awb_mode_;
}

void Camera3AController::SetExposureTime(bool enable_auto,
                                         int64_t exposure_time_nanoseconds) {
  DCHECK(task_runner_->BelongsToCurrentThread());

  if (enable_auto) {
    if (!available_ae_modes_.count(
            cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_ON)) {
      LOG(WARNING) << "Don't support ANDROID_CONTROL_AE_MODE_ON";
      return;
    }
    ae_mode_ = cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_ON;
    capture_metadata_dispatcher_->UnsetRepeatingCaptureMetadata(
        cros::mojom::CameraMetadataTag::ANDROID_SENSOR_EXPOSURE_TIME);
  } else {
    if (!available_ae_modes_.count(
            cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_OFF)) {
      LOG(WARNING) << "Don't support ANDROID_CONTROL_AE_MODE_OFF";
      return;
    }
    ae_mode_ = cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_OFF;
    SetRepeatingCaptureMetadata(
        cros::mojom::CameraMetadataTag::ANDROID_SENSOR_EXPOSURE_TIME,
        exposure_time_nanoseconds);
  }

  Set3AMode(cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE,
            base::checked_cast<uint8_t>(ae_mode_));
  DVLOG(1) << "Setting AE mode to: " << ae_mode_;
}

void Camera3AController::SetFocusDistance(bool enable_auto,
                                          float focus_distance_diopters) {
  DCHECK(task_runner_->BelongsToCurrentThread());

  if (enable_auto) {
    if (!available_af_modes_.count(
            cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_AUTO)) {
      LOG(WARNING) << "Don't support ANDROID_CONTROL_AF_MODE_AUTO";
      return;
    }
    af_mode_ = cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_AUTO;
    capture_metadata_dispatcher_->UnsetRepeatingCaptureMetadata(
        cros::mojom::CameraMetadataTag::ANDROID_LENS_FOCUS_DISTANCE);
  } else {
    if (!available_af_modes_.count(
            cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF)) {
      LOG(WARNING) << "Don't support ANDROID_CONTROL_AE_MODE_OFF";
      return;
    }
    af_mode_ = cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF;
    SetRepeatingCaptureMetadata(
        cros::mojom::CameraMetadataTag::ANDROID_LENS_FOCUS_DISTANCE,
        focus_distance_diopters);
  }

  Set3AMode(cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE,
            base::checked_cast<uint8_t>(af_mode_));
  DVLOG(1) << "Setting AF mode to: " << af_mode_;
}

bool Camera3AController::IsPointOfInterestSupported() {
  return point_of_interest_supported_;
}

void Camera3AController::SetPointOfInterest(gfx::Point point) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  DVLOG(1) << "Setting point of interest to " << point.x() << ", " << point.y();

  if (!IsPointOfInterestSupported()) {
    return;
  }

  if (set_point_of_interest_running_ || ae_locked_for_point_of_interest_ ||
      on_af_trigger_cancelled_callback_) {
    // Cancel the current running one.
    // TODO(shik): Technically we can make the still capture hang if we keep
    // clicking the app quickly enough while taking the picture.
    set_point_of_interest_running_ = false;
    on_3a_mode_set_callback_.Reset();
    on_3a_stabilized_callback_.Reset();
    delayed_ae_unlock_callback_.Cancel();
    SetPointOfInterestUnlockAe();
    SetCaptureMetadata(
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
        cros::mojom::AndroidControlAfTrigger::
            ANDROID_CONTROL_AF_TRIGGER_CANCEL);
    // Due to pipeline dalay, we need to wait until AF_TRIGGER_CANCEL fired to
    // prevent the race condition.
    on_af_trigger_cancelled_callback_ = base::BindOnce(
        &Camera3AController::SetPointOfInterest, GetWeakPtr(), point);
    return;
  } else if (on_3a_stabilized_callback_ || on_3a_mode_set_callback_) {
    // Already stabilizing 3A for other things.
    return;
  }

  set_point_of_interest_running_ = true;

  auto active_array_size = [&]() {
    auto rect = GetMetadataEntryAsSpan<int32_t>(
        static_metadata_,
        cros::mojom::CameraMetadataTag::ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
    DCHECK(!rect.empty());
    // (xmin, ymin, width, height)
    return gfx::Rect(rect[0], rect[1], rect[2], rect[3]);
  }();

  // Mimic the behavior of regionForNormalizedCoord() in GCA.
  int roi_radius =
      static_cast<int>(0.06125 * std::min(active_array_size.width(),
                                          active_array_size.height()));

  // (xmin, ymin, xmax, ymax, weight)
  std::vector<int32_t> region = {
      base::clamp(point.x() - roi_radius, 0, active_array_size.width() - 1),
      base::clamp(point.y() - roi_radius, 0, active_array_size.height() - 1),
      base::clamp(point.x() + roi_radius, 0, active_array_size.width() - 1),
      base::clamp(point.y() + roi_radius, 0, active_array_size.height() - 1),
      1,
  };

  SetRepeatingCaptureMetadata(
      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_MODE,
      cros::mojom::AndroidControlMode::ANDROID_CONTROL_MODE_AUTO);

  if (ae_region_supported_) {
    SetRepeatingCaptureMetadata(
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_REGIONS, region);
  }
  if (af_region_supported_) {
    SetRepeatingCaptureMetadata(
        cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_REGIONS, region);
  }

  ae_mode_ = cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_ON;
  ae_mode_set_ = false;
  SetRepeatingCaptureMetadata(
      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE, ae_mode_);
  af_mode_ = cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_AUTO;
  af_mode_set_ = false;
  SetRepeatingCaptureMetadata(
      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE, af_mode_);
  on_3a_mode_set_callback_ = base::BindOnce(
      &Camera3AController::SetPointOfInterestOn3AModeSet, GetWeakPtr());
}

void Camera3AController::SetPointOfInterestOn3AModeSet() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  Set3aStabilizedCallback(
      base::BindOnce(&Camera3AController::SetPointOfInterestOn3AStabilized,
                     GetWeakPtr()),
      base::Seconds(2));
  SetCaptureMetadata(
      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_TRIGGER,
      cros::mojom::AndroidControlAfTrigger::ANDROID_CONTROL_AF_TRIGGER_START);
}

void Camera3AController::SetPointOfInterestOn3AStabilized() {
  DCHECK(task_runner_->BelongsToCurrentThread());
  set_point_of_interest_running_ = false;
  SetRepeatingCaptureMetadata(
      cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_LOCK,
      cros::mojom::AndroidControlAeLock::ANDROID_CONTROL_AE_LOCK_ON);
  ae_locked_for_point_of_interest_ = true;
  if (on_ae_locked_for_point_of_interest_callback_) {
    std::move(on_ae_locked_for_point_of_interest_callback_).Run();
  }
  delayed_ae_unlock_callback_.Reset(base::BindOnce(
      &Camera3AController::SetPointOfInterestUnlockAe, GetWeakPtr()));
  // TODO(shik): Apply different delays for image capture / video recording.
  task_runner_->PostDelayedTask(
      FROM_HERE, delayed_ae_unlock_callback_.callback(), base::Seconds(4));
}

void Camera3AController::SetPointOfInterestUnlockAe() {
  DCHECK(task_runner_->BelongsToCurrentThread());

  ae_locked_for_point_of_interest_ = false;
  ClearRepeatingCaptureMetadata();
}

base::WeakPtr<Camera3AController> Camera3AController::GetWeakPtr() {
  DCHECK(task_runner_->BelongsToCurrentThread());

  return weak_ptr_factory_.GetWeakPtr();
}

void Camera3AController::Set3AMode(cros::mojom::CameraMetadataTag tag,
                                   uint8_t target_mode) {
  DCHECK(task_runner_->BelongsToCurrentThread());
  DCHECK(tag == cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE ||
         tag == cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE ||
         tag == cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_MODE);

  SetRepeatingCaptureMetadata(tag, target_mode);

  switch (tag) {
    case cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AF_MODE:
      af_mode_set_ = false;
      break;
    case cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AE_MODE:
      ae_mode_set_ = false;
      break;
    case cros::mojom::CameraMetadataTag::ANDROID_CONTROL_AWB_MODE:
      awb_mode_set_ = false;
      break;
    default:
      NOTREACHED() << "Invalid 3A mode: " << tag;
  }
}

void Camera3AController::Set3aStabilizedCallback(base::OnceClosure callback,
                                                 base::TimeDelta time_limit) {
  on_3a_stabilized_callback_ = std::move(callback);
  // TODO(shik): If this function is called before the first capture result
  // metadata is received, |latest_sensor_timestamp_| would be zero.
  artificial_3a_stabilized_deadline_ = latest_sensor_timestamp_ + time_limit;
}

bool Camera3AController::Is3AStabilized() {
  DCHECK(task_runner_->BelongsToCurrentThread());

  if (af_mode_ !=
      cros::mojom::AndroidControlAfMode::ANDROID_CONTROL_AF_MODE_OFF) {
    if (af_state_ != cros::mojom::AndroidControlAfState::
                         ANDROID_CONTROL_AF_STATE_FOCUSED_LOCKED &&
        af_state_ != cros::mojom::AndroidControlAfState::
                         ANDROID_CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
      return false;
    }
  }

  if (ae_mode_ !=
      cros::mojom::AndroidControlAeMode::ANDROID_CONTROL_AE_MODE_OFF) {
    if (ae_state_ != cros::mojom::AndroidControlAeState::
                         ANDROID_CONTROL_AE_STATE_CONVERGED &&
        ae_state_ != cros::mojom::AndroidControlAeState::
                         ANDROID_CONTROL_AE_STATE_FLASH_REQUIRED &&
        ae_state_ != cros::mojom::AndroidControlAeState::
                         ANDROID_CONTROL_AE_STATE_LOCKED) {
      return false;
    }
  }

  if (awb_mode_ ==
      cros::mojom::AndroidControlAwbMode::ANDROID_CONTROL_AWB_MODE_AUTO) {
    if (awb_state_ != cros::mojom::AndroidControlAwbState::
                          ANDROID_CONTROL_AWB_STATE_CONVERGED &&
        awb_state_ != cros::mojom::AndroidControlAwbState::
                          ANDROID_CONTROL_AWB_STATE_LOCKED) {
      return false;
    }
  }

  DVLOG(1) << "3A stabilized";
  return true;
}

template <typename T>
void Camera3AController::SetCaptureMetadata(cros::mojom::CameraMetadataTag tag,
                                            T value) {
  SetCaptureMetadata(tag, std::vector<T>{value});
}

template <typename T>
void Camera3AController::SetCaptureMetadata(cros::mojom::CameraMetadataTag tag,
                                            const std::vector<T>& value) {
  capture_metadata_dispatcher_->SetCaptureMetadata(
      tag, entry_type_of<T>::value, value.size(),
      SerializeMetadataValueFromSpan(base::make_span(value)));
}

template <typename T>
void Camera3AController::SetRepeatingCaptureMetadata(
    cros::mojom::CameraMetadataTag tag,
    T value) {
  SetRepeatingCaptureMetadata(tag, std::vector<T>{value});
}

template <typename T>
void Camera3AController::SetRepeatingCaptureMetadata(
    cros::mojom::CameraMetadataTag tag,
    const std::vector<T>& value) {
  repeating_metadata_tags_.insert(tag);
  capture_metadata_dispatcher_->SetRepeatingCaptureMetadata(
      tag, entry_type_of<T>::value, value.size(),
      SerializeMetadataValueFromSpan(base::make_span(value)));
}

void Camera3AController::ClearRepeatingCaptureMetadata() {
  for (const auto& tag : repeating_metadata_tags_) {
    capture_metadata_dispatcher_->UnsetRepeatingCaptureMetadata(tag);
  }
  repeating_metadata_tags_.clear();
}

}  // namespace media
