/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2008-2009 Sun Microsystems, Inc.
 *      Portions Copyright 2011-2015 ForgeRock AS
 */

package org.opends.guitools.controlpanel.ui;

import java.awt.Component;
import java.awt.GridBagConstraints;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.LinkedHashSet;

import javax.naming.NamingException;
import javax.naming.ldap.InitialLdapContext;
import javax.swing.JLabel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.admin.ads.util.ApplicationTrustManager;
import org.opends.guitools.controlpanel.datamodel.ConfigReadException;
import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent;
import org.opends.guitools.controlpanel.util.BackgroundTask;
import org.opends.guitools.controlpanel.util.Utilities;
import org.opends.quicksetup.UserDataCertificateException;
import org.opends.quicksetup.ui.CertificateDialog;
import org.opends.quicksetup.util.UIKeyStore;
import org.opends.quicksetup.util.Utils;
import org.opends.server.types.DN;
import org.opends.server.util.StaticUtils;

import static com.forgerock.opendj.cli.Utils.*;

import static org.opends.messages.AdminToolMessages.*;
import static org.opends.messages.QuickSetupMessages.*;

/**
 * The panel that appears when the user is asked to provide authentication.
 */
public class LoginPanel extends StatusGenericPanel
{
  private static final long serialVersionUID = 5051556513294844797L;
  private JPasswordField pwd;
  private JTextField dn;
  private JLabel pwdLabel;
  private JLabel dnLabel;
  private String usedUrl;

  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();

  /**
   * Default constructor.
   *
   */
  public LoginPanel()
  {
    super();
    createLayout();
  }

  /** {@inheritDoc} */
  @Override
  public LocalizableMessage getTitle()
  {
    return INFO_CTRL_PANEL_LOGIN_PANEL_TITLE.get();
  }

  /**
   * Creates the layout of the panel (but the contents are not populated here).
   */
  private void createLayout()
  {
    GridBagConstraints gbc = new GridBagConstraints();
    gbc.anchor = GridBagConstraints.WEST;
    gbc.gridx = 0;
    gbc.gridy = 0;

    gbc.weightx = 0.0;
    gbc.gridwidth = 1;
    gbc.fill = GridBagConstraints.NONE;
    dnLabel = Utilities.createPrimaryLabel(INFO_CTRL_PANEL_BIND_DN_LABEL.get());
    add(dnLabel, gbc);
    gbc.insets.left = 10;
    gbc.gridx = 1;
    dn = Utilities.createTextField("cn=Directory Manager", 20);
    gbc.weightx = 1.0;
    gbc.fill = GridBagConstraints.HORIZONTAL;
    add(dn, gbc);
    gbc.insets.top = 10;
    gbc.insets.left = 0;

    gbc.gridx = 0;
    gbc.gridy ++;
    gbc.weightx = 0.0;
    gbc.gridwidth = 1;
    gbc.fill = GridBagConstraints.NONE;
    pwdLabel = Utilities.createPrimaryLabel(
        INFO_CTRL_PANEL_BIND_PASSWORD_LABEL.get());
    add(pwdLabel, gbc);
    gbc.insets.left = 10;
    gbc.gridx = 1;
    pwd = Utilities.createPasswordField();
    gbc.weightx = 1.0;
    gbc.fill = GridBagConstraints.HORIZONTAL;
    add(pwd, gbc);

    addBottomGlue(gbc);
  }

  /** {@inheritDoc} */
  @Override
  public Component getPreferredFocusComponent()
  {
    return pwd;
  }

  /** {@inheritDoc} */
  @Override
  public void configurationChanged(ConfigurationChangeEvent ev)
  {
  }

  /** {@inheritDoc} */
  @Override
  public void toBeDisplayed(boolean visible)
  {
    super.toBeDisplayed(visible);
    if (visible)
    {
      pwd.setText("");
    }
  }

