/*
 * Copyright (C) 2010 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.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS 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 APPLE OR ITS 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/webaudio/audio_param.h"

#include "build/build_config.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_automation_rate.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/modules/webaudio/audio_graph_tracer.h"
#include "third_party/blink/renderer/modules/webaudio/audio_node.h"
#include "third_party/blink/renderer/modules/webaudio/audio_node_output.h"
#include "third_party/blink/renderer/platform/audio/audio_utilities.h"
#include "third_party/blink/renderer/platform/audio/vector_math.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/wtf/math_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/strcat.h"

namespace blink {

AudioParam::AudioParam(BaseAudioContext& context,
                       const String& parent_uuid,
                       AudioParamHandler::AudioParamType param_type,
                       double default_value,
                       V8AutomationRate::Enum rate,
                       AudioParamHandler::AutomationRateMode rate_mode,
                       float min_value,
                       float max_value)
    : InspectorHelperMixin(context.GraphTracer(), parent_uuid),
      handler_(AudioParamHandler::Create(context,
                                         param_type,
                                         default_value,
                                         rate,
                                         rate_mode,
                                         min_value,
                                         max_value)),
      context_(context),
      deferred_task_handler_(&context.GetDeferredTaskHandler()) {}

AudioParam* AudioParam::Create(BaseAudioContext& context,
                               const String& parent_uuid,
                               AudioParamHandler::AudioParamType param_type,
                               double default_value,
                               V8AutomationRate::Enum rate,
                               AudioParamHandler::AutomationRateMode rate_mode,
                               float min_value,
                               float max_value) {
  DCHECK_LE(min_value, max_value);

  return MakeGarbageCollected<AudioParam>(context, parent_uuid, param_type,
                                          default_value, rate, rate_mode,
                                          min_value, max_value);
}

AudioParam::~AudioParam() {
  // The graph lock is required to destroy the handler. And we can't use
  // `context_` to touch it, since that object may also be a dead heap object.
  {
    DeferredTaskHandler::GraphAutoLocker locker(*deferred_task_handler_);
    handler_ = nullptr;
  }
}

void AudioParam::Trace(Visitor* visitor) const {
  visitor->Trace(context_);
  InspectorHelperMixin::Trace(visitor);
  ScriptWrappable::Trace(visitor);
}

float AudioParam::value() const {
  return Handler().Value();
}

void AudioParam::WarnIfOutsideRange(const String& param_method, float value) {
  if (Context()->GetExecutionContext() &&
      (value < minValue() || value > maxValue())) {
    Context()->GetExecutionContext()->AddConsoleMessage(
        MakeGarbageCollected<ConsoleMessage>(
            mojom::ConsoleMessageSource::kJavaScript,
            mojom::ConsoleMessageLevel::kWarning,
            StrCat({Handler().GetParamName(), ".", param_method, " ",
                    String::Number(value), " outside nominal range [",
                    String::Number(minValue()), ", ",
                    String::Number(maxValue()), "]; value will be clamped."})));
  }
}

void AudioParam::setValue(float value) {
  WarnIfOutsideRange("value", value);
  Handler().SetValue(value);
}

void AudioParam::setValue(float value, ExceptionState& exception_state) {
  WarnIfOutsideRange("value", value);

  // Change the intrinsic value so that an immediate query for the value
  // returns the value that the user code provided. It also clamps the value
  // to the nominal range.
  Handler().SetValue(value);

  // Use the intrinsic value (after clamping) to schedule the actual
  // automation event.
  setValueAtTime(Handler().IntrinsicValue(), Context()->currentTime(),
                 exception_state);
}

float AudioParam::defaultValue() const {
  return Handler().DefaultValue();
}

float AudioParam::minValue() const {
  return Handler().MinValue();
}

float AudioParam::maxValue() const {
  return Handler().MaxValue();
}

void AudioParam::SetCustomParamName(const String name) {
  Handler().SetCustomParamName(name);
}

V8AutomationRate AudioParam::automationRate() const {
  return V8AutomationRate(Handler().GetAutomationRate());
}

void AudioParam::setAutomationRate(const V8AutomationRate& rate,
                                   ExceptionState& exception_state) {
  if (Handler().IsAutomationRateFixed()) {
    exception_state.ThrowDOMException(
        DOMExceptionCode::kInvalidStateError,
        StrCat({Handler().GetParamName(),
                ".automationRate is fixed and cannot be changed to \"",
                rate.AsStringView(), "\""}));
    return;
  }

  Handler().SetAutomationRate(rate.AsEnum());
}

AudioParam* AudioParam::setValueAtTime(float value,
                                       double time,
                                       ExceptionState& exception_state) {
  WarnIfOutsideRange("setValueAtTime value", value);
  Handler().SetValueAtTime(value, time, exception_state);
  return this;
}

AudioParam* AudioParam::linearRampToValueAtTime(
    float value,
    double time,
    ExceptionState& exception_state) {
  WarnIfOutsideRange("linearRampToValueAtTime value", value);
  Handler().LinearRampToValueAtTime(value, time, Handler().IntrinsicValue(),
                                    Context()->currentTime(), exception_state);

  return this;
}

AudioParam* AudioParam::exponentialRampToValueAtTime(
    float value,
    double time,
    ExceptionState& exception_state) {
  WarnIfOutsideRange("exponentialRampToValue value", value);
  Handler().ExponentialRampToValueAtTime(
      value, time, Handler().IntrinsicValue(), Context()->currentTime(),
      exception_state);

  return this;
}

AudioParam* AudioParam::setTargetAtTime(float target,
                                        double time,
                                        double time_constant,
                                        ExceptionState& exception_state) {
  WarnIfOutsideRange("setTargetAtTime value", target);
  Handler().SetTargetAtTime(target, time, time_constant, exception_state);
  return this;
}

AudioParam* AudioParam::setValueCurveAtTime(const Vector<float>& curve,
                                            double time,
                                            double duration,
                                            ExceptionState& exception_state) {
  float min = minValue();
  float max = maxValue();

  // Find the first value in the curve (if any) that is outside the
  // nominal range.  It's probably not necessary to produce a warning
  // on every value outside the nominal range.
  for (float value : curve) {
    if (value < min || value > max) {
      WarnIfOutsideRange("setValueCurveAtTime value", value);
      break;
    }
  }

  Handler().SetValueCurveAtTime(curve, time, duration, exception_state);
  return this;
}

AudioParam* AudioParam::cancelScheduledValues(double start_time,
                                              ExceptionState& exception_state) {
  Handler().CancelScheduledValues(start_time, exception_state);
  return this;
}

AudioParam* AudioParam::cancelAndHoldAtTime(double start_time,
                                            ExceptionState& exception_state) {
  Handler().CancelAndHoldAtTime(start_time, exception_state);
  return this;
}

}  // namespace blink
