/*
 * Copyright (C) 2015 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.messaging.datamodel.data;

import android.content.ContentValues;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Color;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.SubscriptionInfo;
import android.text.TextUtils;

import androidx.appcompat.mms.MmsManager;
import androidx.collection.ArrayMap;

import com.android.ex.chips.RecipientEntry;
import com.android.messaging.Factory;
import com.android.messaging.R;
import com.android.messaging.datamodel.DatabaseHelper;
import com.android.messaging.datamodel.DatabaseHelper.ParticipantColumns;
import com.android.messaging.datamodel.DatabaseWrapper;
import com.android.messaging.sms.MmsSmsUtils;
import com.android.messaging.util.Assert;
import com.android.messaging.util.PhoneUtils;
import com.android.messaging.util.TextUtil;

/**
 * A class that encapsulates all of the data for a specific participant in a conversation.
 */
public class ParticipantData implements Parcelable {

    private static final ArrayMap<Integer, String> sSubIdtoParticipantIdCache =
            new ArrayMap<Integer, String>();

    // We always use -1 as default/invalid sub id although system may give us anything negative
    public static final int DEFAULT_SELF_SUB_ID = MmsManager.DEFAULT_SUB_ID;

    // This needs to be something apart from valid or DEFAULT_SELF_SUB_ID
    public static final int OTHER_THAN_SELF_SUB_ID = DEFAULT_SELF_SUB_ID - 1;

    // Active slot ids are non-negative. Using -1 to designate to inactive self participants.
    public static final int INVALID_SLOT_ID = -1;

    // TODO: may make sense to move this to common place?
    public static final long PARTICIPANT_CONTACT_ID_NOT_RESOLVED = -1;
    public static final long PARTICIPANT_CONTACT_ID_NOT_FOUND = -2;

    public static class ParticipantsQuery {
        public static final String[] PROJECTION = new String[] {
            ParticipantColumns._ID,
            ParticipantColumns.SUB_ID,
            ParticipantColumns.SIM_SLOT_ID,
            ParticipantColumns.NORMALIZED_DESTINATION,
            ParticipantColumns.SEND_DESTINATION,
            ParticipantColumns.DISPLAY_DESTINATION,
            ParticipantColumns.FULL_NAME,
            ParticipantColumns.FIRST_NAME,
            ParticipantColumns.PROFILE_PHOTO_URI,
            ParticipantColumns.CONTACT_ID,
            ParticipantColumns.LOOKUP_KEY,
            ParticipantColumns.BLOCKED,
            ParticipantColumns.SUBSCRIPTION_COLOR,
            ParticipantColumns.SUBSCRIPTION_NAME,
            ParticipantColumns.CONTACT_DESTINATION,
        };

        public static final int INDEX_ID                        = 0;
        public static final int INDEX_SUB_ID                    = 1;
        public static final int INDEX_SIM_SLOT_ID               = 2;
        public static final int INDEX_NORMALIZED_DESTINATION    = 3;
        public static final int INDEX_SEND_DESTINATION          = 4;
        public static final int INDEX_DISPLAY_DESTINATION       = 5;
        public static final int INDEX_FULL_NAME                 = 6;
        public static final int INDEX_FIRST_NAME                = 7;
        public static final int INDEX_PROFILE_PHOTO_URI         = 8;
        public static final int INDEX_CONTACT_ID                = 9;
        public static final int INDEX_LOOKUP_KEY                = 10;
        public static final int INDEX_BLOCKED                   = 11;
        public static final int INDEX_SUBSCRIPTION_COLOR        = 12;
        public static final int INDEX_SUBSCRIPTION_NAME         = 13;
        public static final int INDEX_CONTACT_DESTINATION       = 14;
    }

    /**
     * @return The MMS unknown sender participant entity
     */
    public static String getUnknownSenderDestination() {
        // This is a hard coded string rather than a localized one because we don't want it to
        // change when you change locale.
        return "\u02BCUNKNOWN_SENDER!\u02BC";
    }

    private String mParticipantId;
    private int mSubId;
    private int mSlotId;
    private String mNormalizedDestination;
    private String mSendDestination;
    private String mDisplayDestination;
    private String mContactDestination;
    private String mFullName;
    private String mFirstName;
    private String mProfilePhotoUri;
    private long mContactId;
    private String mLookupKey;
    private int mSubscriptionColor;
    private String mSubscriptionName;
    private boolean mIsEmailAddress;
    private boolean mBlocked;

