/*
 * Copyright (C) 2012 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.mms.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;

/**
 * Base class {@link BackgroundLoaderManager} used by {@link MessagingApplication} for loading
 * items (images, thumbnails, pdus, etc.) in the background off of the UI thread.
 * <p>
 * Public methods should only be used from a single thread (typically the UI
 * thread). Callbacks will be invoked on the thread where the ThumbnailManager
 * was instantiated.
 * <p>
 * Uses a thread-pool ExecutorService instead of AsyncTasks since clients may
 * request lots of images around the same time, and AsyncTask may reject tasks
 * in that case and has no way of bounding the number of threads used by those
 * tasks.
 *
 * Based on BooksImageManager by Virgil King.
 */
abstract class BackgroundLoaderManager {
    private static final String TAG = "BackgroundLoaderManager";

    private static final int MAX_THREADS = 2;

    /**
     * URIs for which tasks are currently enqueued. Don't enqueue new tasks for
     * these, just add new callbacks.
     */
    protected final Set<Uri> mPendingTaskUris;

    protected final HashMap<Uri, Set<ItemLoadedCallback>> mCallbacks;

    protected final Executor mExecutor;

    protected final Handler mCallbackHandler;

    BackgroundLoaderManager(Context context) {
        mPendingTaskUris = new HashSet<Uri>();
        mCallbacks = new HashMap<Uri, Set<ItemLoadedCallback>>();
        final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
        final int poolSize = MAX_THREADS;
        mExecutor = new ThreadPoolExecutor(
                poolSize, poolSize, 5, TimeUnit.SECONDS, queue,
                new BackgroundLoaderThreadFactory(getTag()));
        mCallbackHandler = new Handler();
    }

    /**
     * Release memory if possible.
     */
    public void onLowMemory() {
        clear();
    }

    public void clear() {
    }

    /**
     * Return a tag that will be used to name threads so they'll be visible in the debugger.
     */
    public abstract String getTag();

    /**
     * Attempts to add a callback for a resource.
     *
     * @param uri the {@link Uri} of the resource for which a callback is
     *            desired.
     * @param callback the callback to register.
     * @return {@code true} if the callback is guaranteed to be invoked with
     *         a non-null result (as long as there is no error and the
     *         callback is not canceled), or {@code false} if the callback
     *         cannot be registered with this task because the result for
     *         the desired {@link Uri} has already been discarded due to
     *         low-memory.
     * @throws NullPointerException if either argument is {@code null}
     */
    public boolean addCallback(Uri uri, ItemLoadedCallback callback) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Adding image callback " + callback);
        }
        if (uri == null) {
            throw new NullPointerException("uri is null");
        }
        if (callback == null) {
            throw new NullPointerException("callback is null");
        }
        Set<ItemLoadedCallback> callbacks = mCallbacks.get(uri);
        if (callbacks == null) {
            callbacks = new HashSet<ItemLoadedCallback>(4);
            mCallbacks.put(uri, callbacks);
        }
        callbacks.add(callback);
        return true;
    }

    public void cancelCallback(ItemLoadedCallback callback) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Cancelling image callback " + callback);
        }
        for (final Uri uri : mCallbacks.keySet()) {
            final Set<ItemLoadedCallback> callbacks = mCallbacks.get(uri);
            callbacks.remove(callback);
        }
    }

    /**
     * Copies the elements of a {@link Set} into an {@link ArrayList}.
     */
    @SuppressWarnings("unchecked")
    protected static <T> ArrayList<T> asList(Set<T> source) {
        return new ArrayList<T>(source);
    }

    /**
     * {@link ThreadFactory} which sets a meaningful name for the thread.
     */
    private static class BackgroundLoaderThreadFactory implements ThreadFactory {
        private final AtomicInteger mCount = new AtomicInteger(1);
        private final String mTag;

        public BackgroundLoaderThreadFactory(String tag) {
            mTag = tag;
        }

        public Thread newThread(final Runnable r) {
            Thread t =  new Thread(r, mTag + "-" + mCount.getAndIncrement());

            if (t.getPriority() != Thread.MIN_PRIORITY)
                t.setPriority(Thread.MIN_PRIORITY);

            return t;
        }
    }
}
