/*
 * Copyright (C) 2020 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.app.search;

import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.app.search.flags.Flags;
import android.app.slice.SliceManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.widget.RemoteViews;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;

/**
 * A representation of a search result. Search result can be expressed in one of the following:
 * app icon, shortcut, slice, widget, or a custom object using {@link SearchAction}. While
 * app icon ({@link PackageManager}, shortcut {@link ShortcutManager}, slice {@link SliceManager},
 * or widget (@link AppWidgetManager} are published content backed by the system service,
 * {@link SearchAction} is a custom object that the service can use to send search result to the
 * client.
 *
 * These various types of Android primitives could be defined as {@link SearchResultType}. Some
 * times, the result type can define the layout type that that this object can be rendered in.
 * (e.g., app widget). Most times, {@link #getLayoutType()} assigned by the service
 * can recommend which layout this target should be rendered in.
 *
 * The service can also use fields such as {@link #getScore()} to indicate
 * how confidence the search result is and {@link #isHidden()} to indicate
 * whether it is recommended to be shown by default.
 *
 * Finally, {@link #getId()} is the unique identifier of this search target and a single
 * search target is defined by being able to express a single launcheable item. In case the
 * service want to recommend how to combine multiple search target objects to render in a group
 * (e.g., same row), {@link #getParentId()} can be assigned on the sub targets of the group
 * using the primary search target's identifier.
 *
 * @hide
 */
@SystemApi
public final class SearchTarget implements Parcelable {

    public static final int RESULT_TYPE_APPLICATION = 1 << 0;
    public static final int RESULT_TYPE_SHORTCUT = 1 << 1;
    public static final int RESULT_TYPE_SLICE = 1 << 2;
    public static final int RESULT_TYPE_WIDGETS = 1 << 3;

    //     ------
    //    | icon |
    //     ------
    //      text
    public static final String LAYOUT_TYPE_ICON = "icon";

    //     ------                            ------   ------
    //    |      | title                    |(opt)|  |(opt)|
    //    | icon | subtitle (optional)      | icon|  | icon|
    //     ------                            ------  ------
    public static final String LAYOUT_TYPE_ICON_ROW = "icon_row";

    //     ------
    //    | icon | title / subtitle (optional)
    //     ------
    public static final String LAYOUT_TYPE_SHORT_ICON_ROW = "short_icon_row";

