/*
 * Copyright 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ANDROID_VOLUME_SHAPER_H
#define ANDROID_VOLUME_SHAPER_H

#include <cmath>
#include <list>
#include <math.h>
#include <sstream>

#include <binder/Parcel.h>
#include <media/Interpolator.h>
#include <utils/Mutex.h>
#include <utils/RefBase.h>

#pragma push_macro("LOG_TAG")
#undef LOG_TAG
#define LOG_TAG "VolumeShaper"

// turn on VolumeShaper logging
#if 0
#define VS_LOG ALOGD
#else
#define VS_LOG(...)
#endif

namespace android {

// The native VolumeShaper class mirrors the java VolumeShaper class;
// in addition, the native class contains implementation for actual operation.
//
// VolumeShaper methods are not safe for multiple thread access.
// Use VolumeHandler for thread-safe encapsulation of multiple VolumeShapers.
//
// Classes below written are to avoid naked pointers so there are no
// explicit destructors required.

class VolumeShaper {
public:
    // S and T are like template typenames (matching the Interpolator<S, T>)
    using S = float; // time type
    using T = float; // volume type

// Curve and dimension information
// TODO: member static const or constexpr float initialization not permitted in C++11
#define MIN_CURVE_TIME    0.f  // type S: start of VolumeShaper curve (normalized)
#define MAX_CURVE_TIME    1.f  // type S: end of VolumeShaper curve (normalized)
#define MIN_LINEAR_VOLUME 0.f  // type T: silence / mute audio
#define MAX_LINEAR_VOLUME 1.f  // type T: max volume, unity gain
#define MAX_LOG_VOLUME    0.f  // type T: max volume, unity gain in dBFS

    /* kSystemVolumeShapersMax is the maximum number of system VolumeShapers.
     * Each system VolumeShapers has a predefined Id, which ranges from 0
     * to kSystemVolumeShapersMax - 1 and is unique for its usage.
     *
     * "1" is reserved for system ducking.
     */
    static const int kSystemVolumeShapersMax = 16;

    /* kUserVolumeShapersMax is the maximum number of application
     * VolumeShapers for a player/track.  Application VolumeShapers are
     * assigned on creation by the client, and have Ids ranging
     * from kSystemVolumeShapersMax to INT32_MAX.
     *
     * The number of user/application volume shapers is independent to the
     * system volume shapers. If an application tries to create more than
     * kUserVolumeShapersMax to a player, then the apply() will fail.
     * This prevents exhausting server side resources by a potentially malicious
     * application.
     */
    static const int kUserVolumeShapersMax = 16;

    /* VolumeShaper::Status is equivalent to status_t if negative
     * but if non-negative represents the id operated on.
     * It must be expressible as an int32_t for binder purposes.
     */
    using Status = status_t;

    // Local definition for clamp as std::clamp is included in C++17 only.
    // TODO: use the std::clamp version when Android build uses C++17.
    template<typename R>
    static constexpr const R &clamp(const R &v, const R &lo, const R &hi) {
        return (v < lo) ? lo : (hi < v) ? hi : v;
    }

    /* VolumeShaper.Configuration derives from the Interpolator class and adds
     * parameters relating to the volume shape.
     *
     * This parallels the Java implementation and the enums must match.
     * See "frameworks/base/media/java/android/media/VolumeShaper.java" for
     * details on the Java implementation.
     */
    class Configuration : public Interpolator<S, T>, public RefBase {
    public:
        // Must match with VolumeShaper.java in frameworks/base.
        enum Type : int32_t {
            TYPE_ID,
            TYPE_SCALE,
        };

        // Must match with VolumeShaper.java in frameworks/base.
        enum OptionFlag : int32_t {
            OPTION_FLAG_NONE           = 0,
            OPTION_FLAG_VOLUME_IN_DBFS = (1 << 0),
            OPTION_FLAG_CLOCK_TIME     = (1 << 1),

            OPTION_FLAG_ALL            = (OPTION_FLAG_VOLUME_IN_DBFS | OPTION_FLAG_CLOCK_TIME),
        };

        // Bring from base class; must match with VolumeShaper.java in frameworks/base.
        using InterpolatorType = Interpolator<S, T>::InterpolatorType;

        Configuration()
            : Interpolator<S, T>()
            , RefBase()
            , mType(TYPE_SCALE)
            , mOptionFlags(OPTION_FLAG_NONE)
            , mDurationMs(1000.)
            , mId(-1) {
        }

        explicit Configuration(const Configuration &configuration)
            : Interpolator<S, T>(*static_cast<const Interpolator<S, T> *>(&configuration))
            , RefBase()
            , mType(configuration.mType)
            , mOptionFlags(configuration.mOptionFlags)
            , mDurationMs(configuration.mDurationMs)
            , mId(configuration.mId) {
        }

        Type getType() const {
            return mType;
        }

        status_t setType(Type type) {
            switch (type) {
            case TYPE_ID:
            case TYPE_SCALE:
                mType = type;
                return NO_ERROR;
            default:
                ALOGE("invalid Type: %d", type);
                return BAD_VALUE;
            }
        }

        OptionFlag getOptionFlags() const {
            return mOptionFlags;
        }

        status_t setOptionFlags(OptionFlag optionFlags) {
            if ((optionFlags & ~OPTION_FLAG_ALL) != 0) {
                ALOGE("optionFlags has invalid bits: %#x", optionFlags);
                return BAD_VALUE;
            }
            mOptionFlags = optionFlags;
            return NO_ERROR;
        }

        double getDurationMs() const {
            return mDurationMs;
        }

        status_t setDurationMs(double durationMs) {
            if (durationMs > 0.) {
                mDurationMs = durationMs;
                return NO_ERROR;
            }
            // zero, negative, or nan. These values not possible from Java.
            return BAD_VALUE;
        }

        int32_t getId() const {
            return mId;
        }

        void setId(int32_t id) {
            // We permit a negative id here (representing invalid).
            mId = id;
        }

        /* Adjust the volume to be in linear range from MIN_LINEAR_VOLUME to MAX_LINEAR_VOLUME
         * and compensate for log dbFS volume as needed.
         */
        T adjustVolume(T volume) const {
            if ((getOptionFlags() & OPTION_FLAG_VOLUME_IN_DBFS) != 0) {
                const T out = powf(10.f, volume / 10.f);
                VS_LOG("in: %f  out: %f", volume, out);
                volume = out;
            }
            return clamp(volume, MIN_LINEAR_VOLUME /* lo */, MAX_LINEAR_VOLUME /* hi */);
        }

        /* Check if the existing curve is valid.
         */
        status_t checkCurve() const {
            if (mType == TYPE_ID) return NO_ERROR;
            if (this->size() < 2) {
                ALOGE("curve must have at least 2 points");
                return BAD_VALUE;
            }
            if (first().first != MIN_CURVE_TIME || last().first != MAX_CURVE_TIME) {
                ALOGE("curve must start at MIN_CURVE_TIME and end at MAX_CURVE_TIME");
                return BAD_VALUE;
            }
            if ((getOptionFlags() & OPTION_FLAG_VOLUME_IN_DBFS) != 0) {
                for (const auto &pt : *this) {
                    if (!(pt.second <= MAX_LOG_VOLUME) /* handle nan */) {
                        ALOGE("positive volume dbFS");
                        return BAD_VALUE;
                    }
                }
            } else {
                for (const auto &pt : *this) {
                    if (!(pt.second >= MIN_LINEAR_VOLUME)
                            || !(pt.second <= MAX_LINEAR_VOLUME) /* handle nan */) {
                        ALOGE("volume < MIN_LINEAR_VOLUME or > MAX_LINEAR_VOLUME");
                        return BAD_VALUE;
                    }
                }
            }
            return NO_ERROR;
        }

        /* Clamps the volume curve in the configuration to
         * the valid range for log or linear scale.
         */
        void clampVolume() {
            if ((mOptionFlags & OPTION_FLAG_VOLUME_IN_DBFS) != 0) {
                for (auto it = this->begin(); it != this->end(); ++it) {
                    if (!(it->second <= MAX_LOG_VOLUME) /* handle nan */) {
                        it->second = MAX_LOG_VOLUME;
                    }
                }
            } else {
                for (auto it = this->begin(); it != this->end(); ++it) {
                    if (!(it->second >= MIN_LINEAR_VOLUME) /* handle nan */) {
                        it->second = MIN_LINEAR_VOLUME;
                    } else if (!(it->second <= MAX_LINEAR_VOLUME)) {
                        it->second = MAX_LINEAR_VOLUME;
                    }
                }
            }
        }

        /* scaleToStartVolume() is used to set the start volume of a
         * new VolumeShaper curve, when replacing one VolumeShaper
         * with another using the "join" (volume match) option.
         *
         * It works best for monotonic volume ramps or ducks.
         */
        void scaleToStartVolume(T volume) {
            if (this->size() < 2) {
                return;
            }
            const T startVolume = first().second;
            const T endVolume = last().second;
            if (endVolume == startVolume) {
                // match with linear ramp
                const T offset = volume - startVolume;
                static const T scale =  1.f / (MAX_CURVE_TIME - MIN_CURVE_TIME); // nominally 1.f
                for (auto it = this->begin(); it != this->end(); ++it) {
                    it->second = it->second + offset * (MAX_CURVE_TIME - it->first) * scale;
                }
            } else {
                const T  scale = (volume - endVolume) / (startVolume - endVolume);
                for (auto it = this->begin(); it != this->end(); ++it) {
                    it->second = scale * (it->second - endVolume) + endVolume;
                }
            }
            clampVolume();
        }

        // The parcel layout must match VolumeShaper.java
        status_t writeToParcel(Parcel *parcel) const {
            if (parcel == nullptr) return BAD_VALUE;
            return parcel->writeInt32((int32_t)mType)
                    ?: parcel->writeInt32(mId)
                    ?: mType == TYPE_ID
                        ? NO_ERROR
                        : parcel->writeInt32((int32_t)mOptionFlags)
                            ?: parcel->writeDouble(mDurationMs)
                            ?: Interpolator<S, T>::writeToParcel(parcel);
        }

        status_t readFromParcel(const Parcel &parcel) {
            int32_t type, optionFlags;
            return parcel.readInt32(&type)
                    ?: setType((Type)type)
                    ?: parcel.readInt32(&mId)
                    ?: mType == TYPE_ID
                        ? NO_ERROR
                        : parcel.readInt32(&optionFlags)
                            ?: setOptionFlags((OptionFlag)optionFlags)
                            ?: parcel.readDouble(&mDurationMs)
                            ?: Interpolator<S, T>::readFromParcel(parcel)
                            ?: checkCurve();
        }

        // Returns a string for debug printing.
        std::string toString() const {
            std::stringstream ss;
            ss << "VolumeShaper::Configuration{mType=" << static_cast<int32_t>(mType);
            ss << ", mId=" << mId;
            if (mType != TYPE_ID) {
                ss << ", mOptionFlags=" << static_cast<int32_t>(mOptionFlags);
                ss << ", mDurationMs=" << mDurationMs;
                ss << ", " << Interpolator<S, T>::toString().c_str();
            }
            ss << "}";
            return ss.str();
        }

    private:
        Type mType;              // type of configuration
        int32_t mId;             // A valid id is >= 0.
        OptionFlag mOptionFlags; // option flags for the configuration.
        double mDurationMs;      // duration, must be > 0; default is 1000 ms.
    }; // Configuration

    /* VolumeShaper::Operation expresses an operation to perform on the
     * configuration (either explicitly specified or an id).
     *
     * This parallels the Java implementation and the enums must match.
     * See "frameworks/base/media/java/android/media/VolumeShaper.java" for
     * details on the Java implementation.
     */
    class Operation : public RefBase {
    public:
        // Must match with VolumeShaper.java.
        enum Flag : int32_t {
            FLAG_NONE      = 0,
            FLAG_REVERSE   = (1 << 0), // the absence of this indicates "play"
            FLAG_TERMINATE = (1 << 1),
            FLAG_JOIN      = (1 << 2),
            FLAG_DELAY     = (1 << 3),
            FLAG_CREATE_IF_NECESSARY = (1 << 4),

            FLAG_ALL       = (FLAG_REVERSE | FLAG_TERMINATE | FLAG_JOIN | FLAG_DELAY
                            | FLAG_CREATE_IF_NECESSARY),
        };

        Operation()
            : Operation(FLAG_NONE, -1 /* replaceId */) {
        }

        Operation(Flag flags, int replaceId)
            : Operation(flags, replaceId, std::numeric_limits<S>::quiet_NaN() /* xOffset */) {
        }

        explicit Operation(const Operation &operation)
            : Operation(operation.mFlags, operation.mReplaceId, operation.mXOffset) {
        }

        explicit Operation(const sp<Operation> &operation)
            : Operation(*operation.get()) {
        }

        Operation(Flag flags, int replaceId, S xOffset)
            : mFlags(flags)
            , mReplaceId(replaceId)
            , mXOffset(xOffset) {
        }

        int32_t getReplaceId() const {
            return mReplaceId;
        }

        void setReplaceId(int32_t replaceId) {
            mReplaceId = replaceId;
        }

        S getXOffset() const {
            return mXOffset;
        }

        void setXOffset(S xOffset) {
            mXOffset = clamp(xOffset, MIN_CURVE_TIME /* lo */, MAX_CURVE_TIME /* hi */);
        }

        Flag getFlags() const {
            return mFlags;
        }

        /* xOffset is the position on the volume curve and may go backwards
         * if you are in reverse mode. This must be in the range from
         * [MIN_CURVE_TIME, MAX_CURVE_TIME].
         *
         * normalizedTime always increases as time or framecount increases.
         * normalizedTime is nominally from MIN_CURVE_TIME to MAX_CURVE_TIME when
         * running through the curve, but could be outside this range afterwards.
         * If you are reversing, this means the position on the curve, or xOffset,
         * is computed as MAX_CURVE_TIME - normalizedTime, clamped to
         * [MIN_CURVE_TIME, MAX_CURVE_TIME].
         */
        void setNormalizedTime(S normalizedTime) {
            setXOffset((mFlags & FLAG_REVERSE) != 0
                    ? MAX_CURVE_TIME - normalizedTime : normalizedTime);
        }

        status_t setFlags(Flag flags) {
            if ((flags & ~FLAG_ALL) != 0) {
                ALOGE("flags has invalid bits: %#x", flags);
                return BAD_VALUE;
            }
            mFlags = flags;
            return NO_ERROR;
        }

        status_t writeToParcel(Parcel *parcel) const {
            if (parcel == nullptr) return BAD_VALUE;
            return parcel->writeInt32((int32_t)mFlags)
                    ?: parcel->writeInt32(mReplaceId)
                    ?: parcel->writeFloat(mXOffset);
        }

        status_t readFromParcel(const Parcel &parcel) {
            int32_t flags;
            return parcel.readInt32(&flags)
                    ?: parcel.readInt32(&mReplaceId)
                    ?: parcel.readFloat(&mXOffset)
                    ?: setFlags((Flag)flags);
        }

        std::string toString() const {
            std::stringstream ss;
            ss << "VolumeShaper::Operation{mFlags=" << static_cast<int32_t>(mFlags) ;
            ss << ", mReplaceId=" << mReplaceId;
            ss << ", mXOffset=" << mXOffset;
            ss << "}";
            return ss.str();
        }

    private:
        Flag mFlags;        // operation to do
        int32_t mReplaceId; // if >= 0 the id to remove in a replace operation.
        S mXOffset;         // position in the curve to set if a valid number (not nan)
    }; // Operation

    /* VolumeShaper.State is returned when requesting the last
     * state of the VolumeShaper.
     *
     * This parallels the Java implementation.
     * See "frameworks/base/media/java/android/media/VolumeShaper.java" for
     * details on the Java implementation.
     */
    class State : public RefBase {
    public:
        State(T volume, S xOffset)
            : mVolume(volume)
            , mXOffset(xOffset) {
        }

        State()
            : State(NAN, NAN) { }

        T getVolume() const {
            return mVolume;
        }

        void setVolume(T volume) {
            mVolume = volume;
        }

        S getXOffset() const {
            return mXOffset;
        }

        void setXOffset(S xOffset) {
            mXOffset = xOffset;
        }

        status_t writeToParcel(Parcel *parcel) const {
            if (parcel == nullptr) return BAD_VALUE;
            return parcel->writeFloat(mVolume)
                    ?: parcel->writeFloat(mXOffset);
        }

        status_t readFromParcel(const Parcel &parcel) {
            return parcel.readFloat(&mVolume)
                     ?: parcel.readFloat(&mXOffset);
        }

        std::string toString() const {
            std::stringstream ss;
            ss << "VolumeShaper::State{mVolume=" << mVolume;
            ss << ", mXOffset=" << mXOffset;
            ss << "}";
            return ss.str();
        }

    private:
        T mVolume;   // linear volume in the range MIN_LINEAR_VOLUME to MAX_LINEAR_VOLUME
        S mXOffset;  // position on curve expressed from MIN_CURVE_TIME to MAX_CURVE_TIME
    }; // State

    // Internal helper class to do an affine transform for time and amplitude scaling.
    template <typename R>
    class Translate {
    public:
        Translate()
            : mOffset(0)
            , mScale(1) {
        }

        R getOffset() const {
            return mOffset;
        }

        void setOffset(R offset) {
            mOffset = offset;
        }

        R getScale() const {
            return mScale;
        }

        void setScale(R scale) {
            mScale = scale;
        }

        R operator()(R in) const {
            return mScale * (in - mOffset);
        }

        std::string toString() const {
            std::stringstream ss;
            ss << "VolumeShaper::Translate{mOffset=" << mOffset;
            ss << ", mScale=" << mScale;
            ss << "}";
            return ss.str();
        }

    private:
        R mOffset;
        R mScale;
    }; // Translate

    static int64_t convertTimespecToUs(const struct timespec &tv)
    {
        return tv.tv_sec * 1000000ll + tv.tv_nsec / 1000;
    }

    // current monotonic time in microseconds.
    static int64_t getNowUs()
    {
        struct timespec tv;
        if (clock_gettime(CLOCK_MONOTONIC, &tv) != 0) {
            return 0; // system is really sick, just return 0 for consistency.
        }
        return convertTimespecToUs(tv);
    }

    /* Native implementation of VolumeShaper.  This is NOT mirrored
     * on the Java side, so we don't need to mimic Java side layout
     * and data; furthermore, this isn't refcounted as a "RefBase" object.
     *
     * Since we pass configuration and operation as shared pointers (like
     * Java) there is a potential risk that the caller may modify
     * these after delivery.
     */
    VolumeShaper(
            const sp<VolumeShaper::Configuration> &configuration,
            const sp<VolumeShaper::Operation> &operation)
        : mConfiguration(configuration) // we do not make a copy
        , mOperation(operation)         // ditto
        , mStartFrame(-1)
        , mLastVolume(T(1))
        , mLastXOffset(MIN_CURVE_TIME)
        , mDelayXOffset(MIN_CURVE_TIME) {
        if (configuration.get() != nullptr
                && (getFlags() & VolumeShaper::Operation::FLAG_DELAY) == 0) {
            mLastVolume = configuration->first().second;
        }
    }

    // We allow a null operation here, though VolumeHandler always provides one.
    VolumeShaper::Operation::Flag getFlags() const {
        return mOperation == nullptr
                ? VolumeShaper::Operation::FLAG_NONE : mOperation->getFlags();
    }

    /* Returns the last volume and xoffset reported to the AudioFlinger.
     * If the VolumeShaper has not been started, compute what the volume
     * should be based on the initial offset specified.
     */
    sp<VolumeShaper::State> getState() const {
        if (!isStarted()) {
            const T volume = computeVolumeFromXOffset(mDelayXOffset);
            VS_LOG("delayed VolumeShaper, using cached offset:%f for volume:%f",
                    mDelayXOffset, volume);
            return new VolumeShaper::State(volume, mDelayXOffset);
        } else {
            return new VolumeShaper::State(mLastVolume, mLastXOffset);
        }
    }

    S getDelayXOffset() const {
        return mDelayXOffset;
    }

    void setDelayXOffset(S xOffset) {
        mDelayXOffset = clamp(xOffset, MIN_CURVE_TIME /* lo */, MAX_CURVE_TIME /* hi */);
    }

    bool isStarted() const {
        return mStartFrame >= 0;
    }

    /* getVolume() updates the last volume/xoffset state so it is not
     * const, even though logically it may be viewed as const.
     */
    std::pair<T /* volume */, bool /* active */> getVolume(
            int64_t trackFrameCount, double trackSampleRate) {
        if ((getFlags() & VolumeShaper::Operation::FLAG_DELAY) != 0) {
            // We haven't had PLAY called yet, so just return the value
            // as if PLAY were called just now.
            VS_LOG("delayed VolumeShaper, using cached offset %f", mDelayXOffset);
            const T volume = computeVolumeFromXOffset(mDelayXOffset);
            return std::make_pair(volume, false);
        }
        const bool clockTime = (mConfiguration->getOptionFlags()
                & VolumeShaper::Configuration::OPTION_FLAG_CLOCK_TIME) != 0;
        const int64_t frameCount = clockTime ? getNowUs() : trackFrameCount;
        const double sampleRate = clockTime ? 1000000 : trackSampleRate;

        if (mStartFrame < 0) {
            updatePosition(frameCount, sampleRate, mDelayXOffset);
            mStartFrame = frameCount;
        }
        VS_LOG("frameCount: %lld", (long long)frameCount);
        const S x = mXTranslate((T)frameCount);
        VS_LOG("translation to normalized time: %f", x);

        std::tuple<T /* volume */, S /* position */, bool /* active */> vt =
                computeStateFromNormalizedTime(x);

        mLastVolume = std::get<0>(vt);
        mLastXOffset = std::get<1>(vt);
        const bool active = std::get<2>(vt);
        VS_LOG("rescaled time:%f  volume:%f  xOffset:%f  active:%s",
                x, mLastVolume, mLastXOffset, active ? "true" : "false");
        return std::make_pair(mLastVolume, active);
    }

    std::string toString() const {
        std::stringstream ss;
        ss << "VolumeShaper{mStartFrame=" << mStartFrame;
        ss << ", mXTranslate=" << mXTranslate.toString().c_str();
        ss << ", mConfiguration=" <<
                (mConfiguration.get() == nullptr
                        ? "nullptr" : mConfiguration->toString().c_str());
        ss << ", mOperation=" <<
                (mOperation.get() == nullptr
                        ? "nullptr" :  mOperation->toString().c_str());
        ss << "}";
        return ss.str();
    }

    Translate<S> mXTranslate; // translation from frames (usec for clock time) to normalized time.
    sp<VolumeShaper::Configuration> mConfiguration;
    sp<VolumeShaper::Operation> mOperation;

