/*
 * Copyright 2000-2013 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.openapi.ui;

import com.intellij.CommonBundle;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.help.HelpManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.containers.ContainerUtil;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;

/**
 * The DialogBuilder is a simpler alternative to {@link DialogWrapper}.
 * There is no need to create a subclass (which is needed in the DialogWrapper), which can be nice for simple dialogs.
 */
public class DialogBuilder implements Disposable {
  private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.ui.DialogBuilder");

  @NonNls public static final String REQUEST_FOCUS_ENABLED = "requestFocusEnabled";

  private JComponent myCenterPanel;
  private JComponent myNorthPanel;
  private String myTitle;
  private JComponent myPreferedFocusComponent;
  private String myDimensionServiceKey;
  private ArrayList<ActionDescriptor> myActions = null;
  private final MyDialogWrapper myDialogWrapper;
  private Runnable myCancelOperation = null;
  private Runnable myOkOperation = null;

  public int show() {
    return showImpl(true).getExitCode();
  }

  public boolean showAndGet() {
    return showImpl(true).isOK();
  }

  public void showNotModal() {
    showImpl(false);
  }

  public DialogBuilder(@Nullable Project project) {
    myDialogWrapper = new MyDialogWrapper(project, true);
    Disposer.register(myDialogWrapper.getDisposable(), this);
  }

  public DialogBuilder(@Nullable Component parent) {
    myDialogWrapper = new MyDialogWrapper(parent, true);
    Disposer.register(myDialogWrapper.getDisposable(), this);
  }

  public DialogBuilder() {
    this(((Project)null));
  }

  @Override
  public void dispose() {
  }

  private MyDialogWrapper showImpl(boolean isModal) {
    myDialogWrapper.setTitle(myTitle);
    myDialogWrapper.init();
    myDialogWrapper.setModal(isModal);
    myDialogWrapper.show();
    if (isModal) {
      myDialogWrapper.dispose();
    }
    return myDialogWrapper;
  }

  public void setCenterPanel(JComponent centerPanel) {
    myCenterPanel = centerPanel;
  }

  @NotNull
  public DialogBuilder centerPanel(@NotNull JComponent centerPanel) {
    myCenterPanel = centerPanel;
    return this;
  }

  @NotNull
  public DialogBuilder setNorthPanel(@NotNull JComponent northPanel) {
    myNorthPanel = northPanel;
    return this;
  }

  public void setTitle(String title) {
    myTitle = title;
  }

  @NotNull
  public DialogBuilder title(@NotNull String title) {
    myTitle = title;
    return this;
  }

  public void setPreferredFocusComponent(JComponent component) {
    myPreferedFocusComponent = component;
  }

  public void setDimensionServiceKey(@NonNls String dimensionServiceKey) {
    myDimensionServiceKey = dimensionServiceKey;
  }

  public DialogBuilder dimensionKey(@NotNull String dimensionServiceKey) {
    myDimensionServiceKey = dimensionServiceKey;
    return this;
  }

  public void addAction(Action action) {
    addActionDescriptor(new CustomActionDescriptor(action));
  }

  public <T extends ActionDescriptor> T addActionDescriptor(T actionDescriptor) {
    getActionDescriptors().add(actionDescriptor);
    return actionDescriptor;
  }

  private ArrayList<ActionDescriptor> getActionDescriptors() {
    if (myActions == null) removeAllActions();
    return myActions;
  }

  public void setActionDescriptors(ActionDescriptor... descriptors) {
    removeAllActions();
    ContainerUtil.addAll(myActions, descriptors);
  }

  public void removeAllActions() {
    myActions = new ArrayList<>();
  }

  public Window getWindow() {
    return myDialogWrapper.getWindow();
  }

  public CustomizableAction addOkAction() {
    return addActionDescriptor(new OkActionDescriptor());
  }

  public CustomizableAction addCancelAction() {
    return addActionDescriptor(new CancelActionDescriptor());
  }

  public CustomizableAction addCloseButton() {
    CustomizableAction closeAction = addOkAction();
    closeAction.setText(CommonBundle.getCloseButtonText());
    return closeAction;
  }

  public void addDisposable(@NotNull Disposable disposable) {
    Disposer.register(this, disposable);
  }

