package mara.mybox.controller;

import java.awt.image.BufferedImage;
import java.io.File;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Platform;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import static javafx.concurrent.Worker.State.CANCELLED;
import static javafx.concurrent.Worker.State.FAILED;
import static javafx.concurrent.Worker.State.RUNNING;
import static javafx.concurrent.Worker.State.SUCCEEDED;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import javafx.scene.web.PromptData;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebErrorEvent;
import javafx.scene.web.WebEvent;
import javafx.scene.web.WebView;
import javafx.util.Callback;
import mara.mybox.data.HtmlElement;
import mara.mybox.data.StringTable;
import mara.mybox.db.data.ImageClipboard;
import mara.mybox.db.data.VisitHistory;
import mara.mybox.dev.MyBoxLog;
import mara.mybox.fxml.FxSingletonTask;
import mara.mybox.fxml.FxTask;
import mara.mybox.fxml.ImageClipboardTools;
import mara.mybox.fxml.menu.MenuTools;
import mara.mybox.fxml.NodeTools;
import mara.mybox.fxml.PopTools;
import mara.mybox.fxml.TextClipboardTools;
import mara.mybox.fxml.WebViewTools;
import mara.mybox.fxml.WindowTools;
import mara.mybox.fxml.style.HtmlStyles;
import mara.mybox.fxml.style.StyleTools;
import mara.mybox.image.file.ImageFileReaders;
import mara.mybox.tools.FileTools;
import mara.mybox.tools.HtmlReadTools;
import mara.mybox.tools.HtmlWriteTools;
import mara.mybox.tools.StringTools;
import mara.mybox.tools.UrlTools;
import mara.mybox.value.Fxmls;
import static mara.mybox.value.Languages.message;
import mara.mybox.value.UserConfig;
import netscape.javascript.JSObject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;

/**
 * @Author Mara
 * @CreateDate 2021-8-18
 * @License Apache License Version 2.0
 */
public class ControlWebView extends BaseController {

    protected WebEngine webEngine;
    protected double scrollTop, scrollLeft;
    protected ScrollType scrollType;
    protected float zoomScale;
    protected String address, content, style, defaultStyle, initStyle;
    protected Charset charset;
    protected Map<Integer, Document> framesDoc;
    protected EventListener docListener;
    protected Element element;
    protected final SimpleBooleanProperty addressChangedNotify, addressInvalidNotify,
            pageLoadingNotify, pageLoadedNotify;
    protected final String StyleNodeID = "MyBox__Html_Style20211118";
    protected boolean linkInNewTab;

    @FXML
    protected WebView webView;
    @FXML
    protected Label webViewLabel;

    public enum ScrollType {
        Top, Bottom, Last
    }

    public ControlWebView() {
        addressChangedNotify = new SimpleBooleanProperty(false);
        addressInvalidNotify = new SimpleBooleanProperty(false);
        pageLoadingNotify = new SimpleBooleanProperty(false);
        pageLoadedNotify = new SimpleBooleanProperty(false);
    }

    @Override
    public void initValues() {
        try {
            super.initValues();
            zoomScale = 1.0f;
            framesDoc = new HashMap<>();
            charset = Charset.defaultCharset();
            linkInNewTab = false;
            defaultStyle = HtmlStyles.styleValue("Table");
            scrollType = ScrollType.Top;
            parentController = this;
        } catch (Exception e) {
            MyBoxLog.error(e);
        }
    }

    @Override
    public void setFileType() {
        setFileType(VisitHistory.FileType.Html);
    }

    public void setParent(BaseController parent) {
        setParent(parent, ScrollType.Last);
    }

    public void setParent(BaseController parent, ScrollType scrollType) {
        if (parent == null) {
            return;
        }
        this.parentController = parent;
        this.baseName = parent.baseName + "_" + baseName;
        this.scrollType = scrollType;
    }

