package com.limpoxe.fairy.core;

import android.app.Activity;
import android.app.Application;
import android.app.Instrumentation;
import android.app.Service;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
import android.os.Build;
import android.os.IBinder;
import android.os.Process;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Window;

import com.limpoxe.fairy.content.LoadedPlugin;
import com.limpoxe.fairy.content.PluginActivityInfo;
import com.limpoxe.fairy.content.PluginDescriptor;
import com.limpoxe.fairy.content.PluginProviderInfo;
import com.limpoxe.fairy.core.android.HackActivity;
import com.limpoxe.fairy.core.android.HackActivityThread;
import com.limpoxe.fairy.core.android.HackApplication;
import com.limpoxe.fairy.core.android.HackContextImpl;
import com.limpoxe.fairy.core.android.HackContextThemeWrapper;
import com.limpoxe.fairy.core.android.HackContextWrapper;
import com.limpoxe.fairy.core.android.HackLayoutInflater;
import com.limpoxe.fairy.core.android.HackLoadedApk;
import com.limpoxe.fairy.core.android.HackService;
import com.limpoxe.fairy.core.android.HackWindow;
import com.limpoxe.fairy.core.annotation.AnnotationProcessor;
import com.limpoxe.fairy.core.annotation.PluginContainer;
import com.limpoxe.fairy.core.compat.CompatForSupportv7_23_2;
import com.limpoxe.fairy.core.exception.PluginNotFoundError;
import com.limpoxe.fairy.core.exception.PluginNotInitError;
import com.limpoxe.fairy.core.loading.WaitForLoadingPluginActivity;
import com.limpoxe.fairy.manager.PluginManagerHelper;
import com.limpoxe.fairy.manager.PluginManagerProviderClient;
import com.limpoxe.fairy.util.LogUtil;
import com.limpoxe.fairy.util.ProcessUtil;
import com.limpoxe.fairy.util.ResourceUtil;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

//import com.limpoxe.fairy.core.compat.CompatForAppComponentFactoryApi28;

public class PluginInjector {

	/**
	 * 替换宿主程序Application对象的mBase是为了修改它的几个StartActivity、
	 * StartService和SendBroadcast方法
	 */
	static void injectBaseContext(Context context) {
		LogUtil.v("替换宿主程序Application对象的mBase");
		HackContextWrapper wrapper = new HackContextWrapper(context);
		wrapper.setBase(new PluginBaseContextWrapper(wrapper.getBase()));
	}

	/**
	 * 注入Instrumentation主要是为了支持Activity
	 */
	static void injectInstrumentation() {
		// 给Instrumentation添加一层代理,用来实现隐藏api的调用
		LogUtil.d("替换宿主程序Intstrumentation");
		HackActivityThread.wrapInstrumentation();
	}

	static void injectHandlerCallback() {
		LogUtil.v("向宿主程序消息循环插入回调器");
		HackActivityThread.wrapHandler();
	}

	public static void installContentProviders(Context context, Context pluginContext, Collection<PluginProviderInfo> pluginProviderInfos) {
		List<ProviderInfo> hostProviders = context.getPackageManager().queryContentProviders(FairyGlobal.getHostApplication().getPackageName(), Process.myUid(),0);
        boolean isAlreadyAddByHost = false;

		List<ProviderInfo> providers = new ArrayList<ProviderInfo>();
		for (PluginProviderInfo pluginProviderInfo : pluginProviderInfos) {

		    isAlreadyAddByHost = false;

			if (hostProviders != null) {
				for(ProviderInfo hostProvider : hostProviders) {
					if (hostProvider.authority.equals(pluginProviderInfo.getAuthority())) {
						LogUtil.e("此contentProvider已经在宿主中定义,不再安装插件中定义的contentprovider", hostProvider.authority, pluginProviderInfo.getName(), pluginProviderInfo.getName());
						isAlreadyAddByHost = true;
						break;
					}
				}
			}
			if (isAlreadyAddByHost) {
				continue;
			}

			ProviderInfo p = new ProviderInfo();
			p.name = pluginProviderInfo.getName();
			p.authority = pluginProviderInfo.getAuthority();
			p.applicationInfo = new ApplicationInfo(context.getApplicationInfo());
			p.applicationInfo.packageName = pluginContext.getPackageName();
			p.exported = pluginProviderInfo.isExported();
			p.packageName = context.getApplicationInfo().packageName;
			p.grantUriPermissions = pluginProviderInfo.isGrantUriPermissions();
			providers.add(p);
		}

		if(providers.size() > 0) {
			LogUtil.e("为插件安装ContentProvider", pluginContext.getPackageName(), pluginProviderInfos.size());
			//安装的时候使用的是插件的Context, 所有无需对Classloader进行映射处理
			HackActivityThread.get().installContentProviders(pluginContext, providers);
		}
	}

