/*
 * 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:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. 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_impl.h"

#include "base/notreached.h"
#include "build/build_config.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/renderer/bindings/core/v8/dictionary.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_boolean_string.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_union_string_stringsequence.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_constrain_boolean_or_dom_string_parameters.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_constrain_boolean_parameters.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_constrain_dom_string_parameters.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_constrain_double_range.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_constrain_long_range.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_media_track_constraints.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_typedefs.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_boolean_constrainbooleanordomstringparameters_string.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_boolean_constrainbooleanparameters.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_boolean_constraindoublerange_double.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_constraindomstringparameters_string_stringsequence.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_constraindoublerange_double.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_constrainlongrange_long.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/deprecation/deprecation.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/keywords.h"
#include "third_party/blink/renderer/modules/mediastream/media_constraints.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"

namespace blink {
namespace media_constraints_impl {

namespace {

// A naked value is treated as an "ideal" value in the basic constraints,
// but as an exact value in "advanced" constraints.
// https://w3c.github.io/mediacapture-main/#constrainable-interface
enum class NakedValueDisposition { kTreatAsIdeal, kTreatAsExact };

// Old type/value form of constraint. Used in parsing old-style constraints.
struct NameValueStringConstraint {
  NameValueStringConstraint() = default;

  NameValueStringConstraint(String name, String value)
      : name_(name), value_(value) {}

  String name_;
  String value_;
};

// Legal constraint names.

// Legacy getUserMedia() constraints. Sadly still in use.
const char kMinAspectRatio[] = "minAspectRatio";
const char kMaxAspectRatio[] = "maxAspectRatio";
const char kMaxWidth[] = "maxWidth";
const char kMinWidth[] = "minWidth";
const char kMaxHeight[] = "maxHeight";
const char kMinHeight[] = "minHeight";
const char kMaxFrameRate[] = "maxFrameRate";
const char kMinFrameRate[] = "minFrameRate";
const char kMediaStreamSource[] = "chromeMediaSource";
const char kMediaStreamSourceId[] =
    "chromeMediaSourceId";                           // mapped to deviceId
const char kMediaStreamSourceInfoId[] = "sourceId";  // mapped to deviceId
const char kMediaStreamRenderToAssociatedSink[] =
    "chromeRenderToAssociatedSink";
// RenderToAssociatedSink will be going away some time.
const char kEchoCancellation[] = "echoCancellation";
const char kDisableLocalEcho[] = "disableLocalEcho";
// Google-specific constraint keys for a local video source (getUserMedia).
const char kNoiseReduction[] = "googNoiseReduction";

static bool ParseMandatoryConstraintsDictionary(
    const Dictionary& mandatory_constraints_dictionary,
    Vector<NameValueStringConstraint>& mandatory) {
  DummyExceptionStateForTesting exception_state;
  const HashMap<String, String>& mandatory_constraints_hash_map =
      mandatory_constraints_dictionary.GetOwnPropertiesAsStringHashMap(
          exception_state);
  if (exception_state.HadException())
    return false;

  for (const auto& iter : mandatory_constraints_hash_map)
    mandatory.push_back(NameValueStringConstraint(iter.key, iter.value));
  return true;
}

static bool ParseOptionalConstraintsVectorElement(
    const Dictionary& constraint,
    Vector<NameValueStringConstraint>& optional_constraints_vector) {
  DummyExceptionStateForTesting exception_state;
  const Vector<String>& local_names =
      constraint.GetPropertyNames(exception_state);
  if (exception_state.HadException() || local_names.size() != 1) {
    return false;
  }
  const String& key = local_names[0];
  std::optional<String> value = constraint.Get<IDLString>(key, exception_state);
  if (exception_state.HadException() || !value) {
    return false;
  }
  optional_constraints_vector.push_back(NameValueStringConstraint(key, *value));
  return true;
}

static bool Parse(const MediaTrackConstraints* constraints_in,
                  Vector<NameValueStringConstraint>& optional,
                  Vector<NameValueStringConstraint>& mandatory) {
  Vector<NameValueStringConstraint> mandatory_constraints_vector;
  if (constraints_in->hasMandatory()) {
    bool ok = ParseMandatoryConstraintsDictionary(
        Dictionary(constraints_in->mandatory()), mandatory);
    if (!ok)
      return false;
  }

  if (constraints_in->hasOptional()) {
    for (const auto& constraint : constraints_in->optional()) {
      bool ok = ParseOptionalConstraintsVectorElement(Dictionary(constraint),
                                                      optional);
      if (!ok)
        return false;
    }
  }
  return true;
}

static bool ToBoolean(const String& as_string) {
  return as_string == keywords::kTrue;
  // TODO(hta): Check against "false" and return error if it's neither.
  // https://crbug.com/576582
}

static void ParseOldStyleNames(
    ExecutionContext* context,
    const Vector<NameValueStringConstraint>& old_names,
    MediaTrackConstraintSetPlatform& result) {
  if (old_names.size() > 0) {
    UseCounter::Count(context, WebFeature::kOldConstraintsParsed);
  }
  for (const NameValueStringConstraint& constraint : old_names) {
    if (constraint.name_ == kMinAspectRatio) {
      result.aspect_ratio.SetMin(atof(constraint.value_.Utf8().c_str()));
    } else if (constraint.name_ == kMaxAspectRatio) {
      result.aspect_ratio.SetMax(atof(constraint.value_.Utf8().c_str()));
    } else if (constraint.name_ == kMaxWidth) {
      result.width.SetMax(atoi(constraint.value_.Utf8().c_str()));
    } else if (constraint.name_ == kMinWidth) {
      result.width.SetMin(atoi(constraint.value_.Utf8().c_str()));
    } else if (constraint.name_ == kMaxHeight) {
      result.height.SetMax(atoi(constraint.value_.Utf8().c_str()));
    } else if (constraint.name_ == kMinHeight) {
      result.height.SetMin(atoi(constraint.value_.Utf8().c_str()));
    } else if (constraint.name_ == kMinFrameRate) {
      result.frame_rate.SetMin(atof(constraint.value_.Utf8().c_str()));
    } else if (constraint.name_ == kMaxFrameRate) {
      result.frame_rate.SetMax(atof(constraint.value_.Utf8().c_str()));
    } else if (constraint.name_ == kEchoCancellation) {
      result.echo_cancellation.SetExactBoolean(ToBoolean(constraint.value_));
    } else if (constraint.name_ == kMediaStreamSource) {
      // TODO(hta): This has only a few legal values. Should be
      // represented as an enum, and cause type errors.
      // https://crbug.com/576582
      result.media_stream_source.SetExact(constraint.value_);
    } else if (constraint.name_ == kDisableLocalEcho &&
               RuntimeEnabledFeatures::
                   DesktopCaptureDisableLocalEchoControlEnabled()) {
      result.disable_local_echo.SetExact(ToBoolean(constraint.value_));
    } else if (constraint.name_ == kMediaStreamSourceId ||
               constraint.name_ == kMediaStreamSourceInfoId) {
      result.device_id.SetExact(constraint.value_);
    } else if (constraint.name_ == kMediaStreamRenderToAssociatedSink) {
      // TODO(hta): This is a boolean represented as string.
      // Should give TypeError when it's not parseable.
      // https://crbug.com/576582
      result.render_to_associated_sink.SetExact(ToBoolean(constraint.value_));
    } else if (constraint.name_ == kNoiseReduction) {
      result.goog_noise_reduction.SetExact(ToBoolean(constraint.value_));
    }
    // else: Nothing. Unrecognized constraints are simply ignored.
  }
}

static MediaConstraints CreateFromNamedConstraints(
    ExecutionContext* context,
    Vector<NameValueStringConstraint>& mandatory,
    const Vector<NameValueStringConstraint>& optional) {
  MediaTrackConstraintSetPlatform basic;
  MediaTrackConstraintSetPlatform advanced;
  MediaConstraints constraints;
  ParseOldStyleNames(context, mandatory, basic);
  // We ignore unknown names and syntax errors in optional constraints.
  Vector<MediaTrackConstraintSetPlatform> advanced_vector;
  for (const auto& optional_constraint : optional) {
    MediaTrackConstraintSetPlatform advanced_element;
    Vector<NameValueStringConstraint> element_as_list(1, optional_constraint);
    ParseOldStyleNames(context, element_as_list, advanced_element);
    if (!advanced_element.IsUnconstrained())
      advanced_vector.push_back(advanced_element);
  }
  constraints.Initialize(basic, advanced_vector);
  return constraints;
}

void CopyLongConstraint(const V8ConstrainLong* blink_union_form,
                        NakedValueDisposition naked_treatment,
                        LongConstraint& web_form) {
  web_form.SetIsPresent(true);
  switch (blink_union_form->GetContentType()) {
    case V8ConstrainLong::ContentType::kConstrainLongRange: {
      const auto* blink_form = blink_union_form->GetAsConstrainLongRange();
      if (blink_form->hasMin()) {
        web_form.SetMin(blink_form->min());
      }
      if (blink_form->hasMax()) {
        web_form.SetMax(blink_form->max());
      }
      if (blink_form->hasIdeal()) {
        web_form.SetIdeal(blink_form->ideal());
      }
      if (blink_form->hasExact()) {
        web_form.SetExact(blink_form->exact());
      }
      break;
    }
    case V8ConstrainLong::ContentType::kLong:
      switch (naked_treatment) {
        case NakedValueDisposition::kTreatAsIdeal:
          web_form.SetIdeal(blink_union_form->GetAsLong());
          break;
        case NakedValueDisposition::kTreatAsExact:
          web_form.SetExact(blink_union_form->GetAsLong());
          break;
      }
      break;
  }
}

void CopyDoubleConstraint(const V8ConstrainDouble* blink_union_form,
                          NakedValueDisposition naked_treatment,
                          DoubleConstraint& web_form) {
  web_form.SetIsPresent(true);
  switch (blink_union_form->GetContentType()) {
    case V8ConstrainDouble::ContentType::kConstrainDoubleRange: {
      const auto* blink_form = blink_union_form->GetAsConstrainDoubleRange();
      if (blink_form->hasMin()) {
        web_form.SetMin(blink_form->min());
      }
      if (blink_form->hasMax()) {
        web_form.SetMax(blink_form->max());
      }
      if (blink_form->hasIdeal()) {
        web_form.SetIdeal(blink_form->ideal());
      }
      if (blink_form->hasExact()) {
        web_form.SetExact(blink_form->exact());
      }
      break;
    }
    case V8ConstrainDouble::ContentType::kDouble:
      switch (naked_treatment) {
        case NakedValueDisposition::kTreatAsIdeal:
          web_form.SetIdeal(blink_union_form->GetAsDouble());
          break;
        case NakedValueDisposition::kTreatAsExact:
          web_form.SetExact(blink_union_form->GetAsDouble());
          break;
      }
      break;
  }
}

void CopyBooleanOrDoubleConstraint(
    const V8UnionBooleanOrConstrainDouble* blink_union_form,
    NakedValueDisposition naked_treatment,
    DoubleOrBooleanConstraint& web_form) {
  switch (blink_union_form->GetContentType()) {
    case V8UnionBooleanOrConstrainDouble::ContentType::kBoolean:
      web_form.SetIsPresent(true);
      switch (naked_treatment) {
        case NakedValueDisposition::kTreatAsIdeal:
          web_form.SetIdealBoolean(blink_union_form->GetAsBoolean());
          break;
        case NakedValueDisposition::kTreatAsExact:
          web_form.SetExactBoolean(blink_union_form->GetAsBoolean());
          break;
      }
      break;
    case V8UnionBooleanOrConstrainDouble::ContentType::kConstrainDoubleRange:
    case V8UnionBooleanOrConstrainDouble::ContentType::kDouble:
      CopyDoubleConstraint(blink_union_form->GetAsV8ConstrainDouble(),
                           naked_treatment, web_form);
      break;
  }
}

void CopyBooleanOrStringConstraint(
    const V8UnionBooleanOrConstrainBooleanOrDOMStringParametersOrString*
        blink_union_form,
    NakedValueDisposition naked_treatment,
    BooleanOrStringConstraint& web_form) {
  switch (blink_union_form->GetContentType()) {
    case V8UnionBooleanOrConstrainBooleanOrDOMStringParametersOrString::
        ContentType::kBoolean:
      web_form.SetIsPresent(true);
      switch (naked_treatment) {
        case NakedValueDisposition::kTreatAsIdeal:
          web_form.SetIdealBoolean(blink_union_form->GetAsBoolean());
          break;
        case NakedValueDisposition::kTreatAsExact:
          web_form.SetExactBoolean(blink_union_form->GetAsBoolean());
          break;
      }
      break;
    case V8UnionBooleanOrConstrainBooleanOrDOMStringParametersOrString::
        ContentType::kString:
      web_form.SetIsPresent(true);
      switch (naked_treatment) {
        case NakedValueDisposition::kTreatAsIdeal:
          web_form.SetIdealString(blink_union_form->GetAsString());
          break;
        case NakedValueDisposition::kTreatAsExact:
          web_form.SetExactString(blink_union_form->GetAsString());
          break;
      }
      break;
    case V8UnionBooleanOrConstrainBooleanOrDOMStringParametersOrString::
        ContentType::kConstrainBooleanOrDOMStringParameters:
      web_form.SetIsPresent(true);
      ConstrainBooleanOrDOMStringParameters* boolean_or_dom_string =
          blink_union_form->GetAsConstrainBooleanOrDOMStringParameters();
      if (boolean_or_dom_string->hasIdeal()) {
        V8UnionBooleanOrString* ideal_value = boolean_or_dom_string->ideal();
        if (ideal_value->IsBoolean()) {
          web_form.SetIdealBoolean(ideal_value->GetAsBoolean());
        } else {
          CHECK(ideal_value->IsString());
          web_form.SetIdealString(ideal_value->GetAsString());
        }
      }
      if (boolean_or_dom_string->hasExact()) {
        V8UnionBooleanOrString* exact_value = boolean_or_dom_string->exact();
        if (exact_value->IsBoolean()) {
          web_form.SetExactBoolean(exact_value->GetAsBoolean());
        } else {
          CHECK(exact_value->IsString());
          web_form.SetExactString(exact_value->GetAsString());
        }
      }
      break;
  }
}

bool ValidateString(const String& str, String& error_message) {
  if (str.length() > kMaxConstraintStringLength) {
    error_message = "Constraint string too long.";
    return false;
  }
  return true;
}

bool ValidateStringSeq(const Vector<String>& strs, String& error_message) {
  if (strs.size() > kMaxConstraintStringSeqLength) {
    error_message = "Constraint string sequence too long.";
    return false;
  }

  for (const String& str : strs) {
    if (!ValidateString(str, error_message)) {
      return false;
    }
  }

  return true;
}

bool ValidateStringConstraint(
    V8UnionStringOrStringSequence* string_or_string_seq,
    String& error_message) {
  switch (string_or_string_seq->GetContentType()) {
    case V8UnionStringOrStringSequence::ContentType::kString: {
      return ValidateString(string_or_string_seq->GetAsString(), error_message);
    }
    case V8UnionStringOrStringSequence::ContentType::kStringSequence: {
      return ValidateStringSeq(string_or_string_seq->GetAsStringSequence(),
                               error_message);
    }
  }
  NOTREACHED();
}

bool ValidateStringConstraint(const V8ConstrainDOMString* blink_union_form,
                              String& error_message) {
  switch (blink_union_form->GetContentType()) {
    case V8ConstrainDOMString::ContentType::kConstrainDOMStringParameters: {
      const auto* blink_form =
          blink_union_form->GetAsConstrainDOMStringParameters();
      if (blink_form->hasIdeal() &&
          !ValidateStringConstraint(blink_form->ideal(), error_message)) {
        return false;
      }
      if (blink_form->hasExact() &&
          !ValidateStringConstraint(blink_form->exact(), error_message)) {
        return false;
      }
      return true;
    }
    case V8ConstrainDOMString::ContentType::kString:
      return ValidateString(blink_union_form->GetAsString(), error_message);
    case V8ConstrainDOMString::ContentType::kStringSequence:
      return ValidateStringSeq(blink_union_form->GetAsStringSequence(),
                               error_message);
  }
  NOTREACHED();
}

[[nodiscard]] bool ValidateAndCopyStringConstraint(
    const V8ConstrainDOMString* blink_union_form,
    NakedValueDisposition naked_treatment,
    StringConstraint& web_form,
    String& error_message) {
  if (!ValidateStringConstraint(blink_union_form, error_message)) {
    return false;
  }
  web_form.SetIsPresent(true);
  switch (blink_union_form->GetContentType()) {
    case V8ConstrainDOMString::ContentType::kConstrainDOMStringParameters: {
      const auto* blink_form =
          blink_union_form->GetAsConstrainDOMStringParameters();
      if (blink_form->hasIdeal()) {
        switch (blink_form->ideal()->GetContentType()) {
          case V8UnionStringOrStringSequence::ContentType::kString:
            web_form.SetIdeal(
                Vector<String>(1, blink_form->ideal()->GetAsString()));
            break;
          case V8UnionStringOrStringSequence::ContentType::kStringSequence:
            web_form.SetIdeal(blink_form->ideal()->GetAsStringSequence());
            break;
        }
      }
      if (blink_form->hasExact()) {
        switch (blink_form->exact()->GetContentType()) {
          case V8UnionStringOrStringSequence::ContentType::kString:
            web_form.SetExact(
                Vector<String>(1, blink_form->exact()->GetAsString()));
            break;
          case V8UnionStringOrStringSequence::ContentType::kStringSequence:
            web_form.SetExact(blink_form->exact()->GetAsStringSequence());
            break;
        }
      }
      break;
    }
    case V8ConstrainDOMString::ContentType::kString:
      switch (naked_treatment) {
        case NakedValueDisposition::kTreatAsIdeal:
          web_form.SetIdeal(Vector<String>(1, blink_union_form->GetAsString()));
          break;
        case NakedValueDisposition::kTreatAsExact:
          web_form.SetExact(Vector<String>(1, blink_union_form->GetAsString()));
          break;
      }
      break;
    case V8ConstrainDOMString::ContentType::kStringSequence:
      switch (naked_treatment) {
        case NakedValueDisposition::kTreatAsIdeal:
          web_form.SetIdeal(blink_union_form->GetAsStringSequence());
          break;
        case NakedValueDisposition::kTreatAsExact:
          web_form.SetExact(blink_union_form->GetAsStringSequence());
          break;
      }
      break;
  }
  return true;
}

void CopyBooleanConstraint(const V8ConstrainBoolean* blink_union_form,
                           NakedValueDisposition naked_treatment,
                           BooleanConstraint& web_form) {
  web_form.SetIsPresent(true);
  switch (blink_union_form->GetContentType()) {
    case V8ConstrainBoolean::ContentType::kBoolean:
      switch (naked_treatment) {
        case NakedValueDisposition::kTreatAsIdeal:
          web_form.SetIdeal(blink_union_form->GetAsBoolean());
          break;
        case NakedValueDisposition::kTreatAsExact:
          web_form.SetExact(blink_union_form->GetAsBoolean());
          break;
      }
      break;
    case V8ConstrainBoolean::ContentType::kConstrainBooleanParameters: {
      const auto* blink_form =
          blink_union_form->GetAsConstrainBooleanParameters();
      if (blink_form->hasIdeal()) {
        web_form.SetIdeal(blink_form->ideal());
      }
      if (blink_form->hasExact()) {
        web_form.SetExact(blink_form->exact());
      }
      break;
    }
  }
}

bool ValidateAndCopyConstraintSet(
    const MediaTrackConstraintSet* constraints_in,
    NakedValueDisposition naked_treatment,
    MediaTrackConstraintSetPlatform& constraint_buffer,
    String& error_message) {
  if (constraints_in->hasWidth()) {
    CopyLongConstraint(constraints_in->width(), naked_treatment,
                       constraint_buffer.width);
  }

  if (constraints_in->hasHeight()) {
    CopyLongConstraint(constraints_in->height(), naked_treatment,
                       constraint_buffer.height);
  }

  if (constraints_in->hasAspectRatio()) {
    CopyDoubleConstraint(constraints_in->aspectRatio(), naked_treatment,
                         constraint_buffer.aspect_ratio);
  }

  if (constraints_in->hasFrameRate()) {
    CopyDoubleConstraint(constraints_in->frameRate(), naked_treatment,
                         constraint_buffer.frame_rate);
  }

  if (constraints_in->hasFacingMode()) {
    if (!ValidateAndCopyStringConstraint(
            constraints_in->facingMode(), naked_treatment,
            constraint_buffer.facing_mode, error_message)) {
      return false;
    }
  }

  if (constraints_in->hasResizeMode()) {
    if (!ValidateAndCopyStringConstraint(
            constraints_in->resizeMode(), naked_treatment,
            constraint_buffer.resize_mode, error_message)) {
      return false;
    }
  }

  if (constraints_in->hasSampleRate()) {
    CopyLongConstraint(constraints_in->sampleRate(), naked_treatment,
                       constraint_buffer.sample_rate);
  }

  if (constraints_in->hasSampleSize()) {
    CopyLongConstraint(constraints_in->sampleSize(), naked_treatment,
                       constraint_buffer.sample_size);
  }

  if (constraints_in->hasEchoCancellation()) {
    CopyBooleanOrStringConstraint(constraints_in->echoCancellation(),
                                  naked_treatment,
                                  constraint_buffer.echo_cancellation);
  }

  if (constraints_in->hasAutoGainControl()) {
    CopyBooleanConstraint(constraints_in->autoGainControl(), naked_treatment,
                          constraint_buffer.auto_gain_control);
  }

  if (constraints_in->hasNoiseSuppression()) {
    CopyBooleanConstraint(constraints_in->noiseSuppression(), naked_treatment,
                          constraint_buffer.noise_suppression);
  }

  if (constraints_in->hasVoiceIsolation()) {
    CopyBooleanConstraint(constraints_in->voiceIsolation(), naked_treatment,
                          constraint_buffer.voice_isolation);
  }

  if (constraints_in->hasLatency()) {
    CopyDoubleConstraint(constraints_in->latency(), naked_treatment,
                         constraint_buffer.latency);
  }

  if (constraints_in->hasChannelCount()) {
    CopyLongConstraint(constraints_in->channelCount(), naked_treatment,
                       constraint_buffer.channel_count);
  }

  if (constraints_in->hasDeviceId()) {
    if (!ValidateAndCopyStringConstraint(
            constraints_in->deviceId(), naked_treatment,
            constraint_buffer.device_id, error_message)) {
      return false;
    }
  }

  if (constraints_in->hasGroupId()) {
    if (!ValidateAndCopyStringConstraint(
            constraints_in->groupId(), naked_treatment,
            constraint_buffer.group_id, error_message)) {
      return false;
    }
  }

  if (constraints_in->hasExposureCompensation()) {
    CopyDoubleConstraint(constraints_in->exposureCompensation(),
                         naked_treatment,
                         constraint_buffer.exposure_compensation);
  }

  if (constraints_in->hasExposureTime()) {
    CopyDoubleConstraint(constraints_in->exposureTime(), naked_treatment,
                         constraint_buffer.exposure_time);
  }

  if (constraints_in->hasColorTemperature()) {
    CopyDoubleConstraint(constraints_in->colorTemperature(), naked_treatment,
                         constraint_buffer.color_temperature);
  }

  if (constraints_in->hasIso()) {
    CopyDoubleConstraint(constraints_in->iso(), naked_treatment,
                         constraint_buffer.iso);
  }

  if (constraints_in->hasBrightness()) {
    CopyDoubleConstraint(constraints_in->brightness(), naked_treatment,
                         constraint_buffer.brightness);
  }

  if (constraints_in->hasContrast()) {
    CopyDoubleConstraint(constraints_in->contrast(), naked_treatment,
                         constraint_buffer.contrast);
  }

  if (constraints_in->hasSaturation()) {
    CopyDoubleConstraint(constraints_in->saturation(), naked_treatment,
                         constraint_buffer.saturation);
  }

  if (constraints_in->hasSharpness()) {
    CopyDoubleConstraint(constraints_in->sharpness(), naked_treatment,
                         constraint_buffer.sharpness);
  }

  if (constraints_in->hasFocusDistance()) {
    CopyDoubleConstraint(constraints_in->focusDistance(), naked_treatment,
                         constraint_buffer.focus_distance);
  }

  if (constraints_in->hasPan()) {
    CopyBooleanOrDoubleConstraint(constraints_in->pan(), naked_treatment,
                                  constraint_buffer.pan);
  }

  if (constraints_in->hasTilt()) {
    CopyBooleanOrDoubleConstraint(constraints_in->tilt(), naked_treatment,
                                  constraint_buffer.tilt);
  }

  if (constraints_in->hasZoom()) {
    CopyBooleanOrDoubleConstraint(constraints_in->zoom(), naked_treatment,
                                  constraint_buffer.zoom);
  }

  if (constraints_in->hasTorch()) {
    CopyBooleanConstraint(constraints_in->torch(), naked_treatment,
                          constraint_buffer.torch);
  }

  if (constraints_in->hasBackgroundBlur()) {
    CopyBooleanConstraint(constraints_in->backgroundBlur(), naked_treatment,
                          constraint_buffer.background_blur);
  }

  if (constraints_in->hasBackgroundSegmentationMask()) {
    CopyBooleanConstraint(constraints_in->backgroundSegmentationMask(),
                          naked_treatment,
                          constraint_buffer.background_segmentation_mask);
  }

  if (constraints_in->hasEyeGazeCorrection()) {
    CopyBooleanConstraint(constraints_in->eyeGazeCorrection(), naked_treatment,
                          constraint_buffer.eye_gaze_correction);
  }

  if (constraints_in->hasFaceFraming()) {
    CopyBooleanConstraint(constraints_in->faceFraming(), naked_treatment,
                          constraint_buffer.face_framing);
  }

  if (constraints_in->hasDisplaySurface()) {
    if (!ValidateAndCopyStringConstraint(
            constraints_in->displaySurface(), naked_treatment,
            constraint_buffer.display_surface, error_message)) {
      return false;
    }
  }

  if (constraints_in->hasSuppressLocalAudioPlayback()) {
    CopyBooleanConstraint(constraints_in->suppressLocalAudioPlayback(),
                          naked_treatment,
                          constraint_buffer.suppress_local_audio_playback);
  }

  if (constraints_in->hasRestrictOwnAudio()) {
    CopyBooleanConstraint(constraints_in->restrictOwnAudio(), naked_treatment,
                          constraint_buffer.restrict_own_audio);
  }

  return true;
}

template <class T>
bool UseNakedBooleanNotNumeric(const T& input, NakedValueDisposition which) {
  if (input.HasExact() || input.HasIdeal() || input.HasMin() ||
      input.HasMax()) {
    return false;
  }
  switch (which) {
    case NakedValueDisposition::kTreatAsIdeal:
      return input.HasIdealBoolean();
    case NakedValueDisposition::kTreatAsExact:
      return input.HasExactBoolean();
  }
  NOTREACHED();
}

template <class T>
bool UseNakedNumeric(const T& input, NakedValueDisposition which) {
  switch (which) {
    case NakedValueDisposition::kTreatAsIdeal:
      return input.HasIdeal() &&
             !(input.HasExact() || input.HasMin() || input.HasMax());
      break;
    case NakedValueDisposition::kTreatAsExact:
      return input.HasExact() &&
             !(input.HasIdeal() || input.HasMin() || input.HasMax());
      break;
  }
  NOTREACHED();
}

template <class T>
bool UseNakedNonNumeric(const T& input, NakedValueDisposition which) {
  switch (which) {
    case NakedValueDisposition::kTreatAsIdeal:
      return input.HasIdeal() && !input.HasExact();
      break;
    case NakedValueDisposition::kTreatAsExact:
      return input.HasExact() && !input.HasIdeal();
      break;
  }
  NOTREACHED();
}

template <class T>
bool GetNakedBoolean(const T& input, NakedValueDisposition which) {
  switch (which) {
    case NakedValueDisposition::kTreatAsIdeal:
      return input.IdealBoolean();
    case NakedValueDisposition::kTreatAsExact:
      return input.ExactBoolean();
  }
  NOTREACHED();
}

template <typename U, class T>
U GetNakedValue(const T& input, NakedValueDisposition which) {
  switch (which) {
    case NakedValueDisposition::kTreatAsIdeal:
      return input.Ideal();
      break;
    case NakedValueDisposition::kTreatAsExact:
      return input.Exact();
      break;
  }
  NOTREACHED();
}

V8ConstrainLong* ConvertLong(const LongConstraint& input,
                             NakedValueDisposition naked_treatment) {
  if (UseNakedNumeric(input, naked_treatment)) {
    return MakeGarbageCollected<V8ConstrainLong>(
        GetNakedValue<uint32_t>(input, naked_treatment));
  } else if (!input.IsUnconstrained()) {
    ConstrainLongRange* output = ConstrainLongRange::Create();
    if (input.HasExact())
      output->setExact(input.Exact());
    if (input.HasMin())
      output->setMin(input.Min());
    if (input.HasMax())
      output->setMax(input.Max());
    if (input.HasIdeal())
      output->setIdeal(input.Ideal());
    return MakeGarbageCollected<V8ConstrainLong>(output);
  }
  return nullptr;
}

V8ConstrainDouble* ConvertDouble(const DoubleConstraint& input,
                                 NakedValueDisposition naked_treatment) {
  if (UseNakedNumeric(input, naked_treatment)) {
    return MakeGarbageCollected<V8ConstrainDouble>(
        GetNakedValue<double>(input, naked_treatment));
  } else if (!input.IsUnconstrained()) {
    ConstrainDoubleRange* output = ConstrainDoubleRange::Create();
    if (input.HasExact())
      output->setExact(input.Exact());
    if (input.HasIdeal())
      output->setIdeal(input.Ideal());
    if (input.HasMin())
      output->setMin(input.Min());
    if (input.HasMax())
      output->setMax(input.Max());
    return MakeGarbageCollected<V8ConstrainDouble>(output);
  }
  return nullptr;
}

V8UnionBooleanOrConstrainDouble* ConvertBooleanOrDouble(
    const DoubleOrBooleanConstraint& input,
    NakedValueDisposition naked_treatment) {
  if (UseNakedNumeric(input, naked_treatment)) {
    return MakeGarbageCollected<V8UnionBooleanOrConstrainDouble>(
        GetNakedValue<double>(input, naked_treatment));
  } else if (UseNakedBooleanNotNumeric(input, naked_treatment)) {
    return MakeGarbageCollected<V8UnionBooleanOrConstrainDouble>(
        GetNakedBoolean(input, naked_treatment));
  } else if (!input.IsUnconstrained()) {
    ConstrainDoubleRange* output = ConstrainDoubleRange::Create();
    if (input.HasExact())
      output->setExact(input.Exact());
    if (input.HasIdeal())
      output->setIdeal(input.Ideal());
    if (input.HasMin())
      output->setMin(input.Min());
    if (input.HasMax())
      output->setMax(input.Max());
    return MakeGarbageCollected<V8UnionBooleanOrConstrainDouble>(output);
  }
  return nullptr;
}

V8UnionStringOrStringSequence* ConvertStringSequence(
    const Vector<String>& input) {
  if (input.size() > 1) {
    return MakeGarbageCollected<V8UnionStringOrStringSequence>(input);
  } else if (!input.empty()) {
    return MakeGarbageCollected<V8UnionStringOrStringSequence>(input[0]);
  }
  return nullptr;
}

V8ConstrainDOMString* ConvertString(const StringConstraint& input,
                                    NakedValueDisposition naked_treatment) {
  if (UseNakedNonNumeric(input, naked_treatment)) {
    const Vector<String>& input_buffer(
        GetNakedValue<const Vector<String>&>(input, naked_treatment));
    if (input_buffer.size() > 1) {
      return MakeGarbageCollected<V8ConstrainDOMString>(input_buffer);
    } else if (!input_buffer.empty()) {
      return MakeGarbageCollected<V8ConstrainDOMString>(input_buffer[0]);
    }
    return nullptr;
  } else if (!input.IsUnconstrained()) {
    ConstrainDOMStringParameters* output =
        ConstrainDOMStringParameters::Create();
    if (input.HasExact())
      output->setExact(ConvertStringSequence(input.Exact()));
    if (input.HasIdeal())
      output->setIdeal(ConvertStringSequence(input.Ideal()));
    return MakeGarbageCollected<V8ConstrainDOMString>(output);
  }
  return nullptr;
}

V8ConstrainBoolean* ConvertBoolean(const BooleanConstraint& input,
                                   NakedValueDisposition naked_treatment) {
  if (UseNakedNonNumeric(input, naked_treatment)) {
    return MakeGarbageCollected<V8ConstrainBoolean>(
        GetNakedValue<bool>(input, naked_treatment));
  } else if (!input.IsUnconstrained()) {
    ConstrainBooleanParameters* output = ConstrainBooleanParameters::Create();
    if (input.HasExact())
      output->setExact(input.Exact());
    if (input.HasIdeal())
      output->setIdeal(input.Ideal());
    return MakeGarbageCollected<V8ConstrainBoolean>(output);
  }
  return nullptr;
}

V8UnionBooleanOrConstrainBooleanOrDOMStringParametersOrString*
ConvertBooleanOrString(const BooleanOrStringConstraint& input,
                       NakedValueDisposition naked_treatment) {
  if (UseNakedNonNumeric(input, naked_treatment)) {
    switch (naked_treatment) {
      case NakedValueDisposition::kTreatAsIdeal:
        if (input.HasIdealBoolean()) {
          return MakeGarbageCollected<
              V8UnionBooleanOrConstrainBooleanOrDOMStringParametersOrString>(
              input.IdealBoolean());
        }
        if (input.HasIdealString()) {
          return MakeGarbageCollected<
              V8UnionBooleanOrConstrainBooleanOrDOMStringParametersOrString>(
              input.IdealString());
        }
        NOTREACHED();
      case NakedValueDisposition::kTreatAsExact:
        if (input.HasExactBoolean()) {
          return MakeGarbageCollected<
              V8UnionBooleanOrConstrainBooleanOrDOMStringParametersOrString>(
              input.ExactBoolean());
        }
        if (input.HasIdealString()) {
          return MakeGarbageCollected<
              V8UnionBooleanOrConstrainBooleanOrDOMStringParametersOrString>(
              input.ExactString());
        }
        NOTREACHED();
    }
  } else if (!input.IsUnconstrained()) {
    ConstrainBooleanOrDOMStringParameters* output =
        ConstrainBooleanOrDOMStringParameters::Create();
    if (input.HasExactBoolean()) {
      output->setExact(
          MakeGarbageCollected<V8UnionBooleanOrString>(input.ExactBoolean()));
    } else if (input.HasExactString()) {
      output->setExact(
          MakeGarbageCollected<V8UnionBooleanOrString>(input.ExactString()));
    }
    if (input.HasIdealBoolean()) {
      output->setIdeal(
          MakeGarbageCollected<V8UnionBooleanOrString>(input.IdealBoolean()));
    } else if (input.HasIdealString()) {
      output->setIdeal(
          MakeGarbageCollected<V8UnionBooleanOrString>(input.IdealString()));
    }
    return MakeGarbageCollected<
        V8UnionBooleanOrConstrainBooleanOrDOMStringParametersOrString>(output);
  }
  return nullptr;
}

void ConvertConstraintSet(const MediaTrackConstraintSetPlatform& input,
                          NakedValueDisposition naked_treatment,
                          MediaTrackConstraintSet* output) {
  if (!input.width.IsUnconstrained())
    output->setWidth(ConvertLong(input.width, naked_treatment));
  if (!input.height.IsUnconstrained())
    output->setHeight(ConvertLong(input.height, naked_treatment));
  if (!input.aspect_ratio.IsUnconstrained())
    output->setAspectRatio(ConvertDouble(input.aspect_ratio, naked_treatment));
  if (!input.frame_rate.IsUnconstrained())
    output->setFrameRate(ConvertDouble(input.frame_rate, naked_treatment));
  if (!input.facing_mode.IsUnconstrained())
    output->setFacingMode(ConvertString(input.facing_mode, naked_treatment));
  if (!input.resize_mode.IsUnconstrained())
    output->setResizeMode(ConvertString(input.resize_mode, naked_treatment));
  if (!input.sample_rate.IsUnconstrained())
    output->setSampleRate(ConvertLong(input.sample_rate, naked_treatment));
  if (!input.sample_size.IsUnconstrained())
    output->setSampleSize(ConvertLong(input.sample_size, naked_treatment));
  if (!input.echo_cancellation.IsUnconstrained()) {
    output->setEchoCancellation(
        ConvertBooleanOrString(input.echo_cancellation, naked_treatment));
  }
  if (!input.auto_gain_control.IsUnconstrained()) {
    output->setAutoGainControl(
        ConvertBoolean(input.auto_gain_control, naked_treatment));
  }
  if (!input.noise_suppression.IsUnconstrained()) {
    output->setNoiseSuppression(
        ConvertBoolean(input.noise_suppression, naked_treatment));
  }
  if (!input.voice_isolation.IsUnconstrained()) {
    output->setVoiceIsolation(
        ConvertBoolean(input.voice_isolation, naked_treatment));
  }
  if (!input.latency.IsUnconstrained())
    output->setLatency(ConvertDouble(input.latency, naked_treatment));
  if (!input.channel_count.IsUnconstrained())
    output->setChannelCount(ConvertLong(input.channel_count, naked_treatment));
  if (!input.device_id.IsUnconstrained())
    output->setDeviceId(ConvertString(input.device_id, naked_treatment));
  if (!input.group_id.IsUnconstrained())
    output->setGroupId(ConvertString(input.group_id, naked_treatment));
  if (!input.exposure_compensation.IsUnconstrained()) {
    output->setExposureCompensation(
        ConvertDouble(input.exposure_compensation, naked_treatment));
  }
  if (!input.exposure_time.IsUnconstrained()) {
    output->setExposureTime(
        ConvertDouble(input.exposure_time, naked_treatment));
  }
  if (!input.color_temperature.IsUnconstrained()) {
    output->setColorTemperature(
        ConvertDouble(input.color_temperature, naked_treatment));
  }
  if (!input.iso.IsUnconstrained()) {
    output->setIso(ConvertDouble(input.iso, naked_treatment));
  }
  if (!input.brightness.IsUnconstrained()) {
    output->setBrightness(ConvertDouble(input.brightness, naked_treatment));
  }
  if (!input.contrast.IsUnconstrained()) {
    output->setContrast(ConvertDouble(input.contrast, naked_treatment));
  }
  if (!input.saturation.IsUnconstrained()) {
    output->setSaturation(ConvertDouble(input.saturation, naked_treatment));
  }
  if (!input.sharpness.IsUnconstrained()) {
    output->setSharpness(ConvertDouble(input.sharpness, naked_treatment));
  }
  if (!input.focus_distance.IsUnconstrained()) {
    output->setFocusDistance(
        ConvertDouble(input.focus_distance, naked_treatment));
  }
  if (!input.pan.IsUnconstrained())
    output->setPan(ConvertBooleanOrDouble(input.pan, naked_treatment));
  if (!input.tilt.IsUnconstrained())
    output->setTilt(ConvertBooleanOrDouble(input.tilt, naked_treatment));
  if (!input.zoom.IsUnconstrained())
    output->setZoom(ConvertBooleanOrDouble(input.zoom, naked_treatment));
  if (!input.torch.IsUnconstrained()) {
    output->setTorch(ConvertBoolean(input.torch, naked_treatment));
  }
  if (!input.background_blur.IsUnconstrained()) {
    output->setBackgroundBlur(
        ConvertBoolean(input.background_blur, naked_treatment));
  }
  if (!input.background_segmentation_mask.IsUnconstrained()) {
    output->setBackgroundSegmentationMask(
        ConvertBoolean(input.background_segmentation_mask, naked_treatment));
  }
  if (!input.eye_gaze_correction.IsUnconstrained()) {
    output->setEyeGazeCorrection(
        ConvertBoolean(input.eye_gaze_correction, naked_treatment));
  }
  if (!input.face_framing.IsUnconstrained()) {
    output->setFaceFraming(ConvertBoolean(input.face_framing, naked_treatment));
  }
  if (!input.suppress_local_audio_playback.IsUnconstrained()) {
    output->setSuppressLocalAudioPlayback(
        ConvertBoolean(input.suppress_local_audio_playback, naked_treatment));
  }
  if (!input.restrict_own_audio.IsUnconstrained()) {
    output->setRestrictOwnAudio(
        ConvertBoolean(input.restrict_own_audio, naked_treatment));
  }
  // TODO(hta): Decide the future of the nonstandard constraints.
  // If they go forward, they need to be added here.
  // https://crbug.com/605673
}

}  // namespace

MediaConstraints ConvertTrackConstraintsToMediaConstraints(
    const MediaTrackConstraints* constraints_in,
    String& error_message) {
  MediaTrackConstraintSetPlatform constraint_buffer;
  Vector<MediaTrackConstraintSetPlatform> advanced_buffer;
  if (!ValidateAndCopyConstraintSet(constraints_in,
                                    NakedValueDisposition::kTreatAsIdeal,
                                    constraint_buffer, error_message)) {
    return MediaConstraints();
  }
  if (constraints_in->hasAdvanced()) {
    for (const auto& element : constraints_in->advanced()) {
      MediaTrackConstraintSetPlatform advanced_element;
      if (!ValidateAndCopyConstraintSet(element,
                                        NakedValueDisposition::kTreatAsExact,
                                        advanced_element, error_message)) {
        return MediaConstraints();
      }
      advanced_buffer.push_back(advanced_element);
    }
  }
  MediaConstraints constraints;
  constraints.Initialize(constraint_buffer, advanced_buffer);
  return constraints;
}

MediaConstraints Create(ExecutionContext* context,
                        const MediaTrackConstraints* constraints_in,
                        String& error_message) {
  MediaConstraints standard_form =
      ConvertTrackConstraintsToMediaConstraints(constraints_in, error_message);
  if (standard_form.IsNull()) {
    return standard_form;
  }
  if (constraints_in->hasOptional() || constraints_in->hasMandatory()) {
    if (!standard_form.IsUnconstrained()) {
      UseCounter::Count(context, WebFeature::kMediaStreamConstraintsOldAndNew);
      error_message =
          "Malformed constraint: Cannot use both optional/mandatory and "
          "specific or advanced constraints.";
      return MediaConstraints();
    }
    Vector<NameValueStringConstraint> optional;
    Vector<NameValueStringConstraint> mandatory;
    if (!Parse(constraints_in, optional, mandatory)) {
      error_message = "Malformed constraints object.";
      return MediaConstraints();
    }
    UseCounter::Count(context, WebFeature::kMediaStreamConstraintsNameValue);
    return CreateFromNamedConstraints(context, mandatory, optional);
  }
  UseCounter::Count(context, WebFeature::kMediaStreamConstraintsConformant);
  return standard_form;
}

MediaConstraints Create() {
  MediaConstraints constraints;
  constraints.Initialize();
  return constraints;
}

MediaTrackConstraints* ConvertConstraints(const MediaConstraints& input) {
  MediaTrackConstraints* output = MediaTrackConstraints::Create();
  if (input.IsNull())
    return output;
  ConvertConstraintSet(input.Basic(), NakedValueDisposition::kTreatAsIdeal,
                       output);

  HeapVector<Member<MediaTrackConstraintSet>> advanced_vector;
  for (const auto& it : input.Advanced()) {
    if (it.IsUnconstrained())
      continue;
    MediaTrackConstraintSet* element = MediaTrackConstraintSet::Create();
    ConvertConstraintSet(it, NakedValueDisposition::kTreatAsExact, element);
    advanced_vector.push_back(element);
  }
  if (!advanced_vector.empty())
    output->setAdvanced(advanced_vector);

  return output;
}

}  // namespace media_constraints_impl
}  // namespace blink
