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.ui;
  
  import android.opengl.GLES10;
  import android.support.annotation.Nullable;
  import android.support.annotation.RestrictTo;
  import android.support.annotation.RestrictTo.Scope;
  import android.text.TextUtils;
  
  import org.apache.weex.WXSDKInstance;
  import org.apache.weex.common.WXErrorCode;
  import org.apache.weex.common.WXRuntimeException;
  import org.apache.weex.common.WXThread;
  import org.apache.weex.dom.RenderContext;
  import org.apache.weex.performance.WXInstanceApm;
  import org.apache.weex.ui.action.BasicGraphicAction;
  import org.apache.weex.ui.action.GraphicActionBatchAction;
  import org.apache.weex.ui.component.WXComponent;
  import org.apache.weex.utils.WXExceptionUtils;
  import org.apache.weex.utils.WXLogUtils;
  import org.apache.weex.utils.WXUtils;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.concurrent.ConcurrentHashMap;
  
  import javax.microedition.khronos.egl.EGL10;
  import javax.microedition.khronos.egl.EGLConfig;
  import javax.microedition.khronos.egl.EGLContext;
  import javax.microedition.khronos.egl.EGLDisplay;
  import javax.microedition.khronos.egl.EGLSurface;
  
  
  /**
   * Manager class for render operation, mainly for managing {@link RenderContextImpl}.
   * This is <strong>not</strong> a thread-safe class
   */
  public class WXRenderManager {
  
    private volatile ConcurrentHashMap<String, RenderContextImpl> mRenderContext;
    private WXRenderHandler mWXRenderHandler;
    private String mCurrentBatchInstanceId = null;
    private ArrayList<Map<String,Object>> mBatchActions = new ArrayList<>();
    private final int MAX_DROP_FRAME_NATIVE_BATCH = 2000;
    private final static  String sKeyAction = "Action";
    private static int nativeBatchTimes = 0;
    private static int mOpenGLRenderLimitValue = 0;
  
    public WXRenderManager() {
      mRenderContext = new ConcurrentHashMap<>();
      mWXRenderHandler = new WXRenderHandler();
    }
  
    public RenderContext getRenderContext(String instanceId) {
      return mRenderContext.get(instanceId);
    }
  
    public @Nullable
    WXComponent getWXComponent(String instanceId, String ref) {
      if (instanceId == null || TextUtils.isEmpty(ref)) {
        return null;
      }
      RenderContext stmt = getRenderContext(instanceId);
      return stmt == null ? null : stmt.getComponent(ref);
    }
  
    public WXSDKInstance getWXSDKInstance(String instanceId) {
      RenderContextImpl statement = mRenderContext.get(instanceId);
      if (statement == null) {
        return null;
      }
      return statement.getWXSDKInstance();
    }
  
      public static int getOpenGLRenderLimitValue() {
        if(mOpenGLRenderLimitValue == 0){
            int maxsize = 0;
                try {
                    EGL10 egl = (EGL10) EGLContext.getEGL();
                    EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
                    int[] vers = new int[2];
                    egl.eglInitialize(dpy, vers);
                    int[] configAttr = {
                            EGL10.EGL_COLOR_BUFFER_TYPE, EGL10.EGL_RGB_BUFFER,
                            EGL10.EGL_LEVEL, 0,
                            EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
                            EGL10.EGL_NONE};
                    EGLConfig[] configs = new EGLConfig[1];
                    int[] numConfig = new int[1];
                    egl.eglChooseConfig(dpy, configAttr, configs, 1, numConfig);
                    if(numConfig[0] == 0){
                        //There is something wrong with opengl environment.
                        maxsize = -1;
                        egl.eglTerminate(dpy);
                    }else {
                        EGLConfig config = configs[0];
                        int[] surfAttr = {
                                EGL10.EGL_WIDTH, 64,
                                EGL10.EGL_HEIGHT, 64,
                                EGL10.EGL_NONE};
                        EGLSurface surf = egl.eglCreatePbufferSurface(dpy, config, surfAttr);
                        final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;// missing in EGL10
                        int[] ctxAttrib = {
                                EGL_CONTEXT_CLIENT_VERSION, 1,
                                EGL10.EGL_NONE};
                        EGLContext ctx = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, ctxAttrib);
                        egl.eglMakeCurrent(dpy, surf, surf, ctx);
                        int[] maxSize = new int[1];
                        GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
                        egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
                                EGL10.EGL_NO_CONTEXT);
                        egl.eglDestroySurface(dpy, surf);
                        egl.eglDestroyContext(dpy, ctx);
                        egl.eglTerminate(dpy);
                        maxsize = maxSize[0];
                    }
                } catch(Exception e){
                    WXLogUtils.e(WXLogUtils.getStackTrace(e));
                }
            mOpenGLRenderLimitValue = maxsize;
        }
        return mOpenGLRenderLimitValue;
      }
  
    @RestrictTo(Scope.LIBRARY)
    public void postOnUiThread(Runnable runnable, long delayMillis) {
      mWXRenderHandler.postDelayed(WXThread.secure(runnable), delayMillis);
    }
  
    @RestrictTo(Scope.LIBRARY)
    public void postOnUiThread(Runnable runnable,final String instanceId){
      mWXRenderHandler.post(instanceId, WXThread.secure(runnable));
    }
  
    @RestrictTo(Scope.LIBRARY)
    public void postOnUiThread(Runnable runnable){
      mWXRenderHandler.post(WXThread.secure(runnable));
    }
  
    @RestrictTo(Scope.LIBRARY)
    public void removeTask(Runnable runnable){
      mWXRenderHandler.removeCallbacks(runnable);
    }
  
    /**
     * Remove renderStatement, can only be invoked in UI thread.
     *
     * @param instanceId {@link WXSDKInstance#mInstanceId}
     */
    public void removeRenderStatement(String instanceId) {
      if (!WXUtils.isUiThread()) {
        throw new WXRuntimeException("[WXRenderManager] removeRenderStatement can only be called in main thread");
      }
      RenderContextImpl statement = mRenderContext.remove(instanceId);
  
      if (statement != null) {
        statement.destroy();
      }
      if(instanceId == null) {
        mWXRenderHandler.removeCallbacksAndMessages(null);
      } else {
        // use hashCode to match message's what.
        mWXRenderHandler.removeMessages(instanceId.hashCode());
      }
    }
  
    private void postAllStashedGraphicAction(final String instanceId,final BasicGraphicAction action) {
        final RenderContextImpl renderContext = mRenderContext.get(instanceId);
  
        /// keep actions if renderContext still exist
        ArrayList<Map<String, Object>> tmpList = null;
        if (renderContext != null) {
          tmpList = new ArrayList<>(mBatchActions);
        }
  
        // clear stashed actions
        this.mBatchActions.clear();
        mCurrentBatchInstanceId = null;
        nativeBatchTimes = 0;
  
        // return if renderContext has been destroyed
        if (renderContext == null) {
          return;
        }
  
        /// post actions if renderContext still exist
        ArrayList<BasicGraphicAction> actions = new ArrayList<>(tmpList.size());
        for (int i = 0 ; i < tmpList.size(); i ++) {
            Map<String, Object> item = tmpList.get(i);
            Object mustBeAction = item.get(sKeyAction);
            if (!(mustBeAction instanceof BasicGraphicAction)) {
              continue;
            }
            BasicGraphicAction tmpAction = (BasicGraphicAction)mustBeAction;
            if (tmpAction.mActionType == BasicGraphicAction.ActionTypeBatchBegin || tmpAction.mActionType == BasicGraphicAction.ActionTypeBatchEnd) {
              continue;
            }
            actions.add(tmpAction);
        }
        postGraphicAction(instanceId, new GraphicActionBatchAction(action.getWXSDKIntance(),action.getRef(), actions));
    }
  
    public void postGraphicAction(final String instanceId, final BasicGraphicAction action) {
      final RenderContextImpl renderContext = mRenderContext.get(instanceId);
      if (renderContext == null) {
        return;
      }
  
      // If more than two pages exist and mCurrentBatchInstanceId not matches new instanceId, we will post all stashed actions at once.
      // That will cause losing efficacy of batch action, but it is acceptable because it's not serious problem.
      if (mCurrentBatchInstanceId != null && instanceId != null && !mCurrentBatchInstanceId.equals(instanceId) && mBatchActions.size() > 0) {
        Map<String, Object> lastItem = mBatchActions.get(mBatchActions.size() - 1);
        Object mustBeAction = lastItem.get(sKeyAction);
        if (mustBeAction instanceof BasicGraphicAction) {
          BasicGraphicAction lastAction = (BasicGraphicAction)mustBeAction;
          postAllStashedGraphicAction(mCurrentBatchInstanceId, lastAction);
        }
      }
  
      if (action.mActionType == BasicGraphicAction.ActionTypeBatchEnd) {
          postAllStashedGraphicAction(instanceId,action);
          return;
      } else if (action.mActionType == BasicGraphicAction.ActionTypeBatchBegin || this.mBatchActions.size() > 0 ) {
          nativeBatchTimes ++ ;
          if (nativeBatchTimes > MAX_DROP_FRAME_NATIVE_BATCH) {
              postAllStashedGraphicAction(instanceId,action);
          } else {
              HashMap<String, Object> item = new HashMap<>(1);
              item.put(sKeyAction, action);
              mBatchActions.add(item);
              mCurrentBatchInstanceId = instanceId;
              return;
          }
      }
  
      mWXRenderHandler.post(instanceId, action);
    }
  
    public void registerInstance(WXSDKInstance instance) {
      if (instance.getInstanceId() == null) {
        WXExceptionUtils.commitCriticalExceptionRT(null,
                WXErrorCode.WX_RENDER_ERR_INSTANCE_ID_NULL,
                "registerInstance",
                WXErrorCode.WX_RENDER_ERR_INSTANCE_ID_NULL.getErrorMsg() + "instanceId is null",
                null);
      } else {
        mRenderContext.put(instance.getInstanceId(), new RenderContextImpl(instance));
      }
    }
  
    public List<WXSDKInstance> getAllInstances() {
      ArrayList<WXSDKInstance> instances = null;
      if (mRenderContext != null && !mRenderContext.isEmpty()) {
        instances = new ArrayList<WXSDKInstance>();
        for (Map.Entry<String, RenderContextImpl> entry : mRenderContext.entrySet()) {
          RenderContextImpl renderStatement = entry.getValue();
          if (renderStatement != null) {
            instances.add(renderStatement.getWXSDKInstance());
          }
        }
      }
      return instances;
    }
  
    public void registerComponent(String instanceId, String ref, WXComponent comp) {
      RenderContextImpl statement = mRenderContext.get(instanceId);
      if (statement != null) {
        statement.registerComponent(ref, comp);
        if (null != statement.getInstance()){
          statement.getInstance().getApmForInstance().updateMaxStats(
              WXInstanceApm.KEY_PAGE_STATS_MAX_COMPONENT_NUM,
              statement.getComponentCount()
          );
        }
      }
    }
  
    public WXComponent unregisterComponent(String instanceId, String ref) {
      RenderContextImpl statement = mRenderContext.get(instanceId);
      if (statement != null) {
        if (null != statement.getInstance()){
          statement.getInstance().getApmForInstance().updateMaxStats(
              WXInstanceApm.KEY_PAGE_STATS_MAX_COMPONENT_NUM,
              statement.getComponentCount()
          );
        }
        return statement.unregisterComponent(ref);
      } else {
        return null;
      }
    }
  }
  