  public void setButtonsAlignment(@MagicConstant(intValues = {SwingConstants.CENTER, SwingConstants.RIGHT}) int alignment) {
    myDialogWrapper.setButtonsAlignment(alignment);
  }

  public DialogWrapper getDialogWrapper() {
    return myDialogWrapper;
  }

  public void showModal(boolean modal) {
    if (modal) {
      show();
    }
    else {
      showNotModal();
    }
  }

  public void setHelpId(@NonNls String helpId) {
    myDialogWrapper.setHelpId(helpId);
  }

  public void setCancelOperation(Runnable runnable) {
    myCancelOperation = runnable;
  }

  public void setOkOperation(Runnable runnable) {
    myOkOperation = runnable;
  }

  public void setOkActionEnabled(final boolean isEnabled) {
    myDialogWrapper.setOKActionEnabled(isEnabled);
  }

  @NotNull
  public DialogBuilder okActionEnabled(boolean isEnabled) {
    myDialogWrapper.setOKActionEnabled(isEnabled);
    return this;
  }

  @NotNull
  public DialogBuilder resizable(boolean resizable) {
    myDialogWrapper.setResizable(resizable);
    return this;
  }

  public CustomizableAction getOkAction() {
    return get(getActionDescriptors(), OkActionDescriptor.class);
  }

  private static CustomizableAction get(final ArrayList<ActionDescriptor> actionDescriptors, final Class aClass) {
    for (ActionDescriptor actionDescriptor : actionDescriptors) {
      if (actionDescriptor.getClass().isAssignableFrom(aClass)) return (CustomizableAction)actionDescriptor;
    }
    return null;
  }

  public CustomizableAction getCancelAction() {
    return get(getActionDescriptors(), CancelActionDescriptor.class);
  }

  public Component getCenterPanel() {
    return myCenterPanel;
  }

  public interface ActionDescriptor {
    Action getAction(DialogWrapper dialogWrapper);
  }

  public abstract static class DialogActionDescriptor implements ActionDescriptor {
    private final String myName;
    private final Object myMnemonicChar;
    private boolean myIsDefault = false;

    protected DialogActionDescriptor(String name, int mnemonicChar) {
      myName = name;
      myMnemonicChar = mnemonicChar == -1 ? null : Integer.valueOf(mnemonicChar);
    }

    @Override
    public Action getAction(DialogWrapper dialogWrapper) {
      Action action = createAction(dialogWrapper);
      action.putValue(Action.NAME, myName);
      if (myMnemonicChar != null) action.putValue(Action.MNEMONIC_KEY, myMnemonicChar);
      if (myIsDefault) action.putValue(Action.DEFAULT, Boolean.TRUE);
      return action;
    }

    public void setDefault(boolean isDefault) {
      myIsDefault = isDefault;
    }

    protected abstract Action createAction(DialogWrapper dialogWrapper);
  }

  public static class CloseDialogAction extends DialogActionDescriptor {
    private final int myExitCode;

    public CloseDialogAction() {
      this(CommonBundle.getCloseButtonText(), -1, DialogWrapper.CLOSE_EXIT_CODE);
    }

    public CloseDialogAction(String name, int mnemonicChar, int exitCode) {
      super(name, mnemonicChar);
      myExitCode = exitCode;
    }

    public static CloseDialogAction createDefault(String name, int mnemonicChar, int exitCode) {
      CloseDialogAction closeDialogAction = new CloseDialogAction(name, mnemonicChar, exitCode);
      closeDialogAction.setDefault(true);
      return closeDialogAction;
    }

    @Override
    protected Action createAction(final DialogWrapper dialogWrapper) {
      return new AbstractAction(){
        @Override
        public void actionPerformed(@NotNull ActionEvent e) {
          dialogWrapper.close(myExitCode);
        }
      };
    }
  }

  public interface CustomizableAction {
    void setText(String text);
  }

  public static class CustomActionDescriptor implements ActionDescriptor {
    private final Action myAction;

    public CustomActionDescriptor(Action action) {
      myAction = action;
    }

    @Override
    public Action getAction(DialogWrapper dialogWrapper) {
      return myAction;
    }
  }

  private abstract static class BuiltinAction implements ActionDescriptor, CustomizableAction {
    protected String myText = null;

