/*
 * Copyright (C) 2012 Google Inc. All rights reserved.
 * Copyright (C) 2016-2020 Apple 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.
 */

#pragma once

#if ENABLE(MEDIA_STREAM)

#include "RealtimeMediaSourceSupportedConstraints.h"
#include <cstdlib>
#include <wtf/EnumTraits.h>
#include <wtf/Function.h>
#include <wtf/Vector.h>

namespace WebCore {
    
class MediaConstraint {
public:
    enum class DataType : uint8_t { None, Integer, Double, Boolean, String };

    bool isInt() const { return m_dataType == DataType::Integer; }
    bool isDouble() const { return m_dataType == DataType::Double; }
    bool isBoolean() const { return m_dataType == DataType::Boolean; }
    bool isString() const { return m_dataType == DataType::String; }

    DataType dataType() const { return m_dataType; }
    MediaConstraintType constraintType() const { return m_constraintType; }
    const String& name() const { return m_name; }

    template <class Encoder> void encode(Encoder& encoder) const
    {
        encoder << m_constraintType;
        encoder << m_name;
        encoder << m_dataType;
    }

    template <class Decoder> static WARN_UNUSED_RETURN bool decode(Decoder& decoder, MediaConstraint& constraint)
    {
        if (!decoder.decode(constraint.m_constraintType))
            return false;

        if (!decoder.decode(constraint.m_name))
            return false;

        if (!decoder.decode(constraint.m_dataType))
            return false;

        return true;
    }

    void log() const;

protected:
    MediaConstraint(const String& name, MediaConstraintType constraintType, DataType dataType)
        : m_name(name)
        , m_constraintType(constraintType)
        , m_dataType(dataType)
    {
    }

    MediaConstraint() = default;
    ~MediaConstraint() = default;

private:
    String m_name;
    MediaConstraintType m_constraintType { MediaConstraintType::Unknown };
    DataType m_dataType { DataType::None };
};

template<class ValueType>
class NumericConstraint : public MediaConstraint {
public:
    void setMin(ValueType value) { m_min = value; }
    void setMax(ValueType value) { m_max = value; }
    void setExact(ValueType value) { m_exact = value; }
    void setIdeal(ValueType value) { m_ideal = value; }

    bool getMin(ValueType& min) const
    {
        if (!m_min)
            return false;

        min = m_min.value();
        return true;
    }

    bool getMax(ValueType& max) const
    {
        if (!m_max)
            return false;

        max = m_max.value();
        return true;
    }

    bool getExact(ValueType& exact) const
    {
        if (!m_exact)
            return false;

        exact = m_exact.value();
        return true;
    }

    bool getIdeal(ValueType& ideal) const
    {
        if (!m_ideal)
            return false;

        ideal = m_ideal.value();
        return true;
    }

    bool nearlyEqual(double a, double b) const
    {
        // Don't require strict equality when comparing constraints, or many floating point constraint values,
        // e.g. "aspectRatio: 1.333", will never match.
        const double epsilon = 0.00001;
        return std::abs(a - b) <= epsilon;
    }

    double fitnessDistance(ValueType rangeMin, ValueType rangeMax) const
    {
        // https://w3c.github.io/mediacapture-main/#dfn-applyconstraints
        // 1. If the constraint is not supported by the browser, the fitness distance is 0.
        if (isEmpty())
            return 0;

        // 2. If the constraint is required ('min', 'max', or 'exact'), and the settings
        //    dictionary's value for the constraint does not satisfy the constraint, the
        //    fitness distance is positive infinity.
        bool valid = validForRange(rangeMin, rangeMax);
        if (m_exact) {
            if (valid && m_min && m_exact.value() < m_min.value())
                valid = false;
            if (valid && m_max && m_exact.value() > m_max.value())
                valid = false;
            if (!valid)
                return std::numeric_limits<double>::infinity();
        }

        if (m_min) {
            if (valid && m_max && m_min.value() > m_max.value())
                valid = false;
            if (!valid)
                return std::numeric_limits<double>::infinity();
        }

        if (m_max) {
            if (valid && m_min && m_max.value() < m_min.value())
                valid = false;
            if (!valid)
                return std::numeric_limits<double>::infinity();
        }

        // 3. If no ideal value is specified, the fitness distance is 0.
        if (!m_ideal)
            return 0;

        // 4. For all positive numeric non-required constraints (such as height, width, frameRate,
        //    aspectRatio, sampleRate and sampleSize), the fitness distance is the result of the formula
        //
        //         (actual == ideal) ? 0 : |actual - ideal| / max(|actual|,|ideal|)
        ValueType ideal = m_ideal.value();
        if (ideal >= rangeMin && ideal <= rangeMax)
            return 0;

        ideal = ideal > std::max(rangeMin, rangeMax) ? rangeMax : rangeMin;
        return static_cast<double>(std::abs(ideal - m_ideal.value())) / std::max(std::abs(ideal), std::abs(m_ideal.value()));
    }

