package com.ycbjie.webviewlib.wv;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.Keep;
import android.support.v7.app.AlertDialog;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.ViewGroup;
import android.webkit.JavascriptInterface;
import android.widget.EditText;
import android.widget.FrameLayout;
import com.tencent.smtt.export.external.interfaces.JsPromptResult;
import com.tencent.smtt.export.external.interfaces.JsResult;
import com.tencent.smtt.sdk.WebView;
import com.ycbjie.webviewlib.base.RequestInfo;
import com.ycbjie.webviewlib.base.X5WebChromeClient;
import com.ycbjie.webviewlib.base.X5WebViewClient;

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

import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;



/**
 * <pre>
 *     @author yangchong
 *     blog  : https://github.com/yangchong211
 *     time  : 2019/9/10
 *     desc  : 自定义WebView类
 *     revise: demo地址:https://github.com/yangchong211/YCWebView
 *             该demo可以作为学习案例,实现js交互的思路和BridgeWebView不一样,对比学习
 *             该案例参考:https://github.com/wendux/WebViewJavascriptBridge
 * </pre>
 */
public class WvWebView extends WebView {

    private static final String BRIDGE_NAME = "WVJBInterface";
    private static final int EXEC_SCRIPT = 1;
    private static final int LOAD_URL = 2;
    private static final int LOAD_URL_WITH_HEADERS = 3;
    private static final int HANDLE_MESSAGE = 4;
    private final static String CALLBACK_ID_STR = "callbackId";
    private final static String RESPONSE_ID_STR = "responseId";
    private final static String RESPONSE_DATA_STR = "responseData";
    private final static String DATA_STR = "data";
    private final static String HANDLER_NAME_STR = "handlerName";
    private MyHandler mainThreadHandler = null;
    private JavascriptCloseWindowListener javascriptCloseWindowListener=null;
    private ArrayList<WvMessage> startupMessageQueue = null;
    private Map<String, WVJBResponseCallback> responseCallbacks = null;
    private Map<String, WVJBHandler> messageHandlers = null;
    private long uniqueId = 0;
    private boolean alertBoxBlock=true;
    
    @SuppressLint("HandlerLeak")
    private class MyHandler extends Handler {

        private WeakReference<Context> mContextReference;

        MyHandler(Context context) {
            super(Looper.getMainLooper());
            mContextReference = new WeakReference<>(context);
        }

        @Override
        public void handleMessage(Message msg) {
            final Context context = mContextReference.get();
            if (context != null) {
                switch (msg.what) {
                    case EXEC_SCRIPT:
                        evaluateJavascriptUrl((String) msg.obj);
                        break;
                    case LOAD_URL:
                        WvWebView.super.loadUrl((String) msg.obj);
                        break;
                    case LOAD_URL_WITH_HEADERS:
                        RequestInfo info = (RequestInfo) msg.obj;
                        WvWebView.super.loadUrl(info.url, info.headers);
                        break;
                    case HANDLE_MESSAGE:
                        WvWebView.this.handleMessage((String) msg.obj);
                        break;
                    default:
                        break;
                }
            }
        }
    }

    private class WvMessage {
        Object data = null;
        String callbackId = null;
        String handlerName = null;
        String responseId = null;
        Object responseData = null;
    }

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

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

    public interface WVJBResponseCallback<T> {
        void onResult(T data);
    }

    public interface WVJBMethodExistCallback {
        void onResult(boolean exist);
    }

    public interface JavascriptCloseWindowListener {
        /**
         * @return  If true, close the current activity, otherwise, do nothing.
         */
        boolean onClose();
    }

    public interface WVJBHandler<T,R> {
        void handler(T data, WVJBResponseCallback<R> callback);
    }

    public void disableJavascriptAlertBoxSafetyTimeout(boolean disable){
        alertBoxBlock = !disable;
    }

    public void callHandler(String handlerName) {
        callHandler(handlerName, null, null);
    }

