/*
 * Copyright (C) 2018 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.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.net.Uri;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;

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

/**
 * Contains the User Capability Exchange capabilities corresponding to a contact's URI.
 * @hide
 */
@SystemApi
public final class RcsContactUceCapability implements Parcelable {

    /** Contains presence information associated with the contact */
    public static final int CAPABILITY_MECHANISM_PRESENCE = 1;

    /** Contains OPTIONS information associated with the contact */
    public static final int CAPABILITY_MECHANISM_OPTIONS = 2;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = "CAPABILITY_MECHANISM_", value = {
        CAPABILITY_MECHANISM_PRESENCE,
        CAPABILITY_MECHANISM_OPTIONS
    })
    public @interface CapabilityMechanism {}

    /**
     * The capabilities of this contact were requested recently enough to still be considered in
     * the availability window.
     */
    public static final int SOURCE_TYPE_NETWORK = 0;

    /**
     * The capabilities of this contact were retrieved from the cached information in the Enhanced
     * Address Book.
     */
    public static final int SOURCE_TYPE_CACHED = 1;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = "SOURCE_TYPE_", value = {
        SOURCE_TYPE_NETWORK,
        SOURCE_TYPE_CACHED
    })
    public @interface SourceType {}

    /**
     * Capability information for the requested contact has expired and can not be refreshed due to
     * a temporary network error. This is a temporary error and the capabilities of the contact
     * should be queried again at a later time.
     */
    public static final int REQUEST_RESULT_UNKNOWN = 0;

    /**
     * The requested contact was found to be offline when queried. This is only applicable to
     * contact capabilities that were queried via OPTIONS requests and the network returned a
     * 408/480 response.
     */
    public static final int REQUEST_RESULT_NOT_ONLINE = 1;

    /**
     * Capability information for the requested contact was not found. The contact should not be
     * considered an RCS user.
     */
    public static final int REQUEST_RESULT_NOT_FOUND = 2;

    /**
     * Capability information for the requested contact was found successfully.
     */
    public static final int REQUEST_RESULT_FOUND = 3;

    /** @hide */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = "REQUEST_RESULT_", value = {
        REQUEST_RESULT_UNKNOWN,
        REQUEST_RESULT_NOT_ONLINE,
        REQUEST_RESULT_NOT_FOUND,
        REQUEST_RESULT_FOUND
    })
    public @interface RequestResult {}

    /**
     * Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
     * queried through SIP OPTIONS.
     */
    public static final class OptionsBuilder {

        private final RcsContactUceCapability mCapabilities;

        /**
         * Create the Builder, which can be used to set UCE capabilities as well as custom
         * capability extensions.
         * @param contact The contact URI that the capabilities are attached to.
         */
        public OptionsBuilder(@NonNull Uri contact) {
            mCapabilities = new RcsContactUceCapability(contact, CAPABILITY_MECHANISM_OPTIONS,
                    SOURCE_TYPE_NETWORK);
        }

        /**
         * Create the Builder, which can be used to set UCE capabilities as well as custom
         * capability extensions.
         * @param contact The contact URI that the capabilities are attached to.
         * @param sourceType The type where the capabilities of this contact were retrieved from.
         * @hide
         */
        public OptionsBuilder(@NonNull Uri contact, @SourceType int sourceType) {
            mCapabilities = new RcsContactUceCapability(contact, CAPABILITY_MECHANISM_OPTIONS,
                    sourceType);
        }

        /**
         * Set the result of the capabilities request.
         * @param requestResult the request result
         * @return this OptionBuilder
         */
        public @NonNull OptionsBuilder setRequestResult(@RequestResult int requestResult) {
            mCapabilities.mRequestResult = requestResult;
            return this;
        }

        /**
         * Add the feature tag into the capabilities instance.
         * @param tag the supported feature tag
         * @return this OptionBuilder
         */
        public @NonNull OptionsBuilder addFeatureTag(@NonNull String tag) {
            mCapabilities.mFeatureTags.add(tag);
            return this;
        }

        /**
         * Add the list of feature tag into the capabilities instance.
         * @param tags the list of the supported feature tags
         * @return this OptionBuilder
         */
        public @NonNull OptionsBuilder addFeatureTags(@NonNull Set<String> tags) {
            mCapabilities.mFeatureTags.addAll(tags);
            return this;
        }

        /**
         * @return the constructed instance.
         */
        public @NonNull RcsContactUceCapability build() {
            return mCapabilities;
        }
    }

    /**
     * Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
     * queried through a presence server.
     */
    public static final class PresenceBuilder {

        private final RcsContactUceCapability mCapabilities;

        /**
         * Create the builder, which can be used to set UCE capabilities as well as custom
         * capability extensions.
         * @param contact The contact URI that the capabilities are attached to.
         * @param sourceType The type where the capabilities of this contact were retrieved from.
         * @param requestResult the request result
         */
        public PresenceBuilder(@NonNull Uri contact, @SourceType int sourceType,
                @RequestResult int requestResult) {
            mCapabilities = new RcsContactUceCapability(contact, CAPABILITY_MECHANISM_PRESENCE,
                sourceType);
            mCapabilities.mRequestResult = requestResult;
        }

        /**
         * Add the {@link RcsContactPresenceTuple} into the capabilities instance.
         * @param tuple The {@link RcsContactPresenceTuple} to be added into.
         * @return this PresenceBuilder
         */
        public @NonNull PresenceBuilder addCapabilityTuple(@NonNull RcsContactPresenceTuple tuple) {
            mCapabilities.mPresenceTuples.add(tuple);
            return this;
        }

        /**
         * Add the list of {@link RcsContactPresenceTuple} into the capabilities instance.
         * @param tuples The list of the {@link RcsContactPresenceTuple} to be added into.
         * @return this PresenceBuilder
         */
        public @NonNull PresenceBuilder addCapabilityTuples(
                @NonNull List<RcsContactPresenceTuple> tuples) {
            mCapabilities.mPresenceTuples.addAll(tuples);
            return this;
        }

        /**
         * Set the entity URI related to the contact whose capabilities were requested.
         * @param entityUri the 'pres' URL of the PRESENTITY publishing presence document.
         */
        public @NonNull PresenceBuilder setEntityUri(@NonNull Uri entityUri) {
            mCapabilities.mEntityUri = entityUri;
            return this;
        }

        /**
         * @return the RcsContactUceCapability instance.
         */
        public @NonNull RcsContactUceCapability build() {
            return mCapabilities;
        }
    }

    private final Uri mContactUri;
    private @SourceType int mSourceType;
    private @CapabilityMechanism int mCapabilityMechanism;
    private @RequestResult int mRequestResult;
    private Uri mEntityUri;

    private final Set<String> mFeatureTags = new HashSet<>();
    private final List<RcsContactPresenceTuple> mPresenceTuples = new ArrayList<>();

    private RcsContactUceCapability(@NonNull Uri contactUri, @CapabilityMechanism int mechanism,
            @SourceType int sourceType) {
        mContactUri = contactUri;
        mCapabilityMechanism = mechanism;
        mSourceType = sourceType;
    }

    private RcsContactUceCapability(Parcel in) {
        mContactUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
        mCapabilityMechanism = in.readInt();
        mSourceType = in.readInt();
        mRequestResult = in.readInt();
        mEntityUri = in.readParcelable(Uri.class.getClassLoader(), android.net.Uri.class);
        List<String> featureTagList = new ArrayList<>();
        in.readStringList(featureTagList);
        mFeatureTags.addAll(featureTagList);
        in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader(), android.telephony.ims.RcsContactPresenceTuple.class);
    }

    @Override
    public void writeToParcel(@NonNull Parcel out, int flags) {
        out.writeParcelable(mContactUri, flags);
        out.writeInt(mCapabilityMechanism);
        out.writeInt(mSourceType);
        out.writeInt(mRequestResult);
        out.writeParcelable(mEntityUri, flags);
        out.writeStringList(new ArrayList<>(mFeatureTags));
        out.writeParcelableList(mPresenceTuples, flags);
    }

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

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

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

    /**
     * @return The mechanism used to get the capabilities.
     */
    public @CapabilityMechanism int getCapabilityMechanism() {
        return mCapabilityMechanism;
    }

    /**
     * @return The feature tags present in the OPTIONS response from the network.
     * <p>
     * Note: this is only populated if {@link #getCapabilityMechanism} is
     * {@link RcsContactUceCapability#CAPABILITY_MECHANISM_OPTIONS}
     */
    public @NonNull Set<String> getFeatureTags() {
        if (mCapabilityMechanism != CAPABILITY_MECHANISM_OPTIONS) {
            return Collections.emptySet();
        }
        return Collections.unmodifiableSet(mFeatureTags);
    }

    /**
     * @return The tuple elements associated with the presence element portion of the PIDF document
     * contained in the NOTIFY response from the network.
     * <p>
     * Note: this is only populated if {@link #getCapabilityMechanism} is
     * {@link RcsContactUceCapability#CAPABILITY_MECHANISM_PRESENCE}
     */
    public @NonNull List<RcsContactPresenceTuple> getCapabilityTuples() {
        if (mCapabilityMechanism != CAPABILITY_MECHANISM_PRESENCE) {
            return Collections.emptyList();
        }
        return Collections.unmodifiableList(mPresenceTuples);
    }

    /**
     * Get the RcsContactPresenceTuple associated with the given service id.
     * @param serviceId The service id to get the presence tuple.
     * @return The RcsContactPresenceTuple which has the given service id or {@code null} if the
     * service id does not exist in the list of presence tuples returned from the network.
     *
     * <p>
     * Note: this is only populated if {@link #getCapabilityMechanism} is
     * {@link RcsContactUceCapability#CAPABILITY_MECHANISM_PRESENCE}
     */
    public @Nullable RcsContactPresenceTuple getCapabilityTuple(@NonNull String serviceId) {
        if (mCapabilityMechanism != CAPABILITY_MECHANISM_PRESENCE) {
            return null;
        }
        for (RcsContactPresenceTuple tuple : mPresenceTuples) {
            if (tuple.getServiceId() != null && tuple.getServiceId().equals(serviceId)) {
                return tuple;
            }
        }
        return null;
    }

    /**
     * @return the source of the data that was used to populate the capabilities of the requested
     * contact.
     */
    public @SourceType int getSourceType() {
        return mSourceType;
    }

    /**
     * @return the result of querying the capabilities of the requested contact.
     */
    public @RequestResult int getRequestResult() {
        return mRequestResult;
    }

    /**
     * Retrieve the contact URI requested by the applications.
     * @return the URI representing the contact associated with the capabilities.
     */
    public @NonNull Uri getContactUri() {
        return mContactUri;
    }

    /**
     * Retrieve the entity URI of the contact whose presence information is being requested for.
     * @return the URI representing the 'pres' URL of the PRESENTITY publishing presence document
     * or {@code null} if the entity uri does not exist in the presence document.
     */
    public @Nullable Uri getEntityUri() {
        return mEntityUri;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("RcsContactUceCapability");
        if (mCapabilityMechanism == CAPABILITY_MECHANISM_PRESENCE) {
            builder.append("(presence) {");
        } else if (mCapabilityMechanism == CAPABILITY_MECHANISM_OPTIONS) {
            builder.append("(options) {");
        } else {
            builder.append("(?) {");
        }
        if (Build.IS_ENG) {
            builder.append("uri=");
            builder.append(mContactUri);
        } else {
            builder.append("uri (isNull)=");
            builder.append(mContactUri != null ? "XXX" : "null");
        }
        builder.append(", sourceType=");
        builder.append(mSourceType);
        builder.append(", requestResult=");
        builder.append(mRequestResult);
        if (Build.IS_ENG) {
            builder.append("entity uri=");
            builder.append(mEntityUri != null ? mEntityUri : "null");
        } else {
            builder.append("entity uri (isNull)=");
            builder.append(mEntityUri != null ? "XXX" : "null");
        }

        if (mCapabilityMechanism == CAPABILITY_MECHANISM_PRESENCE) {
            builder.append(", presenceTuples={");
            builder.append(mPresenceTuples);
            builder.append("}");
        } else if (mCapabilityMechanism == CAPABILITY_MECHANISM_OPTIONS) {
            builder.append(", featureTags={");
            builder.append(mFeatureTags);
            builder.append("}");
        }

        return builder.toString();
    }
}
