package vandy.mooc.common;

import java.lang.ref.WeakReference;
import java.util.HashMap;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.util.Log;

/**
 * Retains and manages state information between runtime configuration
 * changes to an Activity.  Plays the role of the "Originator" in the
 * Memento pattern.
 */
public class RetainedFragmentManager {
    /**
     * Debugging tag used by the Android logger.
     */
    protected final String TAG = getClass().getSimpleName();

    /**
     * Name used to identify the RetainedFragment.
     */
    private final String mRetainedFragmentTag;
        
    /**
     * WeakReference to the FragmentManager.
     */
    private final WeakReference<FragmentManager> mFragmentManager;

    /**
     * Reference to the RetainedFragment.
     */
    private RetainedFragment mRetainedFragment;

    /**
     * Constructor initializes fields.
     */ 
    public RetainedFragmentManager(FragmentManager fragmentManager,
                                   String retainedFragmentTag) {
        // Store a WeakReference to the Activity.
        mFragmentManager =
            new WeakReference<FragmentManager>(fragmentManager);

        // Store the tag used to identify the RetainedFragment.
        mRetainedFragmentTag = retainedFragmentTag;
    }

    /**
     * Initializes the RetainedFragment the first time it's called.
     *
     * @returns true if it's first time the method's been called, else
     *          false.
     */
    public boolean firstTimeIn() {
        try {
            // Find the RetainedFragment on Activity restarts.  The
            // RetainedFragment has no UI so it must be referenced via
            // a tag.
            mRetainedFragment = (RetainedFragment) 
                mFragmentManager.get().findFragmentByTag(mRetainedFragmentTag);

            // A value of null means it's the first time in, so there's
            // extra work to do.
            if (mRetainedFragment == null) {
                Log.d(TAG,
                      "Creating new RetainedFragment "
                      + mRetainedFragmentTag);

                // Create a new RetainedFragment.
                mRetainedFragment = new RetainedFragment();

                // Commit this RetainedFragment to the FragmentManager.
                mFragmentManager.get().beginTransaction().
                    add(mRetainedFragment,
                        mRetainedFragmentTag).commit();
                return true;
            } 
            // A value of non-null means it's not first time in.
            else {
                Log.d(TAG,
                      "Returning existing RetainedFragment "
                      + mRetainedFragmentTag);
                return false;
            }
        } catch (NullPointerException e) {
            Log.d(TAG,
                  "NPE in firstTimeIn()");
            return false;
        }
    }

    /**
     * Add the @a object with the @a key.
     */
    public void put(String key, Object object) {
        mRetainedFragment.put(key, object);
    }

    /**
     * Add the @a object with its class name.
     */
    public void put(Object object) {
        put(object.getClass().getName(), object);
    }

    /**
     * Get the object with @a key.
     */
    @SuppressWarnings("unchecked")
    public <T> T get(String key) {
        return (T) mRetainedFragment.get(key);
    }

    /**
     * Return the Activity the RetainedFragment is attached to or null
     * if it's not currently attached.
     */
    public Activity getActivity() {
        return mRetainedFragment.getActivity();
    }

    /**
     * "Headless" Fragment that retains state information between
     * configuration changes.  Plays the role of the "Memento" in the
     * Memento pattern.
     */
    public static class RetainedFragment extends Fragment {
        /**
         * Maps keys to objects.
         */
        private HashMap<String, Object> mData =
            new HashMap<String, Object>();

        /**
         * Hook method called when a new instance of Fragment is
         * created.
         *
         * @param savedInstanceState
         *            object that contains saved state information.
         */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            
            // Ensure the data survives runtime configuration changes.
            setRetainInstance(true);
        }

        /**
         * Add the @a object with the @a key.
         */
        public void put(String key, Object object) {
            mData.put(key, object);
        }

        /**
         * Add the @a object with its class name.
         */
        public void put(Object object) {
            put(object.getClass().getName(), object);
        }

        /**
         * Get the object with @a key.
         */
        @SuppressWarnings("unchecked")
        public <T> T get(String key) {
            return (T) mData.get(key);
        }
    }
}
