package com.hjhrq1991.library.tbs;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Looper;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;

import com.tencent.smtt.export.external.interfaces.ConsoleMessage;
import com.tencent.smtt.export.external.interfaces.GeolocationPermissionsCallback;
import com.tencent.smtt.export.external.interfaces.IX5WebChromeClient;
import com.tencent.smtt.export.external.interfaces.JsPromptResult;
import com.tencent.smtt.export.external.interfaces.JsResult;
import com.tencent.smtt.sdk.ValueCallback;
import com.tencent.smtt.sdk.WebChromeClient;
import com.tencent.smtt.sdk.WebStorage;
import com.tencent.smtt.sdk.WebView;

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

@SuppressLint("SetJavaScriptEnabled")
public class TbsBridgeWebView extends WebView implements WebViewJavascriptBridge {

    private final String TAG = "BridgeWebView";

    private BridgeWebViewClient bridgeWebViewClient;

    Map<String, CallBackFunction> responseCallbacks = new HashMap<String, CallBackFunction>();
    Map<String, BridgeHandler> messageHandlers = new HashMap<String, BridgeHandler>();
    BridgeHandler defaultHandler = new DefaultHandler();

    private List<Message> startupMessage = new ArrayList<Message>();

    public List<Message> getStartupMessage() {
        return startupMessage;
    }

    public void setStartupMessage(List<Message> startupMessage) {
        this.startupMessage = startupMessage;
    }

    private long uniqueId = 0;

    public TbsBridgeWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public TbsBridgeWebView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public TbsBridgeWebView(Context context) {
        super(context);
        init();
    }

    /**
     * @param handler default handler,handle messages send by js without assigned handler name,
     *                if js message has handler name, it will be handled by named handlers registered by native
     */
    public void setDefaultHandler(BridgeHandler handler) {
        this.defaultHandler = handler;
    }