    @Override
    public void setText(String text) {
      myText = text;
    }

    @Override
    public Action getAction(DialogWrapper dialogWrapper) {
      Action builtinAction = getBuiltinAction((MyDialogWrapper)dialogWrapper);
      if (myText != null) builtinAction.putValue(Action.NAME, myText);
      return builtinAction;
    }

    protected abstract Action getBuiltinAction(MyDialogWrapper dialogWrapper);
  }

  public static class OkActionDescriptor extends BuiltinAction {
    @Override
    protected Action getBuiltinAction(MyDialogWrapper dialogWrapper) {
      return dialogWrapper.getOKAction();
    }
  }

  public static class CancelActionDescriptor extends BuiltinAction {
    @Override
    protected Action getBuiltinAction(MyDialogWrapper dialogWrapper) {
      return dialogWrapper.getCancelAction();
    }
  }

  private class MyDialogWrapper extends DialogWrapper {
    private String myHelpId = null;
    private MyDialogWrapper(@Nullable Project project, boolean canBeParent) {
      super(project, canBeParent);
    }

    private MyDialogWrapper(Component parent, boolean canBeParent) {
      super(parent, canBeParent);
    }

    public void setHelpId(String helpId) {
      myHelpId = helpId;
    }

    @Nullable
    @Override
    protected String getHelpId() {
      return myHelpId;
    }

    @Override
    public void init() { super.init(); }
    @Override
    @NotNull
    public Action getOKAction() { return super.getOKAction(); } // Make it public
    @Override
    @NotNull
    public Action getCancelAction() { return super.getCancelAction(); } // Make it public

    @Override
    protected JComponent createCenterPanel() { return myCenterPanel; }

    @Override
    protected JComponent createNorthPanel() { return myNorthPanel; }

    @Override
    public void dispose() {
      myPreferedFocusComponent = null;
      super.dispose();
    }

    @Override
    public JComponent getPreferredFocusedComponent() {
      if (myPreferedFocusComponent != null) return myPreferedFocusComponent;
      FocusTraversalPolicy focusTraversalPolicy = null;
      Container container = myCenterPanel;
      while (container != null && (focusTraversalPolicy = container.getFocusTraversalPolicy()) == null && !(container instanceof Window)) {
        container = container.getParent();
      }
      if (focusTraversalPolicy == null) return null;
      Component component = focusTraversalPolicy.getDefaultComponent(myCenterPanel);
      while (!(component instanceof JComponent) && component != null) {
        component = focusTraversalPolicy.getComponentAfter(myCenterPanel, component);
      }
      return (JComponent)component;
    }

    @Override
    protected String getDimensionServiceKey() {
      return myDimensionServiceKey;
    }

    @Override
    protected JButton createJButtonForAction(Action action) {
      JButton button = super.createJButtonForAction(action);
      Object value = action.getValue(REQUEST_FOCUS_ENABLED);
      if (value instanceof Boolean) button.setRequestFocusEnabled(((Boolean)value).booleanValue());
      return button;
    }

    @Override
    public void doCancelAction() {
      if (!getCancelAction().isEnabled()) return;
      if (myCancelOperation != null) {
        myCancelOperation.run();
      }
      else {
        super.doCancelAction();
      }
    }

    @Override
    protected void doOKAction() {
      if (myOkOperation != null) {
        myOkOperation.run();
      }
      else {
        super.doOKAction();
      }
    }

    @Override
    protected void doHelpAction() {
      if (myHelpId == null) {
        super.doHelpAction();
        return;
      }

      HelpManager.getInstance().invokeHelp(myHelpId);
    }

    @Override
    @NotNull
    protected Action[] createActions() {
      if (myActions == null) return super.createActions();
      ArrayList<Action> actions = new ArrayList<>(myActions.size());
      for (ActionDescriptor actionDescriptor : myActions) {
        actions.add(actionDescriptor.getAction(this));
      }
      if (myHelpId != null) actions.add(getHelpAction());
      return actions.toArray(new Action[actions.size()]);
    }
  }

  public void setErrorText(@Nullable final String text) {
    myDialogWrapper.setErrorText(text);
  }

  public void setErrorText(@Nullable final String text, @Nullable JComponent component) {
    myDialogWrapper.setErrorText(text, component);
  }
}