private:
    int64_t mStartFrame; // starting frame, non-negative when started (in usec for clock time)
    T mLastVolume;       // last computed interpolated volume (y-axis)
    S mLastXOffset;      // last computed interpolated xOffset/time (x-axis)
    S mDelayXOffset;     // xOffset to use for first invocation of VolumeShaper.

    // Called internally to adjust mXTranslate for first time start.
    void updatePosition(int64_t startFrame, double sampleRate, S xOffset) {
        double scale = (mConfiguration->last().first - mConfiguration->first().first)
                        / (mConfiguration->getDurationMs() * 0.001 * sampleRate);
        const double minScale = 1. / static_cast<double>(INT64_MAX);
        scale = std::max(scale, minScale);
        VS_LOG("update position: scale %lf  frameCount:%lld, sampleRate:%lf, xOffset:%f",
                scale, (long long) startFrame, sampleRate, xOffset);

        S normalizedTime = (getFlags() & VolumeShaper::Operation::FLAG_REVERSE) != 0 ?
                MAX_CURVE_TIME - xOffset : xOffset;
        mXTranslate.setOffset(static_cast<float>(static_cast<double>(startFrame)
                                                 - static_cast<double>(normalizedTime) / scale));
        mXTranslate.setScale(static_cast<float>(scale));
        VS_LOG("translate: %s", mXTranslate.toString().c_str());
    }

    T computeVolumeFromXOffset(S xOffset) const {
        const T unscaledVolume = mConfiguration->findY(xOffset);
        const T volume = mConfiguration->adjustVolume(unscaledVolume); // handle log scale
        VS_LOG("computeVolumeFromXOffset %f -> %f -> %f", xOffset, unscaledVolume, volume);
        return volume;
    }

    std::tuple<T /* volume */, S /* position */, bool /* active */>
    computeStateFromNormalizedTime(S x) const {
        bool active = true;
        // handle reversal of position
        if (getFlags() & VolumeShaper::Operation::FLAG_REVERSE) {
            x = MAX_CURVE_TIME - x;
            VS_LOG("reversing to %f", x);
            if (x < MIN_CURVE_TIME) {
                x = MIN_CURVE_TIME;
                active = false; // at the end
            } else if (x > MAX_CURVE_TIME) {
                x = MAX_CURVE_TIME; //early
            }
        } else {
            if (x < MIN_CURVE_TIME) {
                x = MIN_CURVE_TIME; // early
            } else if (x > MAX_CURVE_TIME) {
                x = MAX_CURVE_TIME;
                active = false; // at end
            }
        }
        const S xOffset = x;
        const T volume = computeVolumeFromXOffset(xOffset);
        return std::make_tuple(volume, xOffset, active);
    }
}; // VolumeShaper

