/*
 * Copyright (C) 2011 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 Apple Computer, Inc. ("Apple") 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 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.
 */

#ifndef AudioParamTimeline_h
#define AudioParamTimeline_h

#include "core/typed_arrays/DOMTypedArray.h"
#include "modules/webaudio/AudioDestinationNode.h"
#include "modules/webaudio/BaseAudioContext.h"
#include "platform/wtf/Forward.h"
#include "platform/wtf/Threading.h"
#include "platform/wtf/Vector.h"

#include <tuple>

namespace blink {

class AudioParamTimeline {
  DISALLOW_NEW();

 public:
  AudioParamTimeline() {}

  void SetValueAtTime(float value, double time, ExceptionState&);
  void LinearRampToValueAtTime(float value,
                               double time,
                               float initial_value,
                               double call_time,
                               ExceptionState&);
  void ExponentialRampToValueAtTime(float value,
                                    double time,
                                    float initial_value,
                                    double call_time,
                                    ExceptionState&);
  void SetTargetAtTime(float target,
                       double time,
                       double time_constant,
                       ExceptionState&);
  void SetValueCurveAtTime(const Vector<float>& curve,
                           double time,
                           double duration,
                           ExceptionState&);
  void CancelScheduledValues(double start_time, ExceptionState&);
  void CancelAndHoldAtTime(double cancel_time, ExceptionState&);

  // hasValue is set to true if a valid timeline value is returned.
  // otherwise defaultValue is returned.
  float ValueForContextTime(AudioDestinationHandler&,
                            float default_value,
                            bool& has_value,
                            float min_value,
                            float max_value);

  // Given the time range in frames, calculates parameter values into the values
  // buffer and returns the last parameter value calculated for "values" or the
  // defaultValue if none were calculated.  controlRate is the rate (number per
  // second) at which parameter values will be calculated.  It should equal
  // sampleRate for sample-accurate parameter changes, and otherwise will
  // usually match the render quantum size such that the parameter value changes
  // once per render quantum.
  float ValuesForFrameRange(size_t start_frame,
                            size_t end_frame,
                            float default_value,
                            float* values,
                            unsigned number_of_values,
                            double sample_rate,
                            double control_rate,
                            float min_value,
                            float max_value);

  // Returns true if this AudioParam has any events on it.
  bool HasValues() const;

  float SmoothedValue() { return smoothed_value_; }
  void SetSmoothedValue(float v) { smoothed_value_ = v; }

 private:
  class ParamEvent {
   public:
    enum Type {
      kSetValue,
      kLinearRampToValue,
      kExponentialRampToValue,
      kSetTarget,
      kSetValueCurve,
      // For cancelValuesAndHold
      kCancelValues,
      kLastType
    };

    static std::unique_ptr<ParamEvent> CreateLinearRampEvent(
        float value,
        double time,
        float initial_value,
        double call_time);
    static std::unique_ptr<ParamEvent> CreateExponentialRampEvent(
        float value,
        double time,
        float initial_value,
        double call_time);
    static std::unique_ptr<ParamEvent> CreateSetValueEvent(float value,
                                                           double time);
    static std::unique_ptr<ParamEvent>
    CreateSetTargetEvent(float value, double time, double time_constant);
    static std::unique_ptr<ParamEvent> CreateSetValueCurveEvent(
        const Vector<float>& curve,
        double time,
        double duration);
    static std::unique_ptr<ParamEvent> CreateCancelValuesEvent(
        double time,
        std::unique_ptr<ParamEvent> saved_event);
    // Needed for creating a saved event where we want to supply all
    // the possible parameters because we're mostly copying an
    // existing event.
    static std::unique_ptr<ParamEvent> CreateGeneralEvent(
        Type,
        float value,
        double time,
        float initial_value,
        double call_time,
        double time_constant,
        double duration,
        Vector<float>& curve,
        double curve_points_per_second,
        float curve_end_value,
        std::unique_ptr<ParamEvent> saved_event);

    static bool EventPreceeds(const std::unique_ptr<ParamEvent>& a,
                              const std::unique_ptr<ParamEvent>& b) {
      return a->Time() < b->Time();
    }

    Type GetType() const { return type_; }
    float Value() const { return value_; }
    double Time() const { return time_; }
    void SetTime(double new_time) { time_ = new_time; }
    double TimeConstant() const { return time_constant_; }
    double Duration() const { return duration_; }
    const Vector<float>& Curve() const { return curve_; }
    Vector<float>& Curve() { return curve_; }
    float InitialValue() const { return initial_value_; }
    double CallTime() const { return call_time_; }
    bool NeedsTimeClampCheck() const { return needs_time_clamp_check_; }
    void ClearTimeClampCheck() { needs_time_clamp_check_ = false; }

