/*
 * Copyright (C) 2019 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.CheckResult;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * The parameters to pass into {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParams)} in
 * order to select a subset of {@link RcsThread}s present in the message store.
 *
 * @hide
 */
public final class RcsThreadQueryParams implements Parcelable {
    /**
     * Bitmask flag to be used with {@link Builder#setThreadType(int)} to make
     * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParams)} return
     * {@link RcsGroupThread}s.
     */
    public static final int THREAD_TYPE_GROUP = 0x0001;

    /**
     * Bitmask flag to be used with {@link Builder#setThreadType(int)} to make
     * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParams)} return
     * {@link Rcs1To1Thread}s.
     */
    public static final int THREAD_TYPE_1_TO_1 = 0x0002;

    // The type of threads to be filtered with the query
    private final int mThreadType;
    // The list of participants that are expected in the resulting threads
    private final List<Integer> mRcsParticipantIds;
    // The number of RcsThread's that should be returned with this query
    private final int mLimit;
    // The property which the result of the query should be sorted against
    private final @SortingProperty int mSortingProperty;
    // Whether the sorting should be done in ascending
    private final boolean mIsAscending;

    /**
     * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should
     * be sorted in the order of {@link RcsThread} creation time for faster results.
     */
    public static final int SORT_BY_CREATION_ORDER = 0;

    /**
     * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should
     * be sorted according to the timestamp of {@link RcsThread#getSnippet()}
     */
    public static final int SORT_BY_TIMESTAMP = 1;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_TIMESTAMP})
    public @interface SortingProperty {
    }

    /**
     * @hide
     */
    public static final String THREAD_QUERY_PARAMETERS_KEY = "thread_query_parameters";

    RcsThreadQueryParams(int threadType, Set<RcsParticipant> participants,
            int limit, int sortingProperty, boolean isAscending) {
        mThreadType = threadType;
        mRcsParticipantIds = convertParticipantSetToIdList(participants);
        mLimit = limit;
        mSortingProperty = sortingProperty;
        mIsAscending = isAscending;
    }

    private static List<Integer> convertParticipantSetToIdList(Set<RcsParticipant> participants) {
        List<Integer> ids = new ArrayList<>(participants.size());
        for (RcsParticipant participant : participants) {
            ids.add(participant.getId());
        }
        return ids;
    }

    /**
     * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get
     * the list of participant IDs.
     *
     * As we don't expose any integer ID's to API users, this should stay hidden
     *
     * @hide - not meant for public use
     */
    public List<Integer> getRcsParticipantsIds() {
        return Collections.unmodifiableList(mRcsParticipantIds);
    }

    /**
     * @return Returns the bitmask flag for types of {@link RcsThread}s that this query should
     * return.
     */
    public int getThreadType() {
        return mThreadType;
    }

    /**
     * @return Returns the number of {@link RcsThread}s to be returned from the query. A value
     * of 0 means there is no set limit.
     */
    public int getLimit() {
        return mLimit;
    }

    /**
     * @return Returns the property that will be used to sort the result against.
     * @see SortingProperty
     */
    public @SortingProperty int getSortingProperty() {
        return mSortingProperty;
    }

    /**
     * @return Returns {@code true} if the result set will be sorted in ascending order,
     * {@code false} if it will be sorted in descending order.
     */
    public boolean getSortDirection() {
        return mIsAscending;
    }

    /**
     * A helper class to build the {@link RcsThreadQueryParams}.
     */
    public static class Builder {
        private int mThreadType;
        private Set<RcsParticipant> mParticipants;
        private int mLimit = 100;
        private @SortingProperty int mSortingProperty;
        private boolean mIsAscending;

        /**
         * Constructs a {@link RcsThreadQueryParams.Builder} to help build an
         * {@link RcsThreadQueryParams}
         */
        public Builder() {
            mParticipants = new HashSet<>();
        }

        /**
         * Limits the query to only return group threads.
         *
         * @param threadType Whether to limit the query result to group threads.
         * @return The same instance of the builder to chain parameters.
         * @see RcsThreadQueryParams#THREAD_TYPE_GROUP
         * @see RcsThreadQueryParams#THREAD_TYPE_1_TO_1
         */
        @CheckResult
        public Builder setThreadType(int threadType) {
            mThreadType = threadType;
            return this;
        }

        /**
         * Limits the query to only return threads that contain the given participant. If this
         * property was not set, participants will not be taken into account while querying for
         * threads.
         *
         * @param participant The participant that must be included in all of the returned threads.
         * @return The same instance of the builder to chain parameters.
         */
        @CheckResult
        public Builder setParticipant(@NonNull RcsParticipant participant) {
            mParticipants.add(participant);
            return this;
        }

        /**
         * Limits the query to only return threads that contain the given list of participants. If
         * this property was not set, participants will not be taken into account while querying
         * for threads.
         *
         * @param participants An iterable list of participants that must be included in all of the
         *                     returned threads.
         * @return The same instance of the builder to chain parameters.
         */
        @CheckResult
        public Builder setParticipants(@NonNull List<RcsParticipant> participants) {
            mParticipants.addAll(participants);
            return this;
        }

        /**
         * Desired number of threads to be returned from the query. Passing in 0 will return all
         * existing threads at once. The limit defaults to 100.
         *
         * @param limit The number to limit the query result to.
         * @return The same instance of the builder to chain parameters.
         * @throws InvalidParameterException If the given limit is negative.
         */
        @CheckResult
        public Builder setResultLimit(@IntRange(from = 0) int limit)
                throws InvalidParameterException {
            if (limit < 0) {
                throw new InvalidParameterException("The query limit must be non-negative");
            }

            mLimit = limit;
            return this;
        }

        /**
         * Sets the property where the results should be sorted against. Defaults to
         * {@link SortingProperty#SORT_BY_CREATION_ORDER}
         *
         * @param sortingProperty whether to sort in ascending order or not
         * @return The same instance of the builder to chain parameters.
         */
        @CheckResult
        public Builder setSortProperty(@SortingProperty int sortingProperty) {
            mSortingProperty = sortingProperty;
            return this;
        }

        /**
         * Sets whether the results should be sorted ascending or descending
         *
         * @param isAscending whether the results should be sorted ascending
         * @return The same instance of the builder to chain parameters.
         */
        @CheckResult
        public Builder setSortDirection(boolean isAscending) {
            mIsAscending = isAscending;
            return this;
        }

        /**
         * Builds the {@link RcsThreadQueryParams} to use in
         * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParams)}
         *
         * @return An instance of {@link RcsThreadQueryParams} to use with the thread query.
         */
        public RcsThreadQueryParams build() {
            return new RcsThreadQueryParams(mThreadType, mParticipants, mLimit,
                    mSortingProperty, mIsAscending);
        }
    }

    /**
     * Parcelable boilerplate below.
     */
    private RcsThreadQueryParams(Parcel in) {
        mThreadType = in.readInt();
        mRcsParticipantIds = new ArrayList<>();
        in.readList(mRcsParticipantIds, Integer.class.getClassLoader());
        mLimit = in.readInt();
        mSortingProperty = in.readInt();
        mIsAscending = in.readByte() == 1;
    }

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

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

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mThreadType);
        dest.writeList(mRcsParticipantIds);
        dest.writeInt(mLimit);
        dest.writeInt(mSortingProperty);
        dest.writeByte((byte) (mIsAscending ? 1 : 0));
    }
}
