package yuku.alkitab.base.widget;

import android.content.Context;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import yuku.alkitab.base.util.AppLog;
import yuku.alkitab.debug.BuildConfig;

public class TwofingerLinearLayout extends LinearLayout {
	static final String TAG = TwofingerLinearLayout.class.getSimpleName();

	State state = State.none;
	Mode mode = null;
	Listener listener;

	// position of one finger (for swiping left and right)
	PointF onefingerStart = new PointF();

	// distance when twofinger starts
	float startDist;
	// distance when twofinger enters scale mode
	float startScaleDist;

	// average position when twofinger starts
	PointF startAvg = new PointF();

	// minimum distance to be considered swipe
	float threshold_swipe;

	// minimum distance to be considered drag
	float threshold_twofinger_drag;

	// minimum distance change to be considered scale
	float threshold_twofinger_scale;

	// if not enabled, one finger gestures, i.e. swipe, will not be captured.
	// by default it's true
	boolean onefingerEnabled = true;

	// if not enabled, two finger gestures will not be captured.
	// by default it's true
	boolean twofingerEnabled = true;

	public TwofingerLinearLayout(Context context) {
		super(context);
		init();
	}

	public TwofingerLinearLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	private void init() {
		final float density = getResources().getDisplayMetrics().density;
		threshold_swipe = 48.f * density;
		threshold_twofinger_drag = 48.f * density;
		threshold_twofinger_scale = 72.f * density;
	}

	public void setOnefingerEnabled(final boolean onefingerEnabled) {
		this.onefingerEnabled = onefingerEnabled;
	}

	public void setTwofingerEnabled(final boolean twofingerEnabled) {
		this.twofingerEnabled = twofingerEnabled;
	}

	public void setListener(final Listener listener) {
		this.listener = listener;
	}

	@Override
	public boolean onTouchEvent(final MotionEvent event) {
		final int action = event.getActionMasked();

		final int pointerCount = event.getPointerCount();

		float x1 = event.getX(0);
		float y1 = event.getY(0);
		float x2 = 0;
		float y2 = 0;

		if (pointerCount >= 2) {
			x2 = event.getX(1);
			y2 = event.getY(1);
		}

		if (state == State.onefinger_left) {
			listener.onOnefingerLeft();
			state = State.none;
			mode = null;
			return true;
		} else if (state == State.onefinger_right) {
			listener.onOnefingerRight();
			state = State.none;
			mode = null;
			return true;
		} else if (state == State.twofinger_start) {
			if (pointerCount >= 2) {
				startDist = dist(x1 - x2, y1 - y2);
				startAvg.x = 0.5f * (x1 + x2);
				startAvg.y = 0.5f * (y1 + y2);

				listener.onTwofingerStart();
				state = State.twofinger_performing;
			}
			return true;
		} else if (state == State.twofinger_performing) {
			if (pointerCount >= 2) {
				float nowDist = dist(x1 - x2, y1 - y2);

				float nowAvgX = 0.5f * (x1 + x2);
				float nowAvgY = 0.5f * (y1 + y2);
				float dx = nowAvgX - startAvg.x;
				float dy = nowAvgY - startAvg.y;

				// start condition
				if (mode == null) {
					float scale = nowDist / startDist;
					float distChange = Math.abs(nowDist - startDist);

					// Scale mode is started when scale differs by 10~15% or more
					// and distance between two fingers changes by a certain threshold
					if ((scale < 0.9f || scale >= 1.15f) && (distChange > threshold_twofinger_scale)) {
						mode = Mode.scale;
						startScaleDist = nowDist;
					}
				}
				if (mode == null) {
					if (dx > threshold_twofinger_drag || dx < -threshold_twofinger_drag) {
						if (Math.abs(dy) < Math.abs(dx) * 0.5f) {
							mode = Mode.drag_x; // drag in x
						}
					}
					if (dy > threshold_twofinger_drag || dy < -threshold_twofinger_drag) {
						if (Math.abs(dx) < Math.abs(dy) * 0.5f) {
							mode = Mode.drag_y; // drag in y
						}
					}
				}

				if (mode != null) {
					if (mode == Mode.scale) {
						listener.onTwofingerScale(nowDist / startScaleDist);
					} else if (mode == Mode.drag_x) {
						listener.onTwofingerDragX(dx);
					} else if (mode == Mode.drag_y) {
						listener.onTwofingerDragY(dy);
					}
				}

				return true;
			} else {
				listener.onTwofingerEnd(mode);
				state = State.none;
				mode = null;

				return true;
			}
		} else {
			listener.onTwofingerEnd(mode);
			state = State.none;
			mode = null;

			return super.onTouchEvent(event);
		}
	}

