/*
 * 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 2014-2015 ForgeRock AS
 */

package org.opends.guitools.controlpanel.ui;

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

import java.awt.GridBagConstraints;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.opends.guitools.controlpanel.datamodel.BackendDescriptor;
import org.opends.guitools.controlpanel.datamodel.BackupDescriptor;
import org.opends.guitools.controlpanel.datamodel.ControlPanelInfo;
import org.opends.guitools.controlpanel.datamodel.ServerDescriptor;
import org.opends.guitools.controlpanel.event.BackupCreatedEvent;
import org.opends.guitools.controlpanel.event.BackupCreatedListener;
import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent;
import org.opends.guitools.controlpanel.task.Task;
import org.opends.guitools.controlpanel.util.Utilities;
import org.forgerock.i18n.LocalizableMessage;
import org.opends.server.tools.RestoreDB;

/**
 * The panel that appears when the user wants to restore from a backup.
 *
 */
public class RestorePanel extends BackupListPanel
implements BackupCreatedListener
{
  private static final long serialVersionUID = -205585323128518051L;
  private ListSelectionListener listener;
  private JLabel lBackupID;
  private JTextField backupID;

  /**
   * Constructor of the panel.
   *
   */
  public RestorePanel()
  {
    super();
    createLayout();
  }

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

  /** {@inheritDoc} */
  public void backupCreated(BackupCreatedEvent ev)
  {
    boolean refreshList = false;
    File f = new File(parentDirectory.getText());
    File fBackup = ev.getBackupDescriptor().getPath();
    if (fBackup.equals(f))
    {
      refreshList = true;
    }
    else
    {
      f = f.getParentFile();
      if (f != null)
      {
        refreshList = fBackup.equals(f);
      }
    }
    if (refreshList && isVisible())
    {
      // If not visible the list will be refreshed next time the dialog is
      // opened.
      SwingUtilities.invokeLater(new Runnable()
      {
        public void run()
        {
          refreshList();
        }
      });
    }
  }

  /** {@inheritDoc} */
  public void setInfo(ControlPanelInfo info)
  {
    super.setInfo(info);
    info.addBackupCreatedListener(this);
  }

  /** {@inheritDoc} */
  public void toBeDisplayed(boolean visible)
  {
    if (visible)
    {
      listener.valueChanged(null);
    }
  }

  /** {@inheritDoc} */
  public void configurationChanged(ConfigurationChangeEvent ev)
  {
    final ServerDescriptor desc = ev.getNewDescriptor();
    SwingUtilities.invokeLater(new Runnable()
    {
      /** {@inheritDoc} */
      public void run()
      {
        lBackupID.setVisible(!desc.isLocal());
        backupID.setVisible(!desc.isLocal());
      }
    });
    super.configurationChanged(ev);
    updateErrorPaneAndOKButtonIfAuthRequired(desc,
        isLocal() ? INFO_CTRL_PANEL_AUTHENTICATION_REQUIRED_FOR_RESTORE.get() :
      INFO_CTRL_PANEL_CANNOT_CONNECT_TO_REMOTE_DETAILS.get(desc.getHostname()));
  }

  /** {@inheritDoc} */
  protected void verifyBackupClicked()
  {
    LinkedHashSet<LocalizableMessage> errors = new LinkedHashSet<>();
    // Launch the task in another progress dialog.
    ProgressDialog dlg = new ProgressDialog(
        Utilities.createFrame(),
        Utilities.getParentDialog(this),
        INFO_CTRL_PANEL_VERIFY_BACKUP_TITLE.get(), getInfo());
    RestoreTask newTask = new RestoreTask(getInfo(), dlg, true);
    for (Task task : getInfo().getTasks())
    {
      task.canLaunch(newTask, errors);
    }
    if (errors.isEmpty())
    {
      BackupDescriptor backup = getSelectedBackup();
      launchOperation(newTask,
          INFO_CTRL_PANEL_VERIFYING_BACKUP_SUMMARY.get(backup.getID()),
          INFO_CTRL_PANEL_VERIFYING_BACKUP_SUCCESSFUL_SUMMARY.get(),
          INFO_CTRL_PANEL_VERIFYING_BACKUP_SUCCESSFUL_DETAILS.get(),
          ERR_CTRL_PANEL_VERIFYING_BACKUP_ERROR_SUMMARY.get(),
          null,
          ERR_CTRL_PANEL_VERIFYING_BACKUP_ERROR_DETAILS,
          dlg);
      dlg.setVisible(true);
    }
    else
    {
      displayErrorDialog(errors);
    }
  }

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

    gbc.gridwidth = 3;
    addErrorPane(gbc);

    super.createLayout(gbc);

    gbc.insets.top = 10;
    gbc.gridx = 0;
    gbc.gridy ++;
    gbc.insets.left = 0;
    gbc.gridwidth = 1;
    lBackupID = Utilities.createPrimaryLabel(
        INFO_CTRL_PANEL_BACKUP_ID_LABEL.get());
    add(lBackupID, gbc);
    backupID = Utilities.createMediumTextField();
    gbc.weightx = 0.0;
    gbc.gridx = 1;
    gbc.insets.left = 10;
    gbc.insets.right = 40;
    gbc.fill = GridBagConstraints.HORIZONTAL;
    gbc.anchor = GridBagConstraints.WEST;
    gbc.gridwidth = 2;
    add(backupID, gbc);

    listener = new ListSelectionListener()
    {
      public void valueChanged(ListSelectionEvent ev)
      {
        BackupDescriptor backup = getSelectedBackup();
        setEnabledOK(backup != null && !errorPane.isVisible());
      }
    };
    backupList.getSelectionModel().addListSelectionListener(listener);

    addBottomGlue(gbc);
  }

  /** {@inheritDoc} */
  protected void checkOKButtonEnable()
  {
    listener.valueChanged(null);
  }

  /** {@inheritDoc} */
  public void okClicked()
  {
    setPrimaryValid(lPath);
    setPrimaryValid(lAvailableBackups);
    setPrimaryValid(lBackupID);

    final LinkedHashSet<LocalizableMessage> errors = new LinkedHashSet<>();

    BackupDescriptor backup = getSelectedBackup();

    if (isLocal())
    {
      boolean selected = backupList.isVisible() && backup != null;
      if (!selected)
      {
        if (backupList.getRowCount() == 0)
        {
          setPrimaryInvalid(lPath);
          errors.add(ERR_CTRL_PANEL_NO_PARENT_BACKUP_TO_VERIFY.get());
        }
        else
        {
          errors.add(ERR_CTRL_PANEL_REQUIRED_BACKUP_TO_VERIFY.get());
        }
        setPrimaryInvalid(lAvailableBackups);
      }
    }
    else
    {
      String parentPath = parentDirectory.getText();
      if (parentPath == null || parentPath.trim().equals(""))
      {
        errors.add(ERR_CTRL_PANEL_NO_BACKUP_PATH_PROVIDED.get());
        setPrimaryInvalid(lPath);
      }

      String id = backupID.getText();
      if (id == null || id.trim().equals(""))
      {
        errors.add(ERR_CTRL_PANEL_NO_BACKUP_ID_PROVIDED.get());
        setPrimaryInvalid(lBackupID);
      }
    }

    if (errors.isEmpty())
    {
      ProgressDialog progressDialog = new ProgressDialog(
          Utilities.createFrame(), Utilities.getParentDialog(this), getTitle(),
          getInfo());
      RestoreTask newTask = new RestoreTask(getInfo(), progressDialog, false);
      for (Task task : getInfo().getTasks())
      {
        task.canLaunch(newTask, errors);
      }
//    Ask for confirmation
      boolean confirmed = true;
      if (errors.isEmpty())
      {
        confirmed = displayConfirmationDialog(
            INFO_CTRL_PANEL_CONFIRMATION_REQUIRED_SUMMARY.get(),
            INFO_CTRL_PANEL_CONFIRM_RESTORE_DETAILS.get());
      }

      if (errors.isEmpty() && confirmed)
      {
        launchOperation(newTask,
            INFO_CTRL_PANEL_RESTORING_SUMMARY.get(backup.getID()),
            INFO_CTRL_PANEL_RESTORING_SUCCESSFUL_SUMMARY.get(),
            INFO_CTRL_PANEL_RESTORING_SUCCESSFUL_DETAILS.get(),
            ERR_CTRL_PANEL_RESTORING_ERROR_SUMMARY.get(),
            null,
            ERR_CTRL_PANEL_RESTORING_ERROR_DETAILS,
            progressDialog);
        progressDialog.setVisible(true);
        Utilities.getParentDialog(this).setVisible(false);
      }
    }
    if (!errors.isEmpty())
    {
      displayErrorDialog(errors);
    }
  }

  /** {@inheritDoc} */
  public void cancelClicked()
  {
    setPrimaryValid(lPath);
    setPrimaryValid(lAvailableBackups);

    super.cancelClicked();
  }

  /** The task in charge of restoring or verifying the backup. */
  protected class RestoreTask extends Task
  {
    private Set<String> backendSet;
    private String dir;
    private String backupID;
    private boolean verify;

    /**
     * The constructor of the task.
     * @param info the control panel info.
     * @param dlg the progress dialog that shows the progress of the task.
     * @param verify whether this is an actual restore or a verify of the
     * backup.
     */
    public RestoreTask(ControlPanelInfo info, ProgressDialog dlg,
        boolean verify)
    {
      super(info, dlg);
      this.verify = verify;
      if (isLocal())
      {
        BackupDescriptor backup = getSelectedBackup();
        dir = backup.getPath().getAbsolutePath();
        backupID = backup.getID();
      }
      else
      {
        dir = parentDirectory.getText();
        backupID = RestorePanel.this.backupID.getText();
      }
      backendSet = new HashSet<>();
      for (BackendDescriptor backend : info.getServerDescriptor().getBackends())
      {
        if (!backend.isConfigBackend())
        {
          backendSet.add(backend.getBackendID());
        }
      }
    }

    /** {@inheritDoc} */
    public Type getType()
    {
      return Type.RESTORE;
    }

    /** {@inheritDoc} */
    public LocalizableMessage getTaskDescription()
    {
      if (verify)
      {
        return INFO_CTRL_PANEL_VERIFY_TASK_DESCRIPTION.get(backupID, dir);
      }
      else
      {
        return INFO_CTRL_PANEL_RESTORE_TASK_DESCRIPTION.get(backupID, dir);
      }
    }

    /** {@inheritDoc} */
    public boolean canLaunch(Task taskToBeLaunched,
        Collection<LocalizableMessage> incompatibilityReasons)
    {
      boolean canLaunch = true;
      if (state == State.RUNNING && runningOnSameServer(taskToBeLaunched))
      {
        // All the operations are incompatible if they apply to this backend.
        Set<String> backends = new TreeSet<>(taskToBeLaunched.getBackends());
        backends.retainAll(getBackends());
        if (!backends.isEmpty())
        {
          incompatibilityReasons.add(getIncompatibilityMessage(this, taskToBeLaunched));
          canLaunch = false;
        }
      }
      return canLaunch;
    }

    /** {@inheritDoc} */
    public void runTask()
    {
      state = State.RUNNING;
      lastException = null;
      try
      {
        ArrayList<String> arguments = getCommandLineArguments();

        String[] args = new String[arguments.size()];

        arguments.toArray(args);
        if (isServerRunning())
        {
          returnCode = RestoreDB.mainRestoreDB(args, false, outPrintStream,
              errorPrintStream);
        }
        else
        {
          returnCode = executeCommandLine(getCommandLinePath(), args);
        }
        if (returnCode != 0)
        {
          state = State.FINISHED_WITH_ERROR;
        }
        else
        {
          if (!verify)
          {
            for (String backend : getBackends())
            {
              getInfo().unregisterModifiedIndexesInBackend(backend);
            }
          }
          state = State.FINISHED_SUCCESSFULLY;
        }
      }
      catch (Throwable t)
      {
        lastException = t;
        state = State.FINISHED_WITH_ERROR;
      }
      HashSet<BackendDescriptor> backends = new HashSet<>();
      for (BackendDescriptor backend :
        getInfo().getServerDescriptor().getBackends())
      {
        for (String backendID : getBackends())
        {
          if (backendID.equalsIgnoreCase(backend.getBackendID()))
          {
            backends.add(backend);
            break;
          }
        }
      }
      if (!backends.isEmpty())
      {
        getInfo().backendPopulated(backends);
      }
    }

    /** {@inheritDoc} */
    public Set<String> getBackends()
    {
      return backendSet;
    }

    /** {@inheritDoc} */
    protected ArrayList<String> getCommandLineArguments()
    {
      ArrayList<String> args = new ArrayList<>();

      args.add("--backupDirectory");
      args.add(dir);

      args.add("--backupID");
      args.add(backupID);

      if (verify)
      {
        args.add("--dry-run");
      }

      args.addAll(getConnectionCommandLineArguments());

      if (isServerRunning())
      {
        args.addAll(getConfigCommandLineArguments());
      }
      args.add(getNoPropertiesFileArgument());

      return args;
    }

    /** {@inheritDoc} */
    protected String getCommandLinePath()
    {
      return getCommandLinePath("restore");
    }
  }
}