    double CurvePointsPerSecond() const { return curve_points_per_second_; }
    float CurveEndValue() const { return curve_end_value_; }

    // For CancelValues events. Not valid for any other event.
    ParamEvent* SavedEvent() const;
    bool HasDefaultCancelledValue() const;
    void SetCancelledValue(float);

   private:
    // General event
    ParamEvent(Type type,
               float value,
               double time,
               float initial_value,
               double call_time,
               double time_constant,
               double duration,
               Vector<float>& curve,
               double curve_points_per_second,
               float curve_end_value,
               std::unique_ptr<ParamEvent> saved_event);

    // Create simplest event needing just a value and time, like
    // setValueAtTime.
    ParamEvent(Type, float value, double time);

    // Create a linear or exponential ramp that requires an initial
    // value and time in case there is no actual event that preceeds
    // this event.
    ParamEvent(Type,
               float value,
               double time,
               float initial_value,
               double call_time);

    // Create an event needing a time constant (setTargetAtTime)
    ParamEvent(Type, float value, double time, double time_constant);

    // Create a setValueCurve event
    ParamEvent(Type,
               double time,
               double duration,
               const Vector<float>& curve,
               double curve_points_per_second,
               float curve_end_value);

    // Create CancelValues event
    ParamEvent(Type, double time, std::unique_ptr<ParamEvent> saved_event);

    Type type_;

    // The value for the event.  The interpretation of this depends on
    // the event type. Not used for SetValueCurve. For CancelValues,
    // it is the end value to use when cancelling a LinearRampToValue
    // or ExponentialRampToValue event.
    float value_;

    // The time for the event. The interpretation of this depends on
    // the event type.
    double time_;

    // Initial value and time to use for linear and exponential ramps that don't
    // have a preceding event.
    float initial_value_;
    double call_time_;

    // Only used for SetTarget events
    double time_constant_;

    // The following items are only used for SetValueCurve events.
    //
    // The duration of the curve.
    double duration_;
    // The array of curve points.
    Vector<float> curve_;
    // The number of curve points per second. it is used to compute
    // the curve index step when running the automation.
    double curve_points_per_second_;
    // The default value to use at the end of the curve.  Normally
    // it's the last entry in m_curve, but cancelling a SetValueCurve
    // will set this to a new value.
    float curve_end_value_;

    // For CancelValues. If CancelValues is in the middle of an event, this
    // holds the event that is being cancelled, so that processing can
    // continue as if the event still existed up until we reach the actual
    // scheduled cancel time.
    std::unique_ptr<ParamEvent> saved_event_;

    // True if the start time needs to be checked against current time
    // to implement clamping.
    bool needs_time_clamp_check_;

    // True if a default value has been assigned to the CancelValues event.
    bool has_default_cancelled_value_;
  };

  // State of the timeline for the current event.
  struct AutomationState {
    // Parameters for the current automation request.  Number of
    // values to be computed for the automation request
    const unsigned number_of_values;
    // Start and end frames for this automation request
    const size_t start_frame;
    const size_t end_frame;

    // Sample rate and control rate for this request
    const double sample_rate;
    const double control_rate;

    // Parameters needed for processing the current event.
    const size_t fill_to_frame;
    const size_t fill_to_end_frame;

    // Value and time for the current event
    const float value1;
    const double time1;

    // Value and time for the next event, if any.
    const float value2;
    const double time2;

    // The current event, and it's index in the event vector.
    const ParamEvent* event;
    const int event_index;
  };

  void InsertEvent(std::unique_ptr<ParamEvent>, ExceptionState&);
  float ValuesForFrameRangeImpl(size_t start_frame,
                                size_t end_frame,
                                float default_value,
                                float* values,
                                unsigned number_of_values,
                                double sample_rate,
                                double control_rate);

  // Produce a nice string describing the event in human-readable form.
  String EventToString(const ParamEvent&);

  // Automation functions that compute the vlaue of the specified
  // automation at the specified time.
  float LinearRampAtTime(double t,
                         float value1,
                         double time1,
                         float value2,
                         double time2);
  float ExponentialRampAtTime(double t,
                              float value1,
                              double time1,
                              float value2,
                              double time2);
  float TargetValueAtTime(double t,
                          float value1,
                          double time1,
                          float value2,
                          float time_constant);
  float ValueCurveAtTime(double t,
                         double time1,
                         double duration,
                         const float* curve_data,
                         unsigned curve_length);

