/*
 * Copyright 2000-2016 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 git4idea.commands;

import com.intellij.credentialStore.CredentialPromptDialog;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.util.PathUtil;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import git4idea.config.SSHConnectionSettings;
import git4idea.i18n.GitBundle;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static com.intellij.credentialStore.CredentialAttributesKt.CredentialAttributes;

/**
 * Swing GUI handler for the SSH events
 */
public class GitSSHGUIHandler {
  @Nullable private final Project myProject;

  GitSSHGUIHandler(@Nullable Project project) {
    myProject = project;
  }

  public boolean verifyServerHostKey(final String hostname,
                                     final int port,
                                     final String serverHostKeyAlgorithm,
                                     final String fingerprint,
                                     final boolean isNew) {
    final String message;
    if (isNew) {
      message = GitBundle.message("ssh.new.host.key", hostname, port, fingerprint, serverHostKeyAlgorithm);
    }
    else {
      message = GitBundle.message("ssh.changed.host.key", hostname, port, fingerprint, serverHostKeyAlgorithm);
    }
    final AtomicBoolean rc = new AtomicBoolean();
    ApplicationManager.getApplication().invokeAndWait(new Runnable() {
      public void run() {
        rc.set(Messages.YES == Messages.showYesNoDialog(myProject, message, GitBundle.getString("ssh.confirm.key.titile"), null));
      }
    }, ModalityState.any());
    return rc.get();
  }

  @Nullable
  public String askPassphrase(final String username, final String keyPath, boolean resetPassword, final String lastError) {
    String error = processLastError(resetPassword, lastError);
    return CredentialPromptDialog.askPassword(myProject, GitBundle.getString("ssh.ask.passphrase.title"),
                                              "Password for the SSH key \"" + PathUtil.getFileName(keyPath) + "\":",
                                              CredentialAttributes(GitSSHGUIHandler.class, "PASSPHRASE:" + keyPath), resetPassword, error);
  }

  /**
   * Process the last error
   *
   * @param resetPassword true, if last entered password was incorrect
   * @param lastError     the last error
   * @return the error to show on the password dialo or null
   */
  @Nullable
  private String processLastError(boolean resetPassword, final String lastError) {
    String error;
    if (lastError != null && lastError.length() != 0 && !resetPassword) {
      ApplicationManager.getApplication().invokeAndWait(new Runnable() {
        public void run() {
          showError(lastError);
        }
      }, ModalityState.any());
      error = null;
    }
    else {
      error = lastError != null && lastError.length() == 0 ? null : lastError;
    }
    return error;
  }

  private void showError(final String lastError) {
    if (lastError.length() != 0) {
      Messages.showErrorDialog(myProject, lastError, GitBundle.getString("ssh.error.title"));
    }
  }

  /**
   * Reply to challenge in keyboard-interactive scenario
   *
   * @param username    a user name
   * @param name        a name of challenge
   * @param instruction a instructions
   * @param numPrompts  number of prompts
   * @param prompt      prompts
   * @param echo        true if the reply for corresponding prompt should be echoed
   * @param lastError   the last error
   * @return replies to the challenges
   */
  @SuppressWarnings({"UseOfObsoleteCollectionType", "unchecked"})
  public Vector<String> replyToChallenge(final String username,
                                         final String name,
                                         final String instruction,
                                         final int numPrompts,
                                         final Vector<String> prompt,
                                         final Vector<Boolean> echo,
                                         final String lastError) {
    final AtomicReference<Vector<String>> rc = new AtomicReference<>();
    ApplicationManager.getApplication().invokeAndWait(new Runnable() {
      public void run() {
        showError(lastError);
        GitSSHKeyboardInteractiveDialog dialog =
          new GitSSHKeyboardInteractiveDialog(name, numPrompts, instruction, prompt, echo, username);
        if (dialog.showAndGet()) {
          rc.set(dialog.getResults());
        }
      }
    }, ModalityState.any());
    return rc.get();
  }

  /**
   * Ask password
   *
   * @param username      a user name
   * @param resetPassword true if the previous password supplied to the service was incorrect
   * @param lastError     the previous error  @return a password or null if dialog was cancelled.
   */
  @Nullable
  public String askPassword(final String username, boolean resetPassword, final String lastError) {
    String error = processLastError(resetPassword, lastError);
    return CredentialPromptDialog.askPassword(myProject,
                                              GitBundle.getString("ssh.password.title"),
                                              GitBundle.message("ssh.password.message", username),
                                              CredentialAttributes(GitSSHGUIHandler.class, "PASSWORD:" + username),
                                              resetPassword,
                                              error);
  }

  /**
   * Get last successful authentication method. The default implementation returns empty string
   * meaning that last authentication is unknown or failed.
   *
   * @param userName the user name
   * @return the successful authentication method
   */
  public String getLastSuccessful(String userName) {
    SSHConnectionSettings s = SSHConnectionSettings.getInstance();
    String rc = s.getLastSuccessful(userName);
    return rc == null ? "" : rc;
  }

