package com.gh.common.view;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.gh.common.util.PackageUtils;
import com.gh.gamecenter.BuildConfig;
import com.gh.gamecenter.common.constant.Config;
import com.gh.gamecenter.common.utils.DeviceUtils;
import com.gh.gamecenter.common.utils.DialogHelper;
import com.gh.gamecenter.common.utils.EnvHelper;
import com.gh.gamecenter.common.utils.ExtensionsKt;
import com.gh.gamecenter.common.utils.ImageUtils;
import com.gh.gamecenter.core.utils.DisplayUtils;
import com.gh.gamecenter.core.utils.EmptyCallback;
import com.gh.gamecenter.core.utils.GsonUtils;
import com.gh.gamecenter.core.utils.HtmlUtils;
import com.gh.gamecenter.core.utils.MtaHelper;
import com.gh.gamecenter.core.utils.RichEditorUtils;
import com.gh.gamecenter.core.utils.SPUtils;
import com.gh.gamecenter.entity.MtaEvent;
import com.gh.gamecenter.entity.MyVideoEntity;
import com.gh.gamecenter.qa.entity.EditorInsertEntity;
import com.halo.assistant.HaloApp;
import com.lightgame.utils.Utils;

import org.json.JSONObject;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * Copyright (C) 2017 Wasabeef
 * <p>
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.
 */

public class RichEditor extends WebView {
    private boolean mContentOwner;
    private boolean mInputEnabled;

    private int mDefaultImageWidth;
    private int mThumbnailImageWidth;

    private EmptyCallback mInitialLayoutCallback;

    private String mCurrentContent = "";

    public enum Type {
        BOLD,
        ITALIC,
        SUBSCRIPT,
        SUPERSCRIPT,
        STRIKETHROUGH,
        UNDERLINE,
        H1,
        H2,
        H3,
        H4,
        H5,
        H6,
        ORDEREDLIST,
        UNORDEREDLIST,
        JUSTIFYCENTER,
        JUSTIFYFULL,
        JUSTUFYLEFT,
        JUSTIFYRIGHT
    }

    public interface OnTextChangeListener {

        void onTextChange(String text);
    }

    public interface OnDecorationStateListener {

        void onStateChangeListener(String text, List<Type> types);
    }

    public interface AfterInitialLoadListener {

        void onAfterInitialLoad(boolean isReady);
    }

    private static final String SETUP_HTML = "file:///android_asset/editor.html";
    private static final String CALLBACK_SCHEME = "re-callback://";
    private static final String STATE_SCHEME = "re-state://";

    private boolean isReady = false;
    private String mContents;
    private OnTextChangeListener mTextChangeListener;
    private OnDecorationStateListener mDecorationStateListener;
    private AfterInitialLoadListener mLoadListener;
    private WebChromeClientListener mChromeClientListener;
    private PageFinishedListener mPageFinishedListener;

    public RichEditor(Context context) {
        this(context, null);
    }

    public RichEditor(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.webViewStyle);

