/*
 * Copyright (C) 2012 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "third_party/blink/renderer/modules/mediastream/media_constraints.h"

#include <math.h>

#include <algorithm>

#include "base/memory/scoped_refptr.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"

namespace blink {

namespace {

template <typename T>
void MaybeEmitNamedNumber(StringBuilder& builder,
                          bool emit,
                          const char* name,
                          T value) {
  if (!emit) {
    return;
  }
  if (builder.length() > 1) {
    builder.Append(", ");
  }
  builder.Append("\"");
  builder.Append(name);
  builder.Append("\": ");
  builder.AppendNumber(value);
}

template <typename T>
void MaybeEmitNamedString(StringBuilder& builder,
                          bool emit,
                          const char* name,
                          T value) {
  if (!emit) {
    return;
  }
  if (builder.length() > 1) {
    builder.Append(", ");
  }
  builder.Append("\"");
  builder.Append(name);
  builder.Append("\": ");
  builder.Append(value);
}

void MaybeEmitNamedBoolean(StringBuilder& builder,
                           bool emit,
                           const char* name,
                           bool value) {
  if (!emit) {
    return;
  }
  if (builder.length() > 1) {
    builder.Append(", ");
  }
  builder.Append("\"");
  builder.Append(name);
  builder.Append("\": ");
  if (value) {
    builder.Append("true");
  } else {
    builder.Append("false");
  }
}

}  // namespace

class MediaConstraintsPrivate final
    : public ThreadSafeRefCounted<MediaConstraintsPrivate> {
 public:
  static scoped_refptr<MediaConstraintsPrivate> Create();
  static scoped_refptr<MediaConstraintsPrivate> Create(
      const MediaTrackConstraintSetPlatform& basic,
      const Vector<MediaTrackConstraintSetPlatform>& advanced);

  bool IsUnconstrained() const;
  const MediaTrackConstraintSetPlatform& Basic() const;
  MediaTrackConstraintSetPlatform& MutableBasic();
  const Vector<MediaTrackConstraintSetPlatform>& Advanced() const;
  const String ToString() const;

 private:
  MediaConstraintsPrivate(
      const MediaTrackConstraintSetPlatform& basic,
      const Vector<MediaTrackConstraintSetPlatform>& advanced);

  MediaTrackConstraintSetPlatform basic_;
  Vector<MediaTrackConstraintSetPlatform> advanced_;
};

scoped_refptr<MediaConstraintsPrivate> MediaConstraintsPrivate::Create() {
  MediaTrackConstraintSetPlatform basic;
  Vector<MediaTrackConstraintSetPlatform> advanced;
  return base::AdoptRef(new MediaConstraintsPrivate(basic, advanced));
}

scoped_refptr<MediaConstraintsPrivate> MediaConstraintsPrivate::Create(
    const MediaTrackConstraintSetPlatform& basic,
    const Vector<MediaTrackConstraintSetPlatform>& advanced) {
  return base::AdoptRef(new MediaConstraintsPrivate(basic, advanced));
}

MediaConstraintsPrivate::MediaConstraintsPrivate(
    const MediaTrackConstraintSetPlatform& basic,
    const Vector<MediaTrackConstraintSetPlatform>& advanced)
    : basic_(basic), advanced_(advanced) {}

bool MediaConstraintsPrivate::IsUnconstrained() const {
  // TODO(hta): When generating advanced constraints, make sure no empty
  // elements can be added to the m_advanced vector.
  return basic_.IsUnconstrained() && advanced_.empty();
}

const MediaTrackConstraintSetPlatform& MediaConstraintsPrivate::Basic() const {
  return basic_;
}

MediaTrackConstraintSetPlatform& MediaConstraintsPrivate::MutableBasic() {
  return basic_;
}

const Vector<MediaTrackConstraintSetPlatform>&
MediaConstraintsPrivate::Advanced() const {
  return advanced_;
}

const String MediaConstraintsPrivate::ToString() const {
  StringBuilder builder;
  if (!IsUnconstrained()) {
    builder.Append('{');
    builder.Append(Basic().ToString());
    if (!Advanced().empty()) {
      if (builder.length() > 1) {
        builder.Append(", ");
      }
      builder.Append("\"advanced\": [");
      bool first = true;
      for (const auto& constraint_set : Advanced()) {
        if (!first) {
          builder.Append(", ");
        }
        builder.Append('{');
        builder.Append(constraint_set.ToString());
        builder.Append('}');
        first = false;
      }
      builder.Append(']');
    }
    builder.Append('}');
  }
  return builder.ToString();
}

// *Constraints

BaseConstraint::BaseConstraint(const char* name) : name_(name) {}

BaseConstraint::~BaseConstraint() = default;

bool BaseConstraint::HasMandatory() const {
  return HasMin() || HasMax() || HasExact();
}

LongConstraint::LongConstraint(const char* name)
    : BaseConstraint(name),
      min_(),
      max_(),
      exact_(),
      ideal_(),
      has_min_(false),
      has_max_(false),
      has_exact_(false),
      has_ideal_(false) {}

bool LongConstraint::Matches(int32_t value) const {
  if (has_min_ && value < min_) {
    return false;
  }
  if (has_max_ && value > max_) {
    return false;
  }
  if (has_exact_ && value != exact_) {
    return false;
  }
  return true;
}

bool LongConstraint::IsUnconstrained() const {
  return !has_min_ && !has_max_ && !has_exact_ && !has_ideal_;
}

void LongConstraint::ResetToUnconstrained() {
  *this = LongConstraint(GetName());
}

String LongConstraint::ToString() const {
  StringBuilder builder;
  builder.Append('{');
  MaybeEmitNamedNumber(builder, has_min_, "min", min_);
  MaybeEmitNamedNumber(builder, has_max_, "max", max_);
  MaybeEmitNamedNumber(builder, has_exact_, "exact", exact_);
  MaybeEmitNamedNumber(builder, has_ideal_, "ideal", ideal_);
  builder.Append('}');
  return builder.ToString();
}

const double DoubleConstraint::kConstraintEpsilon = 0.00001;

DoubleConstraint::DoubleConstraint(const char* name)
    : BaseConstraint(name),
      min_(),
      max_(),
      exact_(),
      ideal_(),
      has_min_(false),
      has_max_(false),
      has_exact_(false),
      has_ideal_(false) {}

bool DoubleConstraint::Matches(double value) const {
  if (has_min_ && value < min_ - kConstraintEpsilon) {
    return false;
  }
  if (has_max_ && value > max_ + kConstraintEpsilon) {
    return false;
  }
  if (has_exact_ &&
      fabs(static_cast<double>(value) - exact_) > kConstraintEpsilon) {
    return false;
  }
  return true;
}

bool DoubleConstraint::IsUnconstrained() const {
  return !has_min_ && !has_max_ && !has_exact_ && !has_ideal_;
}

void DoubleConstraint::ResetToUnconstrained() {
  *this = DoubleConstraint(GetName());
}

String DoubleConstraint::ToString() const {
  StringBuilder builder;
  builder.Append('{');
  MaybeEmitNamedNumber(builder, has_min_, "min", min_);
  MaybeEmitNamedNumber(builder, has_max_, "max", max_);
  MaybeEmitNamedNumber(builder, has_exact_, "exact", exact_);
  MaybeEmitNamedNumber(builder, has_ideal_, "ideal", ideal_);
  builder.Append('}');
  return builder.ToString();
}

DoubleOrBooleanConstraint::DoubleOrBooleanConstraint(const char* name)
    : DoubleConstraint(name) {}

bool DoubleOrBooleanConstraint::HasMandatory() const {
  return DoubleConstraint::HasMandatory() || HasExactBoolean();
}

bool DoubleOrBooleanConstraint::Matches(double value) const {
  return DoubleConstraint::Matches(value) && MatchesBoolean(true);
}

bool DoubleOrBooleanConstraint::MatchesBoolean(bool value) const {
  if (HasExactBoolean() && ExactBoolean() != value) {
    return false;
  }
  if (!value && DoubleConstraint::HasMandatory()) {
    return false;
  }
  return true;
}

bool DoubleOrBooleanConstraint::IsPresentAndNotFalse() const {
  if (!IsPresent()) {
    return false;
  }
  if (HasExactBoolean()) {
    DCHECK(DoubleConstraint::IsUnconstrained());
    DCHECK(!HasIdealBoolean());
    return ExactBoolean();
  }
  if (HasIdealBoolean()) {
    DCHECK(DoubleConstraint::IsUnconstrained());
    DCHECK(!HasExactBoolean());
    return IdealBoolean();
  }
  return true;
}

bool DoubleOrBooleanConstraint::IsUnconstrained() const {
  return DoubleConstraint::IsUnconstrained() && !HasExactBoolean() &&
         !HasIdealBoolean();
}

void DoubleOrBooleanConstraint::ResetToUnconstrained() {
  *this = DoubleOrBooleanConstraint(GetName());
}

String DoubleOrBooleanConstraint::ToString() const {
  if (DoubleConstraint::IsUnconstrained() &&
      (HasExactBoolean() || HasIdealBoolean())) {
    bool value = HasExactBoolean() ? ExactBoolean() : IdealBoolean();
    return value ? "true" : "false";
  }
  return DoubleConstraint::ToString();
}

StringConstraint::StringConstraint(const char* name)
    : BaseConstraint(name), exact_(), ideal_() {}

bool StringConstraint::Matches(String value) const {
  if (exact_.empty()) {
    return true;
  }
  for (const auto& choice : exact_) {
    if (value == choice) {
      return true;
    }
  }
  return false;
}

bool StringConstraint::IsUnconstrained() const {
  return exact_.empty() && ideal_.empty();
}

const Vector<String>& StringConstraint::Exact() const {
  return exact_;
}

const Vector<String>& StringConstraint::Ideal() const {
  return ideal_;
}

void StringConstraint::ResetToUnconstrained() {
  *this = StringConstraint(GetName());
}

String StringConstraint::ToString() const {
  StringBuilder builder;
  builder.Append('{');
  if (!ideal_.empty()) {
    builder.Append("\"ideal\": [");
    bool first = true;
    for (const auto& iter : ideal_) {
      if (!first) {
        builder.Append(", ");
      }
      builder.Append('"');
      builder.Append(iter);
      builder.Append('"');
      first = false;
    }
    builder.Append(']');
  }
  if (!exact_.empty()) {
    if (builder.length() > 1) {
      builder.Append(", ");
    }
    builder.Append("\"exact\": [");
    bool first = true;
    for (const auto& iter : exact_) {
      if (!first) {
        builder.Append(", ");
      }
      builder.Append('"');
      builder.Append(iter);
      builder.Append('"');
    }
    builder.Append(']');
  }
  builder.Append('}');
  return builder.ToString();
}

BooleanConstraint::BooleanConstraint(const char* name)
    : BaseConstraint(name),
      ideal_(false),
      exact_(false),
      has_ideal_(false),
      has_exact_(false) {}

bool BooleanConstraint::Matches(bool value) const {
  if (has_exact_ && static_cast<bool>(exact_) != value) {
    return false;
  }
  return true;
}

bool BooleanConstraint::IsUnconstrained() const {
  return !has_ideal_ && !has_exact_;
}

void BooleanConstraint::ResetToUnconstrained() {
  *this = BooleanConstraint(GetName());
}

String BooleanConstraint::ToString() const {
  StringBuilder builder;
  builder.Append('{');
  MaybeEmitNamedBoolean(builder, has_exact_, "exact", Exact());
  MaybeEmitNamedBoolean(builder, has_ideal_, "ideal", Ideal());
  builder.Append('}');
  return builder.ToString();
}

BooleanOrStringConstraint::BooleanOrStringConstraint(const char* name)
    : BaseConstraint(name) {}

bool BooleanOrStringConstraint::HasExact() const {
  return HasExactBoolean() || HasExactString();
}

bool BooleanOrStringConstraint::HasIdeal() const {
  return HasIdealBoolean() || HasIdealString();
}

bool BooleanOrStringConstraint::IsUnconstrained() const {
  return !HasExact() && !HasIdeal();
}

void BooleanOrStringConstraint::ResetToUnconstrained() {
  *this = BooleanOrStringConstraint(GetName());
}

String BooleanOrStringConstraint::ToString() const {
  StringBuilder builder;
  builder.Append('{');
  MaybeEmitNamedBoolean(builder, HasExactBoolean(), "exact", ExactBoolean());
  MaybeEmitNamedString(builder, HasExactString(), "exact", ExactString());
  MaybeEmitNamedBoolean(builder, HasIdealBoolean(), "ideal", IdealBoolean());
  MaybeEmitNamedString(builder, HasIdealString(), "exact", IdealString());
  builder.Append('}');
  return builder.ToString();
}

MediaTrackConstraintSetPlatform::MediaTrackConstraintSetPlatform()
    : width("width"),
      height("height"),
      aspect_ratio("aspectRatio"),
      frame_rate("frameRate"),
      facing_mode("facingMode"),
      resize_mode("resizeMode"),
      volume("volume"),
      sample_rate("sampleRate"),
      sample_size("sampleSize"),
      echo_cancellation("echoCancellation"),
      auto_gain_control("autoGainControl"),
      noise_suppression("noiseSuppression"),
      voice_isolation("voiceIsolation"),
      latency("latency"),
      channel_count("channelCount"),
      device_id("deviceId"),
      disable_local_echo("disableLocalEcho"),
      suppress_local_audio_playback("suppressLocalAudioPlayback"),
      restrict_own_audio("restrictOwnAudio"),
      group_id("groupId"),
      display_surface("displaySurface"),
      exposure_compensation("exposureCompensation"),
      exposure_time("exposureTime"),
      color_temperature("colorTemperature"),
      iso("iso"),
      brightness("brightness"),
      contrast("contrast"),
      saturation("saturation"),
      sharpness("sharpness"),
      focus_distance("focusDistance"),
      pan("pan"),
      tilt("tilt"),
      zoom("zoom"),
      torch("torch"),
      background_blur("backgroundBlur"),
      background_segmentation_mask("backgroundSegmentationMask"),
      eye_gaze_correction("eyeGazeCorrection"),
      face_framing("faceFraming"),
      media_stream_source("mediaStreamSource"),
      render_to_associated_sink("chromeRenderToAssociatedSink"),
      goog_noise_reduction("googNoiseReduction") {}

Vector<const BaseConstraint*> MediaTrackConstraintSetPlatform::AllConstraints()
    const {
  return {&width,
          &height,
          &aspect_ratio,
          &frame_rate,
          &facing_mode,
          &resize_mode,
          &volume,
          &sample_rate,
          &sample_size,
          &echo_cancellation,
          &auto_gain_control,
          &noise_suppression,
          &voice_isolation,
          &latency,
          &channel_count,
          &device_id,
          &group_id,
          &display_surface,
          &media_stream_source,
          &disable_local_echo,
          &suppress_local_audio_playback,
          &restrict_own_audio,
          &exposure_compensation,
          &exposure_time,
          &color_temperature,
          &iso,
          &brightness,
          &contrast,
          &saturation,
          &sharpness,
          &focus_distance,
          &pan,
          &tilt,
          &zoom,
          &torch,
          &background_blur,
          &background_segmentation_mask,
          &eye_gaze_correction,
          &face_framing,
          &render_to_associated_sink,
          &goog_noise_reduction};
}

bool MediaTrackConstraintSetPlatform::IsUnconstrained() const {
  for (auto* const constraint : AllConstraints()) {
    if (!constraint->IsUnconstrained()) {
      return false;
    }
  }
  return true;
}

bool MediaTrackConstraintSetPlatform::HasMandatoryOutsideSet(
    const Vector<String>& good_names,
    String& found_name) const {
  for (auto* const constraint : AllConstraints()) {
    if (constraint->HasMandatory()) {
      if (!std::ranges::contains(good_names, constraint->GetName())) {
        found_name = constraint->GetName();
        return true;
      }
    }
  }
  return false;
}

bool MediaTrackConstraintSetPlatform::HasMandatory() const {
  String dummy_string;
  return HasMandatoryOutsideSet(Vector<String>(), dummy_string);
}

bool MediaTrackConstraintSetPlatform::HasMin() const {
  for (auto* const constraint : AllConstraints()) {
    if (constraint->HasMin()) {
      return true;
    }
  }
  return false;
}

bool MediaTrackConstraintSetPlatform::HasExact() const {
  for (auto* const constraint : AllConstraints()) {
    if (constraint->HasExact()) {
      return true;
    }
  }
  return false;
}

String MediaTrackConstraintSetPlatform::ToString() const {
  StringBuilder builder;
  bool first = true;
  for (auto* const constraint : AllConstraints()) {
    if (constraint->IsPresent()) {
      if (!first) {
        builder.Append(", ");
      }
      builder.Append("\"");
      builder.Append(constraint->GetName());
      builder.Append("\": ");
      builder.Append(constraint->ToString());
      first = false;
    }
  }
  return builder.ToString();
}

// MediaConstraints

void MediaConstraints::Assign(const MediaConstraints& other) {
  private_ = other.private_;
}

MediaConstraints::MediaConstraints() = default;

MediaConstraints::MediaConstraints(const MediaConstraints& other) {
  Assign(other);
}

void MediaConstraints::Reset() {
  private_.Reset();
}

bool MediaConstraints::IsUnconstrained() const {
  return private_.IsNull() || private_->IsUnconstrained();
}

void MediaConstraints::Initialize() {
  DCHECK(IsNull());
  private_ = MediaConstraintsPrivate::Create();
}

void MediaConstraints::Initialize(
    const MediaTrackConstraintSetPlatform& basic,
    const Vector<MediaTrackConstraintSetPlatform>& advanced) {
  DCHECK(IsNull());
  private_ = MediaConstraintsPrivate::Create(basic, advanced);
}

const MediaTrackConstraintSetPlatform& MediaConstraints::Basic() const {
  DCHECK(!IsNull());
  return private_->Basic();
}

MediaTrackConstraintSetPlatform& MediaConstraints::MutableBasic() {
  DCHECK(!IsNull());
  return private_->MutableBasic();
}

const Vector<MediaTrackConstraintSetPlatform>& MediaConstraints::Advanced()
    const {
  DCHECK(!IsNull());
  return private_->Advanced();
}

const String MediaConstraints::ToString() const {
  if (IsNull()) {
    return g_empty_string;
  }
  return private_->ToString();
}

}  // namespace blink
