package bndtools.editor.workspace;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.SWT;
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.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.Table;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.ISharedImages;
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.ide.ResourceUtil;
import org.eclipse.ui.plugin.AbstractUIPlugin;

import aQute.bnd.build.model.BndEditModel;
import aQute.bnd.build.model.clauses.HeaderClause;
import aQute.bnd.header.Attrs;
import aQute.bnd.osgi.Constants;
import bndtools.Plugin;

public class PluginsPart extends SectionPart implements PropertyChangeListener {

	private final Image									editImg			= AbstractUIPlugin
		.imageDescriptorFromPlugin(Plugin.PLUGIN_ID, "/icons/pencil.png")
		.createImage();
	private final Image									refreshImg		= AbstractUIPlugin
		.imageDescriptorFromPlugin(Plugin.PLUGIN_ID, "icons/arrow_refresh.png")
		.createImage();

	private final Map<String, IConfigurationElement>	configElements	= new HashMap<>();

	private List<HeaderClause>							data;

	private Table										table;
	private TableViewer									viewer;

	private ToolItem									editItemTool;
	private ToolItem									removeItemTool;

	private BndEditModel								model;

	public PluginsPart(Composite parent, FormToolkit toolkit, int style) {
		super(parent, toolkit, style);

		IConfigurationElement[] configElems = Platform.getExtensionRegistry()
			.getConfigurationElementsFor(Plugin.PLUGIN_ID, "bndPlugins");
		for (IConfigurationElement configElem : configElems) {
			String className = configElem.getAttribute("class");
			configElements.put(className, configElem);
		}

		createSection(getSection(), toolkit);
	}

	final void createSection(Section section, FormToolkit toolkit) {
		section.setText("Plugins");
		section.setDescription("Bnd plugins are used to specify repositories and extended behaviours.");

		createToolBar(section);

		Composite composite = toolkit.createComposite(section, SWT.NONE);
		table = toolkit.createTable(composite, SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER);

		viewer = new TableViewer(table);
		viewer.setContentProvider(ArrayContentProvider.getInstance());
		viewer.setLabelProvider(new PluginClauseLabelProvider(configElements));

		Button btnReload = toolkit.createButton(composite, "Reload", SWT.NONE);
		btnReload.setImage(refreshImg);

		// Listeners
		viewer.addSelectionChangedListener(event -> {
			boolean enable = !viewer.getSelection()
				.isEmpty();
			removeItemTool.setEnabled(enable);
			editItemTool.setEnabled(enable);
		});
		table.addKeyListener(new KeyAdapter() {
			@Override
			public void keyReleased(KeyEvent e) {
				if (e.character == SWT.DEL) {
					doRemove();
				} else if (e.character == '+') {
					doAdd();
				}
			}
		});
		btnReload.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				doReload();
			}
		});

		composite.setLayout(new GridLayout(1, false));
		table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		btnReload.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));

		section.setClient(composite);

	}

	void doReload() {
		IFormPage page = (IFormPage) getManagedForm().getContainer();
		final IFile file = ResourceUtil.getFile(page.getEditorInput());
		if (file != null && file.exists()) {
			WorkspaceJob job = new WorkspaceJob("Reload Plugins") {
				@Override
				public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
					file.touch(monitor);
					return Status.OK_STATUS;
				}
			};
			job.setUser(true);
			job.schedule();
		}
	}

	void createToolBar(Section section) {
		ToolBar toolbar = new ToolBar(section, SWT.FLAT);
		section.setTextClient(toolbar);

		ToolItem addItem = new ToolItem(toolbar, SWT.PUSH);
		addItem.setImage(PlatformUI.getWorkbench()
			.getSharedImages()
			.getImage(ISharedImages.IMG_OBJ_ADD));
		addItem.setToolTipText("Add Plugin");
		addItem.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				doAdd();
			}
		});

		editItemTool = new ToolItem(toolbar, SWT.PUSH);
		editItemTool.setImage(editImg);
		editItemTool.setToolTipText("Edit");
		editItemTool.setEnabled(false);
		editItemTool.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				doEdit();
			}
		});

		removeItemTool = new ToolItem(toolbar, SWT.PUSH);
		removeItemTool.setImage(PlatformUI.getWorkbench()
			.getSharedImages()
			.getImage(ISharedImages.IMG_TOOL_DELETE));
		removeItemTool.setDisabledImage(PlatformUI.getWorkbench()
			.getSharedImages()
			.getImage(ISharedImages.IMG_TOOL_DELETE_DISABLED));
		removeItemTool.setToolTipText("Remove");
		removeItemTool.setEnabled(false);
		removeItemTool.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				doRemove();
			}
		});
	}

	@Override
	public void initialize(IManagedForm form) {
		super.initialize(form);

		model = (BndEditModel) form.getInput();
		model.addPropertyChangeListener(Constants.PLUGIN, this);
	}

	@Override
	public void dispose() {
		super.dispose();
		if (model != null)
			model.removePropertyChangeListener(Constants.PLUGIN, this);
		editImg.dispose();
		refreshImg.dispose();
	}

	@Override
	public void refresh() {
		List<HeaderClause> modelData = model.getPlugins();
		if (modelData != null)
			this.data = new ArrayList<>(modelData);
		else
			this.data = new LinkedList<>();
		viewer.setInput(this.data);
		super.refresh();
	}

	@Override
	public void commit(boolean onSave) {
		super.commit(onSave);
		model.setPlugins(data);
	}

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

	void doAdd() {
		PluginSelectionWizard wizard = new PluginSelectionWizard();
		WizardDialog dialog = new WizardDialog(getManagedForm().getForm()
			.getShell(), wizard);
		if (dialog.open() == Window.OK) {
			HeaderClause newPlugin = wizard.getHeader();

			data.add(newPlugin);
			viewer.add(newPlugin);
			markDirty();
		}
	}

	void doEdit() {
		HeaderClause header = (HeaderClause) ((IStructuredSelection) viewer.getSelection()).getFirstElement();
		if (header != null) {
			Attrs copyOfProperties = new Attrs(header.getAttribs());

			IConfigurationElement configElem = configElements.get(header.getName());
			PluginEditWizard wizard = new PluginEditWizard(configElem, copyOfProperties);
			WizardDialog dialog = new WizardDialog(getManagedForm().getForm()
				.getShell(), wizard);

			if (dialog.open() == Window.OK && wizard.isChanged()) {
				header.getAttribs()
					.clear();
				header.getAttribs()
					.putAll(copyOfProperties);

				viewer.update(header, null);
				markDirty();
			}
		}
	}

	void doRemove() {
		IStructuredSelection sel = (IStructuredSelection) viewer.getSelection();

		viewer.remove(sel.toArray());
		data.removeAll(sel.toList());

		if (!sel.isEmpty())
			markDirty();
	}

	public ISelectionProvider getSelectionProvider() {
		return viewer;
	}

}
