// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.debug.ui;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.chromium.debug.ui.DialogUtils.Message;
import org.chromium.debug.ui.DialogUtils.Optional;
import org.chromium.debug.ui.DialogUtils.ScopeEnabler;
import org.chromium.debug.ui.DialogUtils.Updater;
import org.chromium.debug.ui.DialogUtils.ValueConsumer;
import org.chromium.debug.ui.DialogUtils.ValueSource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.wizard.IWizard;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;

/**
 * A set of utils for creating Eclipse Wizard that is compatible with {@link Updater}.
 */
public class WizardUtils {

  /**
   * A simple implementation of the wizard page that stores mutable pointers
   * to its next/previous pages and a pointer to page elements.
   * @param <PE> type of page elements object that keeps references to all page UI controls
   */
  public static class PageImpl<PE extends PageElements> extends WizardPage {
    private PE pageElements = null;
    private final PageElementsFactory<PE> factory;
    private PageImpl<?> previousPage = null;
    private PageImpl<?> nextPage = null;
    private List<PageListener> listeners = new ArrayList<PageListener>(0);

    public PageImpl(String pageName, PageElementsFactory<PE> factory, String title,
        String description) {
      super(pageName, title, null);
      this.factory = factory;
      setDescription(description);
    }

    public void createControl(Composite parent) {
      pageElements = factory.create(parent);
      setControl(pageElements.getMainControl());
    }

    public IWizardPage getPreviousPageImpl() {
      return previousPage;
    }

    public IWizardPage getNextPageImpl() {
      return nextPage;
    }

    @Override
    public void setVisible(boolean visible) {
      for (PageListener listener : listeners) {
        listener.onSetVisible(visible);
      }
      super.setVisible(visible);
    }

    public void addListener(PageListener listener) {
      listeners.add(listener);
    }

    public void removeListener(PageListener listener) {
      listeners.remove(listener);
    }

    public PE getPageElements() {
      return pageElements;
    }

    public void linkToNextPage(PageImpl<?> next) {
      if (next != null) {
        next.previousPage = this;
      }
      this.nextPage = next;

      LogicBasedWizard wizardImpl = (LogicBasedWizard) getWizard();
      wizardImpl.updateButtons();
    }

    public void unlinkFromNextPage(PageImpl<?> next) {
      if (next != null) {
        if (this.nextPage == next) {
          next.previousPage = null;
          this.nextPage = null;
        }
      }

      LogicBasedWizard wizardImpl = (LogicBasedWizard) getWizard();
      wizardImpl.updateButtons();
    }

    @Override
    public String getMessage() {
      LogicBasedWizard wizardImpl = (LogicBasedWizard) getWizard();
      return wizardImpl.getLogicMessage().getText();
    }

    @Override
    public int getMessageType() {
      LogicBasedWizard wizardImpl = (LogicBasedWizard) getWizard();
      return wizardImpl.getLogicMessage().getPriority().getMessageProviderType();
    }
  }

  /**
   * A base interface for page set object. Its sub-types should have getters to each page of
   * the wizard.
   */
  public interface WizardPageSet {
    /**
     * @return all pages that are accessible from this page set
     */
    List<? extends PageImpl<?>> getAllPages();

    WizardLogic createLogic(LogicBasedWizard wizardImpl);
  }

  /**
   * A base interface for page set object. Its sub-types should have getters to page-specific
   * elements.
   */
  public interface PageElements {
    Control getMainControl();
  }

  public interface PageElementsFactory<T extends PageElements> {
    T create(Composite parent);
  }

  /**
   * An accessor to a wizard logic system. Controls its basic life-cycle.
   */
  public interface WizardLogic {
    void updateAll();
    PageImpl<?> getStartingPage();
    void dispose();
  }

  public interface PageListener {
    void onSetVisible(boolean visible);
  }

  /**
   * A call-back that implements wizard 'finish' button action.
   */
  public interface WizardFinisher {
    boolean performFinish(IWizard wizard, IProgressMonitor monitor);
  }

  /**
   * A simple implementation of Eclipse {@link IWizard} that works with {@link WizardPageSet} and
   * {@link WizardLogic}. The logic is responsible for updating a {@link WizardFinisher} value
   * as user input changes.
   */
  public static class LogicBasedWizard extends Wizard {
    private final WizardPageSet pageSet;
    private WizardFinisher wizardFinisher = null;
    private UpdateButtonsState updateButtonsState = UpdateButtonsState.NOT_READY;
    private Message currentLogicMessage = DialogUtils.NULL_MESSAGE;
    private WizardLogic logic;
    private PageImpl<?> startingPage = null;

