/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.uima.pear.generate;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.apache.uima.pear.PearPlugin;
import org.apache.uima.pear.insd.edit.InsdConstants;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.dialogs.ContainerCheckedTreeViewer;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;
import org.eclipse.ui.model.WorkbenchViewerSorter;

/**
 * Wizard page for exporting resource to a PEAR file.
 */
public class PearFileResourceExportPage extends WizardPage implements InsdConstants {

  /** Preference store key for the last pear file export location. */
  protected static final String PEAR_FILE = "pear_file"; //$NON-NLS-1$

  /** Plugins preference store. */
  protected final IPreferenceStore fPreferenceStore;

  /** Folder or Project that contains the Pear structure. */
  protected final IContainer fCurrentContainer;

  /** Initial selection for the TreeViewer. */
  protected final IStructuredSelection fSelection;

  /** TreeViewer that shows resources to export. */
  protected ContainerCheckedTreeViewer fTreeViewer;

  /** Text input for the. */
  protected Text fDestinationFileInput;

  /** Checkbox for choosing whether pear file should be compressed. */
  protected Button fCompressCheckbox;

  /**
   * constructor.
   *
   * @param selection
   *          Initial selection for the export TreeViewer
   * @param currentContainer
   *          Container (project or folder) with Pear structure
   */
  public PearFileResourceExportPage(final IStructuredSelection selection,
          final IContainer currentContainer) {
    super("pearFileResourceExportPage"); //$NON-NLS-1$
    final PearPlugin plugin = PearPlugin.getDefault();
    fPreferenceStore = plugin.getPreferenceStore();
    setTitle(PearExportMessages.getString("PearExport.exportTitle")); //$NON-NLS-1$
    setDescription(PearExportMessages.getString("PearExport.description")); //$NON-NLS-1$
    fCurrentContainer = currentContainer;
    fSelection = selection;
  }

  /**
   * Update buttons and messages.
   */
  protected void pageStateChanged() {
    // check whether the metadata folder is included for export
    if (!isMetadataIncluded()) {
      setErrorMessage(PearExportMessages.getString("PearExport.metadataRequired")); //$NON-NLS-1$
      setPageComplete(false);
      return;
    }

    // check whether a directory is selected to export to
    final String filename = getDestinationValue();
    if (new File(filename).isDirectory()) {
      setErrorMessage(PearExportMessages.getString("PearFileResourceExportPage.DirectorySelected")); //$NON-NLS-1$
      setPageComplete(false);
      return;
    }

    // check whether a file is selected to export to
    if (filename == null || filename.trim().equals("")) { //$NON-NLS-1$
      setErrorMessage(null);
      setMessage(PearExportMessages.getString("PearFileResourceExportPage.SelectFile"), //$NON-NLS-1$
              IMessageProvider.INFORMATION);
      setPageComplete(false);
      return;
    }

    // If the file to export to exists, display an info stating that
    if (new File(filename).exists()) {
      setErrorMessage(null);
      setMessage(PearExportMessages.format(
              PearExportMessages.getString("PearFileResourceExportPage.FileExistsInfo"), //$NON-NLS-1$
              new Object[] { filename }), IMessageProvider.INFORMATION);
      setPageComplete(true);
      return;
    }

    // Otherwise, remove all messages
    setMessage(null);
    setErrorMessage(null);
    setPageComplete(true);
  }

  /**
   * Checks if is metadata included.
   *
   * @return <code>true</code> if all files in the metadata folder are selected for export or the
   *         members cannot be determined, <code>false</code> otherwise
   */
  protected boolean isMetadataIncluded() {
    final Object[] grayed = fTreeViewer.getGrayedElements();
    if (isMetadataIncluded(grayed)) {
      return false;
    }

    final Object[] checked = fTreeViewer.getCheckedElements();
    return isMetadataIncluded(checked);
  }