	static void injectInstrumetionFor360Safe(Activity activity, Instrumentation pluginInstrumentation) {
		// 检查mInstrumention是否已经替换成功。
		// 之所以要检查,是因为如果手机上安装了360手机卫士等app,它们可能会劫持用户app的ActivityThread对象,
		// 导致在PluginApplication的onCreate方法里面替换mInstrumention可能会失败
		// 所以这里再做一次检查
		HackActivity hackActivity = new HackActivity(activity);
		Instrumentation instrumention = hackActivity.getInstrumentation();
		if (!(instrumention instanceof PluginInstrumentionWrapper)) {
			// 说明被360还原了,这里再次尝试替换
			hackActivity.setInstrumentation(pluginInstrumentation);
		}
	}

	static void injectActivityContext(final Activity activity) {
		if (activity instanceof WaitForLoadingPluginActivity) {
			return;
		}
		if (activity instanceof RealHostClassLoader.TolerantActivity) {
			return;
		}

		LogUtil.v("injectActivityContext");

		PluginContainer container = null;
		boolean isStubActivity = false;

		if (ProcessUtil.isPluginProcess()) {
			// 如果是打开插件中的activity,
			Intent intent = activity.getIntent();
			isStubActivity = PluginManagerProviderClient.isStub(intent.getComponent().getClassName());

			// 或者是打开的用来显示插件组件的宿主activity
			container = AnnotationProcessor.getPluginContainer(activity.getClass());
		}

		HackActivity hackActivity = new HackActivity(activity);

		if (isStubActivity || container != null) {

			// 在activityoncreate之前去完成attachBaseContext的事情

			Context pluginContext = null;
			PluginDescriptor pluginDescriptor = null;

			if (isStubActivity) {
				//是打开插件中的activity

				pluginDescriptor = PluginManagerHelper.getPluginDescriptorByClassName(activity.getClass().getName());
				if(pluginDescriptor == null) {
                    throw new PluginNotFoundError("未找到插件:" + activity.getClass().getName() + ", 插件未安装、或正在安装、或已损坏");
                }

				LoadedPlugin plugin = PluginLauncher.instance().getRunningPlugin(pluginDescriptor.getPackageName());
				if (plugin == null || plugin.pluginApplication == null) {
					throw new PluginNotInitError("插件尚未初始化 " + pluginDescriptor.getPackageName() + " " + plugin);
				}

				pluginContext = PluginCreator.createNewPluginComponentContext(plugin.pluginContext, activity.getBaseContext(), 0);

				//获取插件Application对象
				Application pluginApp = plugin.pluginApplication;

				//重设mApplication
				hackActivity.setApplication(pluginApp);
			} else {

				//是打开的用来显示插件组件的宿主activity, 比如在宿主Activity中显示插件Fragment或者插件View

                String pluginId = container.pluginId();
				if (!TextUtils.isEmpty(pluginId)) {
					//进入这里表示指定了这个宿主Activity "只显示" 某个插件的组件
					// 因此直接将这个Activity的Context也替换成插件的Context
					pluginDescriptor = PluginManagerHelper.getPluginDescriptorByPluginId(pluginId);
					if(pluginDescriptor == null) {
						throw new PluginNotFoundError("未找到插件:" + pluginId + ", 插件未安装、或正在安装、或已损坏");
					}

					//插件可能尚未初始化,确保使用前已经初始化
					LoadedPlugin plugin = PluginLauncher.instance().startPlugin(pluginDescriptor);
					pluginContext = PluginCreator.createNewPluginComponentContext(plugin.pluginContext, activity.getBaseContext(), 0);

				} else {
					//进入这里表示这个宿主可能要同时显示来自多个不同插件的组件, 也就没办法将Context替换成之中某一个插件的context,
					//如果多个不同插件的组件是通过PluginView标签添加的,则会通过注入PluginViewFactory去处理Classloader

					//这一行是为了配合RealHostClassLoader解决在宿主Activity被系统自动恢复时同时自动恢复了来自插件的Fragment而产生的ClassNotFound问题
					PluginInjector.hackHostClassLoaderIfNeeded();

					//不管怎样,如果打开的是宿主的Activity,都需要注入一个Context,用来在宿主中startActivity和sendBroadcast时检查目标是否为插件组件
					Context mainContext = new PluginBaseContextWrapper(activity.getBaseContext());
					hackActivity.setBase(null);
					hackActivity.attachBaseContext(mainContext);
					return;
				}

			}

			PluginActivityInfo pluginActivityInfo = pluginDescriptor.getActivityInfos().get(activity.getClass().getName());

			ActivityInfo activityInfo = hackActivity.getActivityInfo();
			int pluginAppTheme = getPluginTheme(activityInfo, pluginActivityInfo, pluginDescriptor);

			LogUtil.e("Theme", "0x" + Integer.toHexString(pluginAppTheme), activity.getClass().getName());

            //pluginActivityInfo != null的判断是为了避免在Fragment插件嵌入其他Activity时没有pluginActivityInfo造成NPE
            if (pluginActivityInfo != null && pluginActivityInfo.isUseHostPackageName()) {
                LogUtil.e("useHostPackageName true");
                ((PluginContextTheme)pluginContext).setUseHostPackageName(true);
            }

			resetActivityContext(pluginContext, activity, pluginAppTheme);

            //如果是配置了PluginContainer注解和pluginId的宿主Activity,此宿主的Activity的全屏配置可能会被插件的主题覆盖而丢失,可以通过代码设置回去
            resetWindowConfig(pluginContext, pluginDescriptor, activity, activityInfo, pluginActivityInfo);

            String simpleName = activity.getClass().getSimpleName();
			activity.setTitle(simpleName!=null?simpleName:activity.getClass().getName());

		} else {
			// 如果是打开宿主程序的activity,注入一个无害的Context,用来在宿主程序中startService和sendBroadcast时检查打开的对象是否是插件中的对象
			// 插入Context
			Context mainContext = new PluginBaseContextWrapper(activity.getBaseContext());
			hackActivity.setBase(null);
			hackActivity.attachBaseContext(mainContext);
		}
	}

