/*
 * Copyright (C) 2022 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.
 */

package android.telephony.ims;

import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.ims.feature.MmTelFeature;

import java.util.Arrays;
import java.util.Objects;
import java.util.TreeSet;

/**
 * A MediaThreshold represents a series of packet loss rate, jitter and rtp inactivity time
 * thresholds which when crossed should result in a {@link MediaQualityStatus} report being
 * generated by the {@link ImsService} via {@link MmTelFeature#notifyMediaQualityStatusChanged(
 * MediaQualityStatus)}
 *
 * <p/>
 * A {@link MediaQualityStatus} should be triggered when any of various
 * attributes pass one of the thresholds defined here.
 *
 *  @hide
 */
@SystemApi
public final class MediaThreshold implements Parcelable {
    private final int[] mRtpPacketLossRate;
    private final int[] mRtpJitter;
    private final long[] mRtpInactivityTimeMillis;

    /**
     * Retrieves threshold values for RTP packet loss rate in percentage.
     *
     * @return int array including threshold values for packet loss rate
     *
     * @hide
     */
    @NonNull
    @SystemApi
    public int[] getThresholdsRtpPacketLossRate() {
        return mRtpPacketLossRate;
    }

    /**
     * Retrieves threshold values for jitter(RFC3550) in milliseconds.
     *
     * @return int array including threshold values for RTP jitter.
     */
    @NonNull
    public int[] getThresholdsRtpJitterMillis() {
        return mRtpJitter;
    }

    /**
     * Retrieves threshold values for RTP inactivity time in milliseconds.
     *
     * @return int array including threshold values for RTP inactivity time.
     */
    @NonNull
    public long[] getThresholdsRtpInactivityTimeMillis() {
        return mRtpInactivityTimeMillis;
    }

    private MediaThreshold(
            int[] packetLossRateThresholds,
            int[] jitterThresholds,
            long[] inactivityTimeThresholds) {
        mRtpPacketLossRate = packetLossRateThresholds;
        mRtpJitter = jitterThresholds;
        mRtpInactivityTimeMillis = inactivityTimeThresholds;
    }

    /**
     * Creates a new instance of {@link MediaThreshold} from a parcel.
     * @param in The parceled data to read.
     */
    private MediaThreshold(@NonNull Parcel in) {
        mRtpPacketLossRate = in.createIntArray();
        mRtpJitter = in.createIntArray();
        mRtpInactivityTimeMillis = in.createLongArray();
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeIntArray(mRtpPacketLossRate);
        dest.writeIntArray(mRtpJitter);
        dest.writeLongArray(mRtpInactivityTimeMillis);
    }

    public static final @NonNull Creator<MediaThreshold> CREATOR =
            new Creator<MediaThreshold>() {
                @Override
                public MediaThreshold createFromParcel(@NonNull Parcel in) {
                    return new MediaThreshold(in);
                }

                @Override
                public MediaThreshold[] newArray(int size) {
                    return new MediaThreshold[size];
                }
            };

    /**
     * Returns whether the RTP packet loss rate threshold is valid or not.
     *
     * @param packetLossRate packet loss rate
     * @return the threshold is valid or not.
     * @hide
     */
    public static boolean isValidRtpPacketLossRate(int packetLossRate) {
        return (packetLossRate >= 0 && packetLossRate <= 100);
    }

    /**
     * Returns whether the RTP jitter threshold is valid or not.
     *
     * @param jitter jitter value in milliseconds
     * @return the threshold is valid or not.
     * @hide
     */
    public static boolean isValidJitterMillis(int jitter) {
        return (jitter >= 0 && jitter <= 10000);
    }

    /**
     * Returns whether the RTP packet loss rate threshold is valid or not.
     *
     * @param inactivityTime packet loss rate
     * @return the threshold is valid or not.
     * @hide
     */
    public static boolean isValidRtpInactivityTimeMillis(long inactivityTime) {
        return (inactivityTime >= 0 && inactivityTime <= 60000);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MediaThreshold that = (MediaThreshold) o;
        return Arrays.equals(mRtpPacketLossRate, that.mRtpPacketLossRate)
                && Arrays.equals(mRtpJitter, that.mRtpJitter)
                && Arrays.equals(mRtpInactivityTimeMillis, that.mRtpInactivityTimeMillis);
    }

    @Override
    public int hashCode() {
        return Objects.hash(Arrays.hashCode(mRtpPacketLossRate), Arrays.hashCode(mRtpJitter),
                Arrays.hashCode(mRtpInactivityTimeMillis));
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("MediaThreshold{mRtpPacketLossRate=");
        for (int i : mRtpPacketLossRate) {
            sb.append(" ").append(i);
        }
        sb.append(", mRtpJitter=");
        for (int b : mRtpJitter) {
            sb.append(" ").append(b);
        }
        sb.append(", mRtpInactivityTimeMillis=");
        for (long i : mRtpInactivityTimeMillis) {
            sb.append(" ").append(i);
        }
        sb.append("}");
        return sb.toString();
    }