        int widthPixels = getResources().getDisplayMetrics().widthPixels - DisplayUtils.dip2px(40);
        mThumbnailImageWidth = widthPixels / 3;
        mDefaultImageWidth = widthPixels;
    }

    @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"})
    public RichEditor(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ExtensionsKt.fixUiModeIfNeeded(this);

        addJavascriptInterface(new NativeCallBack(), "NativeCallBack");

        setVerticalScrollBarEnabled(false);
        setHorizontalScrollBarEnabled(false);
        getSettings().setJavaScriptEnabled(true);
        setWebChromeClient(new WebChromeClient()); // 不要重写这个方法否则无法加载
        setWebViewClient(createWebViewClient());

        if (Config.DEFAULT_CHANNEL.equals(HaloApp.getInstance().getChannel())
                && Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
            setWebContentsDebuggingEnabled(true);
        }

        loadUrl(SETUP_HTML);
        applyAttributes(context, attrs);
    }

    protected EditorWebViewClient createWebViewClient() {
        return new EditorWebViewClient();
    }

    public void setOnTextChangeListener(OnTextChangeListener listener) {
        mTextChangeListener = listener;
    }

    public void setOnDecorationChangeListener(OnDecorationStateListener listener) {
        mDecorationStateListener = listener;
    }

    public void setOnInitialLoadListener(AfterInitialLoadListener listener) {
        mLoadListener = listener;
    }

    public void setChromeClientListener(WebChromeClientListener chromeClientListener) {
        mChromeClientListener = chromeClientListener;
    }

    public void setPageFinishedListener(PageFinishedListener pageFinishedListener) {
        mPageFinishedListener = pageFinishedListener;
    }

    public void setLayoutCallback(EmptyCallback layoutCallback) {
        mInitialLayoutCallback = layoutCallback;
    }

    private void callback(String text) {
        mContents = text.replaceFirst(CALLBACK_SCHEME, "");
        if (mTextChangeListener != null) {
            mTextChangeListener.onTextChange(mContents);
        }
    }

    private void stateCheck(String text) {
        String state = text.replaceFirst(STATE_SCHEME, "").toUpperCase(Locale.ENGLISH);
        List<Type> types = new ArrayList<>();
        for (Type type : Type.values()) {
            if (TextUtils.indexOf(state, type.name()) != -1) {
                types.add(type);
            }
        }

        if (mDecorationStateListener != null) {
            mDecorationStateListener.onStateChangeListener(state, types);
        }
    }

    private void applyAttributes(Context context, AttributeSet attrs) {
        final int[] attrsArray = new int[]{
                android.R.attr.gravity
        };
        TypedArray ta = context.obtainStyledAttributes(attrs, attrsArray);

        int gravity = ta.getInt(0, NO_ID);
        switch (gravity) {
            case Gravity.LEFT:
                exec("javascript:RE.setTextAlign(\"left\")");
                break;
            case Gravity.RIGHT:
                exec("javascript:RE.setTextAlign(\"right\")");
                break;
            case Gravity.TOP:
                exec("javascript:RE.setVerticalAlign(\"top\")");
                break;
            case Gravity.BOTTOM:
                exec("javascript:RE.setVerticalAlign(\"bottom\")");
                break;
            case Gravity.CENTER_VERTICAL:
                exec("javascript:RE.setVerticalAlign(\"middle\")");
                break;
            case Gravity.CENTER_HORIZONTAL:
                exec("javascript:RE.setTextAlign(\"center\")");
                break;
            case Gravity.CENTER:
                exec("javascript:RE.setVerticalAlign(\"middle\")");
                exec("javascript:RE.setTextAlign(\"center\")");
                break;
        }

        ta.recycle();
    }

    // 如果是编辑状态就不要用缩略图的方案显示(会出现异常数据)
    public void setHtml(String contents, boolean isLoadTbImage) {
        if (contents == null) {
            contents = "";
        }
//        getSettings().setBlockNetworkImage(true); // 先不加载网络图片,因为后面有资源替换操作防止流量无故丢失

        try {
            exec("javascript:RE.setHtmlByVideoStatus('" + URLEncoder.encode(contents, "UTF-8") + "');");

            if (isLoadTbImage) {
                replaceAllDfImage();
                exec("javascript:RE.ImageClickListener()");
            }

            callback(contents);
        } catch (UnsupportedEncodingException e) {
            // No handling
        }
        mContents = contents;

        showLinkStyle(); // 显示隐藏样式
//        getSettings().setBlockNetworkImage(false); // 重新加载图片
    }

    public String getHtml() {
        return TextUtils.isEmpty(mContents) ? "" : mContents;
    }

    public void insertCustomStyleLink(EditorInsertEntity entity) {
        if (entity == null) return;
        // 转译
        String brief = entity.getBrief();
        String title = entity.getTitle();
        brief = TextUtils.isEmpty(brief) ? "" : brief.replaceAll("[\\x00-\\x1F\\x7F]", "").replace("\\", "");
        title = TextUtils.isEmpty(title) ? "" : title.replaceAll("[\\x00-\\x1F\\x7F]", "").replace("\\", "");

        entity.setBrief(TextUtils.htmlEncode(brief));
        entity.setTitle(TextUtils.htmlEncode(title));
        entity.setIcon(TextUtils.htmlEncode(entity.getIcon()));
        exec("javascript:RE.insertCustomStyleLink('" + GsonUtils.toJson(entity) + "');");
    }

    public void insertCustomVideo(MyVideoEntity entity) {
        try {
            JSONObject object = new JSONObject();
            object.put("poster", entity.getPoster());
            object.put("url", entity.getUrl());
            object.put("duration", formatVideoDuration(entity.getLength()));
            object.put("id", entity.getId());
            object.put("status", entity.getStatus());
            exec("javascript:RE.insertCustomVideo('" + object.toString() + "');");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String formatVideoDuration(long seconds) {
        long absSeconds = Math.abs(seconds);
        @SuppressLint("DefaultLocale") String positive = String.format(
                "%02d:%02d",
                absSeconds / 60,
                absSeconds % 60);
        return seconds < 0 ? "-" + positive : positive;
    }

    public void hideLinkStyle() {
        exec("javascript:RE.hideLinkStyle();");
    }

    public void showLinkStyle() {
        exec("javascript:RE.showLinkStyle();");
    }

    public void setEditorFontSize(int px) {
        exec("javascript:RE.setBaseFontSize('" + px + "px');");
    }

    public boolean hasPlaceholderImage() {
        return getHtml().contains("placeholder-image-container");
    }

    @Override
    public void setPadding(int left, int top, int right, int bottom) {
        super.setPadding(left, top, right, bottom);
        exec("javascript:RE.setPadding('" + left + "px', '" + top + "px', '" + right + "px', '" + bottom
                + "px');");
    }

    @Override
    public void setPaddingRelative(int start, int top, int end, int bottom) {
        // still not support RTL.
        setPadding(start, top, end, bottom);
    }

    public void setEditorBackgroundColor(int color) {
        setBackgroundColor(color);
    }

    @Override
    public void setBackgroundColor(int color) {
        super.setBackgroundColor(color);
    }

    @Override
    public void setBackgroundResource(int resid) {
        Bitmap bitmap = RichEditorUtils.decodeResource(getContext(), resid);
        String base64 = RichEditorUtils.toBase64(bitmap);
        bitmap.recycle();

        exec("javascript:RE.setBackgroundImage('url(data:image/png;base64," + base64 + ")');");
    }

    @Override
    public void setBackground(Drawable background) {
        Bitmap bitmap = RichEditorUtils.toBitmap(background);
        String base64 = RichEditorUtils.toBase64(bitmap);
        bitmap.recycle();

        exec("javascript:RE.setBackgroundImage('url(data:image/png;base64," + base64 + ")');");
    }

    public void replaceAllDfImageExcludeGif() {
        exec("javascript:RE.replaceAllDfImage('" +
                ImageUtils.getLimitWidthRule(mDefaultImageWidth) + "','" +
                ImageUtils.getWatermarkWidthGifRule(mThumbnailImageWidth) + "')");
    }


    public void replaceAllDfImage() {
        exec("javascript:RE.replaceAllDfImage('" +
                ImageUtils.getLimitWidthRule(mDefaultImageWidth) + "','" +
                ImageUtils.getDefaultGifRule() + "')");
    }

    public void replaceDfImageByUrl(String imgUrl) {
        exec("javascript:RE.replaceDfImageByUrl('" + imgUrl + "','" +
                ImageUtils.getLimitWidthRule(mDefaultImageWidth) + "','" +
                ImageUtils.getDefaultGifRule() + "');");
    }

    public void loadCSS(String cssFile) {
        String jsCSSImport = "(function() {" +
                "    var head  = document.getElementsByTagName(\"head\")[0];" +
                "    var link  = document.createElement(\"link\");" +
                "    link.rel  = \"stylesheet\";" +
                "    link.type = \"text/css\";" +
                "    link.href = \"" + cssFile + "\";" +
                "    link.media = \"all\";" +
                "    head.appendChild(link);" +
                "}) ();";
        exec("javascript:" + jsCSSImport + "");
    }

    public void insertImage(String url) {
        exec("javascript:RE.prepareInsert();");
        exec("javascript:RE.insertImage('" + url + "');");
    }

    public void insertHtml(String html) {
        exec("javascript:RE.prepareInsert();");
        exec("javascript:RE.insertHTML('" + html + "');");
    }

    public void focusEditor() {
        requestFocus();
        exec("javascript:RE.focus();");
    }

    public void insertLink(String href, String title) {
        exec("javascript:RE.prepareInsert();");
        exec("javascript:RE.insertLink('" + href + "', '" + title + "');");
    }

    public void setBackground(String url) {
        exec("javascript:RE.setBackgroundImage('url(" + url + ")');");
    }

    public void setEditorWidth(int px) {
        exec("javascript:RE.setWidth('" + px + "px');");
    }

    public void setEditorHeight(int px) {
        exec("javascript:RE.setHeight('" + px + "px');");
    }

    public void setPlaceholder(String placeholder) {
        exec("javascript:RE.setPlaceholder('" + placeholder + "');");
    }

    public void setEditorFocus() {
        exec("javascript:RE.setEditorFocus();");
    }

    public void setInputEnabled(Boolean inputEnabled) {
        mInputEnabled = inputEnabled;
        exec("javascript:RE.setInputEnabled(" + inputEnabled + ")");
    }

    public void setTextColor(int color) {
        String hex = convertHexColorString(color);
        exec("javascript:RE.prepareInsert();");
        exec("javascript:RE.setTextColor('" + hex + "');");
    }

    public void setEditorFontColor(int color) {
        String hex = convertHexColorString(color);
        exec("javascript:RE.setBaseTextColor('" + hex + "');");
    }

    public void insertPlaceholderImage(String id) {
        exec("javascript:RE.prepareInsert();");
        exec("javascript:RE.insertPlaceholderImage('" + id + "');");
    }

    public void replacePlaceholderImage(String list) {
        exec("javascript:RE.replacePlaceholderImage('" + list + "');");
    }

    /**
     * 插入视频占位图
     */
    public void insertPlaceholderVideo(String id, String poster) {
        exec("javascript:RE.insertPlaceholderVideo('" + id + "','" + poster + "');");
    }

    /**
     * 更新视频进度条
     */
    public void updateVideoProgress(String id, String progress) {
        exec("javascript:RE.updateVideoProgress('" + id + "','" + progress + "');");
    }


    /**
     * 上传视频完成
     */
    public void videoUploadFinished(String id, String url, String msg) {
        exec("javascript:RE.videoUploadFinished('" + id + "','" + url + "','" + msg + "');");
    }

    /**
     * 更换封面图
     */
    public void changePoster(String id, String poster) {
        exec("javascript:RE.changePoster('" + id + "','" + poster + "');");
    }

    /**
     * 删除视频
     */
    public void delPlaceholderVideo(String id) {
        exec("javascript:RE.delPlaceholderVideo('" + id + "');");
    }

    /**
     * 上传失败
     */
    public void videoUploadFailed(String id) {
        exec("javascript:RE.videoUploadFailed('" + id + "');");
    }

    public void removeFormat() {
        exec("javascript:RE.removeFormat();");
    }

    public void setHeading(int heading) {
        exec("javascript:RE.setHeading('" + heading + "');");
    }

    public void setIndent() {
        exec("javascript:RE.setIndent();");
    }

    public void setOutdent() {
        exec("javascript:RE.setOutdent();");
    }

    public void setAlignLeft() {
        exec("javascript:RE.setJustifyLeft();");
    }

    public void setAlignCenter() {
        exec("javascript:RE.setJustifyCenter();");
    }

    public void setAlignRight() {
        exec("javascript:RE.setJustifyRight();");
    }

    public void setBlockquote() {
        exec("javascript:RE.setBlockquote();");
    }

    public void setBullets() {
        exec("javascript:RE.setBullets();");
    }

    public void setNumbers() {
        exec("javascript:RE.setNumbers();");
    }

    public void undo() {
        exec("javascript:RE.undo();");
    }

    public void redo() {
        exec("javascript:RE.redo();");
    }

    public void setBold() {
        exec("javascript:RE.setBold();");
    }

    public void setItalic() {
        exec("javascript:RE.setItalic();");
    }

    public void setSubscript() {
        exec("javascript:RE.setSubscript();");
    }

    public void setSuperscript() {
        exec("javascript:RE.setSuperscript();");
    }

    public void setStrikeThrough() {
        exec("javascript:RE.setStrikeThrough();");
    }

    public void setUnderline() {
        exec("javascript:RE.setUnderline();");
    }

    public void formatBlock() {
        exec("javascript:RE.formatBlock();");
    }

    public String getText() {
        return HtmlUtils.stripHtml(mContents);
    }

//    在web页面播放视频
//    public void initArticleVideo() {
//        exec("javascript:RE.initArticleVideo();");
//    }

    private String convertHexColorString(int color) {
        return String.format("#%06X", (0xFFFFFF & color));
    }

    protected void exec(final String trigger) {
        if (isReady) {
            load(trigger);
        } else {
            postDelayed(new Runnable() {
                @Override
                public void run() {
                    exec(trigger);
                }
            }, 100);
        }
    }

    private void load(String trigger) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            try {
                evaluateJavascript(trigger, null);
            } catch (IllegalStateException ignore) {
                loadUrl(trigger);
            }
        } else {
            loadUrl(trigger);
        }
    }

    protected class EditorWebViewClient extends WebViewClient {

        @Override
        public void onPageFinished(WebView view, String url) {
            isReady = url.equalsIgnoreCase(SETUP_HTML);
            if (mLoadListener != null) {
                mLoadListener.onAfterInitialLoad(isReady);
            }

            if (mChromeClientListener != null) {
                mChromeClientListener.onPageFinished(view, url);
            }
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            String decode;
            try {
                decode = URLDecoder.decode(url, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                // No handling
                return false;
            }

            if (TextUtils.indexOf(url, CALLBACK_SCHEME) == 0) {
                callback(decode);
                return true;
            } else if (TextUtils.indexOf(url, STATE_SCHEME) == 0) {
                stateCheck(decode);
                return true;
            }

            if (mChromeClientListener != null) {
                return mChromeClientListener.shouldOverrideUrlLoading(view, url);
            }

            return super.shouldOverrideUrlLoading(view, url);
        }
    }

    public void setContentOwner(boolean contentOwner) {
        this.mContentOwner = contentOwner;
    }

    public String getCurrentContent() {
        return mCurrentContent;
    }

    public interface WebChromeClientListener {
        void onPageFinished(WebView view, String url);

        boolean shouldOverrideUrlLoading(WebView view, String url);
    }

    public interface PageFinishedListener {
        void onPageFinished();
    }

    class NativeCallBack {

        /**
         * true:测试接口
         * false:正式接口
         */
        @JavascriptInterface
        public boolean isNativeBuildDebug() {
            return EnvHelper.isDevEnv();
        }

        /**
         * 只在插入HTML之后才有效
         */
        @JavascriptInterface
        public boolean isContentOwner() {
            return mContentOwner;
        }

        /**
         * 当前页面是不是编辑状态
         */
        @JavascriptInterface
        public boolean isInputEnabled() {
            return mInputEnabled;
        }

        /**
         * 获取网络状态
         *
         * @return "WIFI", "2G", "3G", "4G","无网络","未知"
         */
        @JavascriptInterface
        public String getNetworkStatus() {
            return DeviceUtils.getNetwork(HaloApp.getInstance().getApplication());
        }

        @JavascriptInterface
        public void onPageFinished() {
            mPageFinishedListener.onPageFinished();
        }

        @JavascriptInterface
        public String getAppVersion() {
            return BuildConfig.VERSION_NAME;
        }

        @JavascriptInterface
        public int getAppVersionCode() {
            return PackageUtils.getGhVersionCode();
        }

        /**
         * 显示toast
         *
         * @param text toast 内容
         */
        @JavascriptInterface
        public void toast(String text) {
            Utils.toast(HaloApp.getInstance().getApplication(), text);
        }

        /**
         * 显示Dialog弹窗
         *
         * @param title    标题
         * @param message  内容
         * @param positive 确认按钮文本
         * @param negative 取消按钮文本
         */
        @JavascriptInterface
        public void showDialog(String title, String message, String positive, String negative, String jsCallbackCode) {
            DialogHelper.showDialog(getContext(), title, message, positive, negative,
                    () -> post(() -> loadUrl("javascript:" + jsCallbackCode + "(" + true + ")")),
                    () -> post(() -> loadUrl("javascript:" + jsCallbackCode + "(" + false + ")")), false, "", "");
        }

        /**
         * Javascript向App存储数据
         *
         * @param isPermanent true：永久存储 false：临时存储于内存中(注意:数据是不互通的)
         */
        @JavascriptInterface
        public void setDataToNative(String key, String value, boolean isPermanent) {
            key += "-RichEditor";
            if (!TextUtils.isEmpty(key)) {
                if (isPermanent) {
                    if (value == null) {
                        SPUtils.remove(key);
                    } else {
                        SPUtils.setString(key, value);
                    }
                } else {
                    if (value == null) {
                        HaloApp.remove(key);
                    } else {
                        HaloApp.put(key, value);
                    }
                }
            }
        }

        /**
         * Javascript向App获取数据
         *
         * @param isPermanent 获取永久数据还是临时数据(注意:数据是不互通的)
         */
        @JavascriptInterface
        public String getDataFromNative(String key, boolean isPermanent) {
            key += "-RichEditor";
            if (!TextUtils.isEmpty(key)) {
                if (isPermanent) {
                    return SPUtils.getString(key);
                } else {
                    Object object = HaloApp.get(key, false);
                    if (object != null) return object.toString();
                }
            }
            return "";
        }

        @JavascriptInterface
        public void logMtaEvent(String event) {
            try {
                MtaEvent mtaEvent = GsonUtils.fromJson(event, MtaEvent.class);
                MtaHelper.onEvent(mtaEvent.getName(), mtaEvent.getKey(), mtaEvent.getValue());
            } catch (Exception e) {
                if (BuildConfig.DEBUG) {
                    throw e;
                } else {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    public void invalidate() {
        super.invalidate();

        if (mInitialLayoutCallback != null && getHeight() > 0) {
            // 仅初次 layout 完成会有回调
            mInitialLayoutCallback.onCallback();
            mInitialLayoutCallback = null;
        }
    }
}