    // Don't call constructor directly
    private ParticipantData() {
    }

    public static ParticipantData getFromCursor(final Cursor cursor) {
        final ParticipantData pd = new ParticipantData();
        pd.mParticipantId = cursor.getString(ParticipantsQuery.INDEX_ID);
        pd.mSubId = cursor.getInt(ParticipantsQuery.INDEX_SUB_ID);
        pd.mSlotId = cursor.getInt(ParticipantsQuery.INDEX_SIM_SLOT_ID);
        pd.mNormalizedDestination = cursor.getString(
                ParticipantsQuery.INDEX_NORMALIZED_DESTINATION);
        pd.mSendDestination = cursor.getString(ParticipantsQuery.INDEX_SEND_DESTINATION);
        pd.mDisplayDestination = cursor.getString(ParticipantsQuery.INDEX_DISPLAY_DESTINATION);
        pd.mContactDestination = cursor.getString(ParticipantsQuery.INDEX_CONTACT_DESTINATION);
        pd.mFullName = cursor.getString(ParticipantsQuery.INDEX_FULL_NAME);
        pd.mFirstName = cursor.getString(ParticipantsQuery.INDEX_FIRST_NAME);
        pd.mProfilePhotoUri = cursor.getString(ParticipantsQuery.INDEX_PROFILE_PHOTO_URI);
        pd.mContactId = cursor.getLong(ParticipantsQuery.INDEX_CONTACT_ID);
        pd.mLookupKey = cursor.getString(ParticipantsQuery.INDEX_LOOKUP_KEY);
        pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
        pd.mBlocked = cursor.getInt(ParticipantsQuery.INDEX_BLOCKED) != 0;
        pd.mSubscriptionColor = cursor.getInt(ParticipantsQuery.INDEX_SUBSCRIPTION_COLOR);
        pd.mSubscriptionName = cursor.getString(ParticipantsQuery.INDEX_SUBSCRIPTION_NAME);
        pd.maybeSetupUnknownSender();
        return pd;
    }

