package info.papdt.swipeback.mod;

import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.IXposedHookZygoteInit;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodReplacement;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import static de.robv.android.xposed.XposedHelpers.*;

import me.imid.swipebacklayout.lib.SwipeBackLayout;
import me.imid.swipebacklayout.lib.app.SwipeBackActivityHelper;

import info.papdt.swipeback.helper.MySwipeBackHelper;
import info.papdt.swipeback.helper.Settings;
import info.papdt.swipeback.receiver.ClassNameReceiver;
import static info.papdt.swipeback.helper.Utility.*;
import static info.papdt.swipeback.BuildConfig.DEBUG;

public class ModSwipeBack implements IXposedHookLoadPackage, IXposedHookZygoteInit
{
	private static final String TAG = ModSwipeBack.class.getSimpleName();
	
	private Settings mSettings = Settings.getInstance(null);

	@Override
	public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
		if (lpparam.packageName.equals("android")) {
			log("detected android framework");
			try {
				Class<?> ar = findClass("com.android.server.am.ActivityRecord", lpparam.classLoader);

				if (ar != null)
					hookActivityRecord(ar);
			} catch (ClassNotFoundError e) {
				// Exception is thrown in pre-Lollipop system
			}
		}
	}

	@Override
	public void initZygote(IXposedHookZygoteInit.StartupParam startupParam) throws Throwable {
		
		try {
			Class<?> ar = findClass("com.android.server.am.ActivityRecord", null);
			
			if (ar != null)
				hookActivityRecord(ar);
		} catch (ClassNotFoundError e) {
			// Exception is thrown in Lollipop
		}
		
		findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodHook() {
			@Override
			protected void afterHookedMethod(XC_MethodHook.MethodHookParam mhparams) throws Throwable {
				Activity activity = $(mhparams.thisObject);
				
				String packageName = activity.getApplicationInfo().packageName;
				String className = activity.getClass().getName();
				
				if (shouldExclude(packageName, className))
					return;
				
				if (isLauncher(activity, packageName))
					return;
				
				SwipeBackActivityHelper helper = new MySwipeBackHelper(activity);
				helper.onActivityCreate();
				helper.getSwipeBackLayout().setEnableGesture(true);
				
				mSettings.reload();
				int edge = mSettings.getInt(packageName, className, Settings.EDGE, SwipeBackLayout.EDGE_LEFT);
				helper.getSwipeBackLayout().setEdgeTrackingEnabled(edge);
				
				int sensitivity = mSettings.getInt(packageName, className, Settings.SENSITIVITY, 100);
				helper.getSwipeBackLayout().setSensitivity(activity, (float) sensitivity / 100.0f);
				
				setAdditionalInstanceField(activity, "helper", helper);

				// Fix rotation
				ModRotationFix.fixOnActivityCreate(activity);
				
				if (Build.VERSION.SDK_INT >= 21)
					ModSDK21.afterOnCreateSDK21(helper, activity, packageName, className);
			}
		});
		
		findAndHookMethod(Activity.class, "onPostCreate", Bundle.class, new XC_MethodHook() {
			@Override
			protected void afterHookedMethod(XC_MethodHook.MethodHookParam mhparams) throws Throwable {
				SwipeBackActivityHelper helper = $(getAdditionalInstanceField(mhparams.thisObject, "helper"));
				if (helper != null) {
					Activity activity = $(mhparams.thisObject);

					int isFloating = getStaticIntField(findClass("com.android.internal.R.styleable", null), "Window_windowIsFloating");
					if (activity.getWindow().getWindowStyle().getBoolean(isFloating, false)) {
						setAdditionalInstanceField(mhparams.thisObject, "helper", null);
						return;
					}
					
					if ((activity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0)
						return;
					
					helper.onPostCreate();
					
					if (Build.VERSION.SDK_INT == 21)
						ModSDK21.afterOnPostCreateSDK21(mhparams);
				}
			}
		});
		
		findAndHookMethod(Activity.class, "onResume", new XC_MethodHook() {
			@Override
			protected void afterHookedMethod(XC_MethodHook.MethodHookParam mhparams) throws Throwable {
				if (Build.VERSION.SDK_INT < 16)
					return;
				
				mSettings.reload();
				if (!mSettings.getBoolean("global", "global", Settings.NOTIFY_SHORTCUT, false))
					return;
				
				// Notify the user the current class name
				Activity activity = $(mhparams.thisObject);
				String className = activity.getClass().getName();
				String packageName = activity.getPackageName();
				Intent i = new Intent(ClassNameReceiver.ACTION);
				
				// Do not include the app itself
				if (!packageName.equals("info.papdt.swipeback")) {
					i.putExtra(ClassNameReceiver.EXTRA_CLASSNAME, className);
					i.putExtra(ClassNameReceiver.EXTRA_PACKAGENAME, packageName);
				}
				
				activity.sendBroadcast(i);
			}
		});

		findAndHookMethod(Activity.class, "findViewById", int.class, new XC_MethodHook() {
			@Override
			protected void afterHookedMethod(XC_MethodHook.MethodHookParam mhparams) throws Throwable {
				if (mhparams.getResult() == null) {
					SwipeBackActivityHelper helper = $(getAdditionalInstanceField(mhparams.thisObject, "helper"));
					if (helper != null) {
						mhparams.setResult(helper.findViewById((Integer) mhparams.args[0]));
					}
				}
			}
		});
		
		findAndHookMethod(Activity.class, "finish", new XC_MethodHook() {
			@Override
			protected void beforeHookedMethod(XC_MethodHook.MethodHookParam mhparams) throws Throwable {
				Activity activity = $(mhparams.thisObject);

				// Fix rotation first
				ModRotationFix.fixOnActivityFinish(activity);

				MySwipeBackHelper helper = $(getAdditionalInstanceField(mhparams.thisObject, "helper"));
				if (helper != null && helper.getSwipeBackLayout().getScrollPercent() < 1) {
					String packageName = activity.getApplicationInfo().packageName;
					String className = activity.getClass().getName();
					
					mSettings.reload();
					if (!mSettings.getBoolean(packageName, className, Settings.SCROLL_TO_RETURN, false))
						return;
					
					helper.onFinish();
					Object isFinishing = getAdditionalInstanceField(mhparams.thisObject, "isFinishing");
					
					// Replace the default 'finish' by scrollToFinish
					if (isFinishing == null || !(Boolean) isFinishing) {
						setAdditionalInstanceField(mhparams.thisObject, "isFinishing", true);
						mhparams.setResult(null);
						helper.getSwipeBackLayout().scrollToFinishActivity();
					}
				}
			}
		});
		
		ModKK441.hookKK441();
		
		if (Build.VERSION.SDK_INT < 21)
			return;
			
		ModSDK21.zygoteSDK21();
	}
	
	private void hookActivityRecord(Class<?> clazz) throws Throwable {
		XposedBridge.hookAllConstructors(clazz, new XC_MethodHook() {
			@Override
			protected void afterHookedMethod(XC_MethodHook.MethodHookParam mhparams) throws Throwable {
				String packageName = $(getObjectField(mhparams.thisObject, "packageName"));
				ActivityInfo activity = $(getObjectField(mhparams.thisObject, "info"));
				if (shouldExclude(packageName, activity.name))
					return;

				boolean isHome = false;
				if (Build.VERSION.SDK_INT >= 19) {
					isHome = (Boolean) callMethod(mhparams.thisObject, "isHomeActivity");
				} else {
					isHome = getBooleanField(mhparams.thisObject, "isHomeActivity");
				}

				if (!isHome) {
					// fullscreen = false means transparent
					setBooleanField(mhparams.thisObject, "fullscreen", false);
				}
			}
		});
	}

	private boolean shouldExclude(String packageName, String className) {
		if (packageName.equals("com.android.systemui") || className.contains("InCall")) {
			return true;
		} else {
			mSettings.reload();
			return !mSettings.getBoolean(packageName, className, Settings.ENABLE, true);
		}
	}
	
	private static void log(String log) {
		if (DEBUG) {
			XposedBridge.log(TAG + ": " + log);
		}
	}

}