	static void resetActivityContext(final Context pluginContext, final Activity activity,
									 final int pluginAppTheme) {
		if (pluginContext == null) {
			return;
		}

		// 重设BaseContext
		HackContextThemeWrapper hackContextThemeWrapper = new HackContextThemeWrapper(activity);
		hackContextThemeWrapper.setBase(null);
		hackContextThemeWrapper.attachBaseContext(pluginContext);

		// 由于在attach的时候Resource已经被初始化了,所以需要重置Resource
		hackContextThemeWrapper.setResources(null);

		CompatForSupportv7_23_2.fixResource(pluginContext, activity);

		// 重设theme
		if (pluginAppTheme != 0) {
			hackContextThemeWrapper.setTheme(null);
			activity.setTheme(pluginAppTheme);
		}
		// 重设theme
		((PluginContextTheme)pluginContext).mTheme = null;
		pluginContext.setTheme(pluginAppTheme);

		Window window = activity.getWindow();

		HackWindow hackWindow = new HackWindow(window);
		//重设mContext
		hackWindow.setContext(pluginContext);

		//重设mWindowStyle
		hackWindow.setWindowStyle(null);

		// 重设LayoutInflater
		LogUtil.v(window.getClass().getName());
		//注意:这里getWindow().getClass().getName() 不一定是android.view.Window
		//如miui下返回MIUI window
		hackWindow.setLayoutInflater(window.getClass().getName(), LayoutInflater.from(activity));

		// 如果api>=11,还要重设factory2
		if (Build.VERSION.SDK_INT >= 11) {
			new HackLayoutInflater(window.getLayoutInflater()).setPrivateFactory(activity);
		}
	}