/* VolumeHandler combines the volume factors of multiple VolumeShapers associated
 * with a player.  It is thread safe by synchronizing all public methods.
 *
 * This is a native-only implementation.
 *
 * The server side VolumeHandler is used to maintain a list of volume handlers,
 * keep state, and obtain volume.
 *
 * The client side VolumeHandler is used to maintain a list of volume handlers,
 * keep some partial state, and restore if the server dies.
 */
class VolumeHandler : public RefBase {
public:
    using S = float;
    using T = float;

    // A volume handler which just keeps track of active VolumeShapers does not need sampleRate.
    VolumeHandler()
        : VolumeHandler(0 /* sampleRate */) {
    }

    explicit VolumeHandler(uint32_t sampleRate)
        : mSampleRate((double)sampleRate)
        , mLastFrame(0)
        , mVolumeShaperIdCounter(VolumeShaper::kSystemVolumeShapersMax)
        , mLastVolume(1.f, false) {
    }

    VolumeShaper::Status applyVolumeShaper(
            const sp<VolumeShaper::Configuration> &configuration,
            const sp<VolumeShaper::Operation> &operation_in) {
        // make a local copy of operation, as we modify it.
        sp<VolumeShaper::Operation> operation(new VolumeShaper::Operation(operation_in));
        VS_LOG("applyVolumeShaper:configuration: %s", configuration->toString().c_str());
        VS_LOG("applyVolumeShaper:operation: %s", operation->toString().c_str());
        AutoMutex _l(mLock);
        if (configuration == nullptr) {
            ALOGE("null configuration");
            return VolumeShaper::Status(BAD_VALUE);
        }
        if (operation == nullptr) {
            ALOGE("null operation");
            return VolumeShaper::Status(BAD_VALUE);
        }
        const int32_t id = configuration->getId();
        if (id < 0) {
            ALOGE("negative id: %d", id);
            return VolumeShaper::Status(BAD_VALUE);
        }
        VS_LOG("applyVolumeShaper id: %d", id);

        switch (configuration->getType()) {
        case VolumeShaper::Configuration::TYPE_SCALE: {
            const int replaceId = operation->getReplaceId();
            if (replaceId >= 0) {
                VS_LOG("replacing %d", replaceId);
                auto replaceIt = findId_l(replaceId);
                if (replaceIt == mVolumeShapers.end()) {
                    ALOGW("cannot find replace id: %d", replaceId);
                } else {
                    if ((operation->getFlags() & VolumeShaper::Operation::FLAG_JOIN) != 0) {
                        // For join, we scale the start volume of the current configuration
                        // to match the last-used volume of the replacing VolumeShaper.
                        auto state = replaceIt->getState();
                        ALOGD("join: state:%s", state->toString().c_str());
                        if (state->getXOffset() >= 0) { // valid
                            const T volume = state->getVolume();
                            ALOGD("join: scaling start volume to %f", volume);
                            configuration->scaleToStartVolume(volume);
                        }
                    }
                    (void)mVolumeShapers.erase(replaceIt);
                }
                operation->setReplaceId(-1);
            }
            // check if we have another of the same id.
            auto oldIt = findId_l(id);
            if (oldIt != mVolumeShapers.end()) {
                if ((operation->getFlags()
                        & VolumeShaper::Operation::FLAG_CREATE_IF_NECESSARY) != 0) {
                    // TODO: move the case to a separate function.
                    goto HANDLE_TYPE_ID; // no need to create, take over existing id.
                }
                ALOGW("duplicate id, removing old %d", id);
                (void)mVolumeShapers.erase(oldIt);
            }

            /* Check if too many application VolumeShapers (with id >= kSystemVolumeShapersMax).
             * We check on the server side to ensure synchronization and robustness.
             *
             * This shouldn't fail on a replace command unless the replaced id is
             * already invalid (which *should* be checked in the Java layer).
             */
            if (id >= VolumeShaper::kSystemVolumeShapersMax
                    && numberOfUserVolumeShapers_l() >= VolumeShaper::kUserVolumeShapersMax) {
                ALOGW("Too many app VolumeShapers, cannot add to VolumeHandler");
                return VolumeShaper::Status(INVALID_OPERATION);
            }

            // create new VolumeShaper with default behavior.
            mVolumeShapers.emplace_back(configuration, new VolumeShaper::Operation());
            VS_LOG("after adding, number of volumeShapers:%zu", mVolumeShapers.size());
        }
        // fall through to handle the operation
        HANDLE_TYPE_ID:
        case VolumeShaper::Configuration::TYPE_ID: {
            VS_LOG("trying to find id: %d", id);
            auto it = findId_l(id);
            if (it == mVolumeShapers.end()) {
                VS_LOG("couldn't find id: %d", id);
                return VolumeShaper::Status(INVALID_OPERATION);
            }
            if ((operation->getFlags() & VolumeShaper::Operation::FLAG_TERMINATE) != 0) {
                VS_LOG("terminate id: %d", id);
                mVolumeShapers.erase(it);
                break;
            }
            const bool clockTime = (it->mConfiguration->getOptionFlags()
                    & VolumeShaper::Configuration::OPTION_FLAG_CLOCK_TIME) != 0;
            if ((it->getFlags() & VolumeShaper::Operation::FLAG_REVERSE) !=
                    (operation->getFlags() & VolumeShaper::Operation::FLAG_REVERSE)) {
                if (it->isStarted()) {
                    const int64_t frameCount = clockTime ? VolumeShaper::getNowUs() : mLastFrame;
                    const S x = it->mXTranslate((T)frameCount);
                    VS_LOG("reverse normalizedTime: %f", x);
                    // reflect position
                    S target = MAX_CURVE_TIME - x;
                    if (target < MIN_CURVE_TIME) {
                        VS_LOG("clamp to start - begin immediately");
                        target = MIN_CURVE_TIME;
                    }
                    VS_LOG("reverse normalizedTime target: %f", target);
                    it->mXTranslate.setOffset(it->mXTranslate.getOffset()
                            + (x - target) / it->mXTranslate.getScale());
                }
                // if not started, the delay offset doesn't change.
            }
            const S xOffset = operation->getXOffset();
            if (!std::isnan(xOffset)) {
                if (it->isStarted()) {
                    const int64_t frameCount = clockTime ? VolumeShaper::getNowUs() : mLastFrame;
                    const S x = it->mXTranslate((T)frameCount);
                    VS_LOG("normalizedTime translation: %f", x);
                    const S target =
                            (operation->getFlags() & VolumeShaper::Operation::FLAG_REVERSE) != 0 ?
                                    MAX_CURVE_TIME - xOffset : xOffset;
                    VS_LOG("normalizedTime target x offset: %f", target);
                    it->mXTranslate.setOffset(it->mXTranslate.getOffset()
                            + (x - target) / it->mXTranslate.getScale());
                } else {
                    it->setDelayXOffset(xOffset);
                }
            }
            it->mOperation = operation; // replace the operation
        } break;
        }
        return VolumeShaper::Status(id);
    }