    public  void callHandler(String handlerName, Object data) {
        callHandler(handlerName, data, null);
    }

    public  <T> void callHandler(String handlerName, Object data, WVJBResponseCallback<T> responseCallback) {
        sendData(data, responseCallback, handlerName);
    }

    /**
     * Test whether the handler exist in javascript
     * @param handlerName
     * @param callback
     */
    public void hasJavascriptMethod(String handlerName, final WVJBMethodExistCallback callback){
        callHandler("_hasJavascriptMethod", handlerName, new WVJBResponseCallback() {
            @Override
            public void onResult(Object data) {
                callback.onResult((boolean)data);
            }
        });
    }

    /**
     * set a listener for javascript closing the current activity.
     */
    public void setJavascriptCloseWindowListener(JavascriptCloseWindowListener listener){
        javascriptCloseWindowListener=listener;
    }

    /**
     * js调native
     * @param handlerName                               名称
     * @param handler                                   消息
     * @param <T>                                       T
     * @param <R>                                       R
     */
    public <T,R> void registerHandler(String handlerName, WVJBHandler<T,R> handler) {
        if (handlerName == null || handlerName.length() == 0 || handler == null) {
            return;
        }
        messageHandlers.put(handlerName, handler);
    }

    /**
     * send the onResult message to javascript
     * 发送消息给js
     * @param data                                      data
     * @param responseCallback                          callback
     * @param handlerName                               handlerName
     */
    private void sendData(Object data, WVJBResponseCallback responseCallback, String handlerName) {
        if (data == null && (handlerName == null || handlerName.length() == 0)) {
            return;
        }
        WvMessage message = new WvMessage();
        if (data != null) {
            message.data = data;
        }
        if (responseCallback != null) {
            String callbackId = "java_cb_" + (++uniqueId);
            responseCallbacks.put(callbackId, responseCallback);
            message.callbackId = callbackId;
        }
        if (handlerName != null) {
            message.handlerName = handlerName;
        }
        queueMessage(message);
    }

    private synchronized void queueMessage(WvMessage message) {
        if (startupMessageQueue != null) {
            startupMessageQueue.add(message);
        } else {
            dispatchMessage(message);
        }
    }

    private void dispatchMessage(WvMessage message) {
        String messageJson = messageToJsonObject(message).toString();
        evaluateJavascript(String.format("WebViewJavascriptBridge._handleMessageFromJava(%s)", messageJson));
    }

