/*
 * 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.Set;

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

import com.android.mms.LogTag;
import com.android.mms.model.SlideshowModel;
import com.google.android.mms.MmsException;
import com.google.android.mms.pdu.GenericPdu;
import com.google.android.mms.pdu.MultimediaMessagePdu;
import com.google.android.mms.pdu.PduPersister;
import com.google.android.mms.util.PduCache;
import com.google.android.mms.util.PduCacheEntry;

/**
 * Primary {@link PduLoaderManager} implementation used by {@link MessagingApplication}.
 * <p>
 * Public methods should only be used from a single thread (typically the UI
 * thread). Callbacks will be invoked on the thread where the PduLoaderManager
 * was instantiated.
 * <p>
 * Uses a thread-pool ExecutorService instead of AsyncTasks since clients may
 * request lots of pdus 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.
 * <p>
 * PduLoaderManager is used to asynchronously load mms pdu's and then build a slideshow model
 * from that loaded pdu. Then it will call the passed in callback with the result. This class
 * uses the PduCache built into the mms framework. It also manages a local cache of slideshow
 * models. The slideshow cache uses SoftReferences to hang onto the slideshow.
 *
 * Based on BooksImageManager by Virgil King.
 */
public class PduLoaderManager extends BackgroundLoaderManager {
    private static final String TAG = "Mms:PduLoaderManager";

    private static final boolean DEBUG_DISABLE_CACHE = false;
    private static final boolean DEBUG_DISABLE_PDUS = false;
    private static final boolean DEBUG_LONG_WAIT = false;

    private static PduCache mPduCache;
    private final PduPersister mPduPersister;
    private final SimpleCache<Uri, SlideshowModel> mSlideshowCache;
    private final Context mContext;

    public PduLoaderManager(final Context context) {
        super(context);

        mSlideshowCache = new SimpleCache<Uri, SlideshowModel>(8, 16, 0.75f, true);
        mPduCache = PduCache.getInstance();
        mPduPersister = PduPersister.getPduPersister(context);
        mContext = context;
    }

    public ItemLoadedFuture getPdu(Uri uri, boolean requestSlideshow,
            final ItemLoadedCallback<PduLoaded> callback) {
        if (uri == null) {
            throw new NullPointerException();
        }

        PduCacheEntry cacheEntry = null;
        synchronized(mPduCache) {
            if (!mPduCache.isUpdating(uri)) {
                cacheEntry = mPduCache.get(uri);
            }
        }
        final SlideshowModel slideshow = (requestSlideshow && !DEBUG_DISABLE_CACHE) ?
                mSlideshowCache.get(uri) : null;

        final boolean slideshowExists = (!requestSlideshow || slideshow != null);
        final boolean pduExists = (cacheEntry != null && cacheEntry.getPdu() != null);
        final boolean taskExists = mPendingTaskUris.contains(uri);
        final boolean newTaskRequired = (!pduExists || !slideshowExists) && !taskExists;
        final boolean callbackRequired = (callback != null);

        if (pduExists && slideshowExists) {
            if (callbackRequired) {
                PduLoaded pduLoaded = new PduLoaded(cacheEntry.getPdu(), slideshow);
                callback.onItemLoaded(pduLoaded, null);
            }
            return new NullItemLoadedFuture();
        }

        if (callbackRequired) {
            addCallback(uri, callback);
        }

        if (newTaskRequired) {
            mPendingTaskUris.add(uri);
            Runnable task = new PduTask(uri, requestSlideshow);
            mExecutor.execute(task);
        }
        return new ItemLoadedFuture() {
            public void cancel() {
                cancelCallback(callback);
            }
            public boolean isDone() {
                return false;
            }
        };
    }

    @Override
    public void clear() {
        super.clear();

        synchronized(mPduCache) {
            mPduCache.purgeAll();
        }
        mSlideshowCache.clear();
    }

    public void removePdu(Uri uri) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "removePdu: " + uri);
        }
        synchronized(mPduCache) {
            mPduCache.purge(uri);
        }
        mSlideshowCache.remove(uri);
    }

    public String getTag() {
        return TAG;
    }

    public class PduTask implements Runnable {
        private final Uri mUri;
        private final boolean mRequestSlideshow;

        public PduTask(Uri uri, boolean requestSlideshow) {
            if (uri == null) {
                throw new NullPointerException();
            }
            mUri = uri;
            mRequestSlideshow = requestSlideshow;
        }

        /** {@inheritDoc} */
        public void run() {
            if (DEBUG_DISABLE_PDUS) {
                return;
            }
            if (DEBUG_LONG_WAIT) {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                }
            }
            GenericPdu pdu = null;
            SlideshowModel slideshow = null;
            Throwable exception = null;
            try {
                pdu = mPduPersister.load(mUri);
                if (pdu != null && mRequestSlideshow) {
                    slideshow = SlideshowModel.createFromPduBody(mContext,
                            ((MultimediaMessagePdu)pdu).getBody());
                }
            } catch (final MmsException e) {
                Log.e(TAG, "MmsException loading uri: " + mUri, e);
                exception = e;
            }
            final GenericPdu resultPdu = pdu;
            final SlideshowModel resultSlideshow = slideshow;
            final Throwable resultException = exception;
            mCallbackHandler.post(new Runnable() {
                public void run() {
                    final Set<ItemLoadedCallback> callbacks = mCallbacks.get(mUri);
                    if (callbacks != null) {
                        // Make a copy so that the callback can unregister itself
                        for (final ItemLoadedCallback<PduLoaded> callback : asList(callbacks)) {
                            if (Log.isLoggable(TAG, Log.DEBUG)) {
                                Log.d(TAG, "Invoking pdu callback " + callback);
                            }
                            PduLoaded pduLoaded = new PduLoaded(resultPdu, resultSlideshow);
                            callback.onItemLoaded(pduLoaded, resultException);
                        }
                    }
                    // Add the slideshow to the soft cache if the load succeeded
                    if (resultSlideshow != null) {
                        mSlideshowCache.put(mUri, resultSlideshow);
                    }

                    mCallbacks.remove(mUri);
                    mPendingTaskUris.remove(mUri);

                    if (Log.isLoggable(LogTag.PDU_CACHE, Log.DEBUG)) {
                        Log.d(TAG, "Pdu task for " + mUri + "exiting; " + mPendingTaskUris.size()
                                + " remain");
                    }
                }
            });
        }
    }

    public static class PduLoaded {
        public final GenericPdu mPdu;
        public final SlideshowModel mSlideshow;

        public PduLoaded(GenericPdu pdu, SlideshowModel slideshow) {
            mPdu = pdu;
            mSlideshow = slideshow;
        }
    }
}