    sp<VolumeShaper::State> getVolumeShaperState(int id) {
        AutoMutex _l(mLock);
        auto it = findId_l(id);
        if (it == mVolumeShapers.end()) {
            VS_LOG("cannot find state for id: %d", id);
            return nullptr;
        }
        return it->getState();
    }

    /* getVolume() is not const, as it updates internal state.
     * Once called, any VolumeShapers not already started begin running.
     */
    std::pair<T /* volume */, bool /* active */> getVolume(int64_t trackFrameCount) {
        AutoMutex _l(mLock);
        mLastFrame = trackFrameCount;
        T volume(1);
        size_t activeCount = 0;
        for (auto it = mVolumeShapers.begin(); it != mVolumeShapers.end();) {
            const std::pair<T, bool> shaperVolume =
                    it->getVolume(trackFrameCount, mSampleRate);
            volume *= shaperVolume.first;
            activeCount += shaperVolume.second;
            ++it;
        }
        mLastVolume = std::make_pair(volume, activeCount != 0);
        VS_LOG("getVolume: <%f, %s>", mLastVolume.first, mLastVolume.second ? "true" : "false");
        return mLastVolume;
    }

    /* Used by a client side VolumeHandler to ensure all the VolumeShapers
     * indicate that they have been started.  Upon a change in audioserver
     * output sink, this information is used for restoration of the server side
     * VolumeHandler.
     */
    void setStarted() {
        (void)getVolume(mLastFrame);  // getVolume() will start the individual VolumeShapers.
    }

