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.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.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
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(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent 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, "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);
    }
}