    double fitnessDistance(const Vector<ValueType>& discreteCapabilityValues) const
    {
        double minDistance = std::numeric_limits<double>::infinity();

        for (auto& value : discreteCapabilityValues) {
            auto distance = fitnessDistance(value, value);
            if (distance < minDistance)
                minDistance = distance;
        }

        return minDistance;
    }

    bool validForRange(ValueType rangeMin, ValueType rangeMax) const
    {
        if (isEmpty())
            return false;

        if (m_exact) {
            const ValueType exact = m_exact.value();
            if (exact < rangeMin && !nearlyEqual(exact, rangeMin))
                return false;
            if (exact > rangeMax && !nearlyEqual(exact, rangeMax))
                return false;
        }

        if (m_min) {
            const ValueType constraintMin = m_min.value();
            if (constraintMin > rangeMax && !nearlyEqual(constraintMin, rangeMax))
                return false;
        }

        if (m_max) {
            const ValueType constraintMax = m_max.value();
            if (constraintMax < rangeMin && !nearlyEqual(constraintMax, rangeMin))
                return false;
        }

        return true;
    }

    ValueType find(const Function<bool(ValueType)>& function) const
    {
        if (m_min && function(m_min.value()))
            return m_min.value();

        if (m_max && function(m_max.value()))
            return m_max.value();

        if (m_exact && function(m_exact.value()))
            return m_exact.value();

        if (m_ideal && function(m_ideal.value()))
            return m_ideal.value();

        return 0;
    }

    ValueType valueForCapabilityRange(ValueType current, ValueType capabilityMin, ValueType capabilityMax) const
    {
        ValueType value { 0 };
        ValueType min { capabilityMin };
        ValueType max { capabilityMax };

        if (m_exact) {
            ASSERT(validForRange(capabilityMin, capabilityMax));
            return m_exact.value();
        }

        if (m_min) {
            value = m_min.value();
            ASSERT(validForRange(value, capabilityMax));
            if (value > min)
                min = value;
            if (value < min)
                value = min;

            // If there is no ideal, don't change if minimum is smaller than current.
            if (!m_ideal && value < current)
                value = current;
        }

        if (m_max) {
            value = m_max.value();
            ASSERT(validForRange(capabilityMin, value));
            if (value < max)
                max = value;
            if (value > max)
                value = max;
        }

        if (m_ideal)
            value = std::max(min, std::min(max, m_ideal.value()));

        return value;
    }

    std::optional<ValueType> valueForDiscreteCapabilityValues(ValueType current, const Vector<ValueType>& discreteCapabilityValues) const
    {
        std::optional<ValueType> value;
        std::optional<ValueType> min;
        std::optional<ValueType> max;

        if (m_exact) {
            ASSERT(discreteCapabilityValues.contains(m_exact.value()));
            return m_exact.value();
        }

        if (m_min) {
            auto index = discreteCapabilityValues.findMatching([&](ValueType value) { return m_min.value() >= value; });
            if (index != notFound) {
                min = value = discreteCapabilityValues[index];

                // If there is no ideal, don't change if minimum is smaller than current.
                if (!m_ideal && *value < current)
                    value = current;
            }
        }

        if (m_max && m_max.value() >= discreteCapabilityValues[0]) {
            for (auto& discreteValue : discreteCapabilityValues) {
                if (m_max.value() <= discreteValue)
                    max = value = discreteValue;
            }
        }

        if (m_ideal && discreteCapabilityValues.contains(m_ideal.value())) {
            value = m_ideal.value();
            if (max)
                value = std::min(max.value(), *value);
            if (min)
                value = std::max(min.value(), *value);
        }

        return value;
    }

    bool isEmpty() const { return !m_min && !m_max && !m_exact && !m_ideal; }
    bool isMandatory() const { return m_min || m_max || m_exact; }

    template <class Encoder> void encode(Encoder& encoder) const
    {
        MediaConstraint::encode(encoder);

        encoder << m_min;
        encoder << m_max;
        encoder << m_exact;
        encoder << m_ideal;
    }