  /**
   * Set last successful authentication method
   *
   * @param userName the user name
   * @param method   the authentication method, the empty string if authentication process failed.
   * @param error    the error to show to user in case when authentication process failed.
   */
  public void setLastSuccessful(String userName, String method, final String error) {
    SSHConnectionSettings s = SSHConnectionSettings.getInstance();
    s.setLastSuccessful(userName, method);
    if (error != null && error.length() != 0) {
      UIUtil.invokeLaterIfNeeded(new Runnable() {
        public void run() {
          showError(error);
        }
      });
    }
  }

  @SuppressWarnings({"UseOfObsoleteCollectionType"})
  private class GitSSHKeyboardInteractiveDialog extends DialogWrapper {
    /**
     * input fields
     */
    JTextComponent[] inputs;
    /**
     * root panel
     */
    JPanel contents;
    /**
     * number of prompts
     */
    private final int myNumPrompts;
    /**
     * Instructions
     */
    private final String myInstruction;
    /**
     * Prompts
     */
    private final Vector<String> myPrompt;
    /**
     * Array of echo values
     */
    private final Vector<Boolean> myEcho;
    /**
     * A name of user
     */
    private final String myUserName;

    public GitSSHKeyboardInteractiveDialog(String name,
                                           final int numPrompts,
                                           final String instruction,
                                           final Vector<String> prompt,
                                           final Vector<Boolean> echo,
                                           final String userName) {
      super(myProject, true);
      myNumPrompts = numPrompts;
      myInstruction = instruction;
      myPrompt = prompt;
      myEcho = echo;
      myUserName = userName;
      setTitle(GitBundle.message("ssh.keyboard.interactive.title", name));
      init();
      setResizable(true);
      setModal(true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected JComponent createCenterPanel() {
      if (contents == null) {
        int line = 0;
        contents = new JPanel(new GridBagLayout());
        inputs = new JTextComponent[myNumPrompts];
        GridBagConstraints c;
        Insets insets = JBUI.insets(1);
        if (myInstruction.length() != 0) {
          JLabel instructionLabel = new JLabel(myInstruction);
          c = new GridBagConstraints();
          c.insets = insets;
          c.gridx = 0;
          c.gridy = line;
          c.gridwidth = 2;
          c.weightx = 1;
          c.fill = GridBagConstraints.HORIZONTAL;
          c.anchor = GridBagConstraints.WEST;
          line++;
          contents.add(instructionLabel, c);
        }
        c = new GridBagConstraints();
        c.insets = insets;
        c.anchor = GridBagConstraints.WEST;
        c.gridx = 0;
        c.gridy = line;
        contents.add(new JLabel(GitBundle.getString("ssh.keyboard.interactive.username")), c);
        c = new GridBagConstraints();
        c.insets = insets;
        c.gridx = 1;
        c.gridy = line;
        c.gridwidth = 1;
        c.weightx = 1;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.anchor = GridBagConstraints.WEST;
        contents.add(new JLabel(myUserName), c);
        line++;
        for (int i = 0; i < myNumPrompts; i++) {
          c = new GridBagConstraints();
          c.insets = insets;
          c.anchor = GridBagConstraints.WEST;
          c.gridx = 0;
          c.gridy = line;
          JLabel promptLabel = new JLabel(myPrompt.get(i));
          contents.add(promptLabel, c);
          c = new GridBagConstraints();
          c.insets = insets;
          c.gridx = 1;
          c.gridy = line;
          c.gridwidth = 1;
          c.weightx = 1;
          c.fill = GridBagConstraints.HORIZONTAL;
          c.anchor = GridBagConstraints.WEST;
          if (myEcho.get(i).booleanValue()) {
            inputs[i] = new JTextField(32);
          }
          else {
            inputs[i] = new JPasswordField(32);
          }
          contents.add(inputs[i], c);
          line++;
        }
        c = new GridBagConstraints();
        c.gridx = 0;
        c.gridy = line;
        c.gridwidth = 1;
        c.weighty = 1;
        c.fill = GridBagConstraints.VERTICAL;
        c.anchor = GridBagConstraints.CENTER;
        contents.add(new JPanel(), c);
      }
      return contents;
    }

    /**
     * {@inheritDoc}
     */
    @NotNull
    @Override
    protected Action[] createActions() {
      return new Action[]{getOKAction(), getCancelAction()};
    }

    /**
     * @return text entered at prompt
     */
    @SuppressWarnings({"UseOfObsoleteCollectionType"})
    public Vector<String> getResults() {
      Vector<String> rc = new Vector<>(myNumPrompts);
      for (int i = 0; i < myNumPrompts; i++) {
        rc.add(inputs[i].getText());
      }
      return rc;
    }

    @Override
    public JComponent getPreferredFocusedComponent() {
      if (inputs.length > 0) {
        return inputs[0];
      }
      return super.getPreferredFocusedComponent();
    }
  }
}
