/*
 * Created by zhangxiangwei on 2019/12/31.
 * Copyright 2015-2020 Sensors Data Inc.
 *
 * Licensed 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 com.sensorsdata.analytics.android.sdk.visual;

import android.annotation.SuppressLint;
import android.text.TextUtils;
import android.util.LruCache;

import com.sensorsdata.analytics.android.sdk.SALog;
import com.sensorsdata.analytics.android.sdk.visual.model.WebNode;
import com.sensorsdata.analytics.android.sdk.visual.model.WebNodeInfo;
import com.sensorsdata.analytics.android.sdk.visual.util.Dispatch;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

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


public class WebNodesManager {

    private static final String TAG = "SA.Visual.WebNodesManager";
    private static final String CALL_TYPE_VISUALIZED_TRACK = "visualized_track";
    private static final String CALL_TYPE_PAGE_INFO = "page_info";

    private volatile static WebNodesManager mSingleton = null;
    private static LruCache<String, WebNodeInfo> sWebNodesCache;
    private static LruCache<String, WebNodeInfo> sPageInfoCache;
    private static final int LRU_CACHE_MAX_SIZE = 10;
    // 页面信息缓存,和页面截图合并后 Hash
    private String mLastWebNodeMsg = null;
    // 是否含有错误提示信息,有错误提示则需要一直上传页面信息
    private boolean mHasH5AlertInfo;
    // 保存最后一次的 WebView url
    private String mWebViewUrl;

    private WebNodesManager() {
    }

    public static WebNodesManager getInstance() {
        if (mSingleton == null) {
            synchronized (WebNodesManager.class) {
                if (mSingleton == null) {
                    mSingleton = new WebNodesManager();
                }
            }
        }
        return mSingleton;
    }

    @SuppressLint("NewApi")
    void handlerMessage(String message) {
        Dispatch.getInstance().removeCallbacksAndMessages();
        if (!VisualizedAutoTrackService.getInstance().isVisualizedAutoTrackRunning()) {
            return;
        }
        if (TextUtils.isEmpty(message)) {
            return;
        }
        SALog.i(TAG, "handlerMessage: " + message);
        mLastWebNodeMsg = message;
        mHasH5AlertInfo = false;
        try {
            JSONObject jsonObject = new JSONObject(message);
            String callType = jsonObject.optString("callType");
            switch (callType) {
                case CALL_TYPE_VISUALIZED_TRACK:
                    List<WebNode> list = parseResult(message);
                    if (list != null && list.size() > 0) {
                        if (sWebNodesCache == null) {
                            sWebNodesCache = new LruCache<>(LRU_CACHE_MAX_SIZE);
                        }
                        if (!TextUtils.isEmpty(mWebViewUrl)) {
                            sWebNodesCache.put(mWebViewUrl, WebNodeInfo.createWebNodesInfo(list));
                        }
                    }
                    break;
                case CALL_TYPE_PAGE_INFO:
                    WebNodeInfo pageInfo = parsePageInfo(message);
                    if (pageInfo != null) {
                        mWebViewUrl = pageInfo.getUrl();
                        if (sPageInfoCache == null) {
                            sPageInfoCache = new LruCache<>(LRU_CACHE_MAX_SIZE);
                        }
                        sPageInfoCache.put(pageInfo.getUrl(), pageInfo);
                    }
                    break;
                default:
                    break;
            }
        } catch (JSONException e) {
            SALog.printStackTrace(e);
        } catch (Exception e) {
            SALog.printStackTrace(e);
        }
    }

    @SuppressLint("NewApi")
    void handlerFailure(String webViewUrl, String message) {
        Dispatch.getInstance().removeCallbacksAndMessages();
        if (!VisualizedAutoTrackService.getInstance().isVisualizedAutoTrackRunning()) {
            return;
        }
        if (TextUtils.isEmpty(message)) {
            return;
        }
        SALog.i(TAG, "handlerFailure url " + webViewUrl + ",msg: " + message);
        mHasH5AlertInfo = true;
        mLastWebNodeMsg = message;
        List<WebNodeInfo.AlertInfo> list = parseAlertResult(message);
        if (list != null && list.size() > 0) {
            if (sWebNodesCache == null) {
                sWebNodesCache = new LruCache<>(LRU_CACHE_MAX_SIZE);
            }
            sWebNodesCache.put(webViewUrl, WebNodeInfo.createWebAlertInfo(list));
        }
    }

    private List<WebNode> parseResult(String msg) {
        if (TextUtils.isEmpty(msg))
            return null;
        List<WebNode> list = new ArrayList<>();
        Map<String, WebNode> hashMap = new HashMap<>();
        try {
            JSONObject jsonObject = new JSONObject(msg);
            JSONArray array = jsonObject.getJSONArray("data");
            if (array != null && array.length() > 0) {
                for (int i = 0; i < array.length(); i++) {
                    JSONObject object = array.getJSONObject(i);
                    WebNode webNode = new WebNode();
                    webNode.setId(object.optString("id"));
                    webNode.set$element_content(object.optString("$element_content"));
                    webNode.set$element_selector(object.optString("$element_selector"));
                    webNode.setTagName(object.optString("tagName"));
                    webNode.setTop((float) object.optDouble("top"));
                    webNode.setLeft((float) object.optDouble("left"));
                    webNode.setScrollX((float) object.optDouble("scrollX"));
                    webNode.setScrollY((float) object.optDouble("scrollY"));
                    webNode.setWidth((float) object.optDouble("width"));
                    webNode.setHeight((float) object.optDouble("height"));
                    webNode.setScale((float) object.optDouble("scale"));
                    webNode.setVisibility(object.optBoolean("visibility"));
                    webNode.set$url(object.optString("$url"));
                    webNode.setzIndex(object.optInt("zIndex"));
                    webNode.set$title(object.optString("$title"));
                    JSONArray subElementsArray = object.getJSONArray("subelements");
                    List<String> subViewIds = new ArrayList<>();
                    if (subElementsArray != null && subElementsArray.length() > 0) {
                        for (int j = 0; j < subElementsArray.length(); j++) {
                            String subElementsId = subElementsArray.optString(j);
                            if (!TextUtils.isEmpty(subElementsId)) {
                                subViewIds.add(subElementsId);
                                if (!hashMap.containsKey(subElementsId)) {
                                    hashMap.put(subElementsId, webNode);
                                }
                            }
                        }
                    }
                    if (subViewIds.size() > 0) {
                        webNode.setSubelements(subViewIds);
                    }
                    list.add(webNode);
                }
            }
            if (!hashMap.isEmpty()) {
                modifyWebNodes(list, hashMap);
            }
        } catch (JSONException e) {
            SALog.printStackTrace(e);
        } catch (Exception e) {
            SALog.printStackTrace(e);
        }
        return list;
    }

    private WebNodeInfo parsePageInfo(String msg) {
        if (TextUtils.isEmpty(msg))
            return null;
        try {
            JSONObject jsonObject = new JSONObject(msg);
            JSONObject data = jsonObject.getJSONObject("data");
            return WebNodeInfo.createPageInfo(data.optString("$title"), data.optString("$url"));
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;
    }


    private List<WebNodeInfo.AlertInfo> parseAlertResult(String msg) {
        if (TextUtils.isEmpty(msg))
            return null;
        List<WebNodeInfo.AlertInfo> list = null;
        try {
            JSONObject jsonObject = new JSONObject(msg);
            JSONArray array = jsonObject.getJSONArray("data");
            if (array != null && array.length() > 0) {
                list = new ArrayList<>();
                for (int i = 0; i < array.length(); i++) {
                    JSONObject object = array.getJSONObject(i);
                    if (object != null) {
                        list.add(new WebNodeInfo.AlertInfo(
                                object.optString("title"),
                                object.optString("message"),
                                object.optString("link_text"),
                                object.optString("link_url")));
                    }
                }
            }
        } catch (JSONException e) {
            SALog.printStackTrace(e);
        } catch (Exception e) {
            SALog.printStackTrace(e);
        }
        return list;
    }

    /**
     * 前端是根据相对坐标来定位的,但 H5 所有的坐标都是绝对坐标;当存在 subviews 属性时,需要做坐标系修正。
     * 需要考虑到 scrollY、scrollX
     *
     * @param webNodeList
     * @return 经过坐标修正的 WebNode
     */
    private void modifyWebNodes(List<WebNode> webNodeList, Map<String, WebNode> hashMap) {
        if (webNodeList == null || webNodeList.size() == 0) {
            return;
        }
        synchronized (this) {
            for (WebNode webNode : webNodeList) {
                Iterator<Map.Entry<String, WebNode>> iterator = hashMap.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry<String, WebNode> entry = iterator.next();
                    if (webNode != null && entry != null && TextUtils.equals(webNode.getId(), entry.getKey())) {
                        webNode.setTop(webNode.getTop() - webNode.getScrollY() - entry.getValue().getTop());
                        webNode.setLeft(webNode.getLeft() - webNode.getScrollX() - entry.getValue().getLeft());
                    }
                }
            }
        }
    }

    @SuppressLint("NewApi")
    WebNodeInfo getWebNodes(String webViewUrl) {
        if (!VisualizedAutoTrackService.getInstance().isVisualizedAutoTrackRunning()) {
            return null;
        }
        if (sWebNodesCache == null) {
            sWebNodesCache = new LruCache<>(LRU_CACHE_MAX_SIZE);
        }
        return sWebNodesCache.get(webViewUrl);
    }

    @SuppressLint("NewApi")
    WebNodeInfo getWebPageInfo(String webViewUrl) {
        if (!VisualizedAutoTrackService.getInstance().isVisualizedAutoTrackRunning()) {
            return null;
        }
        if (sPageInfoCache == null) {
            sPageInfoCache = new LruCache<>(LRU_CACHE_MAX_SIZE);
        }
        return sPageInfoCache.get(webViewUrl);
    }

    String getLastWebNodeMsg() {
        return mLastWebNodeMsg;
    }

    boolean hasH5AlertInfo() {
        return mHasH5AlertInfo;
    }

    public void clear() {
        mHasH5AlertInfo = false;
        mLastWebNodeMsg = null;
    }

}