package org.bndtools.core.ui.wizards.jpm;

import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.bndtools.core.ui.icons.Icons;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.graphics.Image;
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.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;

import aQute.bnd.build.Workspace;
import aQute.bnd.service.repository.SearchableRepository;
import aQute.bnd.service.repository.SearchableRepository.ResourceDescriptor;
import aQute.lib.exceptions.Exceptions;
import bndtools.central.Central;

public class JpmDependencyWizardPage extends WizardPage {

    private static final String DEFAULT_MESSAGE = "Review the following supplied dependencies supplied before adding them.";

    private final URI originUri;

    private TableViewer viewerDirect;
    private CheckboxTableViewer viewerIndirect;
    private boolean queried = false;

    private SearchableRepository repository;

    private String errorText;
    private Set<ResourceDescriptor> directResources;
    private Set<ResourceDescriptor> indirectResources;
    private Set<ResourceDescriptor> selectedIndirectResources;

    private ResourceDescriptor selection;

    public JpmDependencyWizardPage(URI originUri) {
        super("jpmDependencies");
        this.originUri = originUri;

        setTitle("Add Dependencies from Searchable Repository");
    }

    @Override
    @SuppressWarnings("unused")
    public void createControl(Composite parent) {
        // CREATE CONTROLS
        Composite container = new Composite(parent, SWT.NULL);
        setControl(container);

        Label lblDirect = new Label(container, SWT.NONE);
        lblDirect.setFont(JFaceResources.getBannerFont());
        lblDirect.setText("Direct Dependencies:");

        Table tblDirect = new Table(container, SWT.MULTI | SWT.FULL_SELECTION | SWT.BORDER | SWT.V_SCROLL);
        viewerDirect = new TableViewer(tblDirect);
        viewerDirect.setContentProvider(ArrayContentProvider.getInstance());
        viewerDirect.setLabelProvider(new ResourceDescriptorLabelProvider());

        createHelpLabel(container, "The above dependencies will be added to the project and, if necessary, to the Searchable Repository local index.");

        Label separator = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL);

        Label lblIndirect = new Label(container, SWT.NONE);
        lblIndirect.setFont(JFaceResources.getBannerFont());
        lblIndirect.setText("Transitive Dependencies:");

        Composite cmpIndirect = new Composite(container, SWT.NONE);
        Table tblIndirect = new Table(cmpIndirect, SWT.CHECK | SWT.MULTI | SWT.FULL_SELECTION | SWT.BORDER | SWT.V_SCROLL);
        viewerIndirect = new CheckboxTableViewer(tblIndirect);
        viewerIndirect.setContentProvider(ArrayContentProvider.getInstance());
        viewerIndirect.setLabelProvider(new ResourceDescriptorLabelProvider());

        Button btnSelectAll = new Button(cmpIndirect, SWT.PUSH);
        btnSelectAll.setText("All");

        Button btnSelectNone = new Button(cmpIndirect, SWT.PUSH);
        btnSelectNone.setText("None");

        new Label(cmpIndirect, SWT.NONE);

        createHelpLabel(container, "The above dependencies will be added to the Searchable Repository local index. Checked dependencies will also be added directly to the project.");

        // LISTENERS