	static void resetWindowConfig(final Context pluginContext, final PluginDescriptor pd,
								  final Activity activity,
								  final ActivityInfo activityInfo,
								  final PluginActivityInfo pluginActivityInfo) {

		if (pluginActivityInfo != null) {

			//如果PluginContextTheme的getPackageName返回了插件包名,需要在这里对attribute修正
			activity.getWindow().getAttributes().packageName = FairyGlobal.getHostApplication().getPackageName();

			if (null != pluginActivityInfo.getWindowSoftInputMode()) {
				activity.getWindow().setSoftInputMode((int)Long.parseLong(pluginActivityInfo.getWindowSoftInputMode().replace("0x", ""), 16));
			}
			if (Build.VERSION.SDK_INT >= 14) {
				if (null != pluginActivityInfo.getUiOptions()) {
					activity.getWindow().setUiOptions((int)Long.parseLong(pluginActivityInfo.getUiOptions().replace("0x", ""), 16));
				}
			}
			if (null != pluginActivityInfo.getScreenOrientation()) {
				int orientation = (int)Long.parseLong(pluginActivityInfo.getScreenOrientation());
				//noinspection ResourceType
				if (orientation != activityInfo.screenOrientation && !activity.isChild()) {
					//noinspection ResourceType
                    //框架中只内置了unspec和landscape两种screenOrientation
                    //如果是其他类型,这里通过代码实现切换
                    LogUtil.v("修改screenOrientation");
					activity.setRequestedOrientation(orientation);
				}
			}
			if (Build.VERSION.SDK_INT >= 18 && !activity.isChild()) {
				Boolean isImmersive = ResourceUtil.getBoolean(pluginActivityInfo.getImmersive(), pluginContext);
				if (isImmersive != null) {
					activity.setImmersive(isImmersive);
				}
			}

			String activityClassName = activity.getClass().getName();
			LogUtil.v(activityClassName, "immersive", pluginActivityInfo.getImmersive());
			LogUtil.v(activityClassName, "screenOrientation", pluginActivityInfo.getScreenOrientation());
			LogUtil.v(activityClassName, "launchMode", pluginActivityInfo.getLaunchMode());
			LogUtil.v(activityClassName, "windowSoftInputMode", pluginActivityInfo.getWindowSoftInputMode());
			LogUtil.v(activityClassName, "uiOptions", pluginActivityInfo.getUiOptions());
		}

		//如果是独立插件,由于没有合并资源,这里还需要替换掉 mActivityInfo,
		//避免activity试图通过ActivityInfo中的资源id来读取资源时失败
		activityInfo.icon = pd.getApplicationIcon();
		activityInfo.logo = pd.getApplicationLogo();
		if (Build.VERSION.SDK_INT >= 19) {
			activity.getWindow().setIcon(activityInfo.icon);
			activity.getWindow().setLogo(activityInfo.logo);
		}
	}

	/*package*/static void replaceReceiverContext(Context baseContext, Context newBase) {

		if (HackContextImpl.instanceOf(baseContext)) {
			ContextWrapper receiverRestrictedContext = new HackContextImpl(baseContext).getReceiverRestrictedContext();
			new HackContextWrapper(receiverRestrictedContext).setBase(newBase);
		}
	}

	//这里是因为在多进程情况下,杀死插件进程,自动恢复service时有个bug导致一个service同时存在多个service实例
	//这里做个遍历保护
	//break;
	/*package*/static void replacePluginServiceContext(String serviceName) {
		Map<IBinder, Service> services = HackActivityThread.get().getServices();
		if (services != null) {
			Iterator<Service> itr = services.values().iterator();
			while(itr.hasNext()) {
				Service service = itr.next();
				if (service != null && service.getClass().getName().equals(serviceName) ) {

					replacePluginServiceContext(serviceName,service );
				}

			}
		}
	}

	public static void replacePluginServiceContext(String servieName, Service service) {
		PluginDescriptor pd = PluginManagerHelper.getPluginDescriptorByClassName(servieName);

		LoadedPlugin plugin = PluginLauncher.instance().getRunningPlugin(pd.getPackageName());

		HackService hackService = new HackService(service);
		hackService.setBase(
				PluginCreator.createNewPluginComponentContext(plugin.pluginContext,
						service.getBaseContext(), pd.getApplicationTheme()));
		hackService.setApplication(plugin.pluginApplication);
		hackService.setClassName(PluginManagerProviderClient.bindStubService(service.getClass().getName()));

	}