    private void init() {
        this.setVerticalScrollBarEnabled(false);
        this.setHorizontalScrollBarEnabled(false);
        this.getSettings().setJavaScriptEnabled(true);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            WebView.setWebContentsDebuggingEnabled(true);
        }
        this.setWebViewClient(generateBridgeWebViewClient());
    }

    protected BridgeWebViewClient generateBridgeWebViewClient() {
        return bridgeWebViewClient = new BridgeWebViewClient(this);
    }

    public void handlerReturnData(String url) {
        String functionName = BridgeUtil.getFunctionFromReturnUrl(url);
        CallBackFunction f = responseCallbacks.get(functionName);
        String data = BridgeUtil.getDataFromReturnUrl(url);
        if (f != null) {
            f.onCallBack(data);
            responseCallbacks.remove(functionName);
            return;
        }
    }

    @Override
    public void send(String data) {
        send(data, null);
    }

    @Override
    public void send(String data, CallBackFunction responseCallback) {
        doSend(null, data, responseCallback);
    }

    private void doSend(String handlerName, String data, CallBackFunction responseCallback) {
        Message m = new Message();
        if (!TextUtils.isEmpty(data)) {
            m.setData(data);
        }
        if (responseCallback != null) {
            String callbackStr = String.format(BridgeUtil.CALLBACK_ID_FORMAT, ++uniqueId + (BridgeUtil.UNDERLINE_STR + SystemClock.currentThreadTimeMillis()));
            responseCallbacks.put(callbackStr, responseCallback);
            m.setCallbackId(callbackStr);
        }
        if (!TextUtils.isEmpty(handlerName)) {
            m.setHandlerName(handlerName);
        }
        queueMessage(m);
    }

    private void queueMessage(Message m) {
        if (startupMessage != null) {
            startupMessage.add(m);
        } else {
            dispatchMessage(m);
        }
    }

    public void dispatchMessage(Message m) {
        String messageJson = m.toJson();
        //escape special characters for json string
        messageJson = messageJson.replaceAll("(\\\\)([^utrn])", "\\\\\\\\$1$2");
        messageJson = messageJson.replaceAll("(?<=[^\\\\])(\")", "\\\\\"");
        String javascriptCommand = String.format(BridgeUtil.JS_HANDLE_MESSAGE_FROM_JAVA.replace(BridgeConfig.defaultJs, BridgeConfig.customJs), messageJson);
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            this.loadUrl(javascriptCommand);
        }
    }

    public void flushMessageQueue() {
        if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
            loadUrl(BridgeUtil.JS_FETCH_QUEUE_FROM_JAVA.replace(BridgeConfig.defaultJs, BridgeConfig.customJs), new CallBackFunction() {

                @Override
                public void onCallBack(String data) {
                    // deserializeMessage
                    List<Message> list = null;
                    try {
                        list = Message.toArrayList(data);
                    } catch (Exception e) {
                        e.printStackTrace();
                        return;
                    }
                    if (list == null || list.size() == 0) {
                        return;
                    }
                    for (int i = 0; i < list.size(); i++) {
                        Message m = list.get(i);
                        String responseId = m.getResponseId();
                        // 是否是response
                        if (!TextUtils.isEmpty(responseId)) {
                            CallBackFunction function = responseCallbacks.get(responseId);
                            String responseData = m.getResponseData();
                            function.onCallBack(responseData);
                            responseCallbacks.remove(responseId);
                        } else {
                            CallBackFunction responseFunction = null;
                            // if had callbackId
                            final String callbackId = m.getCallbackId();
                            if (!TextUtils.isEmpty(callbackId)) {
                                responseFunction = new CallBackFunction() {
                                    @Override
                                    public void onCallBack(String data) {
                                        Message responseMsg = new Message();
                                        responseMsg.setResponseId(callbackId);
                                        responseMsg.setResponseData(data);
                                        queueMessage(responseMsg);
                                    }
                                };
                            } else {
                                responseFunction = new CallBackFunction() {
                                    @Override
                                    public void onCallBack(String data) {
                                        // do nothing
                                    }
                                };
                            }
                            BridgeHandler handler;
                            if (!TextUtils.isEmpty(m.getHandlerName())) {
                                handler = messageHandlers.get(m.getHandlerName());
                            } else {
                                handler = defaultHandler;
                            }
                            if (handler != null) {
                                handler.handler(m.getData(), responseFunction);
                            }
                        }
                    }
                }
            });
        }
    }

    public void loadUrl(String jsUrl, CallBackFunction returnCallback) {
        this.loadUrl(jsUrl);
        responseCallbacks.put(BridgeUtil.parseFunctionName(jsUrl, BridgeConfig.customJs), returnCallback);
    }

    /**
     * register handler,so that javascript can call it
     *
     * @param handlerName handlerName
     * @param handler     Handler
     */
    public void registerHandler(String handlerName, BridgeHandler handler) {
        if (handler != null) {
            messageHandlers.put(handlerName, handler);
        }
    }

    /**
     * call javascript registered handler
     *
     * @param handlerName handlerName
     * @param data        data
     * @param callBack    callBack
     */
    public void callHandler(String handlerName, String data, CallBackFunction callBack) {
        doSend(handlerName, data, callBack);
    }

    public void setBridgeWebViewClientListener(BridgeWebViewClientListener bridgeWebViewClientListener) {
        bridgeWebViewClient.setBridgeWebViewClientListener(bridgeWebViewClientListener);
    }

    /**
     * 销毁时调用,移除listener
     */
    public void removeListener() {
        if (bridgeWebViewClient != null) {
            bridgeWebViewClient.removeListener();
        }
        if (onWebChromeClientListener != null) {
            onWebChromeClientListener = null;
        }
    }

    private OnWebChromeClientListener onWebChromeClientListener;

    public void setWebChromeClientListener(OnWebChromeClientListener onWebChromeClientListener) {
        this.onWebChromeClientListener = onWebChromeClientListener;
        setWebChromeClient(newWebChromeClient());
    }

    public void setWebChromeClientListener(WebChromeClientListener webChromeClientListener) {
        this.onWebChromeClientListener = webChromeClientListener;
        setWebChromeClient(newWebChromeClient());
    }

    private WebChromeClient newWebChromeClient() {
        WebChromeClient wvcc = new WebChromeClient() {
            @Override
            public void onExceededDatabaseQuota(String s, String s1, long l, long l1, long l2, WebStorage.QuotaUpdater quotaUpdater) {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.onExceededDatabaseQuota(s, s1, l, l1, l2, quotaUpdater);
                } else {
                    super.onExceededDatabaseQuota(s, s1, l, l1, l2, quotaUpdater);
                }
            }

            @Override
            public Bitmap getDefaultVideoPoster() {
                return onWebChromeClientListener != null ? onWebChromeClientListener.getDefaultVideoPoster() :
                        super.getDefaultVideoPoster();
            }

            @Override
            public void getVisitedHistory(ValueCallback<String[]> valueCallback) {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.getVisitedHistory(valueCallback);
                } else {
                    super.getVisitedHistory(valueCallback);
                }
            }

            @Override
            public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
                return onWebChromeClientListener != null ? onWebChromeClientListener.onConsoleMessage(consoleMessage) :
                        super.onConsoleMessage(consoleMessage);
            }

            @Override
            public boolean onCreateWindow(WebView webView, boolean b, boolean b1, android.os.Message message) {
                return onWebChromeClientListener != null ? onWebChromeClientListener.onCreateWindow(webView, b, b1, message) :
                        super.onCreateWindow(webView, b, b1, message);
            }

            @Override
            public void onGeolocationPermissionsHidePrompt() {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.onGeolocationPermissionsHidePrompt();
                } else {
                    super.onGeolocationPermissionsHidePrompt();
                }
            }

            @Override
            public void onGeolocationPermissionsShowPrompt(String s, GeolocationPermissionsCallback geolocationPermissionsCallback) {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.onGeolocationPermissionsShowPrompt(s, geolocationPermissionsCallback);
                } else {
                    super.onGeolocationPermissionsShowPrompt(s, geolocationPermissionsCallback);
                }
            }

            @Override
            public void onHideCustomView() {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.onHideCustomView();
                } else {
                    super.onHideCustomView();
                }
            }

            @Override
            public boolean onJsAlert(WebView webView, String s, String s1, JsResult jsResult) {
                return onWebChromeClientListener != null ? onWebChromeClientListener.onJsAlert(webView, s, s1, jsResult) :
                        super.onJsAlert(webView, s, s1, jsResult);
            }

            @Override
            public boolean onJsConfirm(WebView webView, String s, String s1, JsResult jsResult) {
                return onWebChromeClientListener != null ? onWebChromeClientListener.onJsConfirm(webView, s, s1, jsResult) :
                        super.onJsConfirm(webView, s, s1, jsResult);
            }

            @Override
            public boolean onJsPrompt(WebView webView, String s, String s1, String s2, JsPromptResult jsPromptResult) {
                return onWebChromeClientListener != null ? onWebChromeClientListener.onJsPrompt(webView, s, s1, s2, jsPromptResult) :
                        super.onJsPrompt(webView, s, s1, s2, jsPromptResult);
            }

            @Override
            public boolean onJsBeforeUnload(WebView webView, String s, String s1, JsResult jsResult) {
                return onWebChromeClientListener != null ? onWebChromeClientListener.onJsBeforeUnload(webView, s, s1, jsResult) :
                        super.onJsBeforeUnload(webView, s, s1, jsResult);
            }

            @Override
            public boolean onJsTimeout() {
                return onWebChromeClientListener != null ? onWebChromeClientListener.onJsTimeout() :
                        super.onJsTimeout();
            }

            @Override
            public void onProgressChanged(WebView webView, int i) {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.onProgressChanged(webView, i);
                } else {
                    super.onProgressChanged(webView, i);
                }
            }

            @Override
            public void onReachedMaxAppCacheSize(long l, long l1, WebStorage.QuotaUpdater quotaUpdater) {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.onReachedMaxAppCacheSize(l, l1, quotaUpdater);
                } else {
                    super.onReachedMaxAppCacheSize(l, l1, quotaUpdater);
                }
            }

            @Override
            public void onReceivedIcon(WebView webView, Bitmap bitmap) {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.onReceivedIcon(webView, bitmap);
                } else {
                    super.onReceivedIcon(webView, bitmap);
                }
            }

            @Override
            public void onReceivedTouchIconUrl(WebView webView, String s, boolean b) {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.onReceivedTouchIconUrl(webView, s, b);
                } else {
                    super.onReceivedTouchIconUrl(webView, s, b);
                }
            }

            @Override
            public void onReceivedTitle(WebView webView, String s) {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.onReceivedTitle(webView, s);
                } else {
                    super.onReceivedTitle(webView, s);
                }
            }

            @Override
            public void onRequestFocus(WebView webView) {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.onRequestFocus(webView);
                } else {
                    super.onRequestFocus(webView);
                }
            }

            @Override
            public void onShowCustomView(View view, IX5WebChromeClient.CustomViewCallback customViewCallback) {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.onShowCustomView(view, customViewCallback);
                } else {
                    super.onShowCustomView(view, customViewCallback);
                }
            }

            @Override
            public void onShowCustomView(View view, int i, IX5WebChromeClient.CustomViewCallback customViewCallback) {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.onShowCustomView(view, i, customViewCallback);
                } else {
                    super.onShowCustomView(view, i, customViewCallback);
                }
            }

            @Override
            public void onCloseWindow(WebView webView) {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.onCloseWindow(webView);
                } else {
                    super.onCloseWindow(webView);
                }
            }

            @Override
            public View getVideoLoadingProgressView() {
                return onWebChromeClientListener != null ? onWebChromeClientListener.getVideoLoadingProgressView() :
                        super.getVideoLoadingProgressView();
            }

            @Override
            public void openFileChooser(ValueCallback<Uri> valueCallback, String s, String s1) {
                if (onWebChromeClientListener != null) {
                    onWebChromeClientListener.openFileChooser(valueCallback, s, s1);
                } else {
                    super.openFileChooser(valueCallback, s, s1);
                }
            }

            @Override
            public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> valueCallback, FileChooserParams fileChooserParams) {
                return onWebChromeClientListener != null ? onWebChromeClientListener.onShowFileChooser(webView, valueCallback, fileChooserParams) :
                        super.onShowFileChooser(webView, valueCallback, fileChooserParams);
            }
        };
        return wvcc;
    }

    /**
     * @param customJs 自定义桥名,可为空,为空时使用默认桥名
     *                 自定义桥名回调,如用自定义桥名,请copy一份WebViewJavascriptBridge.js替换文件名
     *                 及脚本内所有包含"WebViewJavascriptBridge"的内容为你的自定义桥名
     * @author hjhrq1991 created at 6/20/16 17:32.
     */
    public void setCustom(String customJs) {
        BridgeConfig.customJs = !TextUtils.isEmpty(customJs) ? customJs : BridgeConfig.defaultJs;
    }
}