/**
   * @param wizardModel The overall wizard data model containing the aggregate information of all
   *     components in the wizard
   * @param isExiting True if the exit button should trigger an application shutdown
   * @param wizardParameter An optional parameter that can be referenced during construction
   * @param escapeIsCancel If true, ESC cancels the wizard, if false, it does nothing
   */
  protected AbstractWizard(
      M wizardModel, boolean isExiting, Optional wizardParameter, boolean escapeIsCancel) {

    Preconditions.checkNotNull(wizardModel, "'model' must be present");

    log.debug("Building wizard...");

    this.wizardModel = wizardModel;
    this.exiting = isExiting;
    this.wizardParameter = wizardParameter;

    // Subscribe to events
    ViewEvents.subscribe(this);
    CoreEvents.subscribe(this);

    // Optionally bind the ESC key to a Cancel event (escape to safety)
    if (escapeIsCancel) {
      wizardScreenHolder
          .getInputMap(JPanel.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
          .put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "quit");
      wizardScreenHolder.getActionMap().put("quit", getCancelAction());
    }

    // TODO Bind the ENTER key to a Next/Finish/Apply event to speed up data entry through keyboard
    // wizardPanel.getInputMap(JPanel.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "next");
    // wizardPanel.getActionMap().put("next", getNextAction(null));

    log.debug("Populating view map and firing initial state view events...");

    // Populate based on the current locale
    populateWizardViewMap(wizardViewMap);

    // Once all the views are created allow events to occur
    for (Map.Entry<String, AbstractWizardPanelView> entry : wizardViewMap.entrySet()) {

      // Ensure the panel is in the correct starting state
      entry.getValue().fireInitialStateViewEvents();
    }

    wizardScreenHolder.setMinimumSize(
        new Dimension(MultiBitUI.WIZARD_MIN_WIDTH, MultiBitUI.WIZARD_MIN_HEIGHT));
    wizardScreenHolder.setPreferredSize(
        new Dimension(MultiBitUI.WIZARD_MIN_WIDTH, MultiBitUI.WIZARD_MIN_HEIGHT));
    wizardScreenHolder.setSize(
        new Dimension(MultiBitUI.WIZARD_MIN_WIDTH, MultiBitUI.WIZARD_MIN_HEIGHT));

    // Show the panel specified by the initial state
    show(wizardModel.getPanelName());
  }
  /**
   * Hide the wizard
   *
   * <p>This method is guaranteed to run on the EDT
   *
   * @param panelName The panel name
   * @param isExitCancel True if this hide operation comes from an exit or cancel
   * @param wizardPanelView The wizard panel view from the wizard view map
   */
  protected void handleHide(
      final String panelName, final boolean isExitCancel, AbstractWizardPanelView wizardPanelView) {

    log.debug("Handle hide starting: '{}' ExitCancel: {}", panelName, isExitCancel);

    // De-register
    wizardPanelView.deregisterDefaultButton();

    // Ensure we unsubscribe the wizard from all further events
    getWizardModel().unsubscribe();
    unsubscribe();

    // Issue the wizard hide event before the hide takes place to give panel views time to update
    ViewEvents.fireWizardHideEvent(panelName, wizardModel, isExitCancel);

    // Required to run on a new thread since this may take some time to complete
    wizardHideExecutorService.submit(
        new Runnable() {
          @Override
          public void run() {

            log.debug("Hide and deregister wizard: '{}'", this.getClass().getSimpleName());

            // Require some extra time to get the rest of the UI started for credentials wizard
            // There is no chance of the system showing a light box during this time so this
            // operation is safe
            if (CredentialsState.CREDENTIALS_ENTER_PASSWORD.name().equals(panelName)) {
              log.trace("Blocking to allow UI startup to complete");
              Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
            }

            // Work through the view map ensuring all components are deregistered from UI events
            log.trace("Deregister {} views and their component(s)", wizardViewMap.size());
            for (Map.Entry<String, AbstractWizardPanelView> entry : wizardViewMap.entrySet()) {

              AbstractWizardPanelView panelView = entry.getValue();

              // Ensure we deregister the wizard panel view (and model if present) for events
              try {

                // Unsubscribe from events
                panelView.unsubscribe();
                log.trace(
                    "Deregistered wizard panel view '{}' from UI events", panelView.getPanelName());

                if (panelView.getPanelModel().isPresent()) {
                  Object panelModel = panelView.getPanelModel().get();
                  // May get some false positives from this approach
                  CoreEvents.unsubscribe(panelModel);
                  log.trace(
                      "Deregistered wizard panel model '{}' from UI events",
                      panelView.getPanelName());
                }

              } catch (NullPointerException | IllegalArgumentException e) {
                log.warn(
                    "Wizard panel model/view '{}' was not registered", panelView.getPanelName(), e);
              }

              // Deregister all components
              @SuppressWarnings("unchecked")
              List<ModelAndView> mavs = panelView.getComponents();
              for (ModelAndView mav : mavs) {
                mav.unsubscribe();
              }
              log.trace(
                  "Closed {} registered component(s) from wizard panel view '{}'",
                  mavs.size(),
                  panelView.getPanelName());

              // Remove the references
              mavs.clear();
            }

            // Depopulate the map to ensure non-AWT references are removed
            wizardViewMap.clear();

            // Hiding the light box must be on the EDT
            SwingUtilities.invokeLater(
                new Runnable() {
                  @Override
                  public void run() {

                    log.trace("Handle hide remove light box: '{}'", panelName);

                    // This removes the reference to the wizard allowing for garbage collection
                    Panels.hideLightBoxIfPresent();

                    // Clear the deferred hide
                    Panels.setDeferredHideEventInProgress(false);
                  }
                });
          }
        });
  }
 /** This wizard is about to close */
 public void unsubscribe() {
   ViewEvents.unsubscribe(this);
   CoreEvents.unsubscribe(this);
   // Further events are handled by subclasses (e.g. HardwareWallet)
 }
  /**
   * Update the view with any required view events to create a clean initial state (all
   * initialisation will have completed)
   *
   * <p>Default implementation is to disable the "next" button
   */
  public void fireInitialStateViewEvents() {

    // Default is to disable the Next button
    ViewEvents.fireWizardButtonEnabledEvent(getPanelName(), WizardButton.NEXT, false);
  }
  @Subscribe
  public void onVerificationStatusChangedEvent(VerificationStatusChangedEvent event) {

    ViewEvents.fireWizardButtonEnabledEvent(event.getPanelName(), WizardButton.NEXT, event.isOK());
  }
  /** Handle the process of restoring a wallet */
  private void handleRestoreWallet() {

    WelcomeWizardModel model = getWizardModel();

    log.debug("The select wallet choice is {}", model.getSelectWalletChoice());
    log.debug("The restore method is {}", model.getRestoreMethod());

    // There are two sorts of restore wallet method:
    // RESTORE_WALLET_SEED_PHRASE = restore from a seed phrase and timestamp (MBHD soft wallet or
    // Trezor soft wallet)
    // RESTORE_WALLET_BACKUP = restore from a seed phrase and wallet backup

    final boolean backupLocationStatus = handleBackupLocation();

    SwingUtilities.invokeLater(
        new Runnable() {
          @Override
          public void run() {

            LabelDecorator.applyStatusLabel(
                backupLocationStatusLabel, Optional.of(backupLocationStatus));
            backupLocationStatusLabel.setVisible(true);

            // Hide the header view (switching back on is done in
            // MainController#onBitcoinNetworkChangedEvent
            ViewEvents.fireViewChangedEvent(ViewKey.HEADER, false);
          }
        });

    // Give the user the impression of work being done
    Uninterruptibles.sleepUninterruptibly(250, TimeUnit.MILLISECONDS);

    final boolean walletCreatedStatus = handleCreateWalletStatus(model);
    log.debug("Wallet created status: {}", walletCreatedStatus);

    // Update created wallet status
    SwingUtilities.invokeLater(
        new Runnable() {
          @Override
          public void run() {
            LabelDecorator.applyStatusLabel(
                walletCreatedStatusLabel, Optional.of(walletCreatedStatus));
            walletCreatedStatusLabel.setVisible(true);
          }
        });

    // Give the user the impression of work being done
    Uninterruptibles.sleepUninterruptibly(250, TimeUnit.MILLISECONDS);

    final boolean caCertificatesStatus = handleCACertificateStatus();

    // Update the UI
    SwingUtilities.invokeLater(
        new Runnable() {
          @Override
          public void run() {

            LabelDecorator.applyStatusLabel(
                caCertificateStatusLabel, Optional.of(caCertificatesStatus));
            caCertificateStatusLabel.setVisible(true);
          }
        });

    // Give the user the impression of work being done
    Uninterruptibles.sleepUninterruptibly(250, TimeUnit.MILLISECONDS);

    // Allow the Finish button at this point since the Bitcoin network may fail and the user will be
    // trapped
    ViewEvents.fireWizardButtonEnabledEvent(
        WelcomeWizardState.RESTORE_WALLET_REPORT.name(), WizardButton.FINISH, true);

    final boolean walletSynchronizationStatus = handleSynchronizationStatus();

    // Update the UI
    SwingUtilities.invokeLater(
        new Runnable() {
          @Override
          public void run() {

            LabelDecorator.applyStatusLabel(
                synchronizationStatusLabel, Optional.of(walletSynchronizationStatus));
            synchronizationStatusLabel.setVisible(true);
          }
        });
  }
  @Override
  public void fireInitialStateViewEvents() {

    // Enable the finish button
    ViewEvents.fireWizardButtonEnabledEvent(getPanelName(), WizardButton.FINISH, false);
  }