    // handle the onResult message from javascript
    private void handleMessage(String info) {
        try {
            JSONObject jo = new JSONObject(info);
            WvMessage message = JsonObjectToMessage(jo);
            if (message.responseId != null) {
                WVJBResponseCallback responseCallback = responseCallbacks.remove(message.responseId);
                if (responseCallback != null) {
                    responseCallback.onResult(message.responseData);
                }
            } else {
                WVJBResponseCallback responseCallback = null;
                if (message.callbackId != null) {
                    final String callbackId = message.callbackId;
                    responseCallback = new WVJBResponseCallback() {
                        @Override
                        public void onResult(Object data) {
                            WvMessage msg = new WvMessage();
                            msg.responseId = callbackId;
                            msg.responseData = data;
                            dispatchMessage(msg);
                        }
                    };
                }

                WVJBHandler handler;
                handler = messageHandlers.get(message.handlerName);
                if (handler != null) {
                    handler.handler(message.data, responseCallback);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private JSONObject messageToJsonObject(WvMessage message) {
        JSONObject jo = new JSONObject();
        try {
            if (message.callbackId != null) {
                jo.put(CALLBACK_ID_STR, message.callbackId);
            }
            if (message.data != null) {
                jo.put(DATA_STR, message.data);
            }
            if (message.handlerName != null) {
                jo.put(HANDLER_NAME_STR, message.handlerName);
            }
            if (message.responseId != null) {
                jo.put(RESPONSE_ID_STR, message.responseId);
            }
            if (message.responseData != null) {
                jo.put(RESPONSE_DATA_STR, message.responseData);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return jo;
    }

    private WvMessage JsonObjectToMessage(JSONObject jo) {
        WvMessage message = new WvMessage();
        try {
            if (jo.has(CALLBACK_ID_STR)) {
                message.callbackId = jo.getString(CALLBACK_ID_STR);
            }
            if (jo.has(DATA_STR)) {
                message.data = jo.get(DATA_STR);
            }
            if (jo.has(HANDLER_NAME_STR)) {
                message.handlerName = jo.getString(HANDLER_NAME_STR);
            }
            if (jo.has(RESPONSE_ID_STR)) {
                message.responseId = jo.getString(RESPONSE_ID_STR);
            }
            if (jo.has(RESPONSE_DATA_STR)) {
                message.responseData = jo.get(RESPONSE_DATA_STR);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return message;
    }

    @Keep
    void init() {
        mainThreadHandler = new MyHandler(getContext());
        this.responseCallbacks = new HashMap<>();
        this.messageHandlers = new HashMap<>();
        this.startupMessageQueue = new ArrayList<>();
        super.setWebChromeClient(mWebChromeClient);
        super.setWebViewClient(mWebViewClient);
        registerHandler("_hasNativeMethod", new WVJBHandler() {
            @Override
            public void handler(Object data, WVJBResponseCallback callback) {
                callback.onResult(messageHandlers.get(data) != null);
            }
        });
        registerHandler("_closePage", new WVJBHandler() {
            @Override
            public void handler(Object data, WVJBResponseCallback callback) {
                if(javascriptCloseWindowListener==null ||javascriptCloseWindowListener.onClose()){
                    ((Activity) getContext()).onBackPressed();
                }
            }
        });
        registerHandler("_disableJavascriptAlertBoxSafetyTimeout", new WVJBHandler() {
            @Override
            public void handler(Object data, WVJBResponseCallback callback) {
                disableJavascriptAlertBoxSafetyTimeout((boolean)data);
            }
        });
        if(Build.VERSION.SDK_INT> Build.VERSION_CODES.JELLY_BEAN){
            super.addJavascriptInterface(new Object() {
                @Keep
                @JavascriptInterface
                public void notice(String info) {
                    Message msg = mainThreadHandler.obtainMessage(HANDLE_MESSAGE, info);
                    mainThreadHandler.sendMessage(msg);
                }
            }, BRIDGE_NAME);
        }

    }

    private void evaluateJavascriptUrl(String script) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            WvWebView.super.evaluateJavascript(script, null);
        } else {
            super.loadUrl("javascript:" + script);
        }
    }

    /**
     * This method can be called in any thread, and if it is not called in the main thread,
     * it will be automatically distributed to the main thread.
     * @param script
     */
    public void evaluateJavascript(final String script) {
        if (Looper.getMainLooper() == Looper.myLooper()) {
            evaluateJavascriptUrl(script);
        } else {
            Message msg = mainThreadHandler.obtainMessage(EXEC_SCRIPT, script);
            mainThreadHandler.sendMessage(msg);
        }
    }

    /**
     * 这个方法可以在任何线程中调用,如果在主线程中没有调用它,它将自动分配给主线程。通过handler实现不同线程
     * @param url                                   url
     */
    @Override
    public void loadUrl(String url) {
        Message msg = mainThreadHandler.obtainMessage(LOAD_URL, url);
        mainThreadHandler.sendMessage(msg);
    }

    @Override
    public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
        Message msg = mainThreadHandler.obtainMessage(LOAD_URL_WITH_HEADERS, new RequestInfo(url, additionalHttpHeaders));
        mainThreadHandler.sendMessage(msg);
    }

    protected X5WebViewClient generateBridgeWebViewClient() {
        return mWebViewClient;
    }

    protected X5WebChromeClient generateBridgeWebChromeClient() {
        return mWebChromeClient;
    }

    private X5WebChromeClient mWebChromeClient = new X5WebChromeClient(this,(Activity) getContext()) {

        private boolean isShowContent = false;
        private int max = 85;

        @Override
        public void onProgressChanged(WebView view, int newProgress) {
            super.onProgressChanged(view, newProgress);
            if(newProgress>max && !isShowContent) {
                try {
                    InputStream is = view.getContext().getAssets().open("WvWebViewJavascriptBridge.js");
                    int size = is.available();
                    byte[] buffer = new byte[size];
                    is.read(buffer);
                    is.close();
                    String js = new String(buffer);
                    evaluateJavascript(js);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                synchronized (WvWebView.this) {
                    if (startupMessageQueue != null) {
                        for (int i = 0; i < startupMessageQueue.size(); i++) {
                            dispatchMessage(startupMessageQueue.get(i));
                        }
                        startupMessageQueue = null;
                    }
                }
                isShowContent = true;
            }
        }

        @Override
        public boolean onJsAlert(WebView view, String url, final String message, final JsResult result) {
            if(!alertBoxBlock){
                result.confirm();
            }
            Dialog alertDialog = new AlertDialog.Builder(getContext()).
                    setMessage(message).
                    setCancelable(false).
                    setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                            if(alertBoxBlock) {
                                result.confirm();
                            }
                        }
                    })
                    .create();
            alertDialog.show();
            return true;
        }

        @Override
        public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
            if(!alertBoxBlock){
                result.confirm();
            }
            DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    if(alertBoxBlock) {
                        if (which == Dialog.BUTTON_POSITIVE) {
                            result.confirm();
                        } else {
                            result.cancel();
                        }
                    }
                }
            };
            new AlertDialog.Builder(getContext())
                    .setMessage(message)
                    .setCancelable(false)
                    .setPositiveButton(android.R.string.ok, listener)
                    .setNegativeButton(android.R.string.cancel, listener).show();
            return true;
        }

        @Override
        public boolean onJsPrompt(WebView view, String url, final String message,
                                  String defaultValue, final JsPromptResult result) {
            if(Build.VERSION.SDK_INT<= Build.VERSION_CODES.JELLY_BEAN){
                String prefix="_wvjbxx";
                if(message.equals(prefix)){
                    Message msg = mainThreadHandler.obtainMessage(HANDLE_MESSAGE, defaultValue);
                    mainThreadHandler.sendMessage(msg);
                }
                return true;
            }
            if(!alertBoxBlock){
                result.confirm();
            }
            final EditText editText = new EditText(getContext());
            editText.setText(defaultValue);
            if (defaultValue != null) {
                editText.setSelection(defaultValue.length());
            }
            float dpi = getContext().getResources().getDisplayMetrics().density;
            DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    if(alertBoxBlock) {
                        if (which == Dialog.BUTTON_POSITIVE) {
                            result.confirm(editText.getText().toString());
                        } else {
                            result.cancel();
                        }
                    }
                }
            };
            new AlertDialog.Builder(getContext())
                    .setTitle(message)
                    .setView(editText)
                    .setCancelable(false)
                    .setPositiveButton(android.R.string.ok, listener)
                    .setNegativeButton(android.R.string.cancel, listener)
                    .show();
            FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            int t = (int) (dpi * 16);
            layoutParams.setMargins(t, 0, t, 0);
            layoutParams.gravity = Gravity.CENTER_HORIZONTAL;
            editText.setLayoutParams(layoutParams);
            int padding = (int) (15 * dpi);
            editText.setPadding(padding - (int) (5 * dpi), padding, padding, padding);
            return true;
        }
    };

    private X5WebViewClient mWebViewClient = new X5WebViewClient(this,getContext()) {

    };

}