/*
 * Copyright (C) 2011 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 com.android.ex.chips;

import android.net.Uri;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.DisplayNameSources;
import androidx.annotation.DrawableRes;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;

/**
 * Represents one entry inside recipient auto-complete list.
 */
public class RecipientEntry {
    /* package */ static final int INVALID_CONTACT = -1;
    /**
     * A GENERATED_CONTACT is one that was created based entirely on
     * information passed in to the RecipientEntry from an external source
     * that is not a real contact.
     */
    /* package */ static final int GENERATED_CONTACT = -2;

    /** Used when {@link #mDestinationType} is invalid and thus shouldn't be used for display. */
    public static final int INVALID_DESTINATION_TYPE = -1;

    public static final int ENTRY_TYPE_PERSON = 0;

    /**
     * Entry of this type represents the item in auto-complete that asks user to grant permissions
     * to the app. This permission model is introduced in M platform.
     *
     * <p>Entries of this type should have {@link #mPermissions} set as well.
     */
    public static final int ENTRY_TYPE_PERMISSION_REQUEST = 1;

    public static final int ENTRY_TYPE_SIZE = 2;

    private final int mEntryType;

    /**
     * True when this entry is the first entry in a group, which should have a photo and display
     * name, while the second or later entries won't.
     */
    private boolean mIsFirstLevel;
    private final String mDisplayName;

    /** Destination for this contact entry. Would be an email address or a phone number. */
    private final String mDestination;
    /** Type of the destination like {@link Email#TYPE_HOME} */
    private final int mDestinationType;
    /**
     * Label of the destination which will be used when type was {@link Email#TYPE_CUSTOM}.
     * Can be null when {@link #mDestinationType} is {@link #INVALID_DESTINATION_TYPE}.
     */
    private final String mDestinationLabel;
    /** ID for the person */
    private final long mContactId;
    /** ID for the directory this contact came from, or <code>null</code> */
    private final Long mDirectoryId;
    /** ID for the destination */
    private final long mDataId;

    private final Uri mPhotoThumbnailUri;
    /** Configures showing the icon in the chip */
    private final boolean mShouldDisplayIcon;

    private boolean mIsValid;
    /**
     * This can be updated after this object being constructed, when the photo is fetched
     * from remote directories.
     */
    private byte[] mPhotoBytes;

    @DrawableRes private int mIndicatorIconId;
    private String mIndicatorText;

    /** See {@link android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} */
    private final String mLookupKey;

    /** Should be used when type is {@link #ENTRY_TYPE_PERMISSION_REQUEST}. */
    private final String[] mPermissions;

    /** Whether RecipientEntry is in a replaced chip or not. */
    private boolean mInReplacedChip;

    protected RecipientEntry(int entryType, String displayName, String destination,
        int destinationType, String destinationLabel, long contactId, Long directoryId,
        long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid,
        String lookupKey, String[] permissions) {
        this(entryType, displayName, destination, destinationType,
            destinationLabel, contactId, directoryId, dataId, photoThumbnailUri,
            true /* shouldDisplayIcon */, isFirstLevel, isValid, lookupKey, permissions);
    }

    protected RecipientEntry(int entryType, String displayName, String destination,
            int destinationType, String destinationLabel, long contactId, Long directoryId,
            long dataId, Uri photoThumbnailUri, boolean shouldDisplayIcon,
            boolean isFirstLevel, boolean isValid, String lookupKey, String[] permissions) {
        mEntryType = entryType;
        mIsFirstLevel = isFirstLevel;
        mDisplayName = displayName;
        mDestination = destination;
        mDestinationType = destinationType;
        mDestinationLabel = destinationLabel;
        mContactId = contactId;
        mDirectoryId = directoryId;
        mDataId = dataId;
        mPhotoThumbnailUri = photoThumbnailUri;
        mShouldDisplayIcon = shouldDisplayIcon;
        mPhotoBytes = null;
        mIsValid = isValid;
        mLookupKey = lookupKey;
        mIndicatorIconId = 0;
        mIndicatorText = null;
        mPermissions = permissions;
    }