    template <class Decoder> static WARN_UNUSED_RETURN bool decode(Decoder& decoder, NumericConstraint& constraint)
    {
        if (!MediaConstraint::decode(decoder, constraint))
            return false;

        if (!decoder.decode(constraint.m_min))
            return false;
        if (!decoder.decode(constraint.m_max))
            return false;
        if (!decoder.decode(constraint.m_exact))
            return false;
        if (!decoder.decode(constraint.m_ideal))
            return false;
    
        return true;
    }

protected:
    NumericConstraint(const String& name, MediaConstraintType type, DataType dataType)
        : MediaConstraint(name, type, dataType)
    {
    }

    NumericConstraint() = default;

    void innerMerge(const NumericConstraint& other)
    {
        if (other.isEmpty())
            return;

        ValueType value;
        if (other.getExact(value))
            m_exact = value;

        if (other.getMin(value))
            m_min = value;

        if (other.getMax(value))
            m_max = value;

        // https://w3c.github.io/mediacapture-main/#constrainable-interface
        // When processing advanced constraints:
        //   ... the User Agent must attempt to apply, individually, any 'ideal' constraints or
        //   a constraint given as a bare value for the property. Of these properties, it must
        //   satisfy the largest number that it can, in any order.
        if (other.getIdeal(value)) {
            if (!m_ideal || value > m_ideal.value())
                m_ideal = value;
        }
    }

    std::optional<ValueType> m_min;
    std::optional<ValueType> m_max;
    std::optional<ValueType> m_exact;
    std::optional<ValueType> m_ideal;
};

class IntConstraint final : public NumericConstraint<int> {
public:
    IntConstraint(const String& name, MediaConstraintType type)
        : NumericConstraint<int>(name, type, DataType::Integer)
    {
    }

    IntConstraint() = default;

    void merge(const MediaConstraint& other)
    {
        ASSERT(other.isInt());
        NumericConstraint::innerMerge(downcast<const IntConstraint>(other));
    }

    void logAsInt() const;
};

class DoubleConstraint final : public NumericConstraint<double> {
public:
    DoubleConstraint(const String& name, MediaConstraintType type)
        : NumericConstraint<double>(name, type, DataType::Double)
    {
    }

    DoubleConstraint() = default;

    void merge(const MediaConstraint& other)
    {
        ASSERT(other.isDouble());
        NumericConstraint::innerMerge(downcast<DoubleConstraint>(other));
    }

    void logAsDouble() const;
};

class BooleanConstraint final : public MediaConstraint {
public:
    BooleanConstraint(const String& name, MediaConstraintType type)
        : MediaConstraint(name, type, DataType::Boolean)
    {
    }

    BooleanConstraint() = default;

    void setExact(bool value) { m_exact = value; }
    void setIdeal(bool value) { m_ideal = value; }

    bool getExact(bool& exact) const
    {
        if (!m_exact)
            return false;

        exact = m_exact.value();
        return true;
    }

    bool getIdeal(bool& ideal) const
    {
        if (!m_ideal)
            return false;

        ideal = m_ideal.value();
        return true;
    }

    double fitnessDistance(bool value) const
    {
        // https://w3c.github.io/mediacapture-main/#dfn-applyconstraints
        // 1. If the constraint is not supported by the browser, the fitness distance is 0.
        if (isEmpty())
            return 0;

        // 2. If the constraint is required ('min', 'max', or 'exact'), and the settings
        //    dictionary's value for the constraint does not satisfy the constraint, the
        //    fitness distance is positive infinity.
        if (m_exact && value != m_exact.value())
            return std::numeric_limits<double>::infinity();

        // 3. If no ideal value is specified, the fitness distance is 0.
        if (!m_ideal || m_ideal.value() == value)
            return 0;

        // 5. For all string and enum non-required constraints (deviceId, groupId, facingMode,
        // echoCancellation), the fitness distance is the result of the formula
        //        (actual == ideal) ? 0 : 1
        return 1;
    }

    void merge(const MediaConstraint& other)
    {
        ASSERT(other.isBoolean());
        const BooleanConstraint& typedOther = downcast<BooleanConstraint>(other);

        if (typedOther.isEmpty())
            return;

        bool value;
        if (typedOther.getExact(value))
            m_exact = value;

        if (typedOther.getIdeal(value)) {
            if (!m_ideal || (value && !m_ideal.value()))
                m_ideal = value;
        }
    }