  /** {@inheritDoc} */
  @Override
  public void okClicked()
  {
    setPrimaryValid(dnLabel);
    setPrimaryValid(pwdLabel);
    final LinkedHashSet<LocalizableMessage> errors = new LinkedHashSet<>();

    boolean dnInvalid = false;
    boolean pwdInvalid = false;

    if ("".equals(dn.getText().trim()))
    {
      dnInvalid = true;
      errors.add(INFO_EMPTY_DIRECTORY_MANAGER_DN.get());
    }
    else if (!isDN(dn.getText()))
    {
      dnInvalid = true;
      errors.add(INFO_NOT_A_DIRECTORY_MANAGER_DN.get());
    }

    if (pwd.getPassword().length == 0)
    {
      pwdInvalid = true;
      errors.add(INFO_EMPTY_PWD.get());
    }
    if (dnInvalid)
    {
      setPrimaryInvalid(dnLabel);
    }

    if (pwdInvalid)
    {
      setPrimaryInvalid(pwdLabel);
    }

    if (errors.isEmpty())
    {
      setEnabledOK(false);
      setEnabledCancel(false);
      displayMessage(INFO_CTRL_PANEL_VERIFYING_AUTHENTICATION_SUMMARY.get());

      BackgroundTask<InitialLdapContext> worker =
        new BackgroundTask<InitialLdapContext>()
      {
        /** {@inheritDoc} */
        @Override
        public InitialLdapContext processBackgroundTask() throws Throwable
        {
          InitialLdapContext ctx = null;
          try
          {
            usedUrl = getInfo().getAdminConnectorURL();
            ctx = Utilities.getAdminDirContext(getInfo(), dn.getText(),
                String.valueOf(pwd.getPassword()));

            if (getInfo().getDirContext() != null)
            {
              try
              {
                getInfo().getDirContext().close();
              }
              catch (Throwable t)
              {
              }
            }
            if (getInfo().getUserDataDirContext() != null)
            {
              try
              {
                getInfo().getUserDataDirContext().close();
              }
              catch (Throwable t)
              {
              }
            }
            try
            {
              Thread.sleep(500);
            }
            catch (Throwable t)
            {
            }
            SwingUtilities.invokeLater(new Runnable()
            {
              @Override
              public void run()
              {
                displayMessage(
                    INFO_CTRL_PANEL_READING_CONFIGURATION_SUMMARY.get());
              }
            });
            getInfo().setDirContext(ctx);
            getInfo().setUserDataDirContext(null);
            getInfo().regenerateDescriptor();
            return ctx;
          } catch (Throwable t)
          {
            StaticUtils.close(ctx);
            throw t;
          }
        }

        /** {@inheritDoc} */
        @Override
        public void backgroundTaskCompleted(InitialLdapContext ctx,
            Throwable throwable)
        {
          boolean handleCertificateException = false;
          if (throwable != null)
          {
            logger.info(LocalizableMessage.raw("Error connecting: " + throwable, throwable));

            if (isCertificateException(throwable))
            {
              ApplicationTrustManager.Cause cause =
                getInfo().getTrustManager().getLastRefusedCause();

              logger.info(LocalizableMessage.raw("Certificate exception cause: "+cause));
              UserDataCertificateException.Type excType = null;
              if (cause == ApplicationTrustManager.Cause.NOT_TRUSTED)
              {
                excType = UserDataCertificateException.Type.NOT_TRUSTED;
              }
              else if (cause ==
                ApplicationTrustManager.Cause.HOST_NAME_MISMATCH)
              {
                excType = UserDataCertificateException.Type.HOST_NAME_MISMATCH;
              }
              else
              {
                LocalizableMessage msg = getThrowableMsg(
                    INFO_ERROR_CONNECTING_TO_LOCAL.get(), throwable);
                errors.add(msg);
              }

              if (excType != null)
              {
                String h;
                int p;
                try
                {
                  URI uri = new URI(usedUrl);
                  h = uri.getHost();
                  p = uri.getPort();
                }
                catch (Throwable t)
                {
                  logger.warn(LocalizableMessage.raw(
                      "Error parsing ldap url of ldap url.", t));
                  h = INFO_NOT_AVAILABLE_LABEL.get().toString();
                  p = -1;
                }
                UserDataCertificateException udce =
                  new UserDataCertificateException(null,
                      INFO_CERTIFICATE_EXCEPTION.get(h, p),
                      throwable, h, p,
                      getInfo().getTrustManager().getLastRefusedChain(),
                      getInfo().getTrustManager().getLastRefusedAuthType(),
                      excType);

                handleCertificateException(udce);
                handleCertificateException = true;
              }
            }
            else if (throwable instanceof NamingException)
            {
              boolean found = false;
              String providedDn = dn.getText();
              Iterator<DN> it = getInfo().getServerDescriptor().
              getAdministrativeUsers().iterator();
              while (it.hasNext() && !found)
              {
                found = Utils.areDnsEqual(providedDn, it.next().toString());
              }
              if (!found)
              {
                errors.add(INFO_NOT_A_DIRECTORY_MANAGER_IN_CONFIG.get());
              }
              else
              {
                errors.add(Utils.getMessageForException(
                    (NamingException)throwable));
              }

              setPrimaryInvalid(dnLabel);
              setPrimaryInvalid(pwdLabel);
            }
            else if (throwable instanceof ConfigReadException)
            {
              errors.add(((ConfigReadException)throwable).getMessageObject());
            }
            else
            {
              // This is a bug
              throwable.printStackTrace();
              errors.add(getThrowableMsg(INFO_BUG_MSG.get(), throwable));
            }
          }
          displayMainPanel();
          setEnabledCancel(true);
          setEnabledOK(true);
          if (!errors.isEmpty())
          {
            displayErrorDialog(errors);
            pwd.setSelectionStart(0);
            pwd.setSelectionEnd(pwd.getPassword().length);
            pwd.requestFocusInWindow();
          }
          else if (!handleCertificateException)
          {
            Utilities.getParentDialog(LoginPanel.this).setVisible(false);
          }
        }
      };
      worker.startBackgroundTask();
    }
    else
    {
      displayErrorDialog(errors);
      if (dnInvalid)
      {
        dn.setSelectionStart(0);
        dn.setSelectionEnd(dn.getText().length());
        dn.requestFocusInWindow();
      }
      if (pwdInvalid)
      {
        pwd.setSelectionStart(0);
        pwd.setSelectionEnd(pwd.getPassword().length);
        pwd.requestFocusInWindow();
      }

    }
  }