  // Handles the special case where the first event in the timeline
  // starts after |startFrame|.  These initial values are filled using
  // |defaultValue|.  The updated |currentFrame| and |writeIndex| is
  // returned.
  std::tuple<size_t, unsigned> HandleFirstEvent(float* values,
                                                float default_value,
                                                unsigned number_of_values,
                                                size_t start_frame,
                                                size_t end_frame,
                                                double sample_rate,
                                                size_t current_frame,
                                                unsigned write_index);

  // Return true if |currentEvent| starts after |currentFrame|, but
  // also takes into account the |nextEvent| if any.
  bool IsEventCurrent(const ParamEvent* current_event,
                      const ParamEvent* next_event,
                      size_t current_frame,
                      double sample_rate);

  // Clamp event times to current time, if needed.
  void ClampToCurrentTime(int number_of_events,
                          size_t start_frame,
                          double sample_rate);

  // Handle the case where the last event in the timeline is in the
  // past.  Returns false if any event is not in the past. Otherwise,
  // return true and also fill in |values| with |defaultValue|.
  bool HandleAllEventsInThePast(double current_time,
                                double sample_rate,
                                float default_value,
                                unsigned number_of_values,
                                float* values);

  // Handle processing of CancelValue event. If cancellation happens, value2,
  // time2, and nextEventType will be updated with the new value due to
  // cancellation.  The
  std::tuple<float, double, ParamEvent::Type> HandleCancelValues(
      const ParamEvent* current_event,
      ParamEvent* next_event,
      float value2,
      double time2);

  // Process a SetTarget event and the next event is a
  // LinearRampToValue or ExponentialRampToValue event.  This requires
  // special handling because the ramp should start at whatever value
  // the SetTarget event has reached at this time, instead of using
  // the value of the SetTarget event.
  void ProcessSetTargetFollowedByRamp(int event_index,
                                      ParamEvent*& current_event,
                                      ParamEvent::Type next_event_type,
                                      size_t current_frame,
                                      double sample_rate,
                                      double control_rate,
                                      float& value);

  // Handle processing of linearRampEvent, writing the appropriate
  // values to |values|.  Returns the updated |currentFrame|, last
  // computed |value|, and the updated |writeIndex|.
  std::tuple<size_t, float, unsigned> ProcessLinearRamp(
      const AutomationState& current_state,
      float* values,
      size_t current_frame,
      float value,
      unsigned write_index);

  // Handle processing of exponentialRampEvent, writing the appropriate
  // values to |values|.  Returns the updated |currentFrame|, last
  // computed |value|, and the updated |writeIndex|.
  std::tuple<size_t, float, unsigned> ProcessExponentialRamp(
      const AutomationState& current_state,
      float* values,
      size_t current_frame,
      float value,
      unsigned write_index);

  // Handle processing of SetTargetEvent, writing the appropriate
  // values to |values|.  Returns the updated |currentFrame|, last
  // computed |value|, and the updated |writeIndex|.
  std::tuple<size_t, float, unsigned> ProcessSetTarget(
      const AutomationState& current_state,
      float* values,
      size_t current_frame,
      float value,
      unsigned write_index);

  // Handle processing of SetValueCurveEvent, writing the appropriate
  // values to |values|.  Returns the updated |currentFrame|, last
  // computed |value|, and the updated |writeIndex|.
  std::tuple<size_t, float, unsigned> ProcessSetValueCurve(
      const AutomationState& current_state,
      float* values,
      size_t current_frame,
      float value,
      unsigned write_index);

  // Handle processing of CancelValuesEvent, writing the appropriate
  // values to |values|.  Returns the updated |currentFrame|, last
  // computed |value|, and the updated |writeIndex|.
  std::tuple<size_t, float, unsigned> ProcessCancelValues(
      const AutomationState& current_state,
      float* values,
      size_t current_frame,
      float value,
      unsigned write_index);

  // Fill the output vector |values| with the value |defaultValue|,
  // starting at |writeIndex| and continuing up to |endFrame|
  // (exclusive).  |writeIndex| is updated with the new index.
  unsigned FillWithDefault(float* values,
                           float default_value,
                           size_t end_frame,
                           unsigned write_index);

  // Vector of all automation events for the AudioParam.  Access must
  // be locked via m_eventsLock.
  Vector<std::unique_ptr<ParamEvent>> events_;

  mutable Mutex events_lock_;

  // Smoothing (de-zippering)
  float smoothed_value_;
};

}  // namespace blink

#endif  // AudioParamTimeline_h
