/*
 * Copyright 2000-2017 JetBrains s.r.o.
 *
 * 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
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */
package com.intellij.application.options.schemes;

import com.intellij.openapi.Disposable;
import com.intellij.openapi.options.Scheme;
import com.intellij.openapi.ui.AbstractPainter;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeGlassPaneUtil;
import com.intellij.ui.ClickListener;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.ui.GraphicsUtil;
import com.intellij.util.ui.JBUI;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Arrays;

public abstract class AbstractDescriptionAwareSchemesPanel<T extends Scheme> extends AbstractSchemesPanel<T, JPanel> implements Disposable {
  private static final String SHOW_DESCRIPTION_CARD = "show.description.card";
  private static final String EDIT_DESCRIPTION_CARD = "edit.description.card";
  private static final String ERROR_CARD = "error.card";

  private final static KeyStroke ESC_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
  private final static KeyStroke ENTER_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false);

  private DescriptionLabel myDescriptionLabel;
  private JLabel myWarningLabel;
  private JBTextField myDescriptionTextField;
  private CardLayout myLayout;
  private AbstractPainter myPainter;

  public AbstractDescriptionAwareSchemesPanel() {
    super(0);
  }

  @NotNull
  @Override
  protected JPanel createInfoComponent() {
    JPanel panel = new JPanel();
    myLayout = new CardLayout();
    panel.setLayout(myLayout);

    myDescriptionTextField = new JBTextField();
    myDescriptionTextField.addFocusListener(new FocusAdapter() {
      @Override
      public void focusLost(FocusEvent e) {
        showDescription();
      }
    });
    myDescriptionTextField.registerKeyboardAction(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        showDescription();
        IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(getConfigurableFocusComponent(), true));
      }
    }, ESC_KEY_STROKE, JComponent.WHEN_FOCUSED);
    myDescriptionTextField.registerKeyboardAction(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        applyDescription();
        IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(getConfigurableFocusComponent(), true));
      }
    }, ENTER_KEY_STROKE, JComponent.WHEN_FOCUSED);

    myDescriptionLabel = new DescriptionLabel();
    new ClickListener() {
      @Override
      public boolean onClick(@NotNull MouseEvent event, int clickCount) {
        if (clickCount != 2) {
          return false;
        }
        editDescription(myDescriptionLabel.getText());
        return true;
      }
    }.installOn(myDescriptionLabel);

    myWarningLabel = new JLabel();

    panel.add(myDescriptionTextField, EDIT_DESCRIPTION_CARD);
    panel.add(myDescriptionLabel, SHOW_DESCRIPTION_CARD);
    panel.add(myWarningLabel, ERROR_CARD);

    myLayout.show(panel, ERROR_CARD);

    myPainter = new AbstractPainter() {
      @Override
      public boolean needsRepaint() {
        return true;
      }

      @Override
      public void executePaint(Component component, Graphics2D g) {
        if (myDescriptionTextField.isShowing()) {
          GraphicsUtil.setupAntialiasing(g);
          g.setColor(JBColor.GRAY);
          g.drawString(EditableSchemesCombo.EDITING_HINT, 0, -JBUI.scale(5));
        }
      }
    };
    IdeGlassPaneUtil.installPainter(panel, myPainter, this);
    return panel;
  }

  @Override
  public final void showMessage(@Nullable String message, @NotNull MessageType messageType) {
    showMessage(message, messageType, myWarningLabel);
    myLayout.show(myInfoComponent, ERROR_CARD);
  }

  @Override
  public void selectScheme(@Nullable T scheme) {
    super.selectScheme(scheme);
    if (scheme != null) {
      showDescription();
    }
  }

  @Override
  public final void clearMessage() {
    myLayout.show(myInfoComponent, SHOW_DESCRIPTION_CARD);
  }

  public void showDescription() {
    String newDescription = (((DescriptionAwareSchemeActions<T>)getActions()).getDescription(getSelectedScheme()));
    myDescriptionLabel.setAllText(StringUtil.notNullize(newDescription));
    myLayout.show(myInfoComponent, SHOW_DESCRIPTION_CARD);
    myPainter.setNeedsRepaint(true);
  }

  public void editDescription(@Nullable String startValue) {
    myLayout.show(myInfoComponent, EDIT_DESCRIPTION_CARD);
    myDescriptionTextField.setText(StringUtil.notNullize(startValue));
    IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(myDescriptionTextField, true));
    myPainter.setNeedsRepaint(true);
  }

  @NotNull
  protected abstract JComponent getConfigurableFocusComponent();

  private void applyDescription() {
    (((DescriptionAwareSchemeActions<T>)getActions())).setDescription(getSelectedScheme(), myDescriptionTextField.getText());
    showDescription();
  }

  private static class DescriptionLabel extends JBLabel {
    private String myAllText;

    DescriptionLabel() {
      setForeground(JBColor.GRAY);
      setVerticalAlignment(CENTER);
      setHorizontalAlignment(LEFT);
      addComponentListener(new ComponentAdapter() {
        @Override
        public void componentResized(ComponentEvent e) {
          calculateText();
        }
      });
    }

    public void setAllText(String allText) {
      myAllText = allText;
      calculateText();
      revalidate();
      repaint();
    }

    private void calculateText() {
      FontMetrics metrics = getFontMetrics(getFont());
      int width = getSize().width - metrics.stringWidth("...");
      if (width <= 0) {
        setText("...");
        setToolTipText(myAllText);
      }
      char[] text = myAllText.toCharArray();
      int[] charsWidth = new int[text.length];
      for (int i = 0; i < text.length; i++) {
        int w = metrics.charWidth(text[i]);
        for (int j = 0; j <= i; j++) {
          charsWidth[i] += w;
        }
      }
      int idx = Arrays.binarySearch(charsWidth, 0, charsWidth.length, width);
      if (idx < 0) {
        idx = -idx - 1;
      }

      if (idx < myAllText.length()) {
        setText(myAllText.substring(0, idx) + "...");
        setToolTipText(myAllText.substring(idx));
      } else {
        setText(myAllText.substring(0, idx));
      }
    }
  }

  @Override
  public void dispose() {

  }
}