  /** {@inheritDoc} */
  @Override
  public void cancelClicked()
  {
    setPrimaryValid(dnLabel);
    setPrimaryValid(pwdLabel);
    pwd.setText(null);
    super.cancelClicked();
  }

  /**
   * Displays a dialog asking the user to accept a certificate if the user
   * accepts it, we update the trust manager and simulate a click on "OK" to
   * re-check the authentication.
   * This method assumes that we are being called from the event thread.
   */
  private void handleCertificateException(UserDataCertificateException ce)
  {
    CertificateDialog dlg = new CertificateDialog(null, ce);
    dlg.pack();
    Utilities.centerGoldenMean(dlg, Utilities.getParentDialog(this));
    dlg.setVisible(true);
    if (dlg.getUserAnswer() !=
      CertificateDialog.ReturnType.NOT_ACCEPTED)
    {
      X509Certificate[] chain = ce.getChain();
      String authType = ce.getAuthType();
      String host = ce.getHost();

      if (chain != null && authType != null && host != null)
      {
        logger.info(LocalizableMessage.raw("Accepting certificate presented by host "+host));
        getInfo().getTrustManager().acceptCertificate(chain, authType, host);
        /* Simulate a click on the OK by calling in the okClicked method. */
        SwingUtilities.invokeLater(new Runnable()
        {
          @Override
          public void run()
          {
            okClicked();
          }
        });
      }
      else
      {
        if (chain == null)
        {
          logger.warn(LocalizableMessage.raw(
              "The chain is null for the UserDataCertificateException"));
        }
        if (authType == null)
        {
          logger.warn(LocalizableMessage.raw(
              "The auth type is null for the UserDataCertificateException"));
        }
        if (host == null)
        {
          logger.warn(LocalizableMessage.raw(
              "The host is null for the UserDataCertificateException"));
        }
      }
    }
    if (dlg.getUserAnswer() ==
      CertificateDialog.ReturnType.ACCEPTED_PERMANENTLY)
    {
      X509Certificate[] chain = ce.getChain();
      if (chain != null)
      {
        try
        {
          UIKeyStore.acceptCertificate(chain);
        }
        catch (Throwable t)
        {
          logger.warn(LocalizableMessage.raw("Error accepting certificate: "+t, t));
        }
      }
    }
  }
}