    bool isEmpty() const { return !m_exact && !m_ideal; };
    bool isMandatory() const { return bool(m_exact); }

    template <class Encoder> void encode(Encoder& encoder) const
    {
        MediaConstraint::encode(encoder);
        encoder << m_exact;
        encoder << m_ideal;
    }

    template <class Decoder> static WARN_UNUSED_RETURN bool decode(Decoder& decoder, BooleanConstraint& constraint)
    {
        if (!MediaConstraint::decode(decoder, constraint))
            return false;

        if (!decoder.decode(constraint.m_exact))
            return false;
        if (!decoder.decode(constraint.m_ideal))
            return false;

        return true;
    }

    void logAsBoolean() const;

private:
    std::optional<bool> m_exact;
    std::optional<bool> m_ideal;
};

class StringConstraint : public MediaConstraint {
public:
    StringConstraint(const String& name, MediaConstraintType type)
        : MediaConstraint(name, type, DataType::String)
    {
    }

    StringConstraint() = default;

    void setExact(const String& value)
    {
        m_exact.clear();
        m_exact.append(value);
    }

    void appendExact(const String& value)
    {
        m_exact.append(value);
    }

    void setIdeal(const String& value)
    {
        m_ideal.clear();
        m_ideal.append(value);
    }

    void appendIdeal(const String& value)
    {
        m_ideal.append(value);
    }

    bool getExact(Vector<String>& exact) const
    {
        if (!m_exact.isEmpty())
            return false;

        exact = m_exact;
        return true;
    }

    bool getIdeal(Vector<String>& ideal) const
    {
        if (!m_ideal.isEmpty())
            return false;

        ideal = m_ideal;
        return true;
    }

    double fitnessDistance(const String&) const;
    double fitnessDistance(const Vector<String>&) const;

    const String& find(const Function<bool(const String&)>&) const;

    bool isEmpty() const { return m_exact.isEmpty() && m_ideal.isEmpty(); }
    bool isMandatory() const { return !m_exact.isEmpty(); }
    WEBCORE_EXPORT void merge(const MediaConstraint&);

    template <class Encoder> void encode(Encoder& encoder) const
    {
        MediaConstraint::encode(encoder);

        encoder << m_exact;
        encoder << m_ideal;
    }

    template <class Decoder> static WARN_UNUSED_RETURN bool decode(Decoder& decoder, StringConstraint& constraint)
    {
        if (!MediaConstraint::decode(decoder, constraint))
            return false;

        if (!decoder.decode(constraint.m_exact))
            return false;
        if (!decoder.decode(constraint.m_ideal))
            return false;

        return true;
    }

    void removeEmptyStringConstraint()
    {
        m_exact.removeAllMatching([](auto& constraint) {
            return constraint.isEmpty();
        });
        m_ideal.removeAllMatching([](auto& constraint) {
            return constraint.isEmpty();
        });
    }

private:
    Vector<String> m_exact;
    Vector<String> m_ideal;
};

class UnknownConstraint final : public MediaConstraint {
public:
    UnknownConstraint(const String& name, MediaConstraintType type)
        : MediaConstraint(name, type, DataType::None)
    {
    }

private:
    bool isEmpty() const { return true; }
    bool isMandatory() const { return false; }
    void merge(const MediaConstraint&) { }
};

class MediaTrackConstraintSetMap {
public:
    WEBCORE_EXPORT void forEach(Function<void(const MediaConstraint&)>&&) const;
    void filter(const Function<bool(const MediaConstraint&)>&) const;
    bool isEmpty() const;
    WEBCORE_EXPORT size_t size() const;

    WEBCORE_EXPORT void set(MediaConstraintType, std::optional<IntConstraint>&&);
    WEBCORE_EXPORT void set(MediaConstraintType, std::optional<DoubleConstraint>&&);
    WEBCORE_EXPORT void set(MediaConstraintType, std::optional<BooleanConstraint>&&);
    WEBCORE_EXPORT void set(MediaConstraintType, std::optional<StringConstraint>&&);

    std::optional<IntConstraint> width() const { return m_width; }
    std::optional<IntConstraint> height() const { return m_height; }
    std::optional<IntConstraint> sampleRate() const { return m_sampleRate; }
    std::optional<IntConstraint> sampleSize() const { return m_sampleSize; }

    std::optional<DoubleConstraint> aspectRatio() const { return m_aspectRatio; }
    std::optional<DoubleConstraint> frameRate() const { return m_frameRate; }
    std::optional<DoubleConstraint> volume() const { return m_volume; }