    protected RecipientEntry(int entryType, String displayName, String destination,
            int destinationType, String destinationLabel, long contactId, Long directoryId,
            long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid,
            String lookupKey) {
        this(entryType, displayName, destination, destinationType, destinationLabel,
                contactId, directoryId, dataId, photoThumbnailUri, isFirstLevel, isValid,
                lookupKey, null);
    }

    public boolean isValid() {
        return mIsValid;
    }

    /**
     * Determine if this was a RecipientEntry created from recipient info or
     * an entry from contacts.
     */
    public static boolean isCreatedRecipient(long id) {
        return id == RecipientEntry.INVALID_CONTACT || id == RecipientEntry.GENERATED_CONTACT;
    }

    /**
     * Construct a RecipientEntry from just an address that has been entered.
     * This address has not been resolved to a contact and therefore does not
     * have a contact id or photo.
     */
    public static RecipientEntry constructFakeEntry(final String address, final boolean isValid) {
        final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
        final String tokenizedAddress = tokens.length > 0 ? tokens[0].getAddress() : address;

        return new RecipientEntry(ENTRY_TYPE_PERSON, tokenizedAddress, tokenizedAddress,
                INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
                INVALID_CONTACT, null, true, isValid, null /* lookupKey */, null /* permissions */);
    }

    /**
     * Construct a RecipientEntry from just a phone number.
     */
    public static RecipientEntry constructFakePhoneEntry(final String phoneNumber,
            final boolean isValid) {
        return new RecipientEntry(ENTRY_TYPE_PERSON, phoneNumber, phoneNumber,
                INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
                INVALID_CONTACT, null, true, isValid, null /* lookupKey */, null /* permissions */);
    }

    /**
     * Construct a RecipientEntry from just an address that has been entered
     * with both an associated display name. This address has not been resolved
     * to a contact and therefore does not have a contact id or photo.
     */
    public static RecipientEntry constructGeneratedEntry(String display, String address,
            boolean isValid) {
        return new RecipientEntry(ENTRY_TYPE_PERSON, display, address, INVALID_DESTINATION_TYPE,
                null, GENERATED_CONTACT, null /* directoryId */, GENERATED_CONTACT, null, true,
                isValid, null /* lookupKey */, null /* permissions */);
    }

