/*
 * Copyright (C) 2010 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.contacts.quickcontact;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.provider.ContactsContract.CommonDataKinds.SipAddress;
import android.text.TextUtils;

import com.android.contacts.util.PhoneCapabilityTester;
import com.google.common.collect.Sets;

import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

/**
 * Internally hold a cache of scaled icons based on {@link PackageManager}
 * queries, keyed internally on MIME-type.
 */
public class ResolveCache {
    /**
     * Specific list {@link ApplicationInfo#packageName} of apps that are
     * prefered <strong>only</strong> for the purposes of default icons when
     * multiple {@link ResolveInfo} are found to match. This only happens when
     * the user has not selected a default app yet, and they will still be
     * presented with the system disambiguation dialog.
     * If several of this list match (e.g. Android Browser vs. Chrome), we will pick either one
     */
    private static final HashSet<String> sPreferResolve = Sets.newHashSet(
            "com.android.email",
            "com.google.android.email",

            "com.android.phone",

            "com.google.android.apps.maps",

            "com.android.chrome",
            "com.google.android.browser",
            "com.android.browser");

    private final Context mContext;
    private final PackageManager mPackageManager;

    private static ResolveCache sInstance;

    /**
     * Returns an instance of the ResolveCache. Only one internal instance is kept, so
     * the argument packageManagers is ignored for all but the first call
     */
    public synchronized static ResolveCache getInstance(Context context) {
        if (sInstance == null) {
            final Context applicationContext = context.getApplicationContext();
            sInstance = new ResolveCache(applicationContext);

            // Register for package-changes so that we can flush our cache
            final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
            filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
            filter.addDataScheme("package");
            applicationContext.registerReceiver(sInstance.mPackageIntentReceiver, filter);
        }
        return sInstance;
    }

    private synchronized static void flush() {
        sInstance = null;
    }

    /**
     * Called anytime a package is installed, uninstalled etc, so that we can wipe our cache
     */
    private BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            flush();
        }
    };

    /**
     * Cached entry holding the best {@link ResolveInfo} for a specific
     * MIME-type, along with a {@link SoftReference} to its icon.
     */
    private static class Entry {
        public ResolveInfo bestResolve;
        public Drawable icon;
    }

    private HashMap<String, Entry> mCache = new HashMap<String, Entry>();


    private ResolveCache(Context context) {
        mContext = context;
        mPackageManager = context.getPackageManager();
    }

    /**
     * Get the {@link Entry} best associated with the given {@link Action},
     * or create and populate a new one if it doesn't exist.
     */
    protected Entry getEntry(Action action) {
        final String mimeType = action.getMimeType();
        Entry entry = mCache.get(mimeType);
        if (entry != null) return entry;
        entry = new Entry();

        Intent intent = action.getIntent();
        if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType)
                && !PhoneCapabilityTester.isSipPhone(mContext)) {
            intent = null;
        }

        if (intent != null) {
            final List<ResolveInfo> matches = mPackageManager.queryIntentActivities(intent,
                    PackageManager.MATCH_DEFAULT_ONLY);

            // Pick first match, otherwise best found
            ResolveInfo bestResolve = null;
            final int size = matches.size();
            if (size == 1) {
                bestResolve = matches.get(0);
            } else if (size > 1) {
                bestResolve = getBestResolve(intent, matches);
            }

            if (bestResolve != null) {
                final Drawable icon = bestResolve.loadIcon(mPackageManager);

                entry.bestResolve = bestResolve;
                entry.icon = icon;
            }
        }

        mCache.put(mimeType, entry);
        return entry;
    }

    /**
     * Best {@link ResolveInfo} when multiple found. Ties are broken by
     * selecting first from the {@link QuickContactActivity#sPreferResolve} list of
     * preferred packages, second by apps that live on the system partition,
     * otherwise the app from the top of the list. This is
     * <strong>only</strong> used for selecting a default icon for
     * displaying in the track, and does not shortcut the system
     * {@link Intent} disambiguation dialog.
     */
    protected ResolveInfo getBestResolve(Intent intent, List<ResolveInfo> matches) {
        // Try finding preferred activity, otherwise detect disambig
        final ResolveInfo foundResolve = mPackageManager.resolveActivity(intent,
                PackageManager.MATCH_DEFAULT_ONLY);
        final boolean foundDisambig = (foundResolve.match &
                IntentFilter.MATCH_CATEGORY_MASK) == 0;

        if (!foundDisambig) {
            // Found concrete match, so return directly
            return foundResolve;
        }

        // Accept any package from prefer list, otherwise first system app
        ResolveInfo firstSystem = null;
        for (ResolveInfo info : matches) {
            final boolean isSystem = (info.activityInfo.applicationInfo.flags
                    & ApplicationInfo.FLAG_SYSTEM) != 0;
            final boolean isPrefer = sPreferResolve
                    .contains(info.activityInfo.applicationInfo.packageName);

            if (isPrefer) return info;
            if (isSystem && firstSystem == null) firstSystem = info;
        }

        // Return first system found, otherwise first from list
        return firstSystem != null ? firstSystem : matches.get(0);
    }

    /**
     * Check {@link PackageManager} to see if any apps offer to handle the
     * given {@link Action}.
     */
    public boolean hasResolve(Action action) {
        return getEntry(action).bestResolve != null;
    }

    /**
     * Find the best description for the given {@link Action}, usually used
     * for accessibility purposes.
     */
    public CharSequence getDescription(Action action) {
        final CharSequence actionSubtitle = action.getSubtitle();
        final ResolveInfo info = getEntry(action).bestResolve;
        if (info != null) {
            return info.loadLabel(mPackageManager);
        } else if (!TextUtils.isEmpty(actionSubtitle)) {
            return actionSubtitle;
        } else {
            return null;
        }
    }

    /**
     * Return the best icon for the given {@link Action}, which is usually
     * based on the {@link ResolveInfo} found through a
     * {@link PackageManager} query.
     */
    public Drawable getIcon(Action action) {
        return getEntry(action).icon;
    }

    public void clear() {
        mCache.clear();
    }
}
