/**
 * 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.performance;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import org.apache.weex.WXEnvironment;
import org.apache.weex.WXSDKInstance;
import org.apache.weex.WXSDKManager;
import org.apache.weex.bridge.WXBridgeManager;
import org.apache.weex.common.WXErrorCode;
import org.apache.weex.common.WXPerformance;
import org.apache.weex.common.WXRenderStrategy;
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;

public class WXInstanceApm {

    public static final String WEEX_PAGE_TOPIC = "weex_page";

    /************** properties *****************/
    public static final String KEY_PROPERTIES_ERROR_CODE = "wxErrorCode";
    //public static final String KEY_PAGE_PROPERTIES_LAUNCH_ID = "wxLaunchId";
    public static final String KEY_PAGE_PROPERTIES_BIZ_ID = "wxBizID";
    public static final String KEY_PAGE_PROPERTIES_BUBDLE_URL = "wxBundleUrl";
    public static final String KEY_PAGE_PROPERTIES_JSLIB_VERSION = "wxJSLibVersion";
    public static final String KEY_PAGE_PROPERTIES_WEEX_VERSION = "wxSDKVersion";
    public static final String KEY_PAGE_PROPERTIES_REQUEST_TYPE = "wxRequestType";
    public static final String KEY_PAGE_PROPERTIES_CACHE_TYPE = "wxCacheType";
    public static final String KEY_PAGE_PROPERTIES_CACHE_INFO = "wxZCacheInfo";
    public static final String KEY_PAGE_PROPERTIES_JS_FM_INI = "wxJsFrameworkInit";
    public static final String KEY_PAGE_PROPERTIES_CONTAINER_NAME = "wxContainerName";
    public static final String KEY_PAGE_PROPERTIES_INSTANCE_TYPE = "wxInstanceType";
    public static final String KEY_PAGE_PROPERTIES_PARENT_PAGE = "wxParentPage";
    public static final String KEY_PAGE_PROPERTIES_BUNDLE_TYPE = "wxBundleType";
    public static final String KEY_PAGE_PROPERTIES_RENDER_TYPE = "wxRenderType";
    public static final String KEY_PAGE_PROPERTIES_UIKIT_TYPE = "wxUIKitType";

    /************** stages *****************/
    public static final String KEY_PAGE_STAGES_CONTAINER_READY = "wxContainerReady";
    public static final String KEY_PAGE_STAGES_DOWN_BUNDLE_START = "wxStartDownLoadBundle";
    public static final String KEY_PAGE_STAGES_DOWN_BUNDLE_END = "wxEndDownLoadBundle";
    public static final String KEY_PAGE_STAGES_RENDER_ORGIGIN = "wxRenderTimeOrigin";
    public static final String KEY_PAGE_STAGES_LOAD_BUNDLE_START = "wxStartLoadBundle";
    public static final String KEY_PAGE_STAGES_LOAD_BUNDLE_END = "wxEndLoadBundle";
    public static final String KEY_PAGE_STAGES_FIRST_INTERACTION_VIEW = "wxFirstInteractionView";
    public static final String KEY_PAGE_STAGES_CREATE_FINISH= "wxJSBundleCreateFinish";
    public static final String KEY_PAGE_STAGES_FSRENDER = "wxFsRender";
    public static final String KEY_PAGE_STAGES_NEW_FSRENDER = "wxNewFsRender";
    public static final String KEY_PAGE_STAGES_END_EXCUTE_BUNDLE = "wxEndExecuteBundle";
    public static final String KEY_PAGE_STAGES_INTERACTION = "wxInteraction";
    public static final String KEY_PAGE_STAGES_DESTROY = "wxDestroy";

    //Custom preprocessing start, called when activity created or other time. Called by other activity
    public static final String KEY_PAGE_STAGES_CUSTOM_PREPROCESS_START = "wxCustomPreprocessStart";
    //Custom preprocessing end, called when you'are able to start weex render. Called by other activity
    public static final String KEY_PAGE_STAGES_CUSTOM_PREPROCESS_END = "wxCustomPreprocessEnd";

    /************** stats *****************/
    public static final String KEY_PAGE_STATS_BUNDLE_SIZE = "wxBundleSize";
    public static final String KEY_PAGE_STATS_FS_CALL_JS_TIME = "wxFSCallJsTotalTime";
    public static final String KEY_PAGE_STATS_FS_CALL_JS_NUM = "wxFSCallJsTotalNum";
    public static final String KEY_PAGE_STATS_FS_TIMER_NUM = "wxFSTimerCount";
    public static final String KEY_PAGE_STATS_FS_CALL_NATIVE_TIME = "wxFSCallNativeTotalTime";
    public static final String KEY_PAGE_STATS_FS_CALL_NATIVE_NUM = "wxFSCallNativeTotalNum";
    public static final String KEY_PAGE_STATS_FS_CALL_EVENT_NUM = "wxFSCallEventTotalNum";
    public static final String KEY_PAGE_STATS_FS_REQUEST_NUM = "wxFSRequestNum";
    public static final String KEY_PAGE_STATS_CELL_EXCEED_NUM = "wxCellExceedNum";
    public static final String KEY_PAGE_STATS_MAX_DEEP_VIEW = "wxMaxDeepViewLayer";
    public static final String KEY_PAGE_STATS_MAX_DEEP_DOM = "wxMaxDeepVDomLayer";
    public static final String KEY_PAGE_STATS_MAX_COMPONENT_NUM = "wxMaxComponentCount";
    public static final String KEY_PAGE_STATS_WRONG_IMG_SIZE_COUNT = "wxWrongImgSizeCount";
    public static final String KEY_PAGE_STATS_EMBED_COUNT = "wxEmbedCount";
    public static final String KEY_PAGE_STATS_LARGE_IMG_COUNT = "wxLargeImgMaxCount";
    public static final String KEY_PAGE_STATS_BODY_RATIO = "wxBodyRatio";
    public static final String KEY_PAGE_STATS_SCROLLER_NUM = "wxScrollerCount";
    public static final String KEY_PAGE_STATS_CELL_DATA_UN_RECYCLE_NUM = "wxCellDataUnRecycleCount";
    public static final String KEY_PAGE_STATS_CELL_UN_RE_USE_NUM = "wxCellUnReUseCount";
    public static final String KEY_PAGE_STATS_IMG_UN_RECYCLE_NUM = "wxImgUnRecycleCount";

    public static final String KEY_PAGE_STATS_I_SCREEN_VIEW_COUNT = "wxInteractionScreenViewCount";
    public static final String KEY_PAGE_STATS_I_ALL_VIEW_COUNT = "wxInteractionAllViewCount";
    public static final String KEY_PAGE_STATS_I_COMPONENT_CREATE_COUNT = "wxInteractionComponentCreateCount";
    public static final String KEY_PAGE_ANIM_BACK_NUM = "wxAnimationInBackCount";
    public static final String KEY_PAGE_TIMER_BACK_NUM = "wxTimerInBackCount";
    public static final String KEY_PAGE_STATS_ACTUAL_DOWNLOAD_TIME = "wxActualNetworkTime";

    public static final String KEY_PAGE_STATS_IMG_LOAD_NUM = "wxImgLoadCount";
    public static final String KEY_PAGE_STATS_IMG_LOAD_SUCCESS_NUM = "wxImgLoadSuccessCount";
    public static final String KEY_PAGE_STATS_IMG_LOAD_FAIL_NUM = "wxImgLoadFailCount";
    public static final String KEY_PAGE_STATS_NET_NUM = "wxNetworkRequestCount";
    public static final String KEY_PAGE_STATS_NET_SUCCESS_NUM = "wxNetworkRequestSuccessCount";
    public static final String KEY_PAGE_STATS_NET_FAIL_NUM = "wxNetworkRequestFailCount";
    public static final String KEY_PAGE_STATS_JSLIB_INIT_TIME = "wxJSLibInitTime";
    public static final String KEY_PAGE_STATS_VIEW_CREATE_COST = "wxViewCost";
    public static final String KEY_PAGE_STATS_COMPONENT_CREATE_COST = "wxComponentCost";
    public static final String KEY_PAGE_STATS_EXECUTE_JS_CALLBACK_COST = "wxExecJsCallBack";
    public static final String KEY_PAGE_STATS_LAYOUT_TIME = "wxLayoutTime";


    /************** value *****************/
    public static final String VALUE_ERROR_CODE_DEFAULT = "0";
    public static final String VALUE_BUNDLE_LOAD_LENGTH = "wxLoadedLength";

    private String mInstanceId;
    private IWXApmMonitorAdapter apmInstance;
    private Map<String, Double> recordStatsMap;
    public final Map<String, Long> stageMap;
    private Map<String,Object> mPropertiesMap;
    private boolean isFSEnd;
    private boolean mHasInit = false;
    private boolean mEnd = false;
    private boolean hasRecordFistInteractionView =false;
    public final Map<String,Object> extInfo;
    public boolean forceStopRecordInteraction = false;
    public Rect instanceRect;
    public String reportPageName;
    public boolean hasReportLayerOverDraw = false;
    public boolean hasAddView;
    private Handler mUIHandler;

    public Set<String> exceptionRecord = new CopyOnWriteArraySet<String>();

    private double interactionLayoutTime;
    public long componentCreateTime;
    private long interactionComponentCreateTime;
    public long viewCreateTime;
    private long interactionViewCreateTime;
    //next version
    private long wxExecJsCallBackTime;
    private long interactionJsCallBackTime;


    private boolean mHasRecordDetailData = false;

    /**
     * send performance value to js
     **/
    private boolean hasSendInteractionToJS = false;
    public volatile boolean isReady = true;

    public WXInstanceApm(String instanceId) {
        mInstanceId = instanceId;
        extInfo = new ConcurrentHashMap<>();
        stageMap = new ConcurrentHashMap<>();
        mUIHandler = new Handler(Looper.getMainLooper());
        recordStatsMap = new ConcurrentHashMap<>();
        mPropertiesMap = new ConcurrentHashMap<>();
        IApmGenerator generator = WXSDKManager.getInstance().getApmGenerater();
        if (null != generator) {
            apmInstance = generator.generateApmInstance(WEEX_PAGE_TOPIC);
        }
    }

    public void onInstanceReady(boolean isPreDownLoad){
        this.isReady = true;
        if (isPreDownLoad){
            onStage(KEY_PAGE_STAGES_DOWN_BUNDLE_START);
        }
        doInit();
        for (Map.Entry<String,Long> stage:stageMap.entrySet()){
            sendStageInfo(stage.getKey(),stage.getValue());
        }
        for (Map.Entry<String,Double> stats:recordStatsMap.entrySet()){
           sendStats(stats.getKey(),stats.getValue());
        }
        for (Map.Entry<String,Object> p:mPropertiesMap.entrySet()){
           sendProperty(p.getKey(),p.getValue());
        }
    }

    /**
     * record event
     */
    public void onEvent(String name, Object value) {
        if (null == apmInstance) {
            return;
        }
        apmInstance.onEvent(name, value);
    }

    /**
     * record stage
     */
    public void onStage(String name) {
        long time =  WXUtils.getFixUnixTime();
        onStageWithTime(name,time);
    }

    /**
     *
     * @param name stage
     * @param time unixTime ,plz use WXUtils.getFixUnixTime
     */
    public void onStageWithTime(String name,long time){
        if (mEnd){
            return;
        }
        if (null == name){
            return;
        }
        stageMap.put(name,time);
        if (isReady){
            sendStageInfo(name,time);
        }
    }

    private void sendStageInfo(String name,long time){
        if(WXAnalyzerDataTransfer.isOpenPerformance){
            WXAnalyzerDataTransfer.transferPerformance(mInstanceId,"stage",name,time);
        }

        if (KEY_PAGE_STAGES_RENDER_ORGIGIN.equalsIgnoreCase(name)){
            mUIHandler.postDelayed(jsPerformanceCallBack,8000);
        }

        if (null == apmInstance) {
            return;
        }

        apmInstance.onStage(name, time);
    }

    private Runnable jsPerformanceCallBack = new Runnable() {
        @Override
        public void run() {
            sendPerformanceToJS();
        }
    };

    /**
     * record property
     */
    public void addProperty(String key, Object value) {
        if (mEnd){
            return;
        }
        if (null == key || null == value){
            return;
        }
        mPropertiesMap.put(key,value);
        if (isReady){
            sendProperty(key,value);
        }
    }

    private void sendProperty(String key,Object value){
        if(WXAnalyzerDataTransfer.isOpenPerformance){
            WXAnalyzerDataTransfer.transferPerformance(mInstanceId,"properties",key,value);
        }

        if (null == apmInstance) {
            return;
        }
        apmInstance.addProperty(key, value);
    }

    /**
     * record statistic
     */
    public void addStats(String key, double value) {
        if (mEnd){
            return;
        }
        if (null == key){
            return;
        }
        recordStatsMap.put(key,value);
        if (isReady){
            sendStats(key,value);
        }
    }

    private void sendStats(String key,double value){
        if(WXAnalyzerDataTransfer.isOpenPerformance){
            WXAnalyzerDataTransfer.transferPerformance(mInstanceId,"stats",key,value);
        }

        if (null == apmInstance) {
            return;
        }
        apmInstance.addStats(key, value);
    }


    public boolean hasInit(){
        return mHasInit;
    }

    /**
     * start record
     */
    public void doInit() {
        if (!isReady){
            return;
        }
        if (mHasInit){
            return;
        }
        mHasInit = true;
        if (null == apmInstance) {
            return;
        }
        apmInstance.onStart(mInstanceId);
        WXSDKInstance instance = WXSDKManager.getInstance().getAllInstanceMap().get(mInstanceId);
        String url = null == instance ? "unKnowUrl" : instance.getBundleUrl();
        addProperty(KEY_PAGE_PROPERTIES_BUBDLE_URL, url);
        addProperty(KEY_PROPERTIES_ERROR_CODE, VALUE_ERROR_CODE_DEFAULT);
        addProperty(KEY_PAGE_PROPERTIES_JSLIB_VERSION, WXEnvironment.JS_LIB_SDK_VERSION);
        addProperty(KEY_PAGE_PROPERTIES_WEEX_VERSION, WXEnvironment.WXSDK_VERSION);
        addProperty(KEY_PAGE_PROPERTIES_WEEX_VERSION, WXEnvironment.WXSDK_VERSION);
        addStats("wxReInitCount", WXBridgeManager.reInitCount);
        if (null != instance){
            addProperty(KEY_PAGE_PROPERTIES_UIKIT_TYPE, instance.getRenderType());
        }

        addProperty("wxUseRuntimeApi",WXEnvironment.sUseRunTimeApi);
        if (instance != null && (instance.getRenderStrategy() == WXRenderStrategy.DATA_RENDER
                || instance.getRenderStrategy() == WXRenderStrategy.DATA_RENDER_BINARY)) {
            addProperty(KEY_PAGE_PROPERTIES_RENDER_TYPE, WXEnvironment.EAGLE);
        }
        if (null != instance) {
            for (Map.Entry<String, String> entry : instance.getContainerInfo().entrySet()) {
                addProperty(entry.getKey(), entry.getValue());
            }
        }
    }

    public void setPageName(String pageName) {
        if (TextUtils.isEmpty(pageName)) {
            WXSDKInstance instance = WXSDKManager.getInstance().getAllInstanceMap().get(mInstanceId);
            if (null != instance) {
                pageName = instance.getContainerInfo().get(KEY_PAGE_PROPERTIES_CONTAINER_NAME);
            }
        }
        reportPageName = null == apmInstance?pageName:apmInstance.parseReportUrl(pageName);
        reportPageName = TextUtils.isEmpty(reportPageName) ? "emptyPageName" : reportPageName;
        addProperty(KEY_PAGE_PROPERTIES_BIZ_ID, reportPageName);
    }

    public void onAppear(){
        if (null == apmInstance) {
            return;
        }
        apmInstance.onAppear();
    }

    public void onDisAppear(){
        if (null == apmInstance) {
            return;
        }
        apmInstance.onDisappear();
    }

    /**
     * end record
     */
    public void onEnd() {
        if (null == apmInstance || mEnd) {
            return;
        }
        new Handler(Looper.getMainLooper()).removeCallbacks(delayCollectDataTask);
        recordPerformanceDetailData();
        exceptionRecord.clear();
        mUIHandler.removeCallbacks(jsPerformanceCallBack);
        onStage(KEY_PAGE_STAGES_DESTROY);
        if (!mHasInit){
            apmInstance.onEnd();
        }
        mEnd = true;
        if (WXEnvironment.isApkDebugable()){
            printLog();
        }
    }

    public void doDelayCollectData(){
        new Handler(Looper.getMainLooper()).postDelayed(delayCollectDataTask,8000);
    }

    private Runnable delayCollectDataTask = new Runnable() {
        @Override
        public void run() {
            recordPerformanceDetailData();
        }
    };


    private void printLog(){
        Long startDownLoad = stageMap.get(KEY_PAGE_STAGES_DOWN_BUNDLE_START);
        Long endDownLoad = stageMap.get(KEY_PAGE_STAGES_DOWN_BUNDLE_END);
        Long interaction = stageMap.get(KEY_PAGE_STAGES_INTERACTION);
        Long containerReady = stageMap.get(KEY_PAGE_STAGES_CONTAINER_READY);
        if (null != endDownLoad && null != startDownLoad){
            WXLogUtils.d("test->", "downLoadTime: "+ (endDownLoad - startDownLoad));
        }
        if (null != endDownLoad && null != interaction){
            WXLogUtils.d("test->", "renderTime: "+ (interaction - endDownLoad));
        }
        if (null != containerReady && null !=interaction){
            WXLogUtils.d("test->", "showTime: "+ (interaction - containerReady));
        }

    }

    public void arriveNewFsRenderTime(){
        if (null == apmInstance){
            return;
        }
        onStage(WXInstanceApm.KEY_PAGE_STAGES_NEW_FSRENDER);
    }

    public void arriveFSRenderTime() {
        if (null == apmInstance){
            return;
        }
        isFSEnd = true;
        onStage(WXInstanceApm.KEY_PAGE_STAGES_FSRENDER);
    }

    public void arriveInteraction(WXComponent targetComponent) {
        if (null == apmInstance || null == targetComponent || targetComponent.getInstance() == null) {
            return;
        }

        if (WXAnalyzerDataTransfer.isOpenPerformance){
            WXAnalyzerDataTransfer.transferInteractionInfo(targetComponent);
        }


        if (null == apmInstance){
            return;
        }

        WXPerformance performanceRecord = targetComponent.getInstance().getWXPerformance();
        if (null == performanceRecord){
            return;
        }

        long curTime = WXUtils.getFixUnixTime();

        if (WXAnalyzerDataTransfer.isInteractionLogOpen()){
            Log.d(WXAnalyzerDataTransfer.INTERACTION_TAG, "[client][wxinteraction]"
                + targetComponent.getInstance().getInstanceId()
                +","+targetComponent.getComponentType()
                +","+targetComponent.getRef()
                +","+targetComponent.getStyles()
                +","+targetComponent.getAttrs()
            );
        }

        if (!hasRecordFistInteractionView){
            onStage(KEY_PAGE_STAGES_FIRST_INTERACTION_VIEW);
            hasRecordFistInteractionView = true;
        }
        if (forceStopRecordInteraction){
            return;
        }
        long now = WXUtils.getFixUnixTime();
        if (now - preUpdateTime > 50){
            //for performance, reduce jni calls
            WXBridgeManager.getInstance().onInteractionTimeUpdate(mInstanceId);
            preUpdateTime = now;
        }

        interactionComponentCreateTime = componentCreateTime;
        interactionViewCreateTime = viewCreateTime;
        Double layoutTime = recordStatsMap.get("wxLayoutTime");
        interactionLayoutTime = layoutTime ==null? 0:layoutTime;

        performanceRecord.interactionTime = curTime - performanceRecord.renderUnixTimeOrigin;
        performanceRecord.interactionRealUnixTime = System.currentTimeMillis();
        onStageWithTime(KEY_PAGE_STAGES_INTERACTION,curTime);

        updateDiffStats(KEY_PAGE_STATS_I_SCREEN_VIEW_COUNT, 1);
        updateMaxStats(KEY_PAGE_STATS_I_ALL_VIEW_COUNT, performanceRecord.localInteractionViewAddCount);
        WXSDKInstance instance = WXSDKManager.getInstance().getSDKInstance(mInstanceId);
        if (null != instance) {
            updateMaxStats(KEY_PAGE_STATS_I_COMPONENT_CREATE_COUNT, instance.getWXPerformance().componentCount);
        }
    }

    private long preUpdateTime = 0;

    public void updateFSDiffStats(String name, double diffValue) {
        if (null == apmInstance || isFSEnd) {
            return;
        }
        updateDiffStats(name, diffValue);
    }

    public void recordPerformanceDetailData(){
        if (mHasRecordDetailData){
            return;
        }
        mHasRecordDetailData = true;
        addStats(KEY_PAGE_STATS_VIEW_CREATE_COST,interactionViewCreateTime);
        addStats(KEY_PAGE_STATS_COMPONENT_CREATE_COST,interactionComponentCreateTime);
        addStats(KEY_PAGE_STATS_EXECUTE_JS_CALLBACK_COST,interactionJsCallBackTime);
        addStats(KEY_PAGE_STATS_LAYOUT_TIME,interactionLayoutTime);
    }

    public void updateDiffStats(String name, double diffValue) {
        if (null == apmInstance) {
            return;
        }
        Double preVal = recordStatsMap.containsKey(name) ? recordStatsMap.get(name) : 0;
        //fix by use ConcurrentHashMap,but not sure,so report if error still happen
        if (null == preVal){
            WXExceptionUtils.commitCriticalExceptionRT(
                "",
                WXErrorCode.WX_ERR_HASH_MAP_TMP,
                "updateDiffStats",
                "key : "+name,
                null
            );
            return;
        }

        double currentValue = preVal + diffValue;
        addStats(name, currentValue);
    }

    public void updateMaxStats(String name, double currentVal) {
        if (null == apmInstance) {
            return;
        }
        Double maxValue = recordStatsMap.containsKey(name) ? recordStatsMap.get(name) : 0;
        //fix by use ConcurrentHashMap,but not sure,so report if error still happen
        if (null == maxValue){
            WXExceptionUtils.commitCriticalExceptionRT(
                "",
                WXErrorCode.WX_ERR_HASH_MAP_TMP,
                "updateMaxStats",
                "key : "+name,
                null
            );
            return;
        }
        if (maxValue < currentVal) {
            maxValue = currentVal;
            addStats(name, maxValue);
        }
    }

    public void updateRecordInfo(Map<String, Object> extParams) {
        if (null == apmInstance || null == extParams) {
            return;
        }

        addPropeyFromExtParms(KEY_PAGE_PROPERTIES_REQUEST_TYPE, KEY_PAGE_PROPERTIES_REQUEST_TYPE, extParams);
        addPropeyFromExtParms("cacheType", KEY_PAGE_PROPERTIES_CACHE_TYPE, extParams);
        addPropeyFromExtParms("zCacheInfo", KEY_PAGE_PROPERTIES_CACHE_INFO, extParams);

        addStats(KEY_PAGE_STATS_JSLIB_INIT_TIME, WXEnvironment.sJSLibInitTime);
        addProperty(KEY_PAGE_PROPERTIES_JS_FM_INI, WXEnvironment.JsFrameworkInit);

        Object wxNetLibDownBundleTime = extParams.get("actualNetworkTime");
        if (wxNetLibDownBundleTime instanceof Long) {
            updateDiffStats(KEY_PAGE_STATS_ACTUAL_DOWNLOAD_TIME, ((Long)wxNetLibDownBundleTime).doubleValue());
        }
    }

    private void addPropeyFromExtParms(String fromKey, String toKey, Map<String, Object> extParams) {
        Object value = extParams.get(fromKey);
        if (value instanceof String) {
            addProperty(toKey, value);
        }
    }

    /************** called by IWXHttpAdapter implementer *****************/

    public void actionNetRequest() {
        if (!isFSEnd) {
            updateFSDiffStats(WXInstanceApm.KEY_PAGE_STATS_FS_REQUEST_NUM, 1);
        }
        updateDiffStats(WXInstanceApm.KEY_PAGE_STATS_NET_NUM, 1);
    }

    public void actionNetResult(boolean succeed, String errorCode) {
        if (succeed) {
            updateDiffStats(WXInstanceApm.KEY_PAGE_STATS_NET_SUCCESS_NUM, 1);
        } else {
            updateDiffStats(WXInstanceApm.KEY_PAGE_STATS_NET_FAIL_NUM, 1);
        }
    }

    /************** called by IWXImgLoaderAdapter implementer *****************/

    public void actionLoadImg() {
        updateDiffStats(WXInstanceApm.KEY_PAGE_STATS_IMG_LOAD_NUM, 1);
    }

    public void actionLoadImgResult(boolean succeed, String errorCode) {
        if (succeed) {
            updateDiffStats(WXInstanceApm.KEY_PAGE_STATS_IMG_LOAD_SUCCESS_NUM, 1);
        } else {
            updateDiffStats(WXInstanceApm.KEY_PAGE_STATS_IMG_LOAD_FAIL_NUM, 1);
        }
    }

    public void sendPerformanceToJS() {
        if (hasSendInteractionToJS) {
            return;
        }
        hasSendInteractionToJS = true;
        WXSDKInstance instance = WXSDKManager.getInstance().getAllInstanceMap().get(mInstanceId);
        if (null == instance) {
            return;
        }

        Map<String,String> sendProperties = new HashMap<>(2);
        sendProperties.put(KEY_PAGE_PROPERTIES_BIZ_ID,reportPageName);
        sendProperties.put(KEY_PAGE_PROPERTIES_BUBDLE_URL,instance.getBundleUrl());

        Map<String,Long> sendStage = new HashMap<>(1);
        sendStage.put(KEY_PAGE_STAGES_INTERACTION,instance.getWXPerformance().interactionRealUnixTime);

        Map<String, Object> data = new HashMap<>(2);
        data.put("stage",sendStage);
        data.put("properties",sendProperties);

        instance.fireGlobalEventCallback("wx_apm", data);
    }


    public String toPerfString() {
        Long start = stageMap.get(KEY_PAGE_STAGES_RENDER_ORGIGIN);
        Long end = stageMap.get(KEY_PAGE_STAGES_INTERACTION);
        Long wxNewFsRender = stageMap.get(KEY_PAGE_STAGES_NEW_FSRENDER);
        StringBuilder builder = new StringBuilder();
        if(start != null && end != null){
            builder.append("interactiveTime " + (end - start) + "ms");
        }
        if(wxNewFsRender != null){
            builder.append(" wxNewFsRender " + (wxNewFsRender) + "ms");
        }
        return builder.toString();
   }

   public void updateNativePerformanceData(Map<String,String> nativePerformanceData){
        if (null == nativePerformanceData){
            return;
        }
        for (Map.Entry<String,String> entry : nativePerformanceData.entrySet()){
            double value = -1;
            try {
                value = Double.valueOf(entry.getValue());
            }catch (Exception e){
                e.printStackTrace();
            }
            if (value != -1){
                recordStatsMap.put(entry.getKey(),value);
            }
        }
   }
 }