    std::optional<BooleanConstraint> echoCancellation() const { return m_echoCancellation; }
    std::optional<BooleanConstraint> displaySurface() const { return m_displaySurface; }
    std::optional<BooleanConstraint> logicalSurface() const { return m_logicalSurface; }

    std::optional<StringConstraint> facingMode() const { return m_facingMode; }
    std::optional<StringConstraint> deviceId() const { return m_deviceId; }
    std::optional<StringConstraint> groupId() const { return m_groupId; }

    template <class Encoder> void encode(Encoder& encoder) const
    {
        encoder << m_width;
        encoder << m_height;
        encoder << m_sampleRate;
        encoder << m_sampleSize;

        encoder << m_aspectRatio;
        encoder << m_frameRate;
        encoder << m_volume;

        encoder << m_echoCancellation;
        encoder << m_displaySurface;
        encoder << m_logicalSurface;

        encoder << m_facingMode;
        encoder << m_deviceId;
        encoder << m_groupId;
    }

    template <class Decoder> static std::optional<MediaTrackConstraintSetMap> decode(Decoder& decoder)
    {
        MediaTrackConstraintSetMap map;
        if (!decoder.decode(map.m_width))
            return std::nullopt;
        if (!decoder.decode(map.m_height))
            return std::nullopt;
        if (!decoder.decode(map.m_sampleRate))
            return std::nullopt;
        if (!decoder.decode(map.m_sampleSize))
            return std::nullopt;

        if (!decoder.decode(map.m_aspectRatio))
            return std::nullopt;
        if (!decoder.decode(map.m_frameRate))
            return std::nullopt;
        if (!decoder.decode(map.m_volume))
            return std::nullopt;

        if (!decoder.decode(map.m_echoCancellation))
            return std::nullopt;
        if (!decoder.decode(map.m_displaySurface))
            return std::nullopt;
        if (!decoder.decode(map.m_logicalSurface))
            return std::nullopt;

        if (!decoder.decode(map.m_facingMode))
            return std::nullopt;
        if (!decoder.decode(map.m_deviceId))
            return std::nullopt;
        if (!decoder.decode(map.m_groupId))
            return std::nullopt;

        return map;
    }

private:
    std::optional<IntConstraint> m_width;
    std::optional<IntConstraint> m_height;
    std::optional<IntConstraint> m_sampleRate;
    std::optional<IntConstraint> m_sampleSize;

    std::optional<DoubleConstraint> m_aspectRatio;
    std::optional<DoubleConstraint> m_frameRate;
    std::optional<DoubleConstraint> m_volume;

    std::optional<BooleanConstraint> m_echoCancellation;
    std::optional<BooleanConstraint> m_displaySurface;
    std::optional<BooleanConstraint> m_logicalSurface;

    std::optional<StringConstraint> m_facingMode;
    std::optional<StringConstraint> m_deviceId;
    std::optional<StringConstraint> m_groupId;
};

class FlattenedConstraint {
public:

    void set(const MediaConstraint&);
    void merge(const MediaConstraint&);
    void append(const MediaConstraint&);
    const MediaConstraint* find(MediaConstraintType) const;
    bool isEmpty() const { return m_variants.isEmpty(); }

    class iterator {
    public:
        iterator(const FlattenedConstraint* constraint, size_t index)
            : m_constraint(constraint)
            , m_index(index)
#ifndef NDEBUG
            , m_generation(constraint->m_generation)
#endif
        {
        }

        MediaConstraint& operator*() const
        {
            return m_constraint->m_variants.at(m_index).constraint();
        }

        iterator& operator++()
        {
#ifndef NDEBUG
            ASSERT(m_generation == m_constraint->m_generation);
#endif
            m_index++;
            return *this;
        }

        bool operator==(const iterator& other) const { return m_index == other.m_index; }
        bool operator!=(const iterator& other) const { return !(*this == other); }

    private:
        const FlattenedConstraint* m_constraint { nullptr };
        size_t m_index { 0 };
#ifndef NDEBUG
        int m_generation { 0 };
#endif
    };

    const iterator begin() const { return iterator(this, 0); }
    const iterator end() const { return iterator(this, m_variants.size()); }

private:
    class ConstraintHolder {
    public:
        static ConstraintHolder create(const MediaConstraint& value) { return ConstraintHolder(value); }