    public static ParticipantData getFromId(final DatabaseWrapper dbWrapper,
            final String participantId) {
        Cursor cursor = null;
        try {
            cursor = dbWrapper.query(DatabaseHelper.PARTICIPANTS_TABLE,
                    ParticipantsQuery.PROJECTION,
                    ParticipantColumns._ID + " =?",
                    new String[] { participantId }, null, null, null);

            if (cursor.moveToFirst()) {
                return ParticipantData.getFromCursor(cursor);
            } else {
                return null;
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }

    public static ParticipantData getFromRecipientEntry(final RecipientEntry recipientEntry) {
        final ParticipantData pd = new ParticipantData();
        pd.mParticipantId = null;
        pd.mSubId = OTHER_THAN_SELF_SUB_ID;
        pd.mSlotId = INVALID_SLOT_ID;
        pd.mSendDestination = TextUtil.replaceUnicodeDigits(recipientEntry.getDestination());
        pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
        pd.mNormalizedDestination = pd.mIsEmailAddress ?
                pd.mSendDestination :
                PhoneUtils.getDefault().getCanonicalBySystemLocale(pd.mSendDestination);
        pd.mDisplayDestination = pd.mIsEmailAddress ?
                pd.mNormalizedDestination :
                PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
        pd.mFullName = recipientEntry.getDisplayName();
        pd.mFirstName = null;
        pd.mProfilePhotoUri = (recipientEntry.getPhotoThumbnailUri() == null) ? null :
                recipientEntry.getPhotoThumbnailUri().toString();
        pd.mContactId = recipientEntry.getContactId();
        if (pd.mContactId < 0) {
            // ParticipantData only supports real contact ids (>=0) based on faith that the contacts
            // provider will continue to only use non-negative ids.  The UI uses contactId < 0 for
            // special handling. We convert those to 'not resolved'
            pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
        }
        pd.mLookupKey = recipientEntry.getLookupKey();
        pd.mBlocked = false;
        pd.mSubscriptionColor = Color.TRANSPARENT;
        pd.mSubscriptionName = null;
        pd.maybeSetupUnknownSender();
        return pd;
    }

    // Shared code for getFromRawPhoneBySystemLocale and getFromRawPhoneBySimLocale
    private static ParticipantData getFromRawPhone(final String phoneNumber) {
        Assert.isTrue(phoneNumber != null);
        final ParticipantData pd = new ParticipantData();
        pd.mParticipantId = null;
        pd.mSubId = OTHER_THAN_SELF_SUB_ID;
        pd.mSlotId = INVALID_SLOT_ID;
        pd.mSendDestination = TextUtil.replaceUnicodeDigits(phoneNumber);
        pd.mIsEmailAddress = MmsSmsUtils.isEmailAddress(pd.mSendDestination);
        pd.mFullName = null;
        pd.mFirstName = null;
        pd.mProfilePhotoUri = null;
        pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
        pd.mLookupKey = null;
        pd.mBlocked = false;
        pd.mSubscriptionColor = Color.TRANSPARENT;
        pd.mSubscriptionName = null;
        return pd;
    }

    /**
     * Get an instance from a raw phone number and using system locale to normalize it.
     *
     * Use this when creating a participant that is for displaying UI and not associated
     * with a specific SIM. For example, when creating a conversation using user entered
     * phone number.
     *
     * @param phoneNumber The raw phone number
     * @return instance
     */
    public static ParticipantData getFromRawPhoneBySystemLocale(final String phoneNumber) {
        final ParticipantData pd = getFromRawPhone(phoneNumber);
        pd.mNormalizedDestination = pd.mIsEmailAddress ?
                pd.mSendDestination :
                PhoneUtils.getDefault().getCanonicalBySystemLocale(pd.mSendDestination);
        pd.mDisplayDestination = pd.mIsEmailAddress ?
                pd.mNormalizedDestination :
                PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
        pd.maybeSetupUnknownSender();
        return pd;
    }

    /**
     * Get an instance from a raw phone number and using SIM or system locale to normalize it.
     *
     * Use this when creating a participant that is associated with a specific SIM. For example,
     * the sender of a received message or the recipient of a sending message that is already
     * targeted at a specific SIM.
     *
     * @param phoneNumber The raw phone number
     * @return instance
     */
    public static ParticipantData getFromRawPhoneBySimLocale(
            final String phoneNumber, final int subId) {
        final ParticipantData pd = getFromRawPhone(phoneNumber);
        pd.mNormalizedDestination = pd.mIsEmailAddress ?
                pd.mSendDestination :
                PhoneUtils.get(subId).getCanonicalBySimLocale(pd.mSendDestination);
        pd.mDisplayDestination = pd.mIsEmailAddress ?
                pd.mNormalizedDestination :
                PhoneUtils.getDefault().formatForDisplay(pd.mNormalizedDestination);
        pd.maybeSetupUnknownSender();
        return pd;
    }

    public static ParticipantData getSelfParticipant(final int subId) {
        Assert.isTrue(subId != OTHER_THAN_SELF_SUB_ID);
        final ParticipantData pd = new ParticipantData();
        pd.mParticipantId = null;
        pd.mSubId = subId;
        pd.mSlotId = INVALID_SLOT_ID;
        pd.mIsEmailAddress = false;
        pd.mSendDestination = null;
        pd.mNormalizedDestination = null;
        pd.mDisplayDestination = null;
        pd.mFullName = null;
        pd.mFirstName = null;
        pd.mProfilePhotoUri = null;
        pd.mContactId = PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
        pd.mLookupKey = null;
        pd.mBlocked = false;
        pd.mSubscriptionColor = Color.TRANSPARENT;
        pd.mSubscriptionName = null;
        return pd;
    }

    public static String getParticipantId(final DatabaseWrapper db, final int subId) {
        String id;
        synchronized (sSubIdtoParticipantIdCache) {
            id = sSubIdtoParticipantIdCache.get(subId);
        }

        if (id != null) {
            return id;
        }

        try (final Cursor cursor =
                    db.query(DatabaseHelper.PARTICIPANTS_TABLE,
                            new String[] {ParticipantColumns._ID},
                            ParticipantColumns.SUB_ID + " =?",
                            new String[] {Integer.toString(subId)}, null, null, null)) {

            if (cursor.moveToFirst()) {
                // We found an existing participant in the database
                id = cursor.getString(0);
                synchronized (sSubIdtoParticipantIdCache) {
                    // Add it to the cache for next time
                    sSubIdtoParticipantIdCache.put(subId, id);
                }
            }
        }
        return id;
    }

    private void maybeSetupUnknownSender() {
        if (isUnknownSender()) {
            // Because your locale may change, we setup the display string for the unknown sender
            // on the fly rather than relying on the version in the database.
            final Resources resources = Factory.get().getApplicationContext().getResources();
            mDisplayDestination = resources.getString(R.string.unknown_sender);
            mFullName = mDisplayDestination;
        }
    }

    public String getNormalizedDestination() {
        return mNormalizedDestination;
    }

    public String getSendDestination() {
        return mSendDestination;
    }

    public String getDisplayDestination() {
        return mDisplayDestination;
    }

    public String getContactDestination() {
        return mContactDestination;
    }

    public String getFullName() {
        return mFullName;
    }

    public String getFirstName() {
        return mFirstName;
    }

    public String getDisplayName(final boolean preferFullName) {
        if (preferFullName) {
            // Prefer full name over first name
            if (!TextUtils.isEmpty(mFullName)) {
                return mFullName;
            }
            if (!TextUtils.isEmpty(mFirstName)) {
                return mFirstName;
            }
        } else {
            // Prefer first name over full name
            if (!TextUtils.isEmpty(mFirstName)) {
                return mFirstName;
            }
            if (!TextUtils.isEmpty(mFullName)) {
                return mFullName;
            }
        }

        // Fallback to the display destination
        if (!TextUtils.isEmpty(mDisplayDestination)) {
            return mDisplayDestination;
        }

        return Factory.get().getApplicationContext().getResources().getString(
                R.string.unknown_sender);
    }

    public String getProfilePhotoUri() {
        return mProfilePhotoUri;
    }

    public long getContactId() {
        return mContactId;
    }

    public String getLookupKey() {
        return mLookupKey;
    }

    public boolean updatePhoneNumberForSelfIfChanged() {
        final String phoneNumber =
                PhoneUtils.get(mSubId).getCanonicalForSelf(true/*allowOverride*/);
        boolean changed = false;
        if (isSelf() && !TextUtils.equals(phoneNumber, mNormalizedDestination)) {
            mNormalizedDestination = phoneNumber;
            mSendDestination = phoneNumber;
            mDisplayDestination = mIsEmailAddress ?
                    phoneNumber :
                    PhoneUtils.getDefault().formatForDisplay(phoneNumber);
            changed = true;
        }
        return changed;
    }

    public boolean updateSubscriptionInfoForSelfIfChanged(final SubscriptionInfo subscriptionInfo) {
        boolean changed = false;
        if (isSelf()) {
            if (subscriptionInfo == null) {
                // The subscription is inactive. Check if the participant is still active.
                if (isActiveSubscription()) {
                    mSlotId = INVALID_SLOT_ID;
                    mSubscriptionColor = Color.TRANSPARENT;
                    mSubscriptionName = "";
                    changed = true;
                }
            } else {
                final int slotId = subscriptionInfo.getSimSlotIndex();
                final int color = subscriptionInfo.getIconTint();
                final CharSequence name = subscriptionInfo.getDisplayName();
                if (mSlotId != slotId || mSubscriptionColor != color || mSubscriptionName != name) {
                    mSlotId = slotId;
                    mSubscriptionColor = color;
                    mSubscriptionName = name.toString();
                    changed = true;
                }
            }
        }
        return changed;
    }

    public void setFullName(final String fullName) {
        mFullName = fullName;
    }

    public void setFirstName(final String firstName) {
        mFirstName = firstName;
    }

    public void setProfilePhotoUri(final String profilePhotoUri) {
        mProfilePhotoUri = profilePhotoUri;
    }

    public void setContactId(final long contactId) {
        mContactId = contactId;
    }

    public void setLookupKey(final String lookupKey) {
        mLookupKey = lookupKey;
    }

    public void setSendDestination(final String destination) {
        mSendDestination = destination;
    }

    public void setContactDestination(final String destination) {
        mContactDestination = destination;
    }

    public int getSubId() {
        return mSubId;
    }

    /**
     * @return whether this sub is active. Note that {@link ParticipantData#DEFAULT_SELF_SUB_ID} is
     *         is considered as active if there is any active SIM.
     */
    public boolean isActiveSubscription() {
        return mSlotId != INVALID_SLOT_ID;
    }

    public boolean isDefaultSelf() {
        return mSubId == ParticipantData.DEFAULT_SELF_SUB_ID;
    }

    public int getSlotId() {
        return mSlotId;
    }

    /**
     * Slot IDs in the subscription manager is zero-based, but we want to show it
     * as 1-based in UI.
     */
    public int getDisplaySlotId() {
        return getSlotId() + 1;
    }

    public int getSubscriptionColor() {
        Assert.isTrue(isActiveSubscription());
        // Force the alpha channel to 0xff to ensure the returned color is solid.
        return mSubscriptionColor | 0xff000000;
    }

    public String getSubscriptionName() {
        Assert.isTrue(isActiveSubscription());
        return mSubscriptionName;
    }

    public String getId() {
        return mParticipantId;
    }

    public boolean isSelf() {
        return (mSubId != OTHER_THAN_SELF_SUB_ID);
    }

    public boolean isEmail() {
        return mIsEmailAddress;
    }

    public boolean isContactIdResolved() {
        return (mContactId != PARTICIPANT_CONTACT_ID_NOT_RESOLVED);
    }

    public boolean isBlocked() {
        return mBlocked;
    }

    public boolean isUnknownSender() {
        final String unknownSender = ParticipantData.getUnknownSenderDestination();
        return (TextUtils.equals(mSendDestination, unknownSender));
    }

    public ContentValues toContentValues() {
        final ContentValues values = new ContentValues();
        values.put(ParticipantColumns.SUB_ID, mSubId);
        values.put(ParticipantColumns.SIM_SLOT_ID, mSlotId);
        values.put(DatabaseHelper.ParticipantColumns.SEND_DESTINATION, mSendDestination);

        if (!isUnknownSender()) {
            values.put(DatabaseHelper.ParticipantColumns.DISPLAY_DESTINATION, mDisplayDestination);
            values.put(DatabaseHelper.ParticipantColumns.NORMALIZED_DESTINATION,
                    mNormalizedDestination);
            values.put(ParticipantColumns.FULL_NAME, mFullName);
            values.put(ParticipantColumns.FIRST_NAME, mFirstName);
        }

        values.put(ParticipantColumns.PROFILE_PHOTO_URI, mProfilePhotoUri);
        values.put(ParticipantColumns.CONTACT_ID, mContactId);
        values.put(ParticipantColumns.LOOKUP_KEY, mLookupKey);
        values.put(ParticipantColumns.BLOCKED, mBlocked);
        values.put(ParticipantColumns.SUBSCRIPTION_COLOR, mSubscriptionColor);
        values.put(ParticipantColumns.SUBSCRIPTION_NAME, mSubscriptionName);
        return values;
    }

    public ParticipantData(final Parcel in) {
        mParticipantId = in.readString();
        mSubId = in.readInt();
        mSlotId = in.readInt();
        mNormalizedDestination = in.readString();
        mSendDestination = in.readString();
        mDisplayDestination = in.readString();
        mFullName = in.readString();
        mFirstName = in.readString();
        mProfilePhotoUri = in.readString();
        mContactId = in.readLong();
        mLookupKey = in.readString();
        mIsEmailAddress = in.readInt() != 0;
        mBlocked = in.readInt() != 0;
        mSubscriptionColor = in.readInt();
        mSubscriptionName = in.readString();
    }

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

    @Override
    public void writeToParcel(final Parcel dest, final int flags) {
        dest.writeString(mParticipantId);
        dest.writeInt(mSubId);
        dest.writeInt(mSlotId);
        dest.writeString(mNormalizedDestination);
        dest.writeString(mSendDestination);
        dest.writeString(mDisplayDestination);
        dest.writeString(mFullName);
        dest.writeString(mFirstName);
        dest.writeString(mProfilePhotoUri);
        dest.writeLong(mContactId);
        dest.writeString(mLookupKey);
        dest.writeInt(mIsEmailAddress ? 1 : 0);
        dest.writeInt(mBlocked ? 1 : 0);
        dest.writeInt(mSubscriptionColor);
        dest.writeString(mSubscriptionName);
    }

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

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