    std::pair<T /* volume */, bool /* active */> getLastVolume() const {
        AutoMutex _l(mLock);
        return mLastVolume;
    }

    std::string toString() const {
        AutoMutex _l(mLock);
        std::stringstream ss;
        ss << "VolumeHandler{mSampleRate=" << mSampleRate;
        ss << ", mLastFrame=" << mLastFrame;
        ss << ", mVolumeShapers={";
        bool first = true;
        for (const auto &shaper : mVolumeShapers) {
            if (first) {
                first = false;
            } else {
                ss << ", ";
            }
            ss << shaper.toString().c_str();
        }
        ss << "}}";
        return ss.str();
    }

    void forall(const std::function<VolumeShaper::Status (const VolumeShaper &)> &lambda) {
        AutoMutex _l(mLock);
        VS_LOG("forall: mVolumeShapers.size() %zu", mVolumeShapers.size());
        for (const auto &shaper : mVolumeShapers) {
            VolumeShaper::Status status = lambda(shaper);
            VS_LOG("forall applying lambda on shaper (%p): %d", &shaper, (int)status);
        }
    }

    void reset() {
        AutoMutex _l(mLock);
        mVolumeShapers.clear();
        mLastFrame = 0;
        // keep mVolumeShaperIdCounter as is.
    }

