/*******************************************************************************
 * 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.editor.contents;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.util.LocalSelectionTransfer;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
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.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.SectionPart;
import org.eclipse.ui.forms.editor.IFormPage;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.part.ResourceTransfer;

import aQute.bnd.build.model.BndEditModel;
import aQute.bnd.osgi.Constants;
import bndtools.editor.common.PackageDropAdapter;
import bndtools.editor.common.PrivatePackageTableLabelProvider;
import bndtools.internal.pkgselection.IPackageFilter;
import bndtools.internal.pkgselection.JavaSearchScopePackageLister;
import bndtools.internal.pkgselection.PackageSelectionDialog;

public class PrivatePackagesPart extends SectionPart implements PropertyChangeListener {

    private BndEditModel model;
    private List<String> packages = new ArrayList<String>();

    private Table table;
    private TableViewer viewer;
    private IManagedForm managedForm;

    public PrivatePackagesPart(Composite parent, FormToolkit toolkit, int style) {
        super(parent, toolkit, style);
        createSection(getSection(), toolkit);
    }

    void createSection(Section section, FormToolkit toolkit) {
        section.setText("Private Packages");
        section.setDescription("The listed packages will be included in the bundle but not exported.");

        ToolBar toolbar = new ToolBar(section, SWT.FLAT);
        section.setTextClient(toolbar);
        final ToolItem addItem = new ToolItem(toolbar, SWT.PUSH);
        addItem.setImage(PlatformUI.getWorkbench()
            .getSharedImages()
            .getImage(ISharedImages.IMG_OBJ_ADD));
        addItem.setToolTipText("Add");

        final ToolItem removeItem = new ToolItem(toolbar, SWT.PUSH);
        removeItem.setImage(PlatformUI.getWorkbench()
            .getSharedImages()
            .getImage(ISharedImages.IMG_TOOL_DELETE));
        removeItem.setDisabledImage(PlatformUI.getWorkbench()
            .getSharedImages()
            .getImage(ISharedImages.IMG_TOOL_DELETE_DISABLED));
        removeItem.setToolTipText("Remove");
        removeItem.setEnabled(false);

        Composite composite = toolkit.createComposite(section);
        section.setClient(composite);

        table = toolkit.createTable(composite, SWT.FULL_SELECTION | SWT.MULTI | SWT.BORDER);

        viewer = new TableViewer(table);
        viewer.setContentProvider(new ArrayContentProvider());
        viewer.setLabelProvider(new PrivatePackageTableLabelProvider());

        // Listeners
        viewer.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent event) {
                managedForm.fireSelectionChanged(PrivatePackagesPart.this, event.getSelection());
                removeItem.setEnabled(!viewer.getSelection()
                    .isEmpty());
            }
        });
        viewer.addDropSupport(DND.DROP_COPY | DND.DROP_MOVE, new Transfer[] {
            LocalSelectionTransfer.getTransfer(), TextTransfer.getInstance(), ResourceTransfer.getInstance()
        }, new PackageDropAdapter<String>(viewer) {
            @Override
            protected String createNewEntry(String packageName) {
                return packageName;
            }

            @Override
            protected void addRows(int index, Collection<String> rows) {
                if (rows.isEmpty())
                    return; // skip marking dirty
                if (index == -1) {
                    packages.addAll(rows);
                    viewer.add(rows.toArray());
                } else {
                    packages.addAll(index, rows);
                    viewer.refresh();
                }
                viewer.setSelection(new StructuredSelection(rows));
                markDirty();
            }

            @Override
            protected int indexOf(Object object) {
                return 0;
            }
        });
        table.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                if (e.character == SWT.DEL) {
                    doRemovePackages();
                } else if (e.character == '+') {
                    doAddPackages();
                }
            }
        });
        addItem.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                doAddPackages();
            }
        });
        removeItem.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                doRemovePackages();
            }
        });

        // Layout
        GridLayout layout = new GridLayout(1, false);
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        composite.setLayout(layout);

        GridData gd;

        gd = new GridData(SWT.FILL, SWT.FILL, true, true);
        gd.heightHint = 75;
        gd.widthHint = 75;
        table.setLayoutData(gd);
    }

    private void doAddPackages() {
        // Prepare the exclusion list based on existing private packages
        final Set<String> packageNameSet = new HashSet<String>(packages);

        // Create a filter from the exclusion list and packages matching
        // "java.*", which must not be included in a bundle
        IPackageFilter filter = new IPackageFilter() {
            @Override
            public boolean select(String packageName) {
                return !packageName.equals("java") && !packageName.startsWith("java.") && !packageNameSet.contains(packageName);
            }
        };
        IFormPage page = (IFormPage) getManagedForm().getContainer();
        IWorkbenchWindow window = page.getEditorSite()
            .getWorkbenchWindow();

        // Prepare the package lister from the Java project
        IJavaProject javaProject = getJavaProject();
        if (javaProject == null) {
            MessageDialog.openError(getSection().getShell(), "Error", "Cannot add packages: unable to find a Java project associated with the editor input.");
            return;
        }
        IJavaSearchScope searchScope = SearchEngine.createJavaSearchScope(new IJavaElement[] {
            javaProject
        });
        JavaSearchScopePackageLister packageLister = new JavaSearchScopePackageLister(searchScope, window);

        // Create and open the dialog
        PackageSelectionDialog dialog = new PackageSelectionDialog(getSection().getShell(), packageLister, filter, "Select new packages to include in the bundle.");
        dialog.setSourceOnly(true);
        dialog.setMultipleSelection(true);
        if (dialog.open() == Window.OK) {
            Object[] results = dialog.getResult();
            List<String> added = new LinkedList<String>();

            // Select the results
            for (Object result : results) {
                String newPackageName = (String) result;
                if (packages.add(newPackageName)) {
                    added.add(newPackageName);
                }
            }

            // Update the model and view
            if (!added.isEmpty()) {
                viewer.add(added.toArray());
                markDirty();
            }
        }
    }

    private void doRemovePackages() {
        IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
        if (!selection.isEmpty()) {
            Iterator<?> elements = selection.iterator();
            List<Object> removed = new LinkedList<Object>();
            while (elements.hasNext()) {
                Object pkg = elements.next();
                if (packages.remove(pkg))
                    removed.add(pkg);
            }

            if (!removed.isEmpty()) {
                viewer.remove(removed.toArray());
                markDirty();
            }
        }
    }

    @Override
    public void commit(boolean onSave) {
        super.commit(onSave);
        model.setPrivatePackages(packages);
    }

    @Override
    public void refresh() {
        List<String> tmp = model.getPrivatePackages();
        if (tmp != null)
            packages = new ArrayList<String>(tmp);
        else
            packages = new ArrayList<String>();
        viewer.setInput(packages);
        super.refresh();
    }

    @Override
    public void initialize(IManagedForm form) {
        super.initialize(form);
        this.managedForm = form;
        model = (BndEditModel) form.getInput();
        model.addPropertyChangeListener(Constants.PRIVATE_PACKAGE, this);
    }

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

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        IFormPage page = (IFormPage) getManagedForm().getContainer();
        if (page.isActive()) {
            refresh();
        } else {
            markStale();
        }
    }

    private IJavaProject getJavaProject() {
        IFormPage page = (IFormPage) getManagedForm().getContainer();
        IEditorInput input = page.getEditorInput();
        if (!IFileEditorInput.class.isInstance(input)) {
            return null;
        }
        IProject project = ((IFileEditorInput) input).getFile()
            .getProject();
        return JavaCore.create(project);
    }

    public ISelectionProvider getSelectionProvider() {
        return viewer;
    }
}
