/*
 * Copyright 2016 Mario Guggenberger <mg@protyposis.net>
 *
 * 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 net.protyposis.android.spectaculum.effects;

import android.os.Handler;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

import net.protyposis.android.spectaculum.LibraryHelper;
import net.protyposis.android.spectaculum.SpectaculumView;
import net.protyposis.android.spectaculum.gles.GLUtils;

/**
 * Created by Mario on 08.10.2016.
 */
public class TouchNavigation {

    private static final String TAG = TouchNavigation.class.getSimpleName();

    private SpectaculumView mSpectaculumView;
    private boolean mSpectaculumViewTouchEnabled;
    private GestureDetector mGestureDetector;
    private float mPanX;
    private float mPanY;
    private EquirectangularSphereEffect mEffect;
    private BooleanParameter mParameter;
    private boolean mActive;
    private float[] mRotationMatrix = new float[16];

    public TouchNavigation(SpectaculumView spectaculumView) throws Exception {
        mSpectaculumView = spectaculumView;

        if(mSpectaculumView == null) {
            throw new Exception("No Spectaculum view supplied");
        }

        mGestureDetector = new GestureDetector(mSpectaculumView.getContext(), mOnGestureListener);

        // Make a UI handler for activation state toggling
        final Handler h = new Handler();

        // Create an effect parameter to toggle the touch navigation on/off
        mParameter = new BooleanParameter("TouchNav", false, new BooleanParameter.Delegate() {
            @Override
            public void setValue(final Boolean value) {
                // Activate/deactivate on UI thread
                // Parameters are usually set on the GL thread, so we need to transfer this back to the UI thread
                h.post(new Runnable() {
                    @Override
                    public void run() {
                        mActive = value;
                        if(mActive) {
                            activate();
                        } else {
                            deactivate();
                        }
                    }
                });
            }
        });
    }

    public void attachTo(EquirectangularSphereEffect effect) throws Exception {
        if(mEffect != null) {
            throw new Exception("Previous effect is still attached, call detach() first");
        }
        mEffect = effect;
        mEffect.addParameter(mParameter);
    }

    public void detach() {
        mEffect.removeParameter(mParameter);
        mEffect = null;
    }

    public void activate() {
        mSpectaculumView.setOnTouchListener(mOnTouchListener);

        // Store touch enabled state and enable touch which is required for this to work
        mSpectaculumViewTouchEnabled = mSpectaculumView.isTouchEnabled();
        mSpectaculumView.setTouchEnabled(true);
    }

    public void deactivate() {
        mSpectaculumView.setOnTouchListener(null);
        mSpectaculumView.setTouchEnabled(mSpectaculumViewTouchEnabled);
    }

    private void setRotation(float x, float y) {
        // Set rotation matrix
        GLUtils.Matrix.setRotateEulerM(mRotationMatrix, 0, x, y, 0);

        // Update effect and thus the viewport too
        mEffect.setRotationMatrix(mRotationMatrix);
    }

    private View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            return mGestureDetector.onTouchEvent(event);
        }
    };

    private GestureDetector.SimpleOnGestureListener mOnGestureListener = new GestureDetector.SimpleOnGestureListener() {

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            // Scale the scroll/panning distance to rotation degrees
            // The view's with and height are mapped to 180 degree each
            // TODO map motion event positions from view to the rendered sphere and derive rotation
            //      angles to keep touchscreen positions and sphere positions in sync
            mPanX += distanceX / mSpectaculumView.getWidth() * 180f;
            mPanY += distanceY / mSpectaculumView.getHeight() * 180f;

            // Clamp horizontal rotation to avoid rotations beyond 90 degree which inverts the vertical
            // rotation and makes rotation handling more complicated
            mPanY = LibraryHelper.clamp(mPanY, -90, 90);

            // Apply the panning to the viewport
            // Horizontal panning along the view's X axis translates to a rotation around the viewport's Y axis
            // Vertical panning along the view's Y axis translates to a rotation around the viewport's X axis
            setRotation(-mPanY, -mPanX);
            return true;
        }

        @Override
        public boolean onDoubleTap(MotionEvent e) {
            // Reset rotation/viewport to initial value
            mPanX = 0;
            mPanY = 0;
            setRotation(0, 0);
            return true;
        }
    };
}

@@ -977,38 +977,81 @@
 %0A * 
-Created by Mario on 08.10.2016
+Adds viewport navigation by touch scroll gestures to the immersive effect
 .%0A *
@@ -1527,16 +1527,221 @@
 t%5B16%5D;%0A%0A
+    /**%0A     * Creates a touch navigation instance for the supplied view widget.%0A     * @param spectaculumView the view widget where the touch gestures should be read from%0A     * @throws Exception%0A     */%0A
     publ
@@ -2984,32 +2984,265 @@
      %7D);%0A    %7D%0A%0A
+    /**%0A     * Attaches to the effect and adds parameter to toggle touch navigation on/off.%0A     * @param effect the effect to attach touch navigation to%0A     * @throws Exception thrown if there is already an effect attached%0A     */%0A
     public void 
@@ -3503,32 +3503,149 @@
 ameter);%0A    %7D%0A%0A
+    /**%0A     * Detaches touch navigation from the effect it is attached to and removes the toggle parameter.%0A     */%0A
     public void 
@@ -3723,32 +3723,83 @@
  = null;%0A    %7D%0A%0A
+    /**%0A     * Activates touch navigation.%0A     */%0A
     public void 
@@ -4089,24 +4089,77 @@
 ue);%0A    %7D%0A%0A
+    /**%0A     * Deactivates touch navigation.%0A     */%0A
     public v

Add javadoc