content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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 org.apache.weex.bridge;
  
  import android.content.Intent;
  import android.support.annotation.NonNull;
  import android.text.TextUtils;
  import android.view.Menu;
  
  import com.alibaba.fastjson.JSONArray;
  
  import org.apache.weex.WXEaglePluginManager;
  import org.apache.weex.WXSDKInstance;
  import org.apache.weex.WXSDKManager;
  import org.apache.weex.WXSDKEngine;
  import org.apache.weex.adapter.IWXUserTrackAdapter;
  import org.apache.weex.common.Destroyable;
  import org.apache.weex.common.WXException;
  import org.apache.weex.common.WXModule;
  import org.apache.weex.ui.config.ConfigModuleFactory;
  import org.apache.weex.ui.module.WXDomModule;
  import org.apache.weex.ui.module.WXTimerModule;
  import org.apache.weex.utils.WXLogUtils;
  import org.apache.weex.utils.cache.RegisterCache;
  
  import java.io.Serializable;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.ConcurrentMap;
  import org.apache.weex.utils.cache.RegisterCache.ModuleCache;
  
  /**
   * Manager class for weex module. There are two types of modules in weex, one is instance-level module,
   * the other is global-level module. Instance-level module will be created every time an instance
   * is created, while global-level module will be singleton in {@link WXSDKEngine}.
   */
  public class WXModuleManager {
  
    /**
     * module class object dictionary
     */
    private static volatile ConcurrentMap<String, ModuleFactoryImpl> sModuleFactoryMap = new ConcurrentHashMap<>();
    private static Map<String, WXModule> sGlobalModuleMap = new HashMap<>();
    private static Map<String, WXDomModule> sDomModuleMap = new HashMap<>();
  
  
    /**
     * module object dictionary
     * K : instanceId, V : Modules
     */
    private static Map<String, Map<String, WXModule>> sInstanceModuleMap = new ConcurrentHashMap<>();
  
  
    public static boolean registerModule(Map<String, RegisterCache.ModuleCache> moduleCacheMap) {
      if (moduleCacheMap.isEmpty())
        return true;
  
      final Iterator<Entry<String, RegisterCache.ModuleCache>> iterator = moduleCacheMap.entrySet().iterator();
      WXBridgeManager.getInstance()
              .postWithName(new Runnable() {
                @Override
                public void run() {
                  Map<String, Object> modules = new HashMap<>();
  
                  while (iterator.hasNext()) {
                    Entry<String, RegisterCache.ModuleCache> next = iterator.next();
                    RegisterCache.ModuleCache value = next.getValue();
                    String moduleName = value.name;
                    if (TextUtils.equals(moduleName, WXDomModule.WXDOM)) {
                      WXLogUtils.e("Cannot registered module with name 'dom'.");
                      continue;
                    }
  
                    if (sModuleFactoryMap != null && sModuleFactoryMap.containsKey(moduleName)) {
                      WXLogUtils.w("WXComponentRegistry Duplicate the Module name: " + moduleName);
                    }
                    ModuleFactory factory = value.factory;
                    try {
                      registerNativeModule(moduleName, factory);
                    } catch (WXException e) {
                      WXLogUtils.e("registerNativeModule" + e);
                    }
  
                    if (value.global) {
                      try {
                        WXModule wxModule = factory.buildInstance();
                        wxModule.setModuleName(moduleName);
                        sGlobalModuleMap.put(moduleName, wxModule);
                      } catch (Exception e) {
                        WXLogUtils.e(moduleName + " class must have a default constructor without params. ", e);
                      }
                    }
  
                    try {
                      sModuleFactoryMap.put(moduleName, new ModuleFactoryImpl(factory));
                    } catch (Throwable e) {
  
                    }
                    modules.put(moduleName, factory.getMethods());
                  }
                  WXSDKManager.getInstance().registerModules(modules);
                }
              },null,"registerModule From Cache");
      return true;
    }
  
    /**
     * Register module to JavaScript and Android
     */
    public static boolean registerModule(final String moduleName, final ModuleFactory factory, final boolean global) throws WXException {
      if (moduleName == null || factory == null) {
        return false;
      }
  
      if (TextUtils.equals(moduleName,WXDomModule.WXDOM)) {
        WXLogUtils.e("Cannot registered module with name 'dom'.");
        return false;
      }
      WXEaglePluginManager.getInstance().registerModule(moduleName, factory, global);
      if(RegisterCache.getInstance().cacheModule(moduleName,factory,global)) {
        return true;
      }
  
      //execute task in js thread to make sure register order is same as the order invoke register method.
      WXBridgeManager.getInstance()
              .postWithName(new Runnable() {
                @Override
                public void run() {
                  if (sModuleFactoryMap != null && sModuleFactoryMap.containsKey(moduleName)) {
                    WXLogUtils.w("WXComponentRegistry Duplicate the Module name: " + moduleName);
                  }
                  try {
                    registerNativeModule(moduleName, factory);
                  } catch (WXException e) {
                    WXLogUtils.e("registerNativeModule" + e);
                  }
  
                  if (global) {
                    try {
                      WXModule wxModule = factory.buildInstance();
                      wxModule.setModuleName(moduleName);
                      sGlobalModuleMap.put(moduleName, wxModule);
                    } catch (Exception e) {
                      WXLogUtils.e(moduleName + " class must have a default constructor without params. ", e);
                    }
                  }
  
                  registerJSModule(moduleName, factory);
  
                  try {
                    sModuleFactoryMap.put(moduleName, new ModuleFactoryImpl(factory));
                  } catch (Throwable e) {
  
                  }
                }
              },null,"registerModule");
      return true;
  
    }
  
    static boolean registerNativeModule(String moduleName, ModuleFactory factory) throws WXException {
      if (factory == null) {
        return false;
      }
  
      try {
        if (!sModuleFactoryMap.containsKey(moduleName) ) {
          sModuleFactoryMap.put(moduleName, new ModuleFactoryImpl(factory));
        }
      }catch (ArrayStoreException e){
        e.printStackTrace();
        //ignore:
        //may throw this exception:
        //java.lang.String cannot be stored in an array of type java.util.HashMap$HashMapEntry[]
  
        WXLogUtils.e("[WXModuleManager] registerNativeModule Error moduleName:"  + moduleName + " Error:" + e.toString());
      }
      return true;
    }
  
    static boolean registerJSModule(String moduleName, ModuleFactory factory) {
      Map<String, Object> modules = new HashMap<>();
      modules.put(moduleName, factory.getMethods());
      WXSDKManager.getInstance().registerModules(modules);
      return true;
    }
  
    static Object callModuleMethod(final String instanceId, String moduleStr, String methodStr, JSONArray args) {
      ModuleFactory factory = sModuleFactoryMap.get(moduleStr).mFactory;
      if(factory == null){
        WXLogUtils.e("[WXModuleManager] module factory not found.");
        return null;
      }
      final WXModule wxModule = findModule(instanceId, moduleStr,factory);
      if (wxModule == null) {
        return null;
      }
      WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(instanceId);
      wxModule.mWXSDKInstance = instance;
  
      final Invoker invoker = factory.getMethodInvoker(methodStr);
      try {
        if(instance != null) {
          IWXUserTrackAdapter userTrackAdapter = WXSDKManager.getInstance().getIWXUserTrackAdapter();
          if(userTrackAdapter != null) {
            HashMap<String, Serializable> data = new HashMap<String, Serializable>();
            data.put(IWXUserTrackAdapter.MONITOR_ERROR_CODE, "101");
            data.put(IWXUserTrackAdapter.MONITOR_ARG, moduleStr + "." + methodStr);
            data.put(IWXUserTrackAdapter.MONITOR_ERROR_MSG, instance.getBundleUrl());
            userTrackAdapter.commit(instance.getContext(), null, IWXUserTrackAdapter.INVOKE_MODULE, null, data);
          }
          return dispatchCallModuleMethod(instance,wxModule,args,invoker);
        } else {
          WXLogUtils.e("callModuleMethod >>> instance is null");
          return null;
        }
      } catch (Exception e) {
        WXLogUtils.e("callModuleMethod >>> invoke module:" + moduleStr + ", method:" + methodStr + " failed. ", e);
        return null;
      } finally {
        if (wxModule instanceof WXDomModule || wxModule instanceof WXTimerModule) {
          wxModule.mWXSDKInstance = null;
        }
      }
    }
  
    private static Object dispatchCallModuleMethod(@NonNull WXSDKInstance instance, @NonNull WXModule wxModule,
                                                   @NonNull JSONArray args, @NonNull Invoker invoker) throws Exception{
      if(!instance.isPreRenderMode()) {
        return instance.getNativeInvokeHelper().invoke(wxModule,invoker,args);
      }
      // we are in preRender mode
      if(invoker.isRunOnUIThread()) {/*ASYNC CALL*/
  //      DOMAction moduleInvocationAction = Actions.getModuleInvocationAction(wxModule,args,invoker);
  //      WXSDKManager.getInstance().getWXDomManager().postAction(instance.getInstanceId(), moduleInvocationAction,false);
        return null;
      } else {/*SYNC CALL*/
        return instance.getNativeInvokeHelper().invoke(wxModule,invoker,args);
      }
    }
  
    public static boolean hasModule(String module) {
      return sGlobalModuleMap.containsKey(module) || sModuleFactoryMap.containsKey(module);
    }
  
    private static WXModule findModule(String instanceId, String moduleStr,ModuleFactory factory) {
      // find WXModule
      WXModule wxModule = sGlobalModuleMap.get(moduleStr);
  
      //not global module
      if (wxModule == null) {
        Map<String, WXModule> moduleMap = sInstanceModuleMap.get(instanceId);
        if (moduleMap == null) {
          moduleMap = new ConcurrentHashMap<>();
          sInstanceModuleMap.put(instanceId, moduleMap);
        }
        // if cannot find the Module, create a new Module and save it
        wxModule = moduleMap.get(moduleStr);
        if (wxModule == null) {
          try {
            if(factory instanceof ConfigModuleFactory){
              WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(instanceId);
              wxModule = ((ConfigModuleFactory) factory).buildInstance(instance);
            }else{
              wxModule = factory.buildInstance();
            }
            wxModule.setModuleName(moduleStr);
          } catch (Exception e) {
            WXLogUtils.e(moduleStr + " module build instace failed.", e);
            return null;
          }
          moduleMap.put(moduleStr, wxModule);
        }
      }
  
      return wxModule;
    }
  
    /**Hook Activity life cycle callback begin***/
  
  
    public static void onActivityCreate(String instanceId){
  
      Map<String, WXModule> modules = sInstanceModuleMap.get(instanceId);
      if(modules!=null) {
        for (String key : modules.keySet()) {
          WXModule module = modules.get(key);
          if (module != null) {
            module.onActivityCreate();
          } else {
            WXLogUtils.w("onActivityCreate can not find the " + key + " module");
          }
        }
      }
  
    }
  
    public static void onActivityStart(String instanceId){
  
      Map<String, WXModule> modules = sInstanceModuleMap.get(instanceId);
      if(modules!=null) {
        for (String key : modules.keySet()) {
          WXModule module = modules.get(key);
          if (module != null) {
            module.onActivityStart();
          } else {
            WXLogUtils.w("onActivityStart can not find the " + key + " module");
          }
        }
      }
    }
  
    public static void onActivityPause(String instanceId){
      Map<String, WXModule> modules = sInstanceModuleMap.get(instanceId);
      if(modules!=null) {
        for (String key : modules.keySet()) {
          WXModule module = modules.get(key);
          if (module != null) {
            module.onActivityPause();
          } else {
            WXLogUtils.w("onActivityPause can not find the " + key + " module");
          }
        }
      }
    }
  
    public static void onActivityResume(String instanceId){
      Map<String, WXModule> modules = sInstanceModuleMap.get(instanceId);
      if(modules!=null) {
        for (String key : modules.keySet()) {
          WXModule module = modules.get(key);
          if (module != null) {
            module.onActivityResume();
          } else {
            WXLogUtils.w("onActivityResume can not find the " + key + " module");
          }
        }
      }
    }
  
    public static void onActivityStop(String instanceId){
      Map<String, WXModule> modules = sInstanceModuleMap.get(instanceId);
      if(modules!=null) {
        for (String key : modules.keySet()) {
          WXModule module = modules.get(key);
          if (module != null) {
            module.onActivityStop();
          } else {
            WXLogUtils.w("onActivityStop can not find the " + key + " module");
          }
        }
      }
    }
  
    public static void onActivityDestroy(String instanceId){
      Map<String, WXModule> modules = sInstanceModuleMap.get(instanceId);
      if(modules!=null) {
        for (String key : modules.keySet()) {
          WXModule module = modules.get(key);
          if (module != null) {
            module.onActivityDestroy();
          } else {
            WXLogUtils.w("onActivityDestroy can not find the " + key + " module");
          }
        }
      }
    }
  
    public static boolean onActivityBack(String instanceId){
      Map<String, WXModule> modules = sInstanceModuleMap.get(instanceId);
      if(modules!=null) {
        for (String key : modules.keySet()) {
          WXModule module = modules.get(key);
          if (module != null) {
            return module.onActivityBack();
          } else {
            WXLogUtils.w("onActivityCreate can not find the " + key + " module");
          }
        }
      }
      return false;
    }
  
    public static void onActivityResult(String instanceId,int requestCode, int resultCode, Intent data){
  
      Map<String, WXModule> modules = sInstanceModuleMap.get(instanceId);
      if(modules!=null) {
        for (String key : modules.keySet()) {
          WXModule module = modules.get(key);
          if (module != null) {
            module.onActivityResult(requestCode, resultCode, data);
          } else {
            WXLogUtils.w("onActivityResult can not find the " + key + " module");
          }
        }
      }
    }
  
    public static boolean onCreateOptionsMenu(String instanceId,Menu menu) {
      Map<String, WXModule> modules = sInstanceModuleMap.get(instanceId);
      if(modules!=null) {
        for (String key : modules.keySet()) {
          WXModule module = modules.get(key);
          if (module != null) {
            module.onCreateOptionsMenu(menu);
          } else {
            WXLogUtils.w("onActivityResult can not find the " + key + " module");
          }
        }
      }
      return false;
    }
  
    public static void onRequestPermissionsResult(String instanceId ,int requestCode, String[] permissions, int[] grantResults) {
      Map<String, WXModule> modules = sInstanceModuleMap.get(instanceId);
      if(modules!=null) {
        for (String key : modules.keySet()) {
          WXModule module = modules.get(key);
          if (module != null) {
            module.onRequestPermissionsResult(requestCode, permissions, grantResults);
          } else {
            WXLogUtils.w("onActivityResult can not find the " + key + " module");
          }
        }
      }
    }
  
    public static void destroyInstanceModules(String instanceId) {
      sDomModuleMap.remove(instanceId);
      Map<String, WXModule> moduleMap = sInstanceModuleMap.remove(instanceId);
      if (moduleMap == null || moduleMap.size() < 1) {
        return;
      }
      Iterator<Entry<String, WXModule>> iterator = moduleMap.entrySet().iterator();
      Entry<String, WXModule> entry;
      while (iterator.hasNext()) {
        entry = iterator.next();
        WXModule module = entry.getValue();
        if(module instanceof Destroyable){
          ((Destroyable)module).destroy();
        }
  
      }
    }
  
    public static void createDomModule(WXSDKInstance instance){
      if(instance != null) {
        sDomModuleMap.put(instance.getInstanceId(), new WXDomModule(instance));
      }
    }
  
    public static void destoryDomModule(String instanceID){
      sDomModuleMap.remove(instanceID);
    }
  
    public static WXDomModule getDomModule(String instanceId){
      return sDomModuleMap.get(instanceId);
    }
  
    public static void reload(){
      if (sModuleFactoryMap != null && sModuleFactoryMap.size() > 0) {
        for (Map.Entry<String, ModuleFactoryImpl> entry : sModuleFactoryMap.entrySet()) {
          try {
            registerJSModule(entry.getKey(), entry.getValue().mFactory);
          } catch (Throwable e) {
  
          }
        }
      }
    }
  
    /**
     * registerWhenCreateInstance
     */
    public static void registerWhenCreateInstance(){
      if (sModuleFactoryMap != null && sModuleFactoryMap.size() > 0) {
        for (Map.Entry<String, ModuleFactoryImpl> entry : sModuleFactoryMap.entrySet()) {
          try {
            if (!entry.getValue().hasRigster) {
              registerJSModule(entry.getKey(), entry.getValue().mFactory);
            }
          } catch (Throwable e) {
  
          }
        }
      }
    }
  
    /**
     * resetAllModuleState
     */
    public static void resetAllModuleState() {
      if (sModuleFactoryMap != null && sModuleFactoryMap.size() > 0) {
        for (Map.Entry<String, ModuleFactoryImpl> entry : sModuleFactoryMap.entrySet()) {
          entry.getValue().hasRigster = false;
        }
      }
    }
  
    /**
     * resetModuleState
     * @param module
     * @param state
     */
    public static void resetModuleState(String module, boolean state) {
      if (sModuleFactoryMap != null && sModuleFactoryMap.size() > 0) {
        for (Map.Entry<String, ModuleFactoryImpl> entry : sModuleFactoryMap.entrySet()) {
          try {
            if (entry.getKey() != null && entry.getKey().equals(module)) {
              entry.getValue().hasRigster = state;
            }
          } catch (Throwable e) {
  
          }
        }
      }
    }
  }
  