  /**
   * Checks if is metadata included.
   *
   * @param checked
   *          the checked
   * @return <code>true</code> if all files in the metadata folder are selected for export or the
   *         members cannot be determined, <code>false</code> otherwise
   */
  private boolean isMetadataIncluded(final Object[] checked) {
    for (int i = 0; i < checked.length; ++i) {
      if (checked[i] instanceof IAdaptable) {
        final IAdaptable adaptable = (IAdaptable) checked[i];
        final IResource resource = (IResource) adaptable.getAdapter(IResource.class);
        if (resource != null && "metadata".equals(resource.getName())) { //$NON-NLS-1$
          // ContainerCheckedTreeViewer reports it as checked if _any_
          // element is checked
          return true;
        }
      }
    }

    return false;
  }

  /**
   * Opens a file selection dialog to select a pear file as export location and sets the chosen
   * value to the input field.
   */
  protected void handleDestinationBrowseButtonPressed() {
    final FileDialog dialog = new FileDialog(getContainer().getShell(), SWT.SAVE);
    dialog.setFilterExtensions(new String[] { "*.pear", "*.*" }); //$NON-NLS-1$ //$NON-NLS-2$
    dialog.setText(PearExportMessages.getString("PearExport.selectDestinationTitle")); //$NON-NLS-1$
    final String destination = getDestinationValue();
    final int lastDirectoryIndex = destination.lastIndexOf(File.separator);
    if (lastDirectoryIndex != -1) {
      dialog.setFilterPath(destination.substring(0, lastDirectoryIndex));
    }

    final String selectedFileName = dialog.open();
    if (selectedFileName != null) {
      fDestinationFileInput.setText(selectedFileName);
      saveDestinationValue(selectedFileName);
    }
  }

  /**
   * Stores the Pear filename in the preference store.
   *
   * @param filename
   *          the filename
   */
  protected void saveDestinationValue(final String filename) {
    fPreferenceStore.setValue(PEAR_FILE, filename);
    fPreferenceStore.needsSaving();
  }

