/*******************************************************************************
 * Copyright (c) 2010 Neil Bartlett.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Neil Bartlett - initial API and implementation
 *******************************************************************************/
package bndtools.wizards.bndfile;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.jar.Manifest;

import org.bndtools.api.ILogger;
import org.bndtools.api.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.ViewerDropAdapter;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.dnd.TransferData;
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.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.ui.dialogs.ElementTreeSelectionDialog;
import org.eclipse.ui.dialogs.ISelectionStatusValidator;
import org.eclipse.ui.model.WorkbenchContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;

import aQute.bnd.osgi.Constants;
import aQute.bnd.osgi.Jar;
import bndtools.Plugin;
import bndtools.utils.ClassPathLabelProvider;
import bndtools.utils.FileExtensionFilter;

public class JarListWizardPage extends WizardPage {
    private static final ILogger logger = Logger.getLogger(JarListWizardPage.class);

    public static final String PROP_PATHS = "paths";

    private final PropertyChangeSupport propertySupport = new PropertyChangeSupport(this);
    private final Collection<IPath> paths = new LinkedList<IPath>();

    private TableViewer viewer;
    private Button btnAdd;
    private Button btnAddExternal;
    private Button btnRemove;

    public JarListWizardPage(final String pageName) {
        super(pageName);
    }