    /* Sets the configuration id if necessary - This is based on the counter
     * internal to the VolumeHandler.
     */
    void setIdIfNecessary(const sp<VolumeShaper::Configuration> &configuration) {
        if (configuration->getType() == VolumeShaper::Configuration::TYPE_SCALE) {
            const int id = configuration->getId();
            if (id == -1) {
                // Reassign to a unique id, skipping system ids.
                AutoMutex _l(mLock);
                while (true) {
                    if (mVolumeShaperIdCounter == INT32_MAX) {
                        mVolumeShaperIdCounter = VolumeShaper::kSystemVolumeShapersMax;
                    } else {
                        ++mVolumeShaperIdCounter;
                    }
                    if (findId_l(mVolumeShaperIdCounter) != mVolumeShapers.end()) {
                        continue; // collision with an existing id.
                    }
                    configuration->setId(mVolumeShaperIdCounter);
                    ALOGD("setting id to %d", mVolumeShaperIdCounter);
                    break;
                }
            }
        }
    }

private:
    std::list<VolumeShaper>::iterator findId_l(int32_t id) {
        std::list<VolumeShaper>::iterator it = mVolumeShapers.begin();
        for (; it != mVolumeShapers.end(); ++it) {
            if (it->mConfiguration->getId() == id) {
                break;
            }
        }
        return it;
    }

    size_t numberOfUserVolumeShapers_l() const {
        size_t count = 0;
        for (const auto &shaper : mVolumeShapers) {
            count += (shaper.mConfiguration->getId() >= VolumeShaper::kSystemVolumeShapersMax);
        }
        return count;
    }

    mutable Mutex mLock;
    double mSampleRate; // in samples (frames) per second
    int64_t mLastFrame; // logging purpose only, 0 on start
    int32_t mVolumeShaperIdCounter; // a counter to return a unique volume shaper id.
    std::pair<T /* volume */, bool /* active */> mLastVolume;
    std::list<VolumeShaper> mVolumeShapers; // list provides stable iterators on erase
}; // VolumeHandler

} // namespace android

#pragma pop_macro("LOG_TAG")

#endif // ANDROID_VOLUME_SHAPER_H