    /**
     * @hide
     */
    @IntDef(prefix = {"RESULT_TYPE_"}, value = {
            RESULT_TYPE_APPLICATION,
            RESULT_TYPE_SHORTCUT,
            RESULT_TYPE_SLICE,
            RESULT_TYPE_WIDGETS
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface SearchResultType {}
    private final int mResultType;

    /**
     * @hide
     */
    @StringDef(prefix = {"LAYOUT_TYPE_"}, value = {
            LAYOUT_TYPE_ICON,
            LAYOUT_TYPE_ICON_ROW,
            LAYOUT_TYPE_SHORT_ICON_ROW,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface SearchLayoutType {}

    /**
     * Constant to express how the group of {@link SearchTarget} should be rendered on
     * the client side. (e.g., "icon", "icon_row", "short_icon_row")
     */
    @NonNull
    private final String mLayoutType;

    @NonNull
    private final String mId;

    @Nullable
    private String mParentId;

    private final float mScore;

    private final boolean mHidden;

    @NonNull
    private final String mPackageName;
    @NonNull
    private final UserHandle mUserHandle;
    @Nullable
    private final SearchAction mSearchAction;
    @Nullable
    private final ShortcutInfo mShortcutInfo;
    @Nullable
    private final AppWidgetProviderInfo mAppWidgetProviderInfo;
    @Nullable
    private final Uri mSliceUri;
    @Nullable
    private final RemoteViews mRemoteViews;

    @NonNull
    private final Bundle mExtras;

    private SearchTarget(Parcel parcel) {
        mResultType = parcel.readInt();
        mLayoutType = parcel.readString();
        mId = parcel.readString();
        mParentId = parcel.readString();
        mScore = parcel.readFloat();
        mHidden = parcel.readBoolean();

        mPackageName = parcel.readString();
        mUserHandle = UserHandle.of(parcel.readInt());
        mSearchAction = parcel.readTypedObject(SearchAction.CREATOR);
        mShortcutInfo = parcel.readTypedObject(ShortcutInfo.CREATOR);
        mAppWidgetProviderInfo = parcel.readTypedObject(AppWidgetProviderInfo.CREATOR);
        mSliceUri = parcel.readTypedObject(Uri.CREATOR);
        mRemoteViews = parcel.readTypedObject(RemoteViews.CREATOR);
        mExtras = parcel.readBundle(getClass().getClassLoader());
    }

    private SearchTarget(
            int resultType,
            @NonNull String layoutType,
            @NonNull String id,
            @Nullable String parentId,
            float score, boolean hidden,
            @NonNull String packageName,
            @NonNull UserHandle userHandle,
            @Nullable SearchAction action,
            @Nullable ShortcutInfo shortcutInfo,
            @Nullable Uri sliceUri,
            @Nullable RemoteViews remoteViews,
            @Nullable AppWidgetProviderInfo appWidgetProviderInfo,
            @NonNull Bundle extras) {
        mResultType = resultType;
        mLayoutType = Objects.requireNonNull(layoutType);
        mId = Objects.requireNonNull(id);
        mParentId = parentId;
        mScore = score;
        mHidden = hidden;
        mPackageName = Objects.requireNonNull(packageName);
        mUserHandle = Objects.requireNonNull(userHandle);
        mSearchAction = action;
        mShortcutInfo = shortcutInfo;
        mAppWidgetProviderInfo = appWidgetProviderInfo;
        mSliceUri = sliceUri;
        mRemoteViews = remoteViews;
        mExtras = extras != null ? extras : new Bundle();
    }

    /**
     * Retrieves the result type.
     */
    public @SearchResultType int getResultType() {
        return mResultType;
    }

    /**
     * Retrieves the layout type.
     */
    @NonNull
    public @SearchLayoutType String getLayoutType() {
        return mLayoutType;
    }

    /**
     * Retrieves the id of the target.
     */
    @NonNull
    public String getId() {
        return mId;
    }

    /**
     * Retrieves the parent id of the target.
     */
    @NonNull
    public String getParentId() {
        return mParentId;
    }

    /**
     * Retrieves the score of the target.
     */
    public float getScore() {
        return mScore;
    }

    /**
     * Indicates whether this object should be hidden and shown only on demand.
     *
     * @deprecated will be removed once SDK drops
     * @removed
     */
    @Deprecated
    public boolean shouldHide() {
        return mHidden;
    }

    /**
     * Indicates whether this object should be hidden and shown only on demand.
     */
    public boolean isHidden() {
        return mHidden;
    }

    /**
     * Retrieves the package name of the target.
     */
    @NonNull
    public String getPackageName() {
        return mPackageName;
    }

    /**
     * Retrieves the user handle of the target.
     */
    @NonNull
    public UserHandle getUserHandle() {
        return mUserHandle;
    }

    /**
     * Retrieves the shortcut info of the target.
     */
    @Nullable
    public ShortcutInfo getShortcutInfo() {
        return mShortcutInfo;
    }

    /**
     * Return a widget provider info.
     */
    @Nullable
    public AppWidgetProviderInfo getAppWidgetProviderInfo() {
        return mAppWidgetProviderInfo;
    }

    /**
     * Returns a slice uri.
     */
    @Nullable
    public Uri getSliceUri() {
        return mSliceUri;
    }

    /**
     * Returns a remote view.
     */
    @FlaggedApi(Flags.FLAG_REMOTE_VIEWS)
    @Nullable
    public RemoteViews getRemoteViews() {
        return mRemoteViews;
    }

    /**
     * Returns a search action.
     */
    @Nullable
    public SearchAction getSearchAction() {
        return mSearchAction;
    }

    /**
     * Return extra bundle.
     */
    @NonNull
    public Bundle getExtras() {
        return mExtras;
    }

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

    @Override
    public void writeToParcel(@NonNull Parcel parcel, int flags) {
        parcel.writeInt(mResultType);
        parcel.writeString(mLayoutType);
        parcel.writeString(mId);
        parcel.writeString(mParentId);
        parcel.writeFloat(mScore);
        parcel.writeBoolean(mHidden);
        parcel.writeString(mPackageName);
        parcel.writeInt(mUserHandle.getIdentifier());
        parcel.writeTypedObject(mSearchAction, flags);
        parcel.writeTypedObject(mShortcutInfo, flags);
        parcel.writeTypedObject(mAppWidgetProviderInfo, flags);
        parcel.writeTypedObject(mSliceUri, flags);
        parcel.writeTypedObject(mRemoteViews, flags);
        parcel.writeBundle(mExtras);
    }

    /**
     * @see Parcelable.Creator
     */
    @NonNull
    public static final Parcelable.Creator<SearchTarget> CREATOR =
            new Parcelable.Creator<SearchTarget>() {
                public SearchTarget createFromParcel(Parcel parcel) {
                    return new SearchTarget(parcel);
                }

                public SearchTarget[] newArray(int size) {
                    return new SearchTarget[size];
                }
            };

    /**
     * A builder for search target object.
     *
     * @hide
     */
    @SystemApi
    public static final class Builder {
        private int mResultType;
        @NonNull
        private String mLayoutType;
        @NonNull
        private String mId;
        @Nullable
        private String mParentId;
        private float mScore;
        private boolean mHidden;
        @NonNull
        private String mPackageName;
        @NonNull
        private UserHandle mUserHandle;
        @Nullable
        private SearchAction mSearchAction;
        @Nullable
        private ShortcutInfo mShortcutInfo;
        @Nullable
        private Uri mSliceUri;
        @Nullable
        private RemoteViews mRemoteViews;
        @Nullable
        private AppWidgetProviderInfo mAppWidgetProviderInfo;
        @NonNull
        private Bundle mExtras;

        public Builder(@SearchResultType int resultType,
                @SearchLayoutType @NonNull String layoutType,
                @NonNull String id) {
            mId = id;
            mLayoutType = Objects.requireNonNull(layoutType);
            mResultType = resultType;
            mScore = 1f;
            mHidden = false;
        }

        /**
         * Sets the parent id.
         */
        @NonNull
        public Builder setParentId(@NonNull String parentId) {
            mParentId = Objects.requireNonNull(parentId);
            return this;
        }

        /**
         * Sets the package name.
         */
        @NonNull
        public Builder setPackageName(@NonNull String packageName) {
            mPackageName = Objects.requireNonNull(packageName);
            return this;
        }

        /**
         * Sets the user handle.
         */
        @NonNull
        public Builder setUserHandle(@NonNull UserHandle userHandle) {
            mUserHandle = Objects.requireNonNull(userHandle);
            return this;
        }

        /**
         * Sets the shortcut info.
         */
        @NonNull
        public Builder setShortcutInfo(@NonNull ShortcutInfo shortcutInfo) {
            mShortcutInfo = Objects.requireNonNull(shortcutInfo);
            if (mPackageName != null && !mPackageName.equals(shortcutInfo.getPackage())) {
                throw new IllegalStateException("SearchTarget packageName is different from "
                        + "shortcut's packageName");
            }
            mPackageName = shortcutInfo.getPackage();
            return this;
        }

        /**
         * Sets the app widget provider info.
         */
        @NonNull
        public Builder setAppWidgetProviderInfo(
                @NonNull AppWidgetProviderInfo appWidgetProviderInfo) {
            mAppWidgetProviderInfo = Objects.requireNonNull(appWidgetProviderInfo);
            if (mPackageName != null
                    && !mPackageName.equals(appWidgetProviderInfo.provider.getPackageName())) {
                throw new IllegalStateException("SearchTarget packageName is different from "
                        + "appWidgetProviderInfo's packageName");
            }
            return this;
        }

        /**
         * Sets the slice URI.
         */
        @NonNull
        public Builder setSliceUri(@NonNull Uri sliceUri) {
            mSliceUri = sliceUri;
            return this;
        }

        /**
         * Sets the remote view.
         */
        @FlaggedApi(Flags.FLAG_REMOTE_VIEWS)
        @NonNull
        public Builder setRemoteViews(@NonNull RemoteViews remoteViews) {
            mRemoteViews = remoteViews;
            return this;
        }

        /**
         * Set the {@link SearchAction} object to this target.
         */
        @NonNull
        public Builder setSearchAction(@Nullable SearchAction searchAction) {
            mSearchAction = searchAction;
            return this;
        }

        /**
         * Set any extra information that needs to be shared between service and the client.
         */
        @NonNull
        public Builder setExtras(@NonNull Bundle extras) {
            mExtras = Objects.requireNonNull(extras);
            return this;
        }

        /**
         * Sets the score of the object.
         */
        @NonNull
        public Builder setScore(@FloatRange(from = 0.0f, to = 1.0f) float score) {
            mScore = score;
            return this;
        }

        /**
         * Sets whether the result should be hidden (e.g. not visible) by default inside client.
         */
        @NonNull
        public Builder setHidden(boolean hidden) {
            mHidden = hidden;
            return this;
        }

        /**
         * Sets whether the result should be hidden by default inside client.
         * @deprecated will be removed once SDK drops
         * @removed
         */
        @NonNull
        @Deprecated
        public Builder setShouldHide(boolean shouldHide) {
            mHidden = shouldHide;
            return this;
        }

        /**
         * Builds a new SearchTarget instance.
         *
         * @throws IllegalStateException if no target is set
         */
        @NonNull
        public SearchTarget build() {
            return new SearchTarget(mResultType, mLayoutType, mId, mParentId, mScore, mHidden,
                    mPackageName, mUserHandle,
                    mSearchAction, mShortcutInfo, mSliceUri, mRemoteViews, mAppWidgetProviderInfo,
                    mExtras);
        }
    }
}