    private enum UpdateButtonsState {
      /**
       * Do not try to update buttons.
       */
      NOT_READY,

      /**
       * If you need to update buttons now, probably it's already available.
       */
      SHOULD_BE_READY
    }

    public LogicBasedWizard(WizardPageSet pageSet) {
      this.pageSet = pageSet;
      for (PageImpl<?> page : pageSet.getAllPages()) {
        addPage(page);
      }
    }

    Message getLogicMessage() {
      return currentLogicMessage;
    }

    @Override
    public void createPageControls(Composite pageContainer) {
      super.createPageControls(pageContainer);
      this.logic = pageSet.createLogic(this);
      this.startingPage = this.logic.getStartingPage();
      logic.updateAll();
      updateButtonsState = UpdateButtonsState.SHOULD_BE_READY;
    }

    @Override
    public boolean performFinish() {
      if (wizardFinisher == null) {
        return false;
      }
      final boolean[] result = { false };
      IRunnableWithProgress runnable = new IRunnableWithProgress() {
        public void run(IProgressMonitor monitor) throws InvocationTargetException,
            InterruptedException {
          result[0] = wizardFinisher.performFinish(LogicBasedWizard.this, monitor);
        }
      };
      try {
        getContainer().run(false, true, runnable);
        return result[0];
      } catch (InterruptedException e) {
        return false;
      } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
      }
    }

    @Override
    public boolean canFinish() {
      return wizardFinisher != null;
    }

    @Override
    public IWizardPage getNextPage(IWizardPage page) {
      PageImpl<?> pageImpl = (PageImpl<?>) page;
      return pageImpl.getNextPageImpl();
    }

    @Override
    public IWizardPage getPreviousPage(IWizardPage page) {
      PageImpl<?> pageImpl = (PageImpl<?>) page;
      return pageImpl.getPreviousPageImpl();
    }

    @Override
    public IWizardPage getStartingPage() {
      return startingPage;
    }

    public void setWizardFinisher(WizardFinisher wizardFinisher, Message message) {
      this.wizardFinisher = wizardFinisher;
      this.currentLogicMessage = message;
      updateButtons();
    }

    void updateButtons() {
      if (updateButtonsState == UpdateButtonsState.SHOULD_BE_READY) {
        getContainer().updateButtons();
        getContainer().updateMessage();
      }
    }

    @Override
    public void dispose() {
      if (logic != null) {
        logic.dispose();
      }
      super.dispose();
    }
  }

  /**
   * A utility class that ties {@link Updater} together with {@link LogicBasedWizard}.
   * It is fed with {@link WizardFinisher} value and optional warning value.
   * It controls messages and 'finish' button of the wizard.
   */
  public static class WizardFinishController implements ValueConsumer {
    private final ValueSource<? extends Optional<? extends WizardFinisher>> finisherValue;
    private final ValueSource<? extends Optional<?>> warningValue;
    private final LogicBasedWizard wizardImpl;

    public WizardFinishController(
        ValueSource<? extends Optional<? extends WizardFinisher>> finisherValue,
        ValueSource<? extends Optional<? extends Void>> warningValue,
        LogicBasedWizard wizardImpl) {
      this.finisherValue = finisherValue;
      this.warningValue = warningValue;
      this.wizardImpl = wizardImpl;
    }

    public void update(Updater updater) {
      WizardFinisher finisher;
      Set<Message> messages = new HashSet<Message>();
      if (warningValue != null) {
        Optional<?> warnings = warningValue.getValue();
        if (!warnings.isNormal()) {
          messages.addAll(warnings.errorMessages());
        }
      }
      if (finisherValue.getValue().isNormal()) {
        finisher = finisherValue.getValue().getNormal();
      } else {
        finisher = null;
        messages.addAll(finisherValue.getValue().errorMessages());
      }
      Message message = DialogUtils.chooseImportantMessage(messages);
      wizardImpl.setWizardFinisher(finisher, message);
    }
  }

  /**
   * A utility class that ties {@link Updater} together with {@link LogicBasedWizard}.
   * It is plugged into {@link Updater} as a {@link ScopeEnabler} and controls the
   * link to next page reference accordingly.
   */
  public static class NextPageEnabler implements ScopeEnabler {
    private final PageImpl<?> basePage;
    private final PageImpl<?> nextPage;
    public NextPageEnabler(PageImpl<?> basePage, PageImpl<?> nextPage) {
      this.basePage = basePage;
      this.nextPage = nextPage;
    }

    public void setEnabled(boolean enabled, boolean recursive) {
      if (enabled) {
        basePage.linkToNextPage(nextPage);
      } else {
        basePage.unlinkFromNextPage(nextPage);
      }
    }
  }
}