  /**
   * Gets the destination value.
   *
   * @return The value of the Pear file export destination as chosen by the user, or the last used
   *         one if the widget was not created yet
   */
  protected String getDestinationValue() {
    if (fDestinationFileInput != null) {
      return fDestinationFileInput.getText();
    }

    return fPreferenceStore.getString(PEAR_FILE);
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite)
   */
  @Override
  public void createControl(final Composite parent) {
    final Composite container = new Composite(parent, SWT.NONE);
    container.setLayout(new GridLayout());
    container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));

    createSourceControl(container);
    createDestinationControl(container);
    createOptionsGroup(container);

    pageStateChanged();

    setControl(container);
  }

  /**
   * Create the options group with the compression checkbox.
   *
   * @param parent
   *          the parent composite
   */
  protected void createOptionsGroup(final Composite parent) {
    final Group group = new Group(parent, SWT.NONE);
    group.setText(PearExportMessages.getString("PearFileResourceExportPage.Options")); //$NON-NLS-1$
    group.setLayout(new GridLayout());
    group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));

    fCompressCheckbox = new Button(group, SWT.CHECK);
    fCompressCheckbox.setSelection(true);
    fCompressCheckbox
            .setText(PearExportMessages.getString("PearFileResourceExportPage.CompressContents")); //$NON-NLS-1$
  }

  /**
   * Create the TreeViewer for selection of files to export in the Pear file and select/deselect all
   * buttons.
   *
   * @param parent
   *          the parent composite
   */
  protected void createSourceControl(final Composite parent) {
    final Composite container = new Composite(parent, SWT.NONE);
    container.setLayout(new GridLayout());
    container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));

    fTreeViewer = createTreeViewer(container);
    fTreeViewer.setInput(fCurrentContainer);
    fTreeViewer.setCheckedElements(fSelection.toArray());
    fTreeViewer.addCheckStateListener(new ICheckStateListener() {
      @Override
      public void checkStateChanged(final CheckStateChangedEvent event) {
        pageStateChanged();
      }
    });

    final Composite buttonsComposite = new Composite(container, SWT.NONE);
    buttonsComposite.setLayout(new RowLayout(SWT.HORIZONTAL));

    final Button selectAllButton = new Button(buttonsComposite, SWT.PUSH);
    selectAllButton.setText(PearExportMessages.getString("PearFileResourceExportPage.SelectAll")); //$NON-NLS-1$
    selectAllButton.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        setAllChecked(fTreeViewer.getTree().getItems(), true);
        pageStateChanged(); // above doesn't trigger a checkStateChanged
        // event
      }
    });

    final Button deselectAllButton = new Button(buttonsComposite, SWT.PUSH);
    deselectAllButton
            .setText(PearExportMessages.getString("PearFileResourceExportPage.DeselectAll")); //$NON-NLS-1$
    deselectAllButton.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        setAllChecked(fTreeViewer.getTree().getItems(), false);
        pageStateChanged(); // above doesn't trigger a checkStateChanged
        // event
      }
    });
  }

  /**
   * Sets the all checked.
   *
   * @param items
   *          A set of TreeItems that should be (un)checked, including their children
   * @param checked
   *          <code>true</code> to check all items, <code>false</code> to uncheck all items
   */
  protected void setAllChecked(final TreeItem[] items, final boolean checked) {
    for (int i = 0; i < items.length; i++) {
      items[i].setChecked(checked);
      final TreeItem[] children = items[i].getItems();
      setAllChecked(children, checked);
    }
  }

  /**
   * Creates the Pear file export destination controls, i.e. label, input field and browse button
   * 
   * @param parent
   *          the parent composite
   */
  protected void createDestinationControl(final Composite parent) {
    final Composite container = new Composite(parent, SWT.NONE);
    container.setLayout(new GridLayout(3, false));
    container.setLayoutData(new GridData(SWT.FILL, SWT.DEFAULT, true, false));

    final Label toPearFileLabel = new Label(container, SWT.NONE);
    toPearFileLabel.setText(PearExportMessages.getString("PearExport.destinationLabel")); //$NON-NLS-1$

    fDestinationFileInput = new Text(container, SWT.BORDER);
    fDestinationFileInput.setText(fPreferenceStore.getString(PEAR_FILE));
    fDestinationFileInput.setLayoutData(new GridData(SWT.FILL, SWT.DEFAULT, true, false));
    fDestinationFileInput.addModifyListener(new ModifyListener() {
      @Override
      public void modifyText(final ModifyEvent e) {
        pageStateChanged();
        saveDestinationValue(fDestinationFileInput.getText());
      }
    });

    final Button destinationBrowseButton = new Button(container, SWT.PUSH);
    destinationBrowseButton
            .setText(PearExportMessages.getString("PearFileResourceExportPage.Browse")); //$NON-NLS-1$
    destinationBrowseButton.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(final SelectionEvent e) {
        handleDestinationBrowseButtonPressed();
      }
    });
  }

  /**
   * Creates the tree viewer.
   *
   * @param parent
   *          the parent composite
   * @return TreeViewer that shows uses Workbench Content- and LabelProvider
   */
  protected ContainerCheckedTreeViewer createTreeViewer(Composite parent) {
    final ContainerCheckedTreeViewer treeViewer = new ContainerCheckedTreeViewer(parent);

    final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
    gridData.heightHint = 150;
    treeViewer.getTree().setLayoutData(gridData);

    treeViewer.setContentProvider(new WorkbenchContentProvider());
    treeViewer.setLabelProvider(WorkbenchLabelProvider.getDecoratingWorkbenchLabelProvider());
    treeViewer.setSorter(new WorkbenchViewerSorter());

    return treeViewer;
  }

  /**
   * Gets the export runnable.
   *
   * @return An {@link IRunnableWithProgress} that will export the files chosen in the TreeViewer to
   *         the file chosen in the destination input field when run
   */
  public IRunnableWithProgress getExportRunnable() {
    final List files = new ArrayList();
    final Object[] checked = fTreeViewer.getCheckedElements();
    for (int i = 0; i < checked.length; ++i) {
      if (checked[i] instanceof IAdaptable) {
        final IAdaptable adaptable = (IAdaptable) checked[i];
        final IResource resource = (IResource) adaptable.getAdapter(IResource.class);
        if (resource != null && resource.getType() == IResource.FILE) {
          files.add(resource);
        }
      }
    }

    final IFile[] exports = new IFile[files.size()];
    for (int i = 0; i < files.size(); ++i) {
      exports[i] = (IFile) files.get(i);
    }

    return new PearExportOperation(exports, fCurrentContainer, getDestinationValue(),
            fCompressCheckbox.getSelection());
  }
}