	/*package*/static void replaceHostServiceContext(String serviceName) {
		Map<IBinder, Service> services = HackActivityThread.get().getServices();
		if (services != null) {
			Iterator<Service> itr = services.values().iterator();
			while(itr.hasNext()) {
				Service service = itr.next();
				if (service != null && service.getClass().getName().equals(serviceName) ) {
					PluginInjector.injectBaseContext(service);
					break;
				}

			}
		}
	}

	/**
	 * 主题的选择顺序为 先选择插件Activity配置的主题,再选择插件Application配置的主题,
	 * 如果是非独立插件,再选择宿主Activity主题
	 * 如果是独立插件,再选择系统默认主题
	 * @param activityInfo
	 * @param pluginActivityInfo
	 * @param pd
	 * @return
	 */
	private static int getPluginTheme(ActivityInfo activityInfo, PluginActivityInfo pluginActivityInfo, PluginDescriptor pd) {
		int pluginAppTheme = 0;
		if (pluginActivityInfo != null ) {
			pluginAppTheme = ResourceUtil.parseResId(pluginActivityInfo.getTheme());
		}
		if (pluginAppTheme == 0) {
			pluginAppTheme = pd.getApplicationTheme();
		}

		if (pluginAppTheme == 0 && pd.isStandalone()) {
			pluginAppTheme = android.R.style.Theme_DeviceDefault;
		}

		if (pluginAppTheme == 0) {
			//If the activity defines a theme, that is used; else, the application theme is used.
			pluginAppTheme = activityInfo.getThemeResource();
		}
		return pluginAppTheme;
	}

	/**
	 * 如果插件中不包含service、receiver,是不需要替换classloader的
	 */
	public static void hackHostClassLoaderIfNeeded() {
        LogUtil.v("hackHostClassLoaderIfNeeded");

        HackApplication hackApplication = new HackApplication(FairyGlobal.getHostApplication());
		Object mLoadedApk = hackApplication.getLoadedApk();
		if (mLoadedApk == null) {
			//重试一次
			mLoadedApk = hackApplication.getLoadedApk();
		}
		if(mLoadedApk == null) {
			//换个方式再试一次
			mLoadedApk = HackActivityThread.getLoadedApk();
		}
		if (mLoadedApk != null) {
			HackLoadedApk hackLoadedApk = new HackLoadedApk(mLoadedApk);
			ClassLoader originalLoader = hackLoadedApk.getClassLoader();
			if (!(originalLoader instanceof HostClassLoader)) {
				HostClassLoader newLoader = new HostClassLoader("", new RealHostClassLoader("",
						FairyGlobal.getHostApplication().getCacheDir().getAbsolutePath(),/**这里这两个目录参数无实际意义**/
						FairyGlobal.getHostApplication().getCacheDir().getAbsolutePath(),/**这里这两个目录参数无实际意义**/
						originalLoader));
				hackLoadedApk.setClassLoader(newLoader);
			}
		} else {
			LogUtil.e("What!!Why?");
		}
	}

	public static void injectAppComponentFactory() {
		if (Build.VERSION.SDK_INT < 28) {
			return;
		}
		LogUtil.v("hackHostClassLoaderIfNeeded");

		HackApplication hackApplication = new HackApplication(FairyGlobal.getHostApplication());
		Object mLoadedApk = hackApplication.getLoadedApk();
		if (mLoadedApk == null) {
			//重试一次
			mLoadedApk = hackApplication.getLoadedApk();
		}
		if(mLoadedApk == null) {
			//换个方式再试一次
			mLoadedApk = HackActivityThread.getLoadedApk();
		}
		if (mLoadedApk != null) {
			HackLoadedApk hackLoadedApk = new HackLoadedApk(mLoadedApk);
			//Android-P提供了组件钩子,用来拓展组件初始化流程
			//hackLoadedApk.setAppComponentFactory(new CompatForAppComponentFactoryApi28(hackLoadedApk.getAppComponentFactory()));
		} else {
			LogUtil.e("What!!Why?");
		}
	}
}