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.CheckboxTableViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
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(ev -> {
			if (selectedIndirectResources == null)
				selectedIndirectResources = new HashSet<>();

			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(event -> {
			ISelection sel = event.getSelection();
			if (sel.isEmpty())
				selection = (ResourceDescriptor) ((IStructuredSelection) viewerIndirect.getSelection())
					.getFirstElement();
			else
				selection = (ResourceDescriptor) ((IStructuredSelection) sel).getFirstElement();
			getContainer().updateMessage();
		});
		viewerIndirect.addSelectionChangedListener(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<>();
			} 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();
		}
	}

}