    public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
            String destination, int destinationType, String destinationLabel, long contactId,
            Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid,
            String lookupKey) {
        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
                displayName, destination), destination, destinationType, destinationLabel,
                contactId, directoryId, dataId, photoThumbnailUri, true, isValid, lookupKey,
                null /* permissions */);
    }

    public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
            String destination, int destinationType, String destinationLabel, long contactId,
            Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid,
            String lookupKey) {
        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
                displayName, destination), destination, destinationType, destinationLabel,
                contactId, directoryId, dataId, (thumbnailUriAsString != null
                ? Uri.parse(thumbnailUriAsString) : null), true, isValid, lookupKey,
                null /* permissions */);
    }

    public static RecipientEntry constructSecondLevelEntry(String displayName,
            int displayNameSource, String destination, int destinationType,
            String destinationLabel, long contactId, Long directoryId, long dataId,
            String thumbnailUriAsString, boolean isValid, String lookupKey) {
        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
                displayName, destination), destination, destinationType, destinationLabel,
                contactId, directoryId, dataId, (thumbnailUriAsString != null
                ? Uri.parse(thumbnailUriAsString) : null), false, isValid, lookupKey,
                null /* permissions */);
    }

    public static RecipientEntry constructPermissionEntry(String[] permissions) {
        return new RecipientEntry(
                ENTRY_TYPE_PERMISSION_REQUEST,
                "" /* displayName */,
                "" /* destination */,
                Email.TYPE_CUSTOM,
                "" /* destinationLabel */,
                INVALID_CONTACT,
                null /* directoryId */,
                INVALID_CONTACT,
                null /* photoThumbnailUri */,
                true /* isFirstLevel*/,
                false /* isValid */,
                null /* lookupKey */,
                permissions);
    }

    /**
     * @return the display name for the entry.  If the display name source is larger than
     * {@link DisplayNameSources#PHONE} we use the contact's display name, but if not,
     * i.e. the display name came from an email address or a phone number, we don't use it
     * to avoid confusion and just use the destination instead.
     */
    private static String pickDisplayName(int displayNameSource, String displayName,
            String destination) {
        return (displayNameSource > DisplayNameSources.PHONE) ? displayName : destination;
    }

    public int getEntryType() {
        return mEntryType;
    }

    public String getDisplayName() {
        return mDisplayName;
    }

    public String getDestination() {
        return mDestination;
    }

    public int getDestinationType() {
        return mDestinationType;
    }

    public String getDestinationLabel() {
        return mDestinationLabel;
    }

    public long getContactId() {
        return mContactId;
    }

    public Long getDirectoryId() {
        return mDirectoryId;
    }

    public long getDataId() {
        return mDataId;
    }

    public boolean isFirstLevel() {
        return mIsFirstLevel;
    }

    public Uri getPhotoThumbnailUri() {
        return mPhotoThumbnailUri;
    }

    /** Indicates whether the icon in the chip is displayed or not. */
    public boolean shouldDisplayIcon() {
        return mShouldDisplayIcon;
    }

    /** This can be called outside main Looper thread. */
    public synchronized void setPhotoBytes(byte[] photoBytes) {
        mPhotoBytes = photoBytes;
    }

    /** This can be called outside main Looper thread. */
    public synchronized byte[] getPhotoBytes() {
        return mPhotoBytes;
    }

    /**
     * Used together with {@link #ENTRY_TYPE_PERMISSION_REQUEST} and indicates what permissions we
     * need to ask user to grant.
     */
    public String[] getPermissions() {
        return mPermissions;
    }

    public String getLookupKey() {
        return mLookupKey;
    }

    public boolean isSelectable() {
        return mEntryType == ENTRY_TYPE_PERSON || mEntryType == ENTRY_TYPE_PERMISSION_REQUEST;
    }

    @Override
    public String toString() {
        return mDisplayName + " <" + mDestination + ">, isValid=" + mIsValid;
    }

    /**
     * Returns if entry represents the same person as this instance. The default implementation
     * checks whether the contact ids are the same, and subclasses may opt to override this.
     */
    public boolean isSamePerson(final RecipientEntry entry) {
        return entry != null && mContactId == entry.mContactId;
    }

    /**
     * Returns the resource ID for the indicator icon, or 0 if no icon should be displayed.
     */
    @DrawableRes
    public int getIndicatorIconId() {
        return mIndicatorIconId;
    }

    /**
     * Sets the indicator icon to the given resource ID.  Set to 0 to display no icon.
     */
    public void setIndicatorIconId(@DrawableRes int indicatorIconId) {
        mIndicatorIconId = indicatorIconId;
    }

    /**
     * Get the indicator text, or null if no text should be displayed.
     */
    public String getIndicatorText() {
        return mIndicatorText;
    }

    /**
     * Set the indicator text.  Set to null for no text to be displayed.
     */
    public void setIndicatorText(String indicatorText) {
        mIndicatorText = indicatorText;
    }

    /**
     * Get whether this RecipientEntry is in a replaced chip or not. Replaced chip only occurs
     * if {@link RecipientEditTextView} uses a replacement chip for the entry.
     */
    public boolean getInReplacedChip() {
        return mInReplacedChip;
    }

    /**
     * Sets {@link #mInReplacedChip} to {@param inReplacedChip}.
     */
    public void setInReplacedChip(boolean inReplacedChip) {
        mInReplacedChip = inReplacedChip;
    }
}