    /**
     * Provides a convenient way to set the fields of an {@link MediaThreshold} when creating a
     * new instance.
     *
     * <p>The example below shows how you might create a new {@code RtpThreshold}:
     *
     * <pre><code>
     *
     * RtpThreshold = new RtpThreshold.Builder()
     *     .setRtpSessionType({@link MediaQualityStatus#MEDIA_SESSION_TYPE_AUDIO} or
     *                          {@link MediaQualityStatus#MEDIA_SESSION_TYPE_VIDEO})
     *     .setThresholdsRtpPacketLossRate(int[] packetLossRateThresholds)
     *     .setThresholdsRtpJitterMillis(int[] jitterThresholds)
     *     .setThresholdsRtpInactivityTimeMillis(int[] inactivityTimeThresholds)
     *     .build();
     * </code></pre>
     *
     * @hide
     */
    public static final class Builder {
        private int[] mRtpPacketLossRate = null;
        private int[] mRtpJitter = null;
        private long[] mRtpInactivityTimeMillis = null;

        /**
         * Default constructor for the Builder.
         *
         * @hide
         */
        public Builder() {
        }

        /**
         * Set threshold values for RTP packet loss rate in percentage.
         * <p/>
         * The packet loss calculation should be done at least once per
         * second. It should be calculated with at least the last 3 seconds
         * of data.
         *
         * @param packetLossRateThresholds int array for threshold values.
         * @return The same instance of the builder.
         *
         * @hide
         */
        @NonNull
        public Builder setThresholdsRtpPacketLossRate(int[] packetLossRateThresholds) {
            if (packetLossRateThresholds.length > 0) {
                TreeSet<Integer> thresholds = new TreeSet<>();
                for (Integer value : packetLossRateThresholds) {
                    if (isValidRtpPacketLossRate(value)) {
                        thresholds.add(value);
                    }
                }
                int[] targetArray = new int[thresholds.size()];
                int i = 0;
                for (int element : thresholds) {
                    targetArray[i++] = element;
                }
                this.mRtpPacketLossRate = targetArray;
            } else {
                this.mRtpPacketLossRate = packetLossRateThresholds;
            }
            return this;
        }


        /**
         * Set threshold values for RTP jitter in Milliseconds.
         *
         * @param jitterThresholds int array including threshold values for Jitter.
         * @return The same instance of the builder.
         *
         * @hide
         */
        @NonNull
        public Builder setThresholdsRtpJitterMillis(int[] jitterThresholds) {
            if (jitterThresholds.length > 0) {
                TreeSet<Integer> thresholds = new TreeSet<>();
                for (Integer value : jitterThresholds) {
                    if (isValidJitterMillis(value)) {
                        thresholds.add(value);
                    }
                }
                int[] targetArray = new int[thresholds.size()];
                int i = 0;
                for (int element : thresholds) {
                    targetArray[i++] = element;
                }
                this.mRtpJitter = targetArray;
            } else {
                this.mRtpJitter = jitterThresholds;
            }
            return this;
        }

        /**
         * Set threshold values for RTP inactivity time.
         *
         * @param inactivityTimeThresholds int array including threshold
         *                              values for RTP inactivity time.
         * @return The same instance of the builder.
         *
         * @hide
         */
        @NonNull
        public Builder setThresholdsRtpInactivityTimeMillis(long[] inactivityTimeThresholds) {
            if (inactivityTimeThresholds.length > 0) {
                TreeSet<Long> thresholds = new TreeSet<>();
                for (Long value : inactivityTimeThresholds) {
                    if (isValidRtpInactivityTimeMillis(value)) {
                        thresholds.add(value);
                    }
                }
                long[] targetArray = new long[thresholds.size()];
                int i = 0;
                for (long element : thresholds) {
                    targetArray[i++] = element;
                }
                this.mRtpInactivityTimeMillis = targetArray;
            } else {
                this.mRtpInactivityTimeMillis = inactivityTimeThresholds;
            }
            return this;
        }

        /**
         * Build the {@link MediaThreshold}
         *
         * @return the {@link MediaThreshold} object
         *
         * @hide
         */
        @NonNull
        public MediaThreshold build() {
            mRtpPacketLossRate = mRtpPacketLossRate != null ? mRtpPacketLossRate : new int[0];
            mRtpJitter = mRtpJitter != null ? mRtpJitter : new int[0];
            mRtpInactivityTimeMillis =
                    mRtpInactivityTimeMillis != null ? mRtpInactivityTimeMillis : new long[0];
            return new MediaThreshold(mRtpPacketLossRate, mRtpJitter, mRtpInactivityTimeMillis);
        }
    }
}