    @Override
    public void createControl(final Composite parent) {
        setTitle("Select JARs");
        final Composite composite = new Composite(parent, SWT.NONE);

        Label lblHint = new Label(composite, SWT.WRAP);
        lblHint.setText("Selected files (hint: drag files from an external application into this list):");

        final Table table = new Table(composite, SWT.FULL_SELECTION | SWT.MULTI | SWT.BORDER);
        viewer = new TableViewer(table);
        viewer.setContentProvider(new ArrayContentProvider());
        viewer.setLabelProvider(new ClassPathLabelProvider());

        btnAdd = new Button(composite, SWT.PUSH);
        btnAdd.setText("Add");
        btnAddExternal = new Button(composite, SWT.PUSH);
        btnAddExternal.setText("Add External");
        btnRemove = new Button(composite, SWT.PUSH);
        btnRemove.setText("Remove");

        viewer.setInput(paths);
        update();

        // Listeners
        ViewerDropAdapter dropAdapter = new ViewerDropAdapter(viewer) {
            @Override
            public void dragEnter(DropTargetEvent event) {
                super.dragEnter(event);
                event.detail = DND.DROP_COPY;
            }

            @Override
            public boolean validateDrop(Object target, int operation, TransferData transferType) {
                return true;
            }

            @Override
            public boolean performDrop(Object data) {
                if (data instanceof String[]) {
                    String[] newPaths = (String[]) data;
                    List<IPath> added = new ArrayList<IPath>(newPaths.length);
                    for (String path : newPaths) {
                        added.add(new Path(path));
                    }

                    if (!added.isEmpty()) {
                        addToPaths(added);
                        viewer.add(added.toArray());

                        update();
                    }
                }

                return true;
            }
        };
        dropAdapter.setFeedbackEnabled(false);
        dropAdapter.setSelectionFeedbackEnabled(false);
        viewer.addDropSupport(DND.DROP_COPY | DND.DROP_MOVE, new Transfer[] {
            FileTransfer.getInstance()
        }, dropAdapter);
        viewer.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(final SelectionChangedEvent event) {
                update();
            }
        });
        btnAdd.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                // IResource newFile = ResourcesPlugin.getWorkspace().getRoot();
                // if(newFile != null) {
                ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(), new WorkbenchLabelProvider(), new WorkbenchContentProvider());
                dialog.setValidator(new ISelectionStatusValidator() {
                    @Override
                    public IStatus validate(Object[] selection) {
                        if (selection.length > 0 && selection[0] instanceof IFile) {
                            return new Status(IStatus.OK, Plugin.PLUGIN_ID, IStatus.OK, "", null); //$NON-NLS-1$
                        }
                        return new Status(IStatus.ERROR, Plugin.PLUGIN_ID, IStatus.ERROR, "", null); //$NON-NLS-1$
                    }
                });
                dialog.setAllowMultiple(true);
                dialog.setTitle("JAR File Selection");
                dialog.setMessage("Select one or more JAR files.");
                dialog.addFilter(new FileExtensionFilter("jar")); //$NON-NLS-1$
                dialog.setInput(ResourcesPlugin.getWorkspace());

                if (dialog.open() == Window.OK) {
                    Object[] files = dialog.getResult();
                    List<IPath> added = new ArrayList<IPath>(files.length);
                    for (Object file : files) {
                        added.add(((IResource) file).getFullPath()
                            .makeRelative());
                    }
                    if (!added.isEmpty()) {
                        addToPaths(added);
                        viewer.add(added.toArray());
                    }
                }
                // }
                update();
            }
        });
        btnAddExternal.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                FileDialog dialog = new FileDialog(getShell(), SWT.OPEN | SWT.MULTI);
                dialog.setFilterExtensions(new String[] {
                    "*.jar" //$NON-NLS-1$
                });
                String res = dialog.open();
                if (res != null) {
                    IPath filterPath = new Path(dialog.getFilterPath());

                    String[] fileNames = dialog.getFileNames();
                    List<IPath> added = new ArrayList<IPath>(fileNames.length);
                    for (String fileName : fileNames) {
                        added.add(filterPath.append(fileName));
                    }
                    if (!added.isEmpty()) {
                        addToPaths(added);
                        viewer.add(added.toArray());
                    }
                }
                update();
            }
        });
        btnRemove.addSelectionListener(new SelectionAdapter() {
            @SuppressWarnings("unchecked")
            @Override
            public void widgetSelected(SelectionEvent e) {
                removeFromPaths(((IStructuredSelection) viewer.getSelection()).toList());
                viewer.remove(((IStructuredSelection) viewer.getSelection()).toArray());
                update();
            }
        });

        // Layout
        composite.setLayout(new GridLayout(2, false));
        table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 3));
        btnAdd.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
        btnAddExternal.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
        btnRemove.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
        lblHint.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 2, 1));

        setControl(composite);
    }

    private void addToPaths(List<IPath> added) {
        List<IPath> oldPaths = new ArrayList<IPath>(paths.size());
        oldPaths.addAll(paths);

        paths.addAll(added);
        propertySupport.firePropertyChange(PROP_PATHS, oldPaths, paths);
        checkExistingBundles();
    }

    private void removeFromPaths(List<IPath> removed) {
        List<IPath> oldPaths = new ArrayList<IPath>(paths.size());
        oldPaths.addAll(paths);

        paths.removeAll(removed);
        propertySupport.firePropertyChange(PROP_PATHS, oldPaths, paths);
        checkExistingBundles();
    }

    @Override
    public boolean isPageComplete() {
        return !paths.isEmpty();
    }

    private void update() {
        btnRemove.setEnabled(!viewer.getSelection()
            .isEmpty());
        if (isCurrentPage()) {
            getContainer().updateButtons();
            getContainer().updateMessage();
        }
    }

    private void checkExistingBundles() {
        List<IPath> alreadyBundles = new LinkedList<IPath>();
        for (IPath path : paths) {
            Jar jar = null;
            try {
                if (path.isAbsolute()) {
                    jar = new Jar(path.toFile());
                } else {
                    path = ResourcesPlugin.getWorkspace()
                        .getRoot()
                        .getLocation()
                        .append(path);
                    jar = new Jar(path.toFile());
                }

                Manifest manifest = jar.getManifest();
                if (manifest != null) {
                    String bsn = manifest.getMainAttributes()
                        .getValue(Constants.BUNDLE_SYMBOLICNAME);
                    if (bsn != null) {
                        alreadyBundles.add(path);
                    }
                }
            } catch (Exception e) {
                logger.logError("Error inspecting JAR file: " + path.toString(), e);
            } finally {
                if (jar != null)
                    jar.close();
            }
        }

        String warning = null;
        if (!alreadyBundles.isEmpty()) {
            StringBuilder builder = new StringBuilder();
            builder.append("The following JAR files are already bundles: ");
            for (Iterator<IPath> iterator = alreadyBundles.iterator(); iterator.hasNext();) {
                IPath path = iterator.next();
                builder.append(path.toString());
                if (iterator.hasNext())
                    builder.append("; ");
            }
            warning = builder.toString();
        }
        setMessage(warning, IMessageProvider.WARNING);
    }

    public Collection<IPath> getPaths() {
        return paths;
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        propertySupport.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        propertySupport.removePropertyChangeListener(listener);
    }

    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        propertySupport.addPropertyChangeListener(propertyName, listener);
    }

    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        propertySupport.removePropertyChangeListener(propertyName, listener);
    }

}
