package vandy.mooc.presenter;

import java.io.InputStream;
import java.lang.ref.WeakReference;

import vandy.mooc.MVP;
import vandy.mooc.R;
import vandy.mooc.common.GenericPresenter;
import vandy.mooc.model.ImageDownloadsModel;
import vandy.mooc.presenter.strategies.DownloadContext;
import vandy.mooc.presenter.strategies.DownloadWithAsyncTask;
import vandy.mooc.presenter.strategies.DownloadWithMessages;
import vandy.mooc.presenter.strategies.DownloadWithRunnables;
import vandy.mooc.presenter.strategies.ImageStrategy;
import vandy.mooc.presenter.strategies.ResetBitmap;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

/**
 * This class plays the "Presenter" role in the Model-View-Presenter
 * (MVP) pattern by acting upon the Model and the View, i.e., it
 * retrieves data from the Model (e.g., ImageDownloadsModel) and
 * formats it for display in the View (e.g., ImageDownloadsActivity).
 * It expends the GenericPresenter superclass and implements
 * MVP.ProvidedPresenterOps and MVP.RequiredModelOps so it can be
 * created/managed by the GenericPresenter framework.
 */
public class ImageDownloadsPresenter
       extends GenericPresenter<MVP.RequiredPresenterOps,
                                MVP.ProvidedModelOps,
                                ImageDownloadsModel>
       implements MVP.ProvidedPresenterOps,
                  MVP.RequiredPresenterOps {
    /**
     * A WeakReference used to access methods in the View layer.  The
     * WeakReference enables garbage collection.
     */
    private WeakReference<MVP.RequiredViewOps> mView;

    /**
     * Maps buttons (represented via their resource ids) to
     * ImageStrategy implementations.
     */
    private ButtonToImageStrategyMapper mButtonToImageStrategyMapper;

    /**
     * The currently active ImageStrategy, which is used to keep track
     * of whether we need to cancel an ongoing download before
     * initiating a new one.
     */
    private ImageStrategy mActiveImageStrategy = null;

    /**
     * Stores the current image to redisplay after a runtime
     * configuration change.
     */
    private Bitmap mCurrentImage = null;

    /**
     * Stores the default image to display when the "reset image"
     * button is pushed.
     */
    private Bitmap mDefaultImage = null;

    /**
     * Default constructor that's needed by the GenericActivity
     * framework.
     */
    public ImageDownloadsPresenter() {
    }

    /**
     * Hook method called when a new instance of AcronymPresenter is
     * created.  One time initialization code goes here, e.g., storing
     * a WeakReference to the View layer and initializing the Model
     * layer.
     * 
     * @param view
     *            A reference to the View layer.
     */
    @Override
    public void onCreate(MVP.RequiredViewOps view) {
        // Set the WeakReference.
        mView =
            new WeakReference<>(view);

        // Initialize the ImageStrategyMapper that efficiently maps
        // button ids to strategies for downloading and displaying
        // images concurrently.
        mButtonToImageStrategyMapper = 
            new ButtonToImageStrategyMapper
            (new int[] { 
                R.id.runnable_button,
                R.id.messages_button,
                R.id.async_task_button,
                R.id.reset_image_button },
             new ImageStrategy[] {
                new DownloadWithRunnables(),
                new DownloadWithMessages(),
                new DownloadWithAsyncTask(),
                new ResetBitmap()
             });


        // Extract the default image.
        final InputStream is = (InputStream)
            mView.get()
                 .getActivityContext()
                 .getResources()
                 .openRawResource(R.drawable.default_image);

        // Decode an InputStream into a Bitmap that
        // stores the default image.
        mDefaultImage = BitmapFactory.decodeStream(is);

        // Show the default image on the user's screen.
        resetBitmap();

        // Invoke the special onCreate() method in GenericPresenter,
        // passing in the ImageDownloadsModel class to
        // instantiate/manage and "this" to provide
        // ImageDownloadsModel with this MVP.RequiredModelOps
        // instance.
        super.onCreate(ImageDownloadsModel.class,
                       this);
    }

    /**
     * Hook method dispatched by the GenericActivity framework to
     * initialize the ImageDownloadsPresenter object after it's been
     * created.
     *
     * @param view         
     *          The currently active MVP.RequiredViewOps.
     */
    @Override
    public void onConfigurationChange(MVP.RequiredViewOps view) {
        Log.d(TAG,
              "onConfigurationChange() called");

        // Reset the WeakReference.
        mView =
            new WeakReference<>(view);

        // Set the default image.
        mView.get().displayBitmap(mCurrentImage,
                                  null);
    }

    /**
     * Hook method called to shutdown the Model layer.
     *
     * @param isChangeConfigurations
     *        True if a runtime configuration triggered the onDestroy() call.
     */
    @Override
    public void onDestroy(boolean isChangingConfigurations) {
        // Destroy the model.
        getModel().onDestroy(isChangingConfigurations);
    }

    /**
     * Called when a user clicks a button to download an image.
     * 
     * @param buttonResId
     *            Indicates the button pressed by the user.
     * @param url
     *            URL givenby the user.
     */
    public void handleButtonClick(int buttonResId,
                                  String url) {
        // Create a DownloadContext object associated with this user
        // request, which plays the role of the "Context" in the
        // Strategy pattern.
        final DownloadContext downloadContext =
            makeDownloadContext(url);

        // Only one download/display is allowed at a time, so if an
        // operation is already in progress then cancel it first.
        if (mActiveImageStrategy != null) 
            mActiveImageStrategy.cancel(downloadContext);

        // Get the ImageStrategy associated with the buttonResId.
        mActiveImageStrategy = 
            mButtonToImageStrategyMapper.getImageStrategy(buttonResId);

        // Invoke the ImageStrategy to download and display the
        // image concurrently.
        mActiveImageStrategy.downloadAndDisplay(downloadContext);
    }

    /**
     * Factory method that returns the DownloadContext associated with
     * this user request, which plays the role of the "Context" in the
     * Strategy pattern.
     * 
     * @param url
     *        URL to download.
     */
    private DownloadContext makeDownloadContext(String url) {
        // This command is called back after the image is displayed to
        // indicate there's no active ImageStrategy.
        final Runnable completionCommand =
            new Runnable() {
                public void run() {
                    // Indicate there's no active ImageStrategy.
                    mActiveImageStrategy = null;
                }
            };

        // Create a DownloadContext that stores references to the
        // MVP.ProvidedModelOps and completion hook objects in fields,
        // which are used by the various concrete ButtonStrategies to
        // download and display an image concurrently.
        return new DownloadContext(url,
                                   this,
                                   getModel(),
                                   completionCommand);
    }

    /**
     * Set the current image.
     */
    public void setCurrentImage(Bitmap image) {
        mCurrentImage = image;
    }

    /**
     * Reset bitmap display on the user's screen to the default image.
     */
    public void resetBitmap() {
        // Store the current image for subsequent use in case of a
        // runtime configuration change.
        setCurrentImage(mDefaultImage);

        // Set the default image.
        mView.get().displayBitmap(mDefaultImage, null);
    }

    /**
     * Display a downloaded bitmap image if it's non-null; otherwise,
     * it reports an error via a Toast that's displayed on the UI
     * Thread.  This method can be called from either the UI Thread or
     * a background Thread.
     * 
     * @param image
     *            The bitmap image
     * @param completionCommand
     *            Command whose run() hook method is called after the
     *            image is displayed.
     */
    public void displayBitmap(Bitmap image,
                              Runnable completionCommand) {
        // Check to see if we've been interrupted.
        if (Thread.interrupted())
            return;
        else {
            // Store the current image for subsequent use in case of a
            // runtime configuration change.
            setCurrentImage(image);

            // Display this image.
            mView.get()
                 .displayBitmap(image,
                                completionCommand);
        }
    }

    /**
     * Return the Activity context.
     */
    @Override
    public Context getActivityContext() {
        return mView.get().getActivityContext();
    }
    
    /**
     * Return the Application context.
     */
    @Override
    public Context getApplicationContext() {
        return mView.get().getApplicationContext();
    }
}