        // Query JPM and show results *after* dialog is shown. This ensures progress is visible in the dialog's
        // progress bar
        getContainer().getShell()
            .addShellListener(new ShellAdapter() {
                @Override
                public void shellActivated(ShellEvent e) {
                    runQuery();
                }
            });
        viewerIndirect.addCheckStateListener(new ICheckStateListener() {
            @Override
            public void checkStateChanged(CheckStateChangedEvent ev) {
                if (selectedIndirectResources == null)
                    selectedIndirectResources = new HashSet<ResourceDescriptor>();

                ResourceDescriptor resource = (ResourceDescriptor) ev.getElement();
                if (ev.getChecked())
                    selectedIndirectResources.add(resource);
                else
                    selectedIndirectResources.remove(resource);
            }
        });
        btnSelectAll.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                selectedIndirectResources.addAll(indirectResources);
                updateSelectedCheckboxes();
            }
        });
        btnSelectNone.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                selectedIndirectResources.clear();
                updateSelectedCheckboxes();
            }
        });

        viewerDirect.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent event) {
                ISelection sel = event.getSelection();
                if (sel.isEmpty())
                    selection = (ResourceDescriptor) ((IStructuredSelection) viewerIndirect.getSelection()).getFirstElement();
                else
                    selection = (ResourceDescriptor) ((IStructuredSelection) sel).getFirstElement();
                getContainer().updateMessage();
            }
        });
        viewerIndirect.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent event) {
                ISelection sel = event.getSelection();
                if (sel.isEmpty())
                    selection = (ResourceDescriptor) ((IStructuredSelection) viewerDirect.getSelection()).getFirstElement();
                else
                    selection = (ResourceDescriptor) ((IStructuredSelection) sel).getFirstElement();
                getContainer().updateMessage();
            }
        });

        // LAYOUT
        GridLayout layout;
        GridData gd;

        layout = new GridLayout(1, true);
        container.setLayout(layout);

        gd = new GridData(SWT.FILL, SWT.FILL, true, false);
        gd.heightHint = 30;
        tblDirect.setLayoutData(gd);

        gd = new GridData(SWT.FILL, SWT.FILL, true, false);
        separator.setLayoutData(gd);

        gd = new GridData(SWT.FILL, SWT.FILL, true, true);
        cmpIndirect.setLayoutData(gd);

        layout = new GridLayout(2, false);
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        layout.horizontalSpacing = 2;
        layout.verticalSpacing = 2;
        cmpIndirect.setLayout(layout);

        gd = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 3);
        gd.heightHint = 100;
        tblIndirect.setLayoutData(gd);

        gd = new GridData(SWT.FILL, SWT.FILL, false, false);
        btnSelectAll.setLayoutData(gd);

        gd = new GridData(SWT.FILL, SWT.FILL, false, false);
        btnSelectNone.setLayoutData(gd);
    }

    private Control createHelpLabel(Composite container, String text) {
        Label label = new Label(container, SWT.WRAP);
        label.setText(text);

        ControlDecoration decoration = new ControlDecoration(label, SWT.LEFT, container);
        Image imgInfo = FieldDecorationRegistry.getDefault()
            .getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION)
            .getImage();
        decoration.setImage(imgInfo);

        GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false);
        gd.horizontalIndent = imgInfo.getBounds().width;
        gd.widthHint = 100;
        label.setLayoutData(gd);

        return label;
    }

    private void runQuery() {
        if (!queried) {
            errorText = null;
            try {
                Workspace workspace = Central.getWorkspace();
                repository = workspace.getPlugin(SearchableRepository.class);
                if (repository == null)
                    throw new Exception("No searchable repository is configured in the workspace.");

                QueryJpmDependenciesRunnable query = new QueryJpmDependenciesRunnable(originUri, repository);
                getContainer().run(true, true, query);
                queried = true;
                errorText = query.getError();
                directResources = query.getDirectResources();
                indirectResources = query.getIndirectResources();
                selectedIndirectResources = new HashSet<ResourceDescriptor>();
            } catch (InvocationTargetException e) {
                errorText = Exceptions.unrollCause(e, InvocationTargetException.class)
                    .getMessage();
            } catch (InterruptedException e) {
                // ignore
            } catch (Exception e) {
                errorText = e.getMessage();
            } finally {
                updateUi();
            }
        }
    }

    private void updateUi() {
        viewerDirect.setInput(directResources);
        viewerIndirect.setInput(indirectResources);
        updateSelectedCheckboxes();
        getContainer().updateButtons();
        getContainer().updateMessage();
    }

    private void updateSelectedCheckboxes() {
        Object[] selectedArray = selectedIndirectResources != null ? selectedIndirectResources.toArray() : new Object[0];
        viewerIndirect.setCheckedElements(selectedArray);
    }

    @Override
    public boolean isPageComplete() {
        return queried && errorText == null;
    }

    @Override
    public String getErrorMessage() {
        return errorText;
    }

    @Override
    public String getMessage() {
        if (selection != null) {
            return selection.description;
        }

        return DEFAULT_MESSAGE;
    }

    public Set<ResourceDescriptor> getDirectResources() {
        return Collections.unmodifiableSet(directResources);
    }

    public Set<ResourceDescriptor> getIndirectResources() {
        return Collections.unmodifiableSet(indirectResources);
    }

    public Set<ResourceDescriptor> getSelectedIndirectResources() {
        return Collections.unmodifiableSet(selectedIndirectResources);
    }

    public SearchableRepository getRepository() {
        return repository;
    }

    public URI getOriginUri() {
        return originUri;
    }

    private static class ResourceDescriptorLabelProvider extends StyledCellLabelProvider {

        private final Image imgJar = Icons.desc("jar")
            .createImage();

        @Override
        public void update(ViewerCell cell) {
            ResourceDescriptor descriptor = (ResourceDescriptor) cell.getElement();
            StyledString label = new StyledString(descriptor.bsn + " ");
            if (descriptor.version != null)
                label.append(descriptor.version.toString(), StyledString.COUNTER_STYLER);

            cell.setText(label.toString());
            cell.setStyleRanges(label.getStyleRanges());

            cell.setImage(imgJar);
        }

        @Override
        public void dispose() {
            imgJar.dispose();
        }
    }

}
