package uk.co.chrisjenx.paralloid;

import android.graphics.drawable.Drawable;
import android.view.View;

import java.util.Iterator;
import java.util.Set;
import java.util.WeakHashMap;

import uk.co.chrisjenx.paralloid.graphics.ParallaxDrawable;
import uk.co.chrisjenx.paralloid.transform.LinearTransformer;
import uk.co.chrisjenx.paralloid.transform.Transformer;
import uk.co.chrisjenx.paralloid.utils.ParallaxHelper;

/**
 * Parallax Controller that take input from anything.
 * <p/>
 * Created by chris on 23/10/2013
 * Project: Paralloid
 */
public class ParallaxController<T extends Object> implements ParallaxorListener {

    public static final String TAG = ParallaxController.class.getSimpleName();
    /**
     * HashMap which contains the parallaxed views.
     */
    protected WeakHashMap<View, ParallaxViewInfo> mViewHashMap;
    /**
     * The Background of Views to Parallax
     */
    protected WeakHashMap<View, ParallaxDrawable> mParallaxDrawableMap;
    /**
     * The Optional Scroll Changed Listener for the user to listen to scroll events.
     */
    protected OnScrollChangedListener mScrollChangedListener;
    /**
     * Gets set to true if a View which has its own OnScrollListener
     */
    protected boolean mIgnoreOnScrollListener;
    //
    protected int mLastScrollX = 0;
    protected int mLastScrollY = 0;

    /**
     * The Wrapped Object
     */
    protected final T mWrapped;

    protected ParallaxController(T wrapped) {
        mWrapped = wrapped;
    }

    public T getWrapped() {
        return mWrapped;
    }

    /**
     * Add a view to be parallax'd by. If already set this will replace the current factor.
     *
     * @param view       passing in yourself will throw an exception.
     * @param multiplier passing in
     */
    public void parallaxViewBy(View view, float multiplier) {
        parallaxViewBy(view, null, multiplier);
    }

    @Override
    public void parallaxViewBy(View view, Transformer transformer, float multiplier) {
        if (view == null) return;
        if (view == mWrapped)
            throw new IllegalArgumentException("You can't parallax yourself, this would end badly, Parallax other Views");
        checkViewMap();

        mViewHashMap.put(view, new ParallaxViewInfo(multiplier, transformer));
        // We force this to update the view just added
        onScrollChanged(mLastScrollX, mLastScrollY, mLastScrollX, mLastScrollY, true);
    }

    /**
     * @param view       View which to attach the drawable to. (null will do nothing)
     * @param drawable   Drawable to Parallax (null will do nothing)
     * @param multiplier How much to move it by, 1 would be a 1:1 relationship. 0.5 would move the.
     *                   Passing in 0 will make the background not move at all.
     */
    @Override
    public void parallaxViewBackgroundBy(final View view, final Drawable drawable, final float multiplier) {
        if (view == null) return;
        checkBackgroundMap();

        final ParallaxDrawable parallaxBackground = ParallaxHelper.getParallaxDrawable(drawable, multiplier);
        ParallaxHelper.setParallaxBackground(view, parallaxBackground);
        mParallaxDrawableMap.put(view, parallaxBackground);
    }

    /**
     * Feel free to implement {@link OnScrollChangedListener} to get call
     * backs to the wrapped view for scroll changed events.
     * <p/>
     * <b>Note</b>: this will get called, AFTER any parallax modification.
     *
     * @param onScrollChangedListener Null is valid (it will remove it if set).
     */
    @Override
    public void setOnScrollListener(OnScrollChangedListener onScrollChangedListener) {
        mScrollChangedListener = onScrollChangedListener;
    }

    /**
     * Used to do parallax, Your implementation should call directly through to this. Some implementations ignore this
     * on purpose (e.g. the ListView)
     *
     * @param who if Who != getWrapped we ignore scrolling, only the object the controller is attached too can
     *            scroll it.
     */
    @Override
    public final void onScrollChanged(Object who, int l, int t, int oldl, int oldt) {
        if (who != getWrapped() || mIgnoreOnScrollListener) return;
        onScrollChanged(l, t, oldl, oldt, false);
    }

    protected final void onScrollChanged(int offsetX, int offsetY, int oldOffsetX, int oldOffsetY, boolean force) {
        if (offsetX != oldOffsetX || offsetY != oldOffsetY || force) {
            doScrollChanged(offsetX, offsetY, oldOffsetX, oldOffsetY);
        }
    }

    /**
     * Will do the scroll changed stuff.
     *
     * @param x    currentX of Parallaxor View
     * @param y    currentX of Parallaxor View
     * @param oldX Previous X
     * @param oldY Previous Y
     */
    protected final void doScrollChanged(final int x, final int y, final int oldX, final int oldY) {
        doScrollViews(x, y);
        //Parallax this background if we can
        doScrollBackground(x, y);
        // Scroll Changed Listener
        doScrollListener(getWrapped(), x, y, oldX, oldY);

        // Set new LastScrollPos
        mLastScrollX = x;
        mLastScrollY = y;
    }

    // --
    // doScrollChanged Pointers to keep memory consumption down during fast scrolling
    //
    private Set<View> keySetPointer;
    private Iterator<View> iteratorPointer;
    private ParallaxViewInfo parallaxInfoPointer;
    private ParallaxDrawable parallaxDrawablePointer;
    private View viewPointer;
    // --

    private final void doScrollViews(final int x, final int y) {
        if (mViewHashMap == null) return;
        keySetPointer = mViewHashMap.keySet();
        iteratorPointer = keySetPointer.iterator();
        while (iteratorPointer.hasNext()) {
            viewPointer = iteratorPointer.next();

            if (viewPointer == null)
                continue;

            // Get Value
            parallaxInfoPointer = mViewHashMap.get(viewPointer);

            // Parallax the other view
            ParallaxHelper.scrollViewBy(viewPointer, x, y, parallaxInfoPointer.interpolator, parallaxInfoPointer.factor);
        }
    }

    private final void doScrollBackground(final int x, final int y) {
        if (mParallaxDrawableMap == null) return;

        keySetPointer = mParallaxDrawableMap.keySet();
        iteratorPointer = keySetPointer.iterator();
        while (iteratorPointer.hasNext()) {
            viewPointer = iteratorPointer.next();

            if (viewPointer == null)
                continue;

            // Get Value
            parallaxDrawablePointer = mParallaxDrawableMap.get(viewPointer);
            ParallaxHelper.scrollParallaxDrawableBy(parallaxDrawablePointer, x, y);
        }

    }

    private final void doScrollListener(final Object who, final int x, final int y, final int oldX, final int oldY) {
        if (mScrollChangedListener != null && (x != oldX || y != oldY)) {
            mScrollChangedListener.onScrollChanged(who, x, y, oldX, oldY);
        }
    }

    /**
     * Checks the viewMap is ready to use.
     */
    private final void checkViewMap() {
        if (mViewHashMap == null)
            mViewHashMap = new WeakHashMap<View, ParallaxViewInfo>();
    }

    /**
     * Checks the BackgroundMap is ready to use.
     */
    private final void checkBackgroundMap() {
        if (mParallaxDrawableMap == null)
            mParallaxDrawableMap = new WeakHashMap<View, ParallaxDrawable>();
    }
}

@@ -243,70 +243,8 @@
 le;%0A
-import uk.co.chrisjenx.paralloid.transform.LinearTransformer;%0A
 impo
@@ -6530,24 +6530,62 @@
 iewPointer);
+%0A%0A            // Parallax the drawable
 %0A           

clean up