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

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import android.support.annotation.Nullable;
import android.text.TextUtils;
import org.apache.weex.WXEnvironment;
import org.apache.weex.WXSDKInstance;
import org.apache.weex.WXSDKManager;
import org.apache.weex.adapter.IWXConfigAdapter;
import org.apache.weex.adapter.IWXJSExceptionAdapter;
import org.apache.weex.common.WXErrorCode;
import org.apache.weex.common.WXErrorCode.ErrorGroup;
import org.apache.weex.common.WXJSExceptionInfo;
import org.apache.weex.common.WXPerformance;
import org.apache.weex.performance.WXAnalyzerDataTransfer;
import org.apache.weex.performance.WXInstanceApm;
import org.apache.weex.performance.WXStateRecord;

/**
 * Created on 2017/10/13.
 */

public class WXExceptionUtils {

    private static Set<String> sGlobalExceptionRecord = new CopyOnWriteArraySet<String>();

	/**
	 * degradeUrl for degrade case
	 */
	public static String degradeUrl = "BundleUrlDefaultDegradeUrl";

	private static boolean checkNeedReportCauseRepeat(String instanceId, WXErrorCode errCode,String exception){

	    if (TextUtils.isEmpty(exception)){
	        return true;
        }

        if (null != errCode && errCode.getErrorGroup() != ErrorGroup.JS){
	        return true;
        }

        if (TextUtils.isEmpty(instanceId)){
            instanceId = "instanceIdNull";
        }

        String targetException = exception.length() > 200 ?exception.substring(0,200) : exception;
        Set<String> recordExceptionHistory= null;

        WXSDKInstance instance =  WXSDKManager.getInstance().getAllInstanceMap().get(instanceId);
        if (null == instance){
            recordExceptionHistory = sGlobalExceptionRecord;
        }else {
            recordExceptionHistory =  instance.getApmForInstance().exceptionRecord;
        }

        if (null == recordExceptionHistory){
            return true;
        }

        if (recordExceptionHistory.contains(targetException)){
            return false;
        }
        recordExceptionHistory.add(targetException);
        return true;
    }




	/**
	 * commitCriticalExceptionRT eg:JsRuntime Exception or JsFramework Init Exception
	 * @param instanceId
	 * @param errCode
	 * @param function
	 * @param exception
	 * @param extParams
	 */
	public static void commitCriticalExceptionRT(@Nullable final String instanceId,
												 @Nullable final WXErrorCode errCode,
												 @Nullable final String function,
												 @Nullable final String exception,
												 @Nullable final Map<String,String> extParams ) {

        try {
            WXLogUtils.e("weex","commitCriticalExceptionRT :"+errCode+"exception"+exception);
            WXStateRecord.getInstance().recordException(instanceId,exception);
            IWXConfigAdapter configAdapter = WXSDKManager.getInstance().getWxConfigAdapter();
            boolean doCheck = true;
            if (null != configAdapter){
                String value = configAdapter.getConfig("wxapm","check_repeat_report","true");
                doCheck = "true".equalsIgnoreCase(value);
            }
            boolean doReport =true;
            if (doCheck){
                doReport = checkNeedReportCauseRepeat(instanceId,errCode,exception);
            }
            if (!doReport){
                return;
            }
        }catch (Throwable e){
            e.printStackTrace();
        }


		commitCriticalExceptionWithDefaultUrl(
		    "BundleUrlDefault",
            instanceId,
            errCode,
            function,
            exception,
            extParams
        );
	}

