package bndtools.wizards.shared;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

import org.bndtools.api.ILogger;
import org.bndtools.api.Logger;
import org.bndtools.core.ui.ConfigElementLabelProvider;
import org.bndtools.utils.eclipse.CategorisedConfigurationElementComparator;
import org.bndtools.utils.eclipse.CategorisedPrioritisedConfigurationElementTreeContentProvider;
import org.bndtools.utils.osgi.BundleUtils;
import org.bndtools.utils.workspace.FileUtils;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.browser.IWebBrowser;
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
import org.eclipse.ui.forms.events.HyperlinkAdapter;
import org.eclipse.ui.forms.events.HyperlinkEvent;
import org.eclipse.ui.forms.widgets.FormText;
import org.eclipse.ui.forms.widgets.ScrolledFormText;
import org.osgi.framework.Bundle;

import bndtools.Plugin;

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

	public static final String				PROP_ELEMENT	= "selectedElement";
	protected final PropertyChangeSupport	propSupport		= new PropertyChangeSupport(this);

	private Tree							tree;
	private TreeViewer						viewer;
	private ScrolledFormText				txtDescription;

	private IConfigurationElement[]			elements;
	private IConfigurationElement			selectedElement	= null;

	private boolean							shown			= false;

	protected AbstractTemplateSelectionWizardPage(String pageName) {
		super(pageName);
	}

	@Override
	public void createControl(Composite parent) {
		Composite container = new Composite(parent, SWT.NULL);

		setControl(container);
		container.setLayout(new GridLayout(1, false));

		new Label(container, SWT.NONE).setText("Select Template:");

		tree = new Tree(container, SWT.BORDER | SWT.FULL_SELECTION);
		GridData gd_table = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1);
		gd_table.heightHint = 100;
		tree.setLayoutData(gd_table);

		viewer = new TreeViewer(tree);

		Label lblNewLabel = new Label(container, SWT.NONE);
		lblNewLabel.setText("Description:");

		Composite cmpDescription = new Composite(container, SWT.BORDER);
		cmpDescription.setBackground(tree.getBackground());

		txtDescription = new ScrolledFormText(cmpDescription, SWT.V_SCROLL | SWT.H_SCROLL, false);
		FormText formText = new FormText(txtDescription, SWT.NO_FOCUS);
		txtDescription.setFormText(formText);
		txtDescription.setBackground(tree.getBackground());
		formText.setBackground(tree.getBackground());
		formText.setForeground(tree.getForeground());
		formText.setFont("fixed", JFaceResources.getTextFont());
		formText.setFont("italic", JFaceResources.getFontRegistry()
			.getItalic(""));

		GridData gd_cmpDescription = new GridData(SWT.FILL, SWT.FILL, true, true);
		gd_cmpDescription.heightHint = 100;
		cmpDescription.setLayoutData(gd_cmpDescription);

		GridLayout layout_cmpDescription = new GridLayout(1, false);
		cmpDescription.setLayout(layout_cmpDescription);

		GridData gd_txtDescription = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1);
		gd_txtDescription.heightHint = 100;
		txtDescription.setLayoutData(gd_txtDescription);

		viewer.setContentProvider(new CategorisedPrioritisedConfigurationElementTreeContentProvider(true));
		viewer.setLabelProvider(new ConfigElementLabelProvider(parent.getDisplay(), "icons/template.gif"));

		loadData();

		viewer.addSelectionChangedListener(event -> {
			Object selected = ((IStructuredSelection) viewer.getSelection()).getFirstElement();
			if (selected instanceof IConfigurationElement)
				setSelectionFromConfigElement((IConfigurationElement) selected);
			else
				setSelectionFromConfigElement(null);
			updateUI();
		});

		txtDescription.getFormText()
			.addHyperlinkListener(new HyperlinkAdapter() {
				@Override
				public void linkActivated(HyperlinkEvent event) {
					IWorkbenchBrowserSupport browserSupport = PlatformUI.getWorkbench()
						.getBrowserSupport();
					try {
						IWebBrowser externalBrowser = browserSupport.getExternalBrowser();
						externalBrowser.openURL(new URL((String) event.getHref()));
					} catch (PartInitException e) {
						logger.logError("Error opening external browser.", e);
					} catch (MalformedURLException e) {
						// Ignore
					}
				}
			});

		updateUI();
	}

	private void updateUI() {
		if (selectedElement == null) {
			setPageComplete(false);
			setMessage("Select a template", IMessageProvider.INFORMATION);
		} else {
			setPageComplete(true);
			setMessage(null);
		}
	}

	protected abstract IConfigurationElement[] loadConfigurationElements();

	private void loadData() {
		elements = loadConfigurationElements();
		Arrays.sort(elements, new CategorisedConfigurationElementComparator(true));

		viewer.setInput(elements);
		viewer.expandAll();
	}

	private void setSelectionFromConfigElement(IConfigurationElement element) {
		showTemplateDescription(element);

		IConfigurationElement old = this.selectedElement;
		this.selectedElement = element;
		propSupport.firePropertyChange(PROP_ELEMENT, old, element);
	}

	private void showTemplateDescription(IConfigurationElement element) {
		String browserText = "";
		if (element != null) {
			browserText = "<form>No description available.</form>";
			String name = element.getAttribute("name");
			String htmlAttr = element.getAttribute("doc");
			if (htmlAttr != null) {
				String bsn = element.getContributor()
					.getName();
				Bundle bundle = BundleUtils.findBundle(Plugin.getDefault()
					.getBundleContext(), bsn, null);
				if (bundle != null) {
					URL htmlUrl = bundle.getResource(htmlAttr);
					if (htmlUrl == null)
						browserText = String.format("<form>No description for %s.</form>", name);
					else
						try {
							byte[] bytes = FileUtils.readFully(htmlUrl.openStream());
							browserText = new String(bytes, StandardCharsets.UTF_8);
						} catch (IOException e) {
							logger.logError("Error reading template description document.", e);
						}
				}
			}
		}
		txtDescription.setText(browserText);
	}

	@Override
	public void setVisible(boolean visible) {
		super.setVisible(visible);

		if (visible && !shown) {
			shown = true;
			if (elements.length > 0) {
				setSelectionFromConfigElement(elements[0]);

				ISelection selection = new StructuredSelection(elements[0]);
				viewer.setSelection(selection);
			}
		}
	}

	@Override
	public boolean isPageComplete() {
		return shown && super.isPageComplete();
	}

	public IConfigurationElement getSelectedElement() {
		return selectedElement;
	}

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

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

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

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