/*
 * Copyright (C) 2017 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.slice;

import android.annotation.NonNull;
import android.annotation.StringDef;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Pair;
import android.widget.RemoteViews;

import com.android.internal.util.ArrayUtils;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.List;


/**
 * A SliceItem is a single unit in the tree structure of a {@link Slice}.
 *
 * A SliceItem a piece of content and some hints about what that content
 * means or how it should be displayed. The types of content can be:
 * <li>{@link #FORMAT_SLICE}</li>
 * <li>{@link #FORMAT_TEXT}</li>
 * <li>{@link #FORMAT_IMAGE}</li>
 * <li>{@link #FORMAT_ACTION}</li>
 * <li>{@link #FORMAT_INT}</li>
 * <li>{@link #FORMAT_LONG}</li>
 * <li>{@link #FORMAT_REMOTE_INPUT}</li>
 * <li>{@link #FORMAT_BUNDLE}</li>
 *
 * The hints that a {@link SliceItem} are a set of strings which annotate
 * the content. The hints that are guaranteed to be understood by the system
 * are defined on {@link Slice}.
 */
public final class SliceItem implements Parcelable {

    private static final String TAG = "SliceItem";

    /**
     * @hide
     */
    @StringDef(prefix = { "FORMAT_" }, value = {
            FORMAT_SLICE,
            FORMAT_TEXT,
            FORMAT_IMAGE,
            FORMAT_ACTION,
            FORMAT_INT,
            FORMAT_LONG,
            FORMAT_REMOTE_INPUT,
            FORMAT_BUNDLE,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface SliceType {}

    /**
     * A {@link SliceItem} that contains a {@link Slice}
     */
    public static final String FORMAT_SLICE = "slice";
    /**
     * A {@link SliceItem} that contains a {@link CharSequence}
     */
    public static final String FORMAT_TEXT = "text";
    /**
     * A {@link SliceItem} that contains an {@link Icon}
     */
    public static final String FORMAT_IMAGE = "image";
    /**
     * A {@link SliceItem} that contains a {@link PendingIntent}
     *
     * Note: Actions contain 2 pieces of data, In addition to the pending intent, the
     * item contains a {@link Slice} that the action applies to.
     */
    public static final String FORMAT_ACTION = "action";
    /**
     * A {@link SliceItem} that contains an int.
     */
    public static final String FORMAT_INT = "int";
    /**
     * A {@link SliceItem} that contains a long.
     */
    public static final String FORMAT_LONG = "long";
    /**
     * @deprecated TO BE REMOVED
     * @removed
     */
    @Deprecated
    public static final String FORMAT_TIMESTAMP = FORMAT_LONG;
    /**
     * A {@link SliceItem} that contains a {@link RemoteInput}.
     */
    public static final String FORMAT_REMOTE_INPUT = "input";
    /**
     * A {@link SliceItem} that contains a {@link Bundle}.
     */
    public static final String FORMAT_BUNDLE = "bundle";

    /**
     * @hide
     */
    protected @Slice.SliceHint
    String[] mHints;
    private final String mFormat;
    private final String mSubType;
    private final Object mObj;

    /**
     * @hide
     */
    public SliceItem(Object obj, @SliceType String format, String subType,
            List<String> hints) {
        this(obj, format, subType, hints.toArray(new String[hints.size()]));
    }

    /**
     * @hide
     */
    public SliceItem(Object obj, @SliceType String format, String subType,
            @Slice.SliceHint String[] hints) {
        mHints = hints;
        mFormat = format;
        mSubType = subType;
        mObj = obj;
    }

    /**
     * @hide
     */
    public SliceItem(PendingIntent intent, Slice slice, String format, String subType,
            @Slice.SliceHint String[] hints) {
        this(new Pair<>(intent, slice), format, subType, hints);
    }

    /**
     * Gets all hints associated with this SliceItem.
     * @return Array of hints.
     */
    public @NonNull @Slice.SliceHint List<String> getHints() {
        return Arrays.asList(mHints);
    }

    /**
     * Get the format of this SliceItem.
     * <p>
     * The format will be one of the following types supported by the platform:
     * <li>{@link #FORMAT_SLICE}</li>
     * <li>{@link #FORMAT_TEXT}</li>
     * <li>{@link #FORMAT_IMAGE}</li>
     * <li>{@link #FORMAT_ACTION}</li>
     * <li>{@link #FORMAT_INT}</li>
     * <li>{@link #FORMAT_LONG}</li>
     * <li>{@link #FORMAT_REMOTE_INPUT}</li>
     * <li>{@link #FORMAT_BUNDLE}</li>
     * @see #getSubType() ()
     */
    public String getFormat() {
        return mFormat;
    }

    /**
     * Get the sub-type of this SliceItem.
     * <p>
     * Subtypes provide additional information about the type of this information beyond basic
     * interpretations inferred by {@link #getFormat()}. For example a slice may contain
     * many {@link #FORMAT_TEXT} items, but only some of them may be {@link Slice#SUBTYPE_MESSAGE}.
     * @see #getFormat()
     */
    public String getSubType() {
        return mSubType;
    }

    /**
     * @return The text held by this {@link #FORMAT_TEXT} SliceItem
     */
    public CharSequence getText() {
        return (CharSequence) mObj;
    }

    /**
     * @return The parcelable held by this {@link #FORMAT_BUNDLE} SliceItem
     */
    public Bundle getBundle() {
        return (Bundle) mObj;
    }

    /**
     * @return The icon held by this {@link #FORMAT_IMAGE} SliceItem
     */
    public Icon getIcon() {
        return (Icon) mObj;
    }

    /**
     * @return The pending intent held by this {@link #FORMAT_ACTION} SliceItem
     */
    public PendingIntent getAction() {
        return ((Pair<PendingIntent, Slice>) mObj).first;
    }

    /**
     * @hide This isn't final
     */
    public RemoteViews getRemoteView() {
        return (RemoteViews) mObj;
    }

    /**
     * @return The remote input held by this {@link #FORMAT_REMOTE_INPUT} SliceItem
     */
    public RemoteInput getRemoteInput() {
        return (RemoteInput) mObj;
    }

    /**
     * @return The color held by this {@link #FORMAT_INT} SliceItem
     */
    public int getInt() {
        return (Integer) mObj;
    }

    /**
     * @return The slice held by this {@link #FORMAT_ACTION} or {@link #FORMAT_SLICE} SliceItem
     */
    public Slice getSlice() {
        if (FORMAT_ACTION.equals(getFormat())) {
            return ((Pair<PendingIntent, Slice>) mObj).second;
        }
        return (Slice) mObj;
    }

    /**
     * @return The long held by this {@link #FORMAT_LONG} SliceItem
     */
    public long getLong() {
        return (Long) mObj;
    }

    /**
     * @deprecated replaced by {@link #getLong()}
     * @removed
     */
    @Deprecated
    public long getTimestamp() {
        return (Long) mObj;
    }

    /**
     * @param hint The hint to check for
     * @return true if this item contains the given hint
     */
    public boolean hasHint(@Slice.SliceHint String hint) {
        return ArrayUtils.contains(mHints, hint);
    }

    /**
     * @hide
     */
    public SliceItem(Parcel in) {
        mHints = in.readStringArray();
        mFormat = in.readString();
        mSubType = in.readString();
        mObj = readObj(mFormat, in);
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeStringArray(mHints);
        dest.writeString(mFormat);
        dest.writeString(mSubType);
        writeObj(dest, flags, mObj, mFormat);
    }

    /**
     * @hide
     */
    public boolean hasHints(@Slice.SliceHint String[] hints) {
        if (hints == null) return true;
        for (String hint : hints) {
            if (!TextUtils.isEmpty(hint) && !ArrayUtils.contains(mHints, hint)) {
                return false;
            }
        }
        return true;
    }

    /**
     * @hide
     */
    public boolean hasAnyHints(@Slice.SliceHint String[] hints) {
        if (hints == null) return false;
        for (String hint : hints) {
            if (ArrayUtils.contains(mHints, hint)) {
                return true;
            }
        }
        return false;
    }

    private static String getBaseType(String type) {
        int index = type.indexOf('/');
        if (index >= 0) {
            return type.substring(0, index);
        }
        return type;
    }

    private static void writeObj(Parcel dest, int flags, Object obj, String type) {
        switch (getBaseType(type)) {
            case FORMAT_SLICE:
            case FORMAT_IMAGE:
            case FORMAT_REMOTE_INPUT:
            case FORMAT_BUNDLE:
                ((Parcelable) obj).writeToParcel(dest, flags);
                break;
            case FORMAT_ACTION:
                ((Pair<PendingIntent, Slice>) obj).first.writeToParcel(dest, flags);
                ((Pair<PendingIntent, Slice>) obj).second.writeToParcel(dest, flags);
                break;
            case FORMAT_TEXT:
                TextUtils.writeToParcel((CharSequence) obj, dest, flags);
                break;
            case FORMAT_INT:
                dest.writeInt((Integer) obj);
                break;
            case FORMAT_TIMESTAMP:
                dest.writeLong((Long) obj);
                break;
        }
    }

    private static Object readObj(String type, Parcel in) {
        switch (getBaseType(type)) {
            case FORMAT_SLICE:
                return Slice.CREATOR.createFromParcel(in);
            case FORMAT_TEXT:
                return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
            case FORMAT_IMAGE:
                return Icon.CREATOR.createFromParcel(in);
            case FORMAT_ACTION:
                return new Pair<>(
                        PendingIntent.CREATOR.createFromParcel(in),
                        Slice.CREATOR.createFromParcel(in));
            case FORMAT_INT:
                return in.readInt();
            case FORMAT_TIMESTAMP:
                return in.readLong();
            case FORMAT_REMOTE_INPUT:
                return RemoteInput.CREATOR.createFromParcel(in);
            case FORMAT_BUNDLE:
                return Bundle.CREATOR.createFromParcel(in);
        }
        throw new RuntimeException("Unsupported type " + type);
    }

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

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