    @Override
    public void initControls() {
        try {
            webEngine = webView.getEngine();

            webView.setCache(UserConfig.getBoolean(interfaceName + "Cache", false));
            webEngine.setJavaScriptEnabled(UserConfig.getBoolean(interfaceName + "JavaScriptEnabled", true));

            webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<Worker.State>() {
                @Override
                public void changed(ObservableValue ov, Worker.State oldState, Worker.State newState) {
                    Platform.runLater(() -> {
                        worker(newState);
                    });
                    Platform.requestNextPulse();
                }
            });

            // http://blogs.kiyut.com/tonny/2013/07/30/javafx-webview-addhyperlinklistener/
            docListener = new EventListener() {
                @Override
                public void handleEvent(org.w3c.dom.events.Event ev) {
                    docEvent(ev);
                }
            };

            webEngine.getLoadWorker().exceptionProperty().addListener(new ChangeListener<Throwable>() {
                @Override
                public void changed(ObservableValue<? extends Throwable> ov, Throwable ot, Throwable nt) {
                    if (nt == null) {
                        return;
                    }
                    Platform.runLater(() -> {
                        setWebViewLabel(nt.getMessage());
                        alertError(nt.getMessage());
                    });
                }
            });

            webEngine.setOnStatusChanged(new EventHandler<WebEvent<String>>() {
                @Override
                public void handle(WebEvent<String> ev) {
                    Platform.runLater(() -> {
                        statusChanged(ev);
                    });
                }
            });

            webEngine.locationProperty().addListener(new ChangeListener<String>() {
                @Override
                public void changed(ObservableValue ov, String oldv, String newv) {
                    Platform.runLater(() -> {
                        locationChanged(oldv, newv);
                    });
                }
            });

            webEngine.setOnAlert(new EventHandler<WebEvent<String>>() {
                @Override
                public void handle(WebEvent<String> ev) {
                    alert(ev);
                }
            });

            webEngine.setOnError(new EventHandler<WebErrorEvent>() {
                @Override
                public void handle(WebErrorEvent event) {
                    error(event);
                }
            });

            webEngine.setPromptHandler(new Callback< PromptData, String>() {
                @Override
                public String call(PromptData p) {
                    return prompt(p);
                }
            });

            webEngine.setConfirmHandler(new Callback< String, Boolean>() {
                @Override
                public Boolean call(String message) {
                    return confirm(message);
                }
            });

        } catch (Exception e) {
            MyBoxLog.error(e);
        }
    }

    public void worker(Worker.State state) {
        try {
            switch (state) {
                case READY:
                    ready();
                    break;
                case RUNNING:
                    running();
                    break;
                case SUCCEEDED:
                    succeeded();
                    break;
                case CANCELLED:
                    if (timer != null) {
                        timer.cancel();
                    }
                    setWebViewLabel(message("Canceled"));
                    break;
                case FAILED:
                    if (timer != null) {
                        timer.cancel();
                    }
                    setWebViewLabel(message("Failed"));
                    break;
                default:
                    setWebViewLabel(state.name());
            }
        } catch (Exception e) {
            MyBoxLog.error(e);
        }
    }

    public void docEvent(org.w3c.dom.events.Event ev) {
        try {
            if (ev == null) {
                return;
            }
            String domEventType = ev.getType();
            String tag = null, href = null;
            if (ev.getTarget() != null) {
                element = (Element) ev.getTarget();
                HtmlElement htmlElement = new HtmlElement(element, charset);
                tag = htmlElement.getTag();
                if (tag != null) {
                    href = htmlElement.getHref();
                }
            } else {
                element = null;
            }
//            MyBoxLog.console(webView.getId() + " " + domEventType + " " + tag + " " + href);
            if (element == null) {
                return;
            }
            if (href != null) {
                String target = element.getAttribute("target");
                HtmlElement htmlElement = new HtmlElement(element, charset);
                if ("click".equals(domEventType)) {
                    if (target != null && !target.equalsIgnoreCase("_blank")) {
                        ev.stopPropagation();
                        ev.preventDefault();
                        Platform.runLater(() -> {
                            executeScript("if ( window.frames." + target
                                    + ".document.readyState==\"complete\") control.frameNameReady('" + target + "');");
                            executeScript("window.frames." + target + ".document.onreadystatechange = "
                                    + "function(){ if ( window.frames." + target
                                    + ".document.readyState==\"complete\") control.frameNameReady('" + target + "'); }");
                        });
                        Platform.requestNextPulse();
                    } else if (!href.startsWith("javascript:")) {
                        String clickAction = UserConfig.getString("WebViewWhenLeftClickImageOrLink", "PopMenu");
                        String url = htmlElement.getDecodedAddress();
                        if (linkInNewTab) {
                            ev.stopPropagation();
                            ev.preventDefault();
                            Platform.runLater(() -> {
                                WebBrowserController.openAddress(url, true);
                            });
                            Platform.requestNextPulse();
                        } else if (!"AsPage".equals(clickAction)) {
                            ev.stopPropagation();
                            ev.preventDefault();
                            Platform.runLater(() -> {
                                if (clickAction == null || "PopMenu".equals(clickAction)) {
                                    popLinkMenu(htmlElement);
                                } else if ("Load".equals(clickAction)) {
                                    loadAddress(url);
                                } else if ("System".equals(clickAction)) {
                                    browse(url);
                                } else {
                                    WebBrowserController.openAddress(url, "OpenSwitch".equals(clickAction));
                                }
                            });
                            Platform.requestNextPulse();
                        }
                    }
                } else if ("contextmenu".equals(domEventType)) {
                    ev.stopPropagation();
                    ev.preventDefault();
                    Platform.runLater(() -> {
                        popLinkMenu(htmlElement);
                    });
                    Platform.requestNextPulse();
                }

            } else if ("contextmenu".equals(domEventType) && !"frame".equalsIgnoreCase(tag)) {
                ev.stopPropagation();
                ev.preventDefault();
                Platform.runLater(() -> {
                    popElementMenu(element);
                });
                Platform.requestNextPulse();
            }

            MenuWebviewController menu = MenuWebviewController.running(webView);
            if (menu != null) {
                menu.setElement(element);
            }
        } catch (Exception e) {
            MyBoxLog.error(e);
        }
    }

    public void statusChanged(WebEvent<String> ev) {
        try {
//            MyBoxLog.console(ev.toString());
            setWebViewLabel(ev.getData());
        } catch (Exception e) {
            MyBoxLog.error(e);
        }
    }

    public void locationChanged(String ov, String nv) {
        try {
            if (webViewLabel != null && nv != null) {
                Platform.runLater(() -> {
                    setWebViewLabel(URLDecoder.decode(nv, charset));
                });
            }
        } catch (Exception e) {
            MyBoxLog.error(e);
        }
    }

    public void alert(WebEvent<String> ev) {
        try {
            if (UserConfig.getBoolean("WebViewInterceptPopWindow", false)) {
                return;
            }
//            MyBoxLog.console(ev.toString());
            Platform.runLater(() -> {
                alertInformation(ev.getData());
            });
        } catch (Exception e) {
            MyBoxLog.error(e);
        }
    }

    public void error(WebErrorEvent ev) {
        try {
            if (UserConfig.getBoolean("WebViewInterceptPopWindow", false)) {
                return;
            }
//            MyBoxLog.console(ev.toString());
            Platform.runLater(() -> {
                alertError(ev.getMessage());
            });
        } catch (Exception e) {
            MyBoxLog.error(e);
        }
    }

    public String prompt(PromptData p) {
        try {
            if (UserConfig.getBoolean("WebViewInterceptPopWindow", false)) {
                return null;
            }
            return PopTools.askValue(getTitle(), null, p.getMessage(), p.getDefaultValue());
        } catch (Exception e) {
            MyBoxLog.error(e);
        }
        return null;
    }

    public Boolean confirm(String message) {
        try {
            if (UserConfig.getBoolean("WebViewInterceptPopWindow", false)) {
                return false;
            }
            return PopTools.askSure(getTitle(), message);
        } catch (Exception e) {
            MyBoxLog.error(e);
            return false;
        }
    }

    /*
        status
     */
    public void clear() {
        loadContent("");
    }

    protected void reset() {
        if (timer != null) {
            timer.cancel();
        }
        content = null;
        webEngine.getLoadWorker().cancel();
//        clearListener(webEngine.getDocument());
    }

    protected void ready() {
        try {
            if (timer != null) {
                timer.cancel();
            }
            framesDoc.clear();
            charset = Charset.defaultCharset();
        } catch (Exception e) {
            MyBoxLog.console(e);
        }
    }

    protected void running() {
        try {
            pageLoadingNotify.set(!pageLoadingNotify.get());
            timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    Platform.runLater(() -> {
                        initDoc(webEngine.getDocument());
                    });
                    Platform.requestNextPulse();
                }
            }, 300, 100);
        } catch (Exception e) {
            MyBoxLog.console(e);
        }
    }

    protected boolean initDoc(Document doc) {
        try {
            if (doc == null) {
                return false;
            }
            Object winObject = executeScript("window");
            Object docObject = executeScript("document");
            if (winObject == null || docObject == null) {
                return false;
            }
            if (timer != null) {
                timer.cancel();
            }
            ((JSObject) winObject).setMember("control", this);
            setListeners(doc);

            return true;
        } catch (Exception e) {
            MyBoxLog.console(e);
            return false;
        }
    }

    protected void setListeners(Document doc) {
        try {
            if (doc == null) {
                return;
            }
            String js = "if ( document.addEventListener ) "
                    + "{  document.addEventListener('mouseover', (event) => {\n"
                    + "      const link = event.target.closest('a');\n"
                    + "      if (link && link.href) {\n"
                    + "        try {\n"
                    + "              const url = new URL(link.href);\n"
                    + "              control.setWebViewLabel(url.href);\n"
                    + "        } catch (error) {}\n"
                    + "      }\n"
                    + "  });"
                    + "  document.addEventListener('mouseout', (event) => {\n"
                    + "     control.setWebViewLabel(null);})\n"
                    + "} ";
            executeScript(js);

            EventTarget t = (EventTarget) doc.getDocumentElement();
            t.addEventListener("contextmenu", docListener, true);
            t.addEventListener("click", docListener, true);

        } catch (Exception e) {
            MyBoxLog.console(e);
        }
    }

    protected void succeeded() {
        try {
            if (timer != null) {
                timer.cancel();
            }
            Document doc = webEngine.getDocument();
            if (doc != null) {
                initDoc(doc);
                NodeList frameList = doc.getElementsByTagName("frame");
                for (int i = 0; i < frameList.getLength(); i++) {
                    executeScript("if ( window.frames[" + i + "].document.readyState==\"complete\") control.frameIndexReady(" + i + ");");
                    executeScript("window.frames[" + i + "].document.onreadystatechange = "
                            + "function(){ if ( window.frames[" + i + "].document.readyState==\"complete\") control.frameIndexReady(" + i + "); }");
                }

                executeScript("window.onscroll=function(){ control.scrolled();}");
            }

            if (initStyle != null) {
                writeStyle(initStyle);
            } else {
                String prefix = UserConfig.getBoolean(baseName + "ShareHtmlStyle", true) ? "AllInterface" : baseName;
                writeStyle(UserConfig.getString(prefix + "HtmlStyle", defaultStyle));
            }

            try {
                executeScript("document.body.contentEditable=" + UserConfig.getBoolean(baseName + "Editable", false));
            } catch (Exception e) {
            }

            if (null == scrollType) {
                executeScript("setTimeout(window.scrollTo(" + scrollLeft + "," + scrollTop + "), 1000);");
            } else {
                switch (scrollType) {
                    case Bottom:
                        executeScript("setTimeout(window.scrollTo(0, document.documentElement.scrollHeight || document.body.scrollHeight), 1000);");
                        break;
                    case Top:
                        executeScript("window.scrollTo(0, 0);");
                        break;
                    default:
                        executeScript("setTimeout(window.scrollTo(" + scrollLeft + "," + scrollTop + "), 1000);");
                        break;
                }
            }
            setWebViewLabel(message("Loaded"));

            pageLoadedNotify.set(!pageLoadedNotify.get());

        } catch (Exception e) {
            MyBoxLog.console(e);
        }
    }

    public void scrolled() {
        try {
            scrollTop = (int) executeScript("document.documentElement.scrollTop || document.body.scrollTop;");
            scrollLeft = (int) executeScript("document.documentElement.scrollLeft || document.body.scrollLeft;");
//            MyBoxLog.console("scrolled:" + scrollTop + " " + scrollLeft);
        } catch (Exception e) {
            MyBoxLog.error(e);
        }
    }

    public void frameIndexReady(int frameIndex) {
        try {
//            MyBoxLog.console(frameIndex);
            if (framesDoc.containsKey(frameIndex)) {
                return;
            }
            Timer atimer = new Timer();
            atimer.schedule(new TimerTask() {
                @Override
                public void run() {
                    Platform.runLater(() -> {
                        setFrameListener(frameIndex);
                    });
                }
            }, 500);
        } catch (Exception e) {
            MyBoxLog.error(e);
        }
    }

    public void frameNameReady(String frameName) {
        try {
            int frameIndex = WebViewTools.frameIndex(webEngine, frameName);
//            MyBoxLog.console(frameName + "   " + frameIndex);
            frameIndexReady(frameIndex);
        } catch (Exception e) {
            MyBoxLog.error(e);
        }
    }

    protected void setFrameListener(int frameIndex) {
        try {
            if (frameIndex < 0) {
                return;
            }
            Object c = executeScript("window.frames[" + frameIndex + "].document");
            if (c == null) {
                return;
            }
            Document frame = (Document) c;
            framesDoc.put(frameIndex, frame);

            setListeners(frame);
        } catch (Exception e) {
            MyBoxLog.error(e);
        }
    }

    /*
        value
     */
    public Charset charset() {
        try {
            Object head = executeScript("document.head.outerHTML");
            if (head == null) {
                return charset;
            }
            Charset hc = HtmlReadTools.charset((String) head);
            if (hc != null) {
                charset = hc;
            }
        } catch (Exception e) {
            MyBoxLog.error(e);
        }
        return charset;
    }

    public String loadedHtml() {
        if (content != null) {
            return content;
        } else {
            return currentHtml();
        }
    }

    public String currentHtml() {
        return HtmlReadTools.removeNode(WebViewTools.getHtml(webEngine), StyleNodeID);
    }

    public String title() {
        String title = webEngine.getTitle();
        if (title == null || title.isBlank()) {
            title = address;
        }
        return title;
    }

    public void initStyle(String style) {
        this.initStyle = style;
    }

    public boolean hasHtml() {
        String html = loadedHtml();
        return html != null && !html.isBlank();
    }

    /*
        source
     */
    public boolean loadFile(File file) {
        if (file == null || !file.exists()) {
            addressInvalidNotify.set(!addressInvalidNotify.get());
            return false;
        }
        return loadAddress(UrlTools.decodeURL(file, Charset.defaultCharset()));
    }

    public boolean loadURI(URI uri) {
        if (uri == null) {
            addressInvalidNotify.set(!addressInvalidNotify.get());
            return false;
        }
        return loadAddress(uri.toString());
    }

    public boolean loadAddress(String value) {
        value = UrlTools.checkURL(value, Charset.forName("UTF-8"));
        if (value == null) {
            addressInvalidNotify.set(!addressInvalidNotify.get());
            return false;
        }
        goAddress(value);
        return true;
    }

    public boolean loadContent(String contents) {
        return loadContent(null, contents);
    }

    public boolean loadContent(String address, String content) {
        setAddress(address);
        writeContent(content);
        return true;
    }

    public void writeContent(String content) {
        reset();
        this.content = content;
        webEngine.loadContent(content == null ? "" : content);
    }

    private boolean setAddress(String value) {
        try {
            setSourceFile(null);
            address = UrlTools.checkURL(value, Charset.forName("UTF-8"));
            if (address != null && address.startsWith("file:/")) {
                File file = new File(address.substring(6));
                if (file.exists()) {
                    setSourceFile(file);
                }
            }
            addressChangedNotify.set(!addressChangedNotify.get());
            return true;
        } catch (Exception e) {
            addressInvalidNotify.set(!addressInvalidNotify.get());
            return false;
        }
    }

    @Override
    public void setSourceFile(File file) {
        this.sourceFile = file;
        if (address == null && sourceFile != null) {
            address = sourceFile.toURI().toString();
        }
    }

    private void goAddress(String value) {
        try {
            if (!setAddress(value)) {
                return;
            }
            reset();
            setWebViewLabel(message("Loading..."));
            webEngine.load(address);
        } catch (Exception e) {
            MyBoxLog.error(e);
            addressInvalidNotify.set(!addressInvalidNotify.get());
        }
    }

    /*
        action
     */
    public void setWebViewLabel(String string) {
        if (webViewLabel != null) {
            Platform.runLater(() -> {
                if (string == null || string.isBlank()) {
                    webViewLabel.setText("");
                } else {
                    webViewLabel.setText(URLDecoder.decode(string, charset));
                }
            });
        }
    }

    public Object executeScript(String js) {
        try {
            if (js == null || js.isBlank()) {
                return null;
            }
            return webEngine.executeScript(js);
        } catch (Exception e) {
            MyBoxLog.console(e + "\n" + js);
            return null;
        }
    }

    public void setStyle(String style) {
        String prefix = UserConfig.getBoolean(baseName + "ShareHtmlStyle", true) ? "AllInterface" : baseName;
        UserConfig.setString(prefix + "HtmlStyle", style);

        writeStyle(style);
    }

    public void writeStyle(String style) {
        WebViewTools.removeNode(webEngine, StyleNodeID);
        this.style = style;
        if (style != null && !style.isBlank()) {
            WebViewTools.addStyle(webEngine, style, StyleNodeID);
        }
    }

    public void popLinkMenu(HtmlElement htmlElement) {
        if (htmlElement == null) {
            return;
        }
        String href = htmlElement.getHref();
        if (href == null) {
            return;
        }
        String linkAddress = htmlElement.getAddress();
        String finalAddress = htmlElement.getDecodedAddress();
        String tag = htmlElement.getTag();
        String name = htmlElement.getName();
        boolean showName = name != null && !name.isBlank() && !name.equalsIgnoreCase(href);
        String title = "";
        if (showName) {
            title = message("Name") + ": " + StringTools.menuPrefix(name) + "\n";
        }
        title += message("Link") + ": " + StringTools.menuPrefix(URLDecoder.decode(href, charset));
        if (!linkAddress.equalsIgnoreCase(href)) {
            title += "\n" + message("Address") + ": " + StringTools.menuPrefix(finalAddress);
        }

        List<MenuItem> items = MenuTools.initMenu(title, false);

        if (!linkInNewTab) {
            items.add(clickedMenu());
        }

        MenuItem menu = new MenuItem(message("QueryNetworkAddress"), StyleTools.getIconImageView("iconQuery.png"));
        menu.setOnAction((ActionEvent event) -> {
            NetworkQueryAddressController controller
                    = (NetworkQueryAddressController) WindowTools.openStage(Fxmls.NetworkQueryAddressFxml);
            controller.queryUrl(finalAddress);
        });
        items.add(menu);

        menu = new MenuItem(message("AddAsFavorite"), StyleTools.getIconImageView("iconStar.png"));
        menu.setOnAction((ActionEvent event) -> {
            ControlDataWebFavorite.open(myController, name, finalAddress);
        });
        items.add(menu);

        items.add(new SeparatorMenuItem());

        Menu openMenu = new Menu(message("Open"), StyleTools.getIconImageView("iconWindow.png"));
        items.add(openMenu);

        menu = new MenuItem(message("OpenLinkInNewTab"));
        menu.setOnAction((ActionEvent event) -> {
            WebBrowserController.openAddress(finalAddress, false);
        });
        openMenu.getItems().add(menu);

        menu = new MenuItem(message("OpenLinkInNewTabSwitch"));
        menu.setOnAction((ActionEvent event) -> {
            WebBrowserController.openAddress(finalAddress, true);
        });
        openMenu.getItems().add(menu);

        menu = new MenuItem(message("OpenLinkByCurrent"));
        menu.setOnAction((ActionEvent event) -> {
            loadAddress(finalAddress);
        });
        openMenu.getItems().add(menu);

        menu = new MenuItem(message("OpenLinkBySystem"));
        menu.setOnAction((ActionEvent event) -> {
            browse(finalAddress);
        });
        openMenu.getItems().add(menu);

        menu = new MenuItem(message("Edit"), StyleTools.getIconImageView("iconEdit.png"));
        menu.setOnAction((ActionEvent event) -> {
            HtmlEditorController.openAddress(finalAddress);
        });
        items.add(menu);

        if (tag.equalsIgnoreCase("img")) {
            Menu imageMenu = new Menu(message("Image"), StyleTools.getIconImageView("iconSample.png"));
            items.add(imageMenu);
            if (ImageClipboardTools.isMonitoringCopy()) {
                menu = new MenuItem(message("CopyImageToClipboards"), StyleTools.getIconImageView("iconCopySystem.png"));
            } else {
                menu = new MenuItem(message("CopyImageToSystemClipboard"), StyleTools.getIconImageView("iconCopySystem.png"));
            }
            menu.setOnAction((ActionEvent event) -> {
                handleImage(finalAddress, name, "toSystemClipboard");
            });
            imageMenu.getItems().add(menu);

            menu = new MenuItem(message("CopyImageToMyBoxClipboard"), StyleTools.getIconImageView("iconCopy.png"));
            menu.setOnAction((ActionEvent event) -> {
                handleImage(finalAddress, name, "toMyBoxClipboard");
            });
            imageMenu.getItems().add(menu);

            menu = new MenuItem(message("ViewImage"), StyleTools.getIconImageView("iconView.png"));
            menu.setOnAction((ActionEvent event) -> {
                handleImage(finalAddress, name, "view");
            });
            imageMenu.getItems().add(menu);

            menu = new MenuItem(message("EditImage"), StyleTools.getIconImageView("iconEdit.png"));
            menu.setOnAction((ActionEvent event) -> {
                handleImage(finalAddress, name, "edit");
            });
            imageMenu.getItems().add(menu);
        }

        Menu dlMenu = new Menu(message("Download"), StyleTools.getIconImageView("iconDownload.png"));
        items.add(dlMenu);
        menu = new MenuItem(message("DownloadBySysBrowser"), StyleTools.getIconImageView("iconDownload.png"));
        menu.setOnAction((ActionEvent event) -> {
            browse(finalAddress);
        });
        dlMenu.getItems().add(menu);

        menu = new MenuItem(message("DownloadByMyBox"), StyleTools.getIconImageView("iconDownload.png"));
        menu.setOnAction((ActionEvent event) -> {
            WebBrowserController controller = WebBrowserController.oneOpen();
            controller.download(finalAddress, name);
        });
        dlMenu.getItems().add(menu);

        Menu copyMenu = new Menu(message("Copy"), StyleTools.getIconImageView("iconCopySystem.png"));
        items.add(copyMenu);

