package com.teskalabs.cvio.inapp;

import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;

import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class InAppInputManager implements Application.ActivityLifecycleCallbacks {

	private static final String TAG = InAppInputManager.class.getName();

	private final Object currentActivityLock = new Object(); // Synchronize access to currentActivity and currentRootView
	private WeakReference<Activity> currentActivity = null;

	private boolean button1Pressed = false;
	private int metaState = 0; // State of the meta keys

	public InAppInputManager(Context context) {
		Application app = null;
		try {
			 app = (Application) context;
		}
		catch (ClassCastException e)
		{
			app =  null;
		}

		if (app == null)
		{
			Log.e(TAG, "Provided context is not an application - input events will not work");
			return;
		}

		app.registerActivityLifecycleCallbacks(this);
	}

	private View obtainTargetView() {
		final Activity a = obtainActivity();
		//Log.i(TAG, "obtainTargetView a:"+a);
		if (a == null) return null;

		View v = a.findViewById(android.R.id.content).getRootView();
		//Log.i(TAG, "obtainTargetView vr:"+a);
		if (v == null) return null;
		if (v.hasWindowFocus()) return v; // Quick way

		List<View> lv = getWindowManagerViews();
		for(View vi : lv)
		{
			//Log.i(TAG, "obtainTargetView vi:"+vi+" f:"+vi.hasWindowFocus());
			if (vi.hasWindowFocus()) return vi;
		}

		return v;
	}

	private Activity obtainActivity() {
		synchronized (currentActivityLock) {
			if (currentActivity == null) return null;
			return currentActivity.get();
		}
	}

		//

	public void onMouseEvent(int buttonMask, int x, int y) {

		if ((!button1Pressed) && ((buttonMask & 1) != 0))
		{
			injectTouchEvent(1, MotionEvent.ACTION_DOWN, x, y);
			button1Pressed = true;
		}

		else if (button1Pressed)
		{
			if ((buttonMask & 1) == 0)
			{
				injectTouchEvent(1, MotionEvent.ACTION_UP, x, y);
				button1Pressed = false;
			}

			else injectTouchEvent(1, MotionEvent.ACTION_MOVE, x, y);
		}
	}

	private void injectTouchEvent(int buttonId, int event, int x, int y)
	{
		final View view = obtainTargetView();
		if (view == null) return;
		final Activity activity = obtainActivity();
		if (activity == null) return;

		int viewLocation[] = new int[2];
		view.getLocationOnScreen(viewLocation);

		MotionEvent.PointerProperties pp = new MotionEvent.PointerProperties();
		pp.toolType = MotionEvent.TOOL_TYPE_FINGER;
		pp.id = 0;
		MotionEvent.PointerProperties[] pps = new MotionEvent.PointerProperties[]{pp};

		MotionEvent.PointerCoords pc = new MotionEvent.PointerCoords();
		pc.size = 1;
		pc.pressure = 1;
		pc.x = x - viewLocation[0];
		pc.y = y - viewLocation[1];
		MotionEvent.PointerCoords[] pcs = new MotionEvent.PointerCoords[]{pc};

		long t = SystemClock.uptimeMillis();

		final MotionEvent e = MotionEvent.obtain(
				t,          // long downTime
				t + 100,    // long eventTime
				event,      // int action
				pps.length, // int pointerCount
				pps,        // MotionEvent.PointerProperties[] pointerProperties
				pcs,        // MotionEvent.PointerCoords[] pointerCoords
				0,          // int metaState
				0,          // int buttonState
				1,          // float xPrecision
				1,          // float yPrecision
				1,          // int deviceId
				0,          // int edgeFlags
				InputDevice.SOURCE_TOUCHSCREEN, //int source
				0           // int flags
		);

		activity.runOnUiThread(new Runnable() {
			public void run() {

				view.dispatchTouchEvent(e);
			}
		});
	}

	///

	public void onKeyboardEvent(boolean down, KeySym ks)
	{
		if ((ks == KeySym.XK_Escape) && ((this.metaState & (KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_RIGHT_ON)) != 0))
		{
			if (!down) return;
			injectBackEvent();
			return;
		}

		if (ks.keyeventCode != -1)
		{
			injectKeyboardEvent(down, ks);

			if (ks.metaState != 0)
			{
				if (down) this.metaState |= ks.metaState;
				else this.metaState &= ~ks.metaState;
			}

			return;
		}

		Log.i(TAG, "onKeyboardEvent: down:"+down+" keySym:"+ks);


	}

	private void injectKeyboardEvent(boolean down, KeySym ks) {
		final View view = obtainTargetView();
		if (view == null) return;
		final Activity activity = obtainActivity();
		if (activity == null) return;

		long t = SystemClock.uptimeMillis();

		int action = KeyEvent.ACTION_UP;
		if (down) action = KeyEvent.ACTION_DOWN;

		final KeyEvent e = new KeyEvent(
				t,               // downTime
				t + 100,         // eventTime
				action,          // action (ACTION_DOWN / ACTION_UP)
				ks.keyeventCode, // code
				0,               // Repeat
				this.metaState,
				0,              // Device Id
				0               //ks.code
		);

//		Log.i(TAG, "Injecting:"+e);

		activity.runOnUiThread(new Runnable() {
			public void run() {
				view.dispatchKeyEvent(e);
			}
		});
	}

	///

	private void injectBackEvent()
	{
		final Activity activity = obtainActivity();
		if (activity == null) return;

		activity.runOnUiThread(new Runnable() {
			public void run() {
				activity.onBackPressed();
			}
		});
	}

	///

	@Override
	public void onActivityCreated(Activity activity, Bundle bundle) {

	}

	@Override
	public void onActivityStarted(Activity activity) {
		Log.i(TAG, "onActivityStarted:" + activity);
		synchronized (currentActivityLock) {
			this.currentActivity = new WeakReference<>(activity);
		}
	}

	@Override
	public void onActivityResumed(Activity activity) {
		Log.i(TAG, "onActivityResumed:" + activity);
		synchronized (currentActivityLock) {
			this.currentActivity = new WeakReference<>(activity);
		}
	}

	@Override
	public void onActivityPaused(Activity activity) {
		Log.i(TAG, "onActivityPaused:" + activity);
		synchronized (currentActivityLock) {
			if ((this.currentActivity != null) && (this.currentActivity.get() == activity)) {
				this.currentActivity = null;
			}
		}
	}

	@Override
	public void onActivityStopped(Activity activity) {
		Log.i(TAG, "onActivityStopped:" + activity);
		synchronized (currentActivityLock) {
			if ((this.currentActivity != null) && (this.currentActivity.get() == activity)) {
				this.currentActivity = null;
			}
		}
	}

	@Override
	public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {
	}

	@Override
	public void onActivityDestroyed(Activity activity) {

	}

	///

	private static List<View> getWindowManagerViews() {
		try {

			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
					Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {

				// get the list from WindowManagerImpl.mViews
				Class wmiClass = Class.forName("android.view.WindowManagerImpl");
				Object wmiInstance = wmiClass.getMethod("getDefault").invoke(null);

				return viewsFromWM(wmiClass, wmiInstance);

			} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {

				// get the list from WindowManagerGlobal.mViews
				Class wmgClass = Class.forName("android.view.WindowManagerGlobal");
				Object wmgInstance = wmgClass.getMethod("getInstance").invoke(null);

				return viewsFromWM(wmgClass, wmgInstance);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

		return new ArrayList<View>();
	}

	private static List<View> viewsFromWM(Class wmClass, Object wmInstance) throws Exception {

		Field viewsField = wmClass.getDeclaredField("mViews");
		viewsField.setAccessible(true);
		Object views = viewsField.get(wmInstance);

		if (views instanceof List) {
			return (List<View>) viewsField.get(wmInstance);
		} else if (views instanceof View[]) {
			return Arrays.asList((View[])viewsField.get(wmInstance));
		}

		return new ArrayList<View>();
	}

}