// Copyright 2000-2020 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;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.util.ui.JBInsets;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleRole;
import javax.swing.*;
import java.awt.*;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map.Entry;

/**
 * @param <K>  the type of an object used as a key
 * @param <UI> the type of an object used to create a component
 * @param <V>  the type of a component to create
 */
public abstract class CardLayoutPanel<K, UI, V extends Component> extends JComponent implements Accessible, Disposable {
  private final IdentityHashMap<K, V> myContent = new IdentityHashMap<>();
  private volatile boolean myDisposed;
  private K myKey;

  /**
   * Prepares the specified key object to return the temporary object
   * that can be used to create a component view.
   * This method may be called on a pooled thread.
   *
   * @param key the key object
   * @return the object used to create a component
   */
  protected abstract UI prepare(K key);

  /**
   * Creates a component view from the temporary object.
   * This method is usually called on the EDT.
   *
   * @param ui
   * @return
   */
  protected abstract V create(UI ui);

  protected void dispose(K key) {
  }

  @Override
  public void dispose() {
    if (!myDisposed) {
      myDisposed = true;
      removeAll();
    }
  }

  public K getKey() {
    return myKey;
  }

  public V getValue(K key, boolean create) {
    V value = myContent.get(key);
    return create && value == null && !myContent.containsKey(key)
           ? createValue(key, prepare(key))
           : value;
  }

  private V createValue(K key, UI ui) {
    V value = create(ui);
    myContent.put(key, value);
    if (value != null) {
      value.setVisible(false);
      add(value);
    }
    return value;
  }

  public ActionCallback select(K key, boolean now) {
    myKey = key;
    ActionCallback callback = new ActionCallback();
    if (now) {
      select(callback, key, prepare(key));
    }
    else {
      selectLater(callback, key);
    }
    return callback;
  }

  private void select(ActionCallback callback, K key, UI ui) {
    if (myKey != key) {
      callback.setRejected();
    }
    else {
      V value = myContent.get(key);
      if (value == null && !myContent.containsKey(key)) {
        value = createValue(key, ui);
      }
      for (Component component : getComponents()) {
        component.setVisible(component == value);
      }
      callback.setDone();
    }
  }

  private void selectLater(final ActionCallback callback, final K key) {
    ModalityState modality = ModalityState.stateForComponent(this);
    ApplicationManager.getApplication().executeOnPooledThread(() -> {
      if (!myDisposed) {
        final UI ui1 = prepare(key);
        ApplicationManager.getApplication().invokeLater(() -> {
          if (!myDisposed) {
            select(callback, key, ui1);
          }
          else callback.setRejected();
        }, modality);
      }
      else callback.setRejected();
    });
  }

  @Override
  public void doLayout() {
    Rectangle bounds = new Rectangle(getWidth(), getHeight());
    JBInsets.removeFrom(bounds, getInsets());
    for (Component component : getComponents()) {
      component.setBounds(bounds);
    }
  }

  @Override
  public Dimension getPreferredSize() {
    if (isPreferredSizeSet()) {
      return super.getPreferredSize();
    }
    for (Component component : getComponents()) {
      if (component.isVisible()) {
        Dimension size = component.getPreferredSize();
        JBInsets.addTo(size, getInsets());
        return size;
      }
    }
    return super.getPreferredSize();
  }

  @Override
  public Dimension getMinimumSize() {
    if (isMinimumSizeSet()) {
      return super.getMinimumSize();
    }
    for (Component component : getComponents()) {
      if (component.isVisible()) {
        Dimension size = component.getMinimumSize();
        JBInsets.addTo(size, getInsets());
        return size;
      }
    }
    return super.getMinimumSize();
  }

  @Override
  public void remove(int index) {
    super.remove(index);
    // dispose corresponding entries only
    Iterator<Entry<K, V>> it = myContent.entrySet().iterator();
    while (it.hasNext()) {
      Entry<K, V> entry = it.next();
      V value = entry.getValue();
      if (value != null && this != value.getParent()) {
        dispose(entry.getKey());
        it.remove();
      }
    }
  }

  @Nullable
  protected final V resetValue(@NotNull K key) {
    V content = myContent.remove(key);
    if (content != null) {
      for (Component component : getComponents()) {
        if (component == content) {
          remove(component);
        }
      }
      if (myKey == key) {
        //select again
        myKey = null;
        select(key, true);
      }
    }
    
    return content;
  }
  
  @Override
  public void removeAll() {
    super.removeAll();
    // dispose all entries
    for (K key : myContent.keySet()) {
      dispose(key);
    }
    myContent.clear();
  }

  @Override
  public AccessibleContext getAccessibleContext() {
    if (accessibleContext == null) {
      accessibleContext = new AccessibleCardLayoutPanel();
    }
    return accessibleContext;
  }

  protected class AccessibleCardLayoutPanel extends AccessibleJComponent {
    @Override
    public AccessibleRole getAccessibleRole() {
      return AccessibleRole.PANEL;
    }
  }

  protected final boolean isDisposed() {
    return myDisposed;
  }
}

@@ -3972,32 +3972,22 @@
         
-Dimension size =
+return
  compone
@@ -4013,72 +4013,8 @@
 ();%0A
-        JBInsets.addTo(size, getInsets());%0A        return size;%0A
     
@@ -4285,24 +4285,14 @@
     
-Dimension size =
+return
  com
@@ -4320,72 +4320,8 @@
 ();%0A
-        JBInsets.addTo(size, getInsets());%0A        return size;%0A
     

revert 84e232cc614ee44656734e4e97b95b8fb9689063 that adds insets twice