// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ui.jcef;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.JBColor;
import org.cef.CefClient;
import org.cef.browser.CefBrowser;
import org.cef.handler.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.util.HashMap;
import java.util.Map;

/**
 * @author tav
 */
public class JCEFHtmlPanel implements Disposable {
  @NotNull
  private final JPanel myPanelWrapper;
  private static final CefClient ourCefClient;
  private static final Map<CefBrowser, JCEFHtmlPanel> ourCefBrowser2Panel = new HashMap<>();
  // browser demands some valid URL for loading html content
  private final static String ourUrl = JCEFHtmlPanel.class.getResource(JCEFHtmlPanel.class.getSimpleName() + ".class").toExternalForm();

  private @NotNull final CefBrowser myCefBrowser;
  private boolean myIsCefBrowserCreated;
  private @Nullable String myHtml;

  static {
    ourCefClient = JBCefApp.getInstance().createClient();
    ourCefClient.addLifeSpanHandler(new CefLifeSpanHandlerAdapter() {
      @Override
      public void onAfterCreated(CefBrowser browser) {
        JCEFHtmlPanel panel = ourCefBrowser2Panel.get(browser);
        if (panel != null) {
          panel.myIsCefBrowserCreated = true;
          if (panel.myHtml != null) {
            browser.loadString(panel.myHtml, ourUrl);
            panel.myHtml = null;
          }
        }
      }
    });
    ourCefClient.addLoadHandler(new CefLoadHandlerAdapter() {
      @Override
      public void onLoadingStateChange(CefBrowser browser, boolean isLoading, boolean canGoBack, boolean canGoForward) {
        JCEFHtmlPanel panel = ourCefBrowser2Panel.get(browser);
        if (panel != null) {
          panel.onLoadingStateChange(browser, isLoading, canGoBack, canGoForward);
        }
      }
    });
    ourCefClient.addFocusHandler(new CefFocusHandlerAdapter() {
      @Override
      public boolean onSetFocus(CefBrowser browser, FocusSource source) {
        if (source == FocusSource.FOCUS_SOURCE_NAVIGATION) return true;
        // Workaround: JCEF doesn't change current focus on the client side.
        // Clear the focus manually and this will report focus loss to the client
        // and will let focus return to the client on mouse click.
        // tav [todo]: the opposite is inadequate
        KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
        return false;
      }
    });
    Disposer.register(ApplicationManager.getApplication(), () -> {
      ourCefBrowser2Panel.clear();
      ourCefClient.dispose();
    });
  }

  public JCEFHtmlPanel() {
    myPanelWrapper = new JPanel(new BorderLayout());
    myPanelWrapper.setBackground(JBColor.background());

    myCefBrowser = ourCefClient.createBrowser("about:blank", false, false);
    ourCefBrowser2Panel.put(myCefBrowser, this);

    myPanelWrapper.add(myCefBrowser.getUIComponent(), BorderLayout.CENTER);
  }

  @NotNull
  public JComponent getComponent() {
    return myPanelWrapper;
  }

  public CefBrowser getBrowser() {
    return myCefBrowser;
  }

  public void setHtml(@NotNull String html) {
    final String htmlToRender = prepareHtml(html);
    if (!myIsCefBrowserCreated) {
      myHtml = htmlToRender;
    }
    else {
      myCefBrowser.loadString(htmlToRender, ourUrl);
    }
  }

  @NotNull
  protected String prepareHtml(@NotNull String html) {
    return html;
  }

  protected void onLoadingStateChange(CefBrowser browser, boolean isLoading, boolean canGoBack, boolean canGoForward) {
  }

  @Override
  public void dispose() {
    ourCefBrowser2Panel.remove(myCefBrowser);
    myCefBrowser.close(true);
  }
}

@@ -423,24 +423,67 @@
 .handler.*;%0A
+import org.jetbrains.annotations.Contract;%0A
 import org.j
@@ -600,56 +600,8 @@
 t.*;
-%0Aimport java.util.HashMap;%0Aimport java.util.Map;
 %0A%0A/*
@@ -678,194 +678,51 @@
 %7B%0A  
-@NotNull%0A  private final JPanel myPanelWrapper;%0A  private static final CefClient ourCefClient;%0A  private static final Map%3CCefBrowser, JCEFHtmlPanel%3E ourCefBrowser2Panel = new HashMap%3C%3E()
+private static final CefClient ourCefClient
 ;%0A  
@@ -794,21 +794,21 @@
 ate 
-final 
 static 
+final 
 Stri
@@ -924,24 +924,16 @@
 %0A%0A  
-private 
 @NotNull
  fin
@@ -928,16 +928,74 @@
 @NotNull
+ private final MyComponent myComponent;%0A  @NotNull private
  final C
@@ -1065,24 +1065,16 @@
 ;%0A  
-private 
 @Nullabl
 e St
@@ -1069,16 +1069,24 @@
 @Nullabl
+e privat
 e String
@@ -1335,39 +1335,32 @@
 panel = 
-ourCefBrowser2
+getJCEFHtml
 Panel
-.get
 (browser
@@ -1833,31 +1833,24 @@
 l = 
-ourCefBrowser2
+getJCEFHtml
 Panel
-.get
 (bro
@@ -2692,43 +2692,8 @@
 %3E %7B%0A
-      ourCefBrowser2Panel.clear();%0A
     
@@ -2768,33 +2768,35 @@
   my
-PanelWrapper = new JPanel
+Component = new MyComponent
 (new
@@ -2819,28 +2819,25 @@
 ;%0A    my
-PanelWrapper
+Component
 .setBack
@@ -2951,72 +2951,19 @@
     
-ourCefBrowser2Panel.put(myCefBrowser, this);%0A%0A    myPanelWrapper
+myComponent
 .add
@@ -3086,20 +3086,17 @@
 n my
-PanelWrapper
+Component
 ;%0A  
@@ -3659,19 +3659,18 @@
 ) %7B%0A    
-our
+my
 CefBrows
@@ -3675,66 +3675,452 @@
 wser
-2Panel.remove(myCefBrowser);%0A    myCefBrowser.close(true);
+.close(true);%0A  %7D%0A%0A  @Contract(%22null-%3Enull; !null-%3E!null%22)%0A  private static JCEFHtmlPanel getJCEFHtmlPanel(CefBrowser browser) %7B%0A    if (browser == null) return null;%0A    return ((MyComponent)browser.getUIComponent().getParent()).getJCEFHtmlPanel();%0A  %7D%0A%0A  private class MyComponent extends JPanel %7B%0A    MyComponent(BorderLayout layout) %7B%0A      super(layout);%0A    %7D%0A%0A    JCEFHtmlPanel getJCEFHtmlPanel() %7B%0A      return JCEFHtmlPanel.this;%0A    %7D
 %0A  %7D

Remove ourCefBrowser2Panel map