	float dist(float dx, float dy) {
		return (float) Math.sqrt(dx * dx + dy * dy);
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent event) {
		final int action = event.getActionMasked();
		final int pointerCount = event.getPointerCount();

		// one finger for swipe left/right
		if (pointerCount == 1) {
			if (action == MotionEvent.ACTION_DOWN) {
				onefingerStart.x = event.getX();
				onefingerStart.y = event.getY();
			} else if (action == MotionEvent.ACTION_MOVE) {
				if (onefingerStart.x == Float.MIN_VALUE) {
					// invalidated
				} else {
					float dx = event.getX() - onefingerStart.x;
					float dy = event.getY() - onefingerStart.y;
					float ady = Math.abs(dy);

					if (onefingerEnabled && dx > threshold_swipe && ady < 0.5f * threshold_swipe) {
						// swipe to right
						state = State.onefinger_right;
						return true;
					} else if (onefingerEnabled && dx < -threshold_swipe && ady < 0.5f * threshold_swipe) {
						// swipe to left
						state = State.onefinger_left;
						return true;
					} else if (ady > threshold_swipe) {
						// invalidate
						onefingerStart.x = Float.MIN_VALUE;
					}
				}
			}
		} else if (pointerCount == 2 && twofingerEnabled) {
			if (action == MotionEvent.ACTION_POINTER_DOWN) {
				state = State.twofinger_start;
				return true;
			}
		}

		return false;
	}

	enum State {
		none,
		onefinger_left,
		onefinger_right,
		twofinger_start,
		twofinger_performing,
	}

	public enum Mode {
		scale,
		drag_x,
		drag_y,
	}

	public interface Listener {
		void onOnefingerLeft();
		void onOnefingerRight();
		void onTwofingerStart();
		void onTwofingerScale(float scale);
		void onTwofingerDragX(float dx);
		void onTwofingerDragY(float dy);
		void onTwofingerEnd(@Nullable Mode mode);
	}

	public abstract static class OnefingerListener implements Listener {
		@Override
		public void onTwofingerStart() {}

		@Override
		public void onTwofingerScale(final float scale) {}

		@Override
		public void onTwofingerDragX(final float dx) {}

		@Override
		public void onTwofingerDragY(final float dy) {}

		@Override
		public void onTwofingerEnd(@Nullable final Mode mode) {}
	}

	// From API 19
	/**
	 * Returns a string that represents the symbolic name of the specified unmasked action
	 * such as "ACTION_DOWN", "ACTION_POINTER_DOWN(3)" or an equivalent numeric constant
	 * such as "35" if unknown.
	 *
	 * @param action The unmasked action.
	 * @return The symbolic name of the specified action.
	 * @see android.view.MotionEvent#getAction()
	 */
	public static String actionToString(int action) {
		switch (action) {
			case MotionEvent.ACTION_DOWN:
				return "ACTION_DOWN";
			case MotionEvent.ACTION_UP:
				return "ACTION_UP";
			case MotionEvent.ACTION_CANCEL:
				return "ACTION_CANCEL";
			case MotionEvent.ACTION_OUTSIDE:
				return "ACTION_OUTSIDE";
			case MotionEvent.ACTION_MOVE:
				return "ACTION_MOVE";
			case MotionEvent.ACTION_HOVER_MOVE:
				return "ACTION_HOVER_MOVE";
			case MotionEvent.ACTION_SCROLL:
				return "ACTION_SCROLL";
			case MotionEvent.ACTION_HOVER_ENTER:
				return "ACTION_HOVER_ENTER";
			case MotionEvent.ACTION_HOVER_EXIT:
				return "ACTION_HOVER_EXIT";
		}
		int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
		switch (action & MotionEvent.ACTION_MASK) {
			case MotionEvent.ACTION_POINTER_DOWN:
				return "ACTION_POINTER_DOWN(" + index + ")";
			case MotionEvent.ACTION_POINTER_UP:
				return "ACTION_POINTER_UP(" + index + ")";
			default:
				return Integer.toString(action);
		}
	}
}