    public static void commitCriticalExceptionWithDefaultUrl(
        @Nullable final String defaultUrl,
        @Nullable final String instanceId,
        @Nullable final WXErrorCode errCode,
        @Nullable final String function,
        @Nullable final String exception,
        @Nullable final Map<String,String> extParams
    ){
        IWXJSExceptionAdapter adapter = WXSDKManager.getInstance().getIWXJSExceptionAdapter();
        WXSDKInstance instance = null;
        WXJSExceptionInfo exceptionCommit;
        String bundleUrlCommit = TextUtils.isEmpty(defaultUrl)?"BundleUrlDefault":defaultUrl;
        String instanceIdCommit = "InstanceIdDefalut";
        String exceptionMsgCommit = exception;
        Map<String, String> commitMap = extParams;
        if (null == commitMap){
            commitMap = new HashMap<>();
        }
        commitMap.put("wxSdkInitStartTime", String.valueOf(WXEnvironment.sSDKInitStart));
        commitMap.put("wxSDKInitCostTime", String.valueOf(WXEnvironment.sSDKInitTime));
        commitMap.put("wxSDKCurExceptionTime", String.valueOf(System.currentTimeMillis()));
        commitMap.put("wxUseRuntimeApi",String.valueOf(WXEnvironment.sUseRunTimeApi));
        if (!TextUtils.isEmpty(instanceId)) {
            instanceIdCommit = instanceId;
            instance = WXSDKManager.getInstance().getAllInstanceMap().get(instanceId);

            if (null != instance) {
                bundleUrlCommit = instance.getApmForInstance().reportPageName;
                Object loadLength = instance.getApmForInstance().extInfo.get(WXInstanceApm.VALUE_BUNDLE_LOAD_LENGTH);
                String loadLengthStr = (loadLength instanceof Integer)?String.valueOf(loadLength):"unknownLength";
                commitMap.put(WXInstanceApm.VALUE_BUNDLE_LOAD_LENGTH,loadLengthStr);
                commitMap.put("templateInfo",instance.getTemplateInfo());
                if (TextUtils.isEmpty(bundleUrlCommit) || bundleUrlCommit.equals(WXPerformance.DEFAULT)) {
                    if (!TextUtils.equals(degradeUrl, "BundleUrlDefaultDegradeUrl")) {
                        bundleUrlCommit = degradeUrl;
                    } else
                        bundleUrlCommit = WXSDKInstance.requestUrl;
                }
                for (Map.Entry<String,String> entry: instance.getContainerInfo().entrySet()){
                    commitMap.put(entry.getKey(),entry.getValue());
                }
                commitMap.put("wxStageList",convertStageToStr(instance));
                String bundleTemplate = instance.getTemplate();
                if (null == bundleTemplate){
                    bundleTemplate = "has recycle by gc";
                }else {
                    int length = bundleTemplate.length();
                    bundleTemplate = bundleTemplate.substring(0,Math.min(length,300));
                }
                commitMap.put("wxTemplateOfBundle",bundleTemplate);

                Long pageStartTime = instance.getApmForInstance().stageMap.get(WXInstanceApm.KEY_PAGE_STAGES_DOWN_BUNDLE_START);
                if (null == pageStartTime){
                    pageStartTime = instance.getApmForInstance().stageMap.get(WXInstanceApm.KEY_PAGE_STAGES_RENDER_ORGIGIN);
                }
                if (null != pageStartTime){
                    commitMap.put("wxUseTime", String.valueOf(WXUtils.getFixUnixTime() - pageStartTime));
                }
            }
        } else {//instance is null for instance id is null
            if (commitMap.size() > 0) {
                bundleUrlCommit = TextUtils.isEmpty(commitMap.get("weexUrl")) ? commitMap.get("weexUrl")
                    : commitMap.get("bundleUrl");
            }
        }

        String illegalValue = commitMap.get("errorCode");
        if (null != illegalValue && illegalValue.length()>200){
            commitMap.remove("errorCode");
        }

        exceptionCommit = new WXJSExceptionInfo(instanceIdCommit, bundleUrlCommit, errCode, function, exceptionMsgCommit, commitMap);
        if (adapter != null) {
            adapter.onJSException(exceptionCommit);
        }

        WXAnalyzerDataTransfer.transferError(exceptionCommit, instanceId);
    }

    private static String convertStageToStr(WXSDKInstance instance) {
        if (null == instance || null == instance.getApmForInstance() || instance.getApmForInstance().stageMap.isEmpty()) {
            return "noStageRecord";
        }
        List<Entry<String, Long>> list = new ArrayList<>(instance.getApmForInstance().stageMap.entrySet());
        Collections.sort(list, new Comparator<Entry<String, Long>>() {
            @Override
            public int compare(Entry<String, Long> o1, Entry<String, Long> o2) {
                return (int)(o1.getValue() - o2.getValue());
            }
        });

        StringBuilder builder = new StringBuilder();
        for (Map.Entry<String, Long> entry : list) {
            builder.append(entry.getKey()).append(':').append(entry.getValue()).append("->");
        }
        return builder.toString();
    }

}