        ~ConstraintHolder()
        {
            if (m_value.asRaw) {
                switch (dataType()) {
                case MediaConstraint::DataType::Integer:
                    delete m_value.asInteger;
                    break;
                case MediaConstraint::DataType::Double:
                    delete m_value.asDouble;
                    break;
                case MediaConstraint::DataType::Boolean:
                    delete m_value.asBoolean;
                    break;
                case MediaConstraint::DataType::String:
                    delete m_value.asString;
                    break;
                case MediaConstraint::DataType::None:
                    ASSERT_NOT_REACHED();
                    break;
                }
            }
#ifndef NDEBUG
            m_value.asRaw = reinterpret_cast<MediaConstraint*>(0xbbadbeef);
#endif
        }

        ConstraintHolder(ConstraintHolder&& other)
        {
            switch (other.dataType()) {
            case MediaConstraint::DataType::Integer:
                m_value.asInteger = std::exchange(other.m_value.asInteger, nullptr);
                break;
            case MediaConstraint::DataType::Double:
                m_value.asDouble = std::exchange(other.m_value.asDouble, nullptr);
                break;
            case MediaConstraint::DataType::Boolean:
                m_value.asBoolean = std::exchange(other.m_value.asBoolean, nullptr);
                break;
            case MediaConstraint::DataType::String:
                m_value.asString = std::exchange(other.m_value.asString, nullptr);
                break;
            case MediaConstraint::DataType::None:
                ASSERT_NOT_REACHED();
                break;
            }
        }

        MediaConstraint& constraint() const { return *m_value.asRaw; }
        MediaConstraint::DataType dataType() const { return constraint().dataType(); }
        MediaConstraintType constraintType() const { return constraint().constraintType(); }

    private:
        explicit ConstraintHolder(const MediaConstraint& value)
        {
            switch (value.dataType()) {
            case MediaConstraint::DataType::Integer:
                m_value.asInteger = new IntConstraint(downcast<const IntConstraint>(value));
                break;
            case MediaConstraint::DataType::Double:
                m_value.asDouble = new DoubleConstraint(downcast<DoubleConstraint>(value));
                break;
            case MediaConstraint::DataType::Boolean:
                m_value.asBoolean = new BooleanConstraint(downcast<BooleanConstraint>(value));
                break;
            case MediaConstraint::DataType::String:
                m_value.asString = new StringConstraint(downcast<StringConstraint>(value));
                break;
            case MediaConstraint::DataType::None:
                ASSERT_NOT_REACHED();
                break;
            }
        }
        
        union {
            MediaConstraint* asRaw;
            IntConstraint* asInteger;
            DoubleConstraint* asDouble;
            BooleanConstraint* asBoolean;
            StringConstraint* asString;
        } m_value;
    };

    Vector<ConstraintHolder> m_variants;
#ifndef NDEBUG
    int m_generation { 0 };
#endif
};

struct MediaConstraints {
    void setDefaultVideoConstraints();
    bool isConstraintSet(const Function<bool(const MediaTrackConstraintSetMap&)>&);

    MediaTrackConstraintSetMap mandatoryConstraints;
    Vector<MediaTrackConstraintSetMap> advancedConstraints;
    bool isValid { false };
};
    
} // namespace WebCore

#define SPECIALIZE_TYPE_TRAITS_MEDIACONSTRAINT(ConstraintType, predicate) \
SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::ConstraintType) \
static bool isType(const WebCore::MediaConstraint& constraint) { return constraint.predicate; } \
SPECIALIZE_TYPE_TRAITS_END()

SPECIALIZE_TYPE_TRAITS_MEDIACONSTRAINT(IntConstraint, isInt())
SPECIALIZE_TYPE_TRAITS_MEDIACONSTRAINT(DoubleConstraint, isDouble())
SPECIALIZE_TYPE_TRAITS_MEDIACONSTRAINT(StringConstraint, isString())
SPECIALIZE_TYPE_TRAITS_MEDIACONSTRAINT(BooleanConstraint, isBoolean())

namespace WTF {

template<> struct EnumTraits<WebCore::MediaConstraint::DataType> {
    using values = EnumValues<
        WebCore::MediaConstraint::DataType,
        WebCore::MediaConstraint::DataType::None,
        WebCore::MediaConstraint::DataType::Integer,
        WebCore::MediaConstraint::DataType::Double,
        WebCore::MediaConstraint::DataType::Boolean,
        WebCore::MediaConstraint::DataType::String
    >;
};

} // namespace WTF

#endif // ENABLE(MEDIA_STREAM)
