/*******************************************************************************
 * Copyright (c) 2010, 2013 Ericsson
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License v1.0 which
 * accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Patrick Tasse - Initial API and implementation
 *******************************************************************************/

package org.eclipse.linuxtools.internal.tmf.ui.parsers.wizards;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.linuxtools.internal.tmf.ui.Activator;
import org.eclipse.linuxtools.internal.tmf.ui.Messages;
import org.eclipse.linuxtools.internal.tmf.ui.parsers.custom.CustomTraceDefinition;
import org.eclipse.linuxtools.internal.tmf.ui.parsers.custom.CustomXmlTrace;
import org.eclipse.linuxtools.internal.tmf.ui.parsers.custom.CustomXmlTraceDefinition;
import org.eclipse.linuxtools.internal.tmf.ui.parsers.custom.CustomXmlTraceDefinition.InputAttribute;
import org.eclipse.linuxtools.internal.tmf.ui.parsers.custom.CustomXmlTraceDefinition.InputElement;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.TitleEvent;
import org.eclipse.swt.browser.TitleListener;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * Input wizard page for custom XML trace parsers.
 *
 * @author Patrick Tasse
 */
public class CustomXmlParserInputWizardPage extends WizardPage {

    private static final String DEFAULT_TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; //$NON-NLS-1$
    private static final String SIMPLE_DATE_FORMAT_URL = "http://java.sun.com/javase/6/docs/api/java/text/SimpleDateFormat.html#skip-navbar_top"; //$NON-NLS-1$
    private static final Image ELEMENT_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/element_icon.gif"); //$NON-NLS-1$
    private static final Image ADD_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/add_button.gif"); //$NON-NLS-1$
    private static final Image ADD_NEXT_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/addnext_button.gif"); //$NON-NLS-1$
    private static final Image ADD_CHILD_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/addchild_button.gif"); //$NON-NLS-1$
    private static final Image ADD_MANY_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/addmany_button.gif"); //$NON-NLS-1$
    private static final Image DELETE_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/delete_button.gif"); //$NON-NLS-1$
    private static final Image MOVE_UP_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/moveup_button.gif"); //$NON-NLS-1$
    private static final Image MOVE_DOWN_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/movedown_button.gif"); //$NON-NLS-1$
    private static final Image HELP_IMAGE = Activator.getDefault().getImageFromPath("/icons/elcl16/help_button.gif"); //$NON-NLS-1$
    private static final Color COLOR_LIGHT_RED = new Color(Display.getDefault(), 255, 192, 192);
    private static final Color COLOR_TEXT_BACKGROUND = Display.getDefault().getSystemColor(SWT.COLOR_WHITE);
    private static final Color COLOR_WIDGET_BACKGROUND = Display.getDefault().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);

    private final ISelection selection;
    private CustomXmlTraceDefinition definition;
    private String editDefinitionName;
    private String defaultDescription;
    private ElementNode selectedElement;
    private Composite container;
    private Text logtypeText;
    private Text timeStampOutputFormatText;
    private Text timeStampPreviewText;
    private Button removeButton;
    private Button addChildButton;
    private Button addNextButton;
    private Button moveUpButton;
    private Button moveDownButton;
    private ScrolledComposite elementScrolledComposite;
    private TreeViewer treeViewer;
    private Composite elementContainer;
    private Text errorText;
    private StyledText inputText;
    private Font fixedFont;
    private UpdateListener updateListener;
    private Browser helpBrowser;
    private Element documentElement;

    // variables used recursively through element traversal
    private String timeStampValue;
    private String timeStampFormat;
    private boolean timeStampFound;
    private int logEntriesCount;
    private boolean logEntryFound;

    /**
     * Constructor
     *
     * @param selection
     *            Selection object
     * @param definition
     *            Trace definition
     */
    protected CustomXmlParserInputWizardPage(ISelection selection, CustomXmlTraceDefinition definition) {
        super("CustomXmlParserWizardPage"); //$NON-NLS-1$
        if (definition == null) {
            setTitle(Messages.CustomXmlParserInputWizardPage_titleNew);
            defaultDescription = Messages.CustomXmlParserInputWizardPage_descriptionNew;
        } else {
            setTitle(Messages.CustomXmlParserInputWizardPage_titleEdit);
            defaultDescription = Messages.CustomXmlParserInputWizardPage_descriptionEdit;
        }
        setDescription(defaultDescription);
        this.selection = selection;
        this.definition = definition;
        if (definition != null) {
            this.editDefinitionName = definition.definitionName;
        }
    }

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

        updateListener = new UpdateListener();

        Composite headerComposite = new Composite(container, SWT.FILL);
        GridLayout headerLayout = new GridLayout(5, false);
        headerLayout.marginHeight = 0;
        headerLayout.marginWidth = 0;
        headerComposite.setLayout(headerLayout);
        headerComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

        Label logtypeLabel = new Label(headerComposite, SWT.NULL);
        logtypeLabel.setText(Messages.CustomXmlParserInputWizardPage_logType);

        logtypeText = new Text(headerComposite, SWT.BORDER | SWT.SINGLE);
        logtypeText.setLayoutData(new GridData(120, SWT.DEFAULT));

        Label timeStampFormatLabel = new Label(headerComposite, SWT.NULL);
        timeStampFormatLabel.setText(Messages.CustomXmlParserInputWizardPage_timestampFormat);

        timeStampOutputFormatText = new Text(headerComposite, SWT.BORDER | SWT.SINGLE);
        timeStampOutputFormatText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        timeStampOutputFormatText.setText(DEFAULT_TIMESTAMP_FORMAT);

        Button dateFormatHelpButton = new Button(headerComposite, SWT.PUSH);
        dateFormatHelpButton.setImage(HELP_IMAGE);
        dateFormatHelpButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_dateFormatHelp);
        dateFormatHelpButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                openHelpShell(SIMPLE_DATE_FORMAT_URL);
            }
        });

        Label timeStampPreviewLabel = new Label(headerComposite, SWT.NULL);
        timeStampPreviewLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 3, 1));
        timeStampPreviewLabel.setText(Messages.CustomXmlParserInputWizardPage_preview);

        timeStampPreviewText = new Text(headerComposite, SWT.BORDER | SWT.SINGLE | SWT.READ_ONLY);
        timeStampPreviewText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
        timeStampPreviewText.setText("*no time stamp element or attribute*"); //$NON-NLS-1$

        createButtonBar();

        SashForm vSash = new SashForm(container, SWT.VERTICAL);
        vSash.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        vSash.setBackground(vSash.getDisplay().getSystemColor(SWT.COLOR_GRAY));

        SashForm hSash = new SashForm(vSash, SWT.HORIZONTAL);
        hSash.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        ScrolledComposite treeScrolledComposite = new ScrolledComposite(hSash, SWT.V_SCROLL | SWT.H_SCROLL);
        treeScrolledComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        Composite treeContainer = new Composite(treeScrolledComposite, SWT.NONE);
        treeContainer.setLayout(new FillLayout());
        treeScrolledComposite.setContent(treeContainer);
        treeScrolledComposite.setExpandHorizontal(true);
        treeScrolledComposite.setExpandVertical(true);

        treeViewer = new TreeViewer(treeContainer, SWT.SINGLE | SWT.BORDER);
        treeViewer.setContentProvider(new InputElementTreeNodeContentProvider());
        treeViewer.setLabelProvider(new InputElementTreeLabelProvider());
        treeViewer.addSelectionChangedListener(new InputElementTreeSelectionChangedListener());
        treeContainer.layout();

        treeScrolledComposite
                .setMinSize(treeContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x, treeContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).y);

        elementScrolledComposite = new ScrolledComposite(hSash, SWT.V_SCROLL);
        elementScrolledComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        elementContainer = new Composite(elementScrolledComposite, SWT.NONE);
        GridLayout gl = new GridLayout();
        gl.marginHeight = 1;
        gl.marginWidth = 0;
        elementContainer.setLayout(gl);
        elementScrolledComposite.setContent(elementContainer);
        elementScrolledComposite.setExpandHorizontal(true);
        elementScrolledComposite.setExpandVertical(true);

        if (definition == null) {
            definition = new CustomXmlTraceDefinition();
        }
        loadDefinition(definition);
        treeViewer.expandAll();
        elementContainer.layout();

        logtypeText.addModifyListener(updateListener);
        timeStampOutputFormatText.addModifyListener(updateListener);

        elementScrolledComposite.setMinSize(elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x,
                elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).y - 1);

        hSash.setWeights(new int[] { 1, 2 });

        if (definition.rootInputElement == null) {
            removeButton.setEnabled(false);
            addChildButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addDocumentElement);
            addNextButton.setEnabled(false);
            moveUpButton.setEnabled(false);
            moveDownButton.setEnabled(false);
        } else { // root is selected
            addNextButton.setEnabled(false);
        }

        Composite sashBottom = new Composite(vSash, SWT.NONE);
        GridLayout sashBottomLayout = new GridLayout(2, false);
        sashBottomLayout.marginHeight = 0;
        sashBottomLayout.marginWidth = 0;
        sashBottom.setLayout(sashBottomLayout);

        Label previewLabel = new Label(sashBottom, SWT.NULL);
        previewLabel.setText(Messages.CustomXmlParserInputWizardPage_previewInput);

        errorText = new Text(sashBottom, SWT.SINGLE | SWT.READ_ONLY);
        errorText.setBackground(COLOR_WIDGET_BACKGROUND);
        errorText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        errorText.setVisible(false);

        inputText = new StyledText(sashBottom, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
        if (fixedFont == null) {
            if (System.getProperty("os.name").contains("Windows")) { //$NON-NLS-1$ //$NON-NLS-2$
                fixedFont = new Font(Display.getCurrent(), new FontData("Courier New", 10, SWT.NORMAL)); //$NON-NLS-1$
            } else {
                fixedFont = new Font(Display.getCurrent(), new FontData("Monospace", 10, SWT.NORMAL)); //$NON-NLS-1$
            }
        }
        inputText.setFont(fixedFont);
        GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1);
        gd.heightHint = inputText.computeSize(SWT.DEFAULT, inputText.getLineHeight() * 4).y;
        gd.widthHint = 800;
        inputText.setLayoutData(gd);
        inputText.setText(getSelectionText());
        inputText.addModifyListener(new ModifyListener() {
            @Override
            public void modifyText(ModifyEvent e) {
                parseXmlInput(inputText.getText());
            }
        });
        inputText.addModifyListener(updateListener);

        vSash.setWeights(new int[] { hSash.computeSize(SWT.DEFAULT, SWT.DEFAULT).y, sashBottom.computeSize(SWT.DEFAULT, SWT.DEFAULT).y });

        setControl(container);
    }

    private void createButtonBar() {
        Composite buttonBar = new Composite(container, SWT.NONE);
        GridLayout buttonBarLayout = new GridLayout(6, false);
        buttonBarLayout.marginHeight = 0;
        buttonBarLayout.marginWidth = 0;
        buttonBar.setLayout(buttonBarLayout);

        removeButton = new Button(buttonBar, SWT.PUSH);
        removeButton.setImage(DELETE_IMAGE);
        removeButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_removeElement);
        removeButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                if (treeViewer.getSelection().isEmpty() || selectedElement == null) {
                    return;
                }
                removeElement();
                InputElement inputElement = (InputElement) ((IStructuredSelection) treeViewer.getSelection()).getFirstElement();
                if (inputElement == definition.rootInputElement) {
                    definition.rootInputElement = null;
                } else {
                    inputElement.parentElement.childElements.remove(inputElement);
                }
                treeViewer.refresh();
                validate();
                updatePreviews();
                removeButton.setEnabled(false);
                if (definition.rootInputElement == null) {
                    addChildButton.setEnabled(true);
                    addChildButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addDocumentEleemnt);
                } else {
                    addChildButton.setEnabled(false);
                }
                addNextButton.setEnabled(false);
                moveUpButton.setEnabled(false);
                moveDownButton.setEnabled(false);
            }
        });

        addChildButton = new Button(buttonBar, SWT.PUSH);
        addChildButton.setImage(ADD_CHILD_IMAGE);
        addChildButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addChildElement);
        addChildButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                InputElement inputElement = new InputElement("", false, CustomXmlTraceDefinition.TAG_IGNORE, 0, "", null); //$NON-NLS-1$ //$NON-NLS-2$
                if (definition.rootInputElement == null) {
                    definition.rootInputElement = inputElement;
                    inputElement.elementName = getChildNameSuggestion(null);
                } else if (treeViewer.getSelection().isEmpty()) {
                    return;
                } else {
                    InputElement parentInputElement = (InputElement) ((IStructuredSelection) treeViewer.getSelection()).getFirstElement();
                    parentInputElement.addChild(inputElement);
                    inputElement.elementName = getChildNameSuggestion(parentInputElement);
                }
                treeViewer.refresh();
                treeViewer.setSelection(new StructuredSelection(inputElement), true);
            }
        });

        addNextButton = new Button(buttonBar, SWT.PUSH);
        addNextButton.setImage(ADD_NEXT_IMAGE);
        addNextButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addNextElement);
        addNextButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                InputElement inputElement = new InputElement("", false, CustomXmlTraceDefinition.TAG_IGNORE, 0, "", null); //$NON-NLS-1$ //$NON-NLS-2$
                if (definition.rootInputElement == null) {
                    definition.rootInputElement = inputElement;
                    inputElement.elementName = getChildNameSuggestion(null);
                } else if (treeViewer.getSelection().isEmpty()) {
                    return;
                } else {
                    InputElement previousInputElement = (InputElement) ((IStructuredSelection) treeViewer.getSelection()).getFirstElement();
                    if (previousInputElement == definition.rootInputElement) {
                        return;
                    }
                    previousInputElement.addNext(inputElement);
                    inputElement.elementName = getChildNameSuggestion(inputElement.parentElement);
                }
                treeViewer.refresh();
                treeViewer.setSelection(new StructuredSelection(inputElement), true);
            }
        });

        Button feelingLuckyButton = new Button(buttonBar, SWT.PUSH);
        feelingLuckyButton.setImage(ADD_MANY_IMAGE);
        feelingLuckyButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_feelingLucky);
        feelingLuckyButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                InputElement inputElement = null;
                if (definition.rootInputElement == null) {
                    if (getChildNameSuggestion(null).length() != 0) {
                        inputElement = new InputElement(getChildNameSuggestion(null), false, CustomXmlTraceDefinition.TAG_IGNORE, 0, "", null); //$NON-NLS-1$
                        definition.rootInputElement = inputElement;
                        feelingLucky(inputElement);
                    } else {
                        return;
                    }
                } else if (treeViewer.getSelection().isEmpty()) {
                    return;
                } else {
                    inputElement = (InputElement) ((IStructuredSelection) treeViewer.getSelection()).getFirstElement();
                    feelingLucky(inputElement);
                }
                treeViewer.refresh();
                treeViewer.setSelection(new StructuredSelection(inputElement), true);
                treeViewer.expandToLevel(inputElement, AbstractTreeViewer.ALL_LEVELS);
            }
        });

        moveUpButton = new Button(buttonBar, SWT.PUSH);
        moveUpButton.setImage(MOVE_UP_IMAGE);
        moveUpButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_moveUp);
        moveUpButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                if (treeViewer.getSelection().isEmpty()) {
                    return;
                }
                InputElement inputElement = (InputElement) ((IStructuredSelection) treeViewer.getSelection()).getFirstElement();
                if (inputElement == definition.rootInputElement) {
                    return;
                }
                inputElement.moveUp();
                treeViewer.refresh();
                validate();
                updatePreviews();
            }
        });

        moveDownButton = new Button(buttonBar, SWT.PUSH);
        moveDownButton.setImage(MOVE_DOWN_IMAGE);
        moveDownButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_moveDown);
        moveDownButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                if (treeViewer.getSelection().isEmpty()) {
                    return;
                }
                InputElement inputElement = (InputElement) ((IStructuredSelection) treeViewer.getSelection()).getFirstElement();
                if (inputElement == definition.rootInputElement) {
                    return;
                }
                inputElement.moveDown();
                treeViewer.refresh();
                validate();
                updatePreviews();
            }
        });
    }

    private void feelingLucky(InputElement inputElement) {
        while (true) {
            String attributeName = getAttributeNameSuggestion(inputElement);
            if (attributeName.length() == 0) {
                break;
            }
            InputAttribute attribute = new InputAttribute(attributeName, attributeName, 0, ""); //$NON-NLS-1$
            inputElement.addAttribute(attribute);
        }
        while (true) {
            String childName = getChildNameSuggestion(inputElement);
            if (childName.length() == 0) {
                break;
            }
            InputElement childElement = new InputElement(childName, false, CustomXmlTraceDefinition.TAG_IGNORE, 0, "", null); //$NON-NLS-1$
            inputElement.addChild(childElement);
            feelingLucky(childElement);
        }
    }

    private static class InputElementTreeNodeContentProvider implements ITreeContentProvider {

        @Override
        public Object[] getElements(Object inputElement) {
            CustomXmlTraceDefinition def = (CustomXmlTraceDefinition) inputElement;
            if (def.rootInputElement != null) {
                return new Object[] { def.rootInputElement };
            }
            return new Object[0];
        }

        @Override
        public Object[] getChildren(Object parentElement) {
            InputElement inputElement = (InputElement) parentElement;
            if (inputElement.childElements == null) {
                return new InputElement[0];
            }
            return inputElement.childElements.toArray();
        }

        @Override
        public boolean hasChildren(Object element) {
            InputElement inputElement = (InputElement) element;
            return (inputElement.childElements != null && inputElement.childElements.size() > 0);
        }

        @Override
        public void dispose() {
        }

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        }

        @Override
        public Object getParent(Object element) {
            InputElement inputElement = (InputElement) element;
            return inputElement.parentElement;
        }
    }

    private static class InputElementTreeLabelProvider extends ColumnLabelProvider {

        @Override
        public Image getImage(Object element) {
            return ELEMENT_IMAGE;
        }

        @Override
        public String getText(Object element) {
            InputElement inputElement = (InputElement) element;
            return (inputElement.elementName.trim().length() == 0) ? "?" : inputElement.elementName; //$NON-NLS-1$
        }
    }

    private class InputElementTreeSelectionChangedListener implements ISelectionChangedListener {
        @Override
        public void selectionChanged(SelectionChangedEvent event) {
            if (selectedElement != null) {
                selectedElement.dispose();
            }
            if (!(event.getSelection().isEmpty()) && event.getSelection() instanceof IStructuredSelection) {
                IStructuredSelection sel = (IStructuredSelection) event.getSelection();
                InputElement inputElement = (InputElement) sel.getFirstElement();
                selectedElement = new ElementNode(elementContainer, inputElement);
                elementContainer.layout();
                elementScrolledComposite.setMinSize(elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x,
                        elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).y - 1);
                container.layout();
                validate();
                updatePreviews();
                removeButton.setEnabled(true);
                addChildButton.setEnabled(true);
                addChildButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addChildElement);
                if (definition.rootInputElement == inputElement) {
                    addNextButton.setEnabled(false);
                } else {
                    addNextButton.setEnabled(true);
                }
                moveUpButton.setEnabled(true);
                moveDownButton.setEnabled(true);
            } else {
                removeButton.setEnabled(false);
                if (definition.rootInputElement == null) {
                    addChildButton.setEnabled(true);
                    addChildButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addDocumentElement);
                } else {
                    addChildButton.setEnabled(false);
                }
                addNextButton.setEnabled(false);
                moveUpButton.setEnabled(false);
                moveDownButton.setEnabled(false);
            }
        }
    }

    @Override
    public void dispose() {
        if (fixedFont != null) {
            fixedFont.dispose();
            fixedFont = null;
        }
        super.dispose();
    }

    private void loadDefinition(CustomXmlTraceDefinition def) {
        logtypeText.setText(def.definitionName);
        timeStampOutputFormatText.setText(def.timeStampOutputFormat);
        treeViewer.setInput(def);

        if (def.rootInputElement != null) {
            treeViewer.setSelection(new StructuredSelection(def.rootInputElement));
        }
    }

    private String getName(InputElement inputElement) {
        String name = (inputElement.elementName.trim().length() == 0) ? "?" : inputElement.elementName.trim(); //$NON-NLS-1$
        if (inputElement.parentElement == null) {
            return name;
        }
        return getName(inputElement.parentElement) + " : " + name; //$NON-NLS-1$
    }

    private String getName(InputAttribute inputAttribute, InputElement inputElement) {
        String name = (inputAttribute.attributeName.trim().length() == 0) ? "?" : inputAttribute.attributeName.trim(); //$NON-NLS-1$
        return getName(inputElement) + " : " + name; //$NON-NLS-1$
    }

    @Override
    public void setVisible(boolean visible) {
        if (visible) {
            validate();
            updatePreviews();
        }
        super.setVisible(visible);
    }

    /**
     * Get the global list of input names.
     *
     * @return The list of input names
     */
    public List<String> getInputNames() {
        return getInputNames(definition.rootInputElement);
    }

    /**
     * Get the list of input names for a given element.
     *
     * @param inputElement
     *            The element
     * @return The input names for this element
     */
    public List<String> getInputNames(InputElement inputElement) {
        List<String> inputs = new ArrayList<String>();
        if (inputElement.inputName != null && !inputElement.inputName.equals(CustomXmlTraceDefinition.TAG_IGNORE)) {
            String inputName = inputElement.inputName;
            if (!inputs.contains(inputName)) {
                inputs.add(inputName);
            }
        }
        if (inputElement.attributes != null) {
            for (InputAttribute attribute : inputElement.attributes) {
                String inputName = attribute.inputName;
                if (!inputs.contains(inputName)) {
                    inputs.add(inputName);
                }
            }
        }
        if (inputElement.childElements != null) {
            for (InputElement childInputElement : inputElement.childElements) {
                for (String inputName : getInputNames(childInputElement)) {
                    if (!inputs.contains(inputName)) {
                        inputs.add(inputName);
                    }
                }
            }
        }
        return inputs;
    }

    private void removeElement() {
        selectedElement.dispose();
        selectedElement = null;
        elementContainer.layout();
        elementScrolledComposite.setMinSize(elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x,
                elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).y - 1);
        container.layout();
    }

    private String getSelectionText() {
        InputStream inputStream = null;
        if (this.selection instanceof IStructuredSelection) {
            Object sel = ((IStructuredSelection) this.selection).getFirstElement();
            if (sel instanceof IFile) {
                IFile file = (IFile) sel;
                try {
                    inputStream = file.getContents();
                } catch (CoreException e) {
                    return ""; //$NON-NLS-1$
                }
            }
        }
        if (inputStream != null) {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new InputStreamReader(inputStream));
                StringBuilder sb = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    sb.append(line + "\n"); //$NON-NLS-1$
                }
                parseXmlInput(sb.toString());
                reader.close();
                return sb.toString();
            } catch (IOException e) {
                return ""; //$NON-NLS-1$
            }
        }
        return ""; //$NON-NLS-1$
    }

    private void parseXmlInput(final String string) {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();

            // The following allows xml parsing without access to the dtd
            EntityResolver resolver = new EntityResolver() {
                @Override
                public InputSource resolveEntity(String publicId, String systemId) {
                    String empty = ""; //$NON-NLS-1$
                    ByteArrayInputStream bais = new ByteArrayInputStream(empty.getBytes());
                    return new InputSource(bais);
                }
            };
            db.setEntityResolver(resolver);

            // The following catches xml parsing exceptions
            db.setErrorHandler(new ErrorHandler() {
                @Override
                public void error(SAXParseException saxparseexception) throws SAXException {
                }

                @Override
                public void warning(SAXParseException saxparseexception) throws SAXException {
                }

                @Override
                public void fatalError(SAXParseException saxparseexception) throws SAXException {
                    if (string.trim().length() != 0) {
                        errorText.setText(saxparseexception.getMessage());
                        errorText.setBackground(COLOR_LIGHT_RED);
                        errorText.setVisible(true);
                    }
                    throw saxparseexception;
                }
            });

            errorText.setVisible(false);
            Document doc = null;
            doc = db.parse(new ByteArrayInputStream(string.getBytes()));
            documentElement = doc.getDocumentElement();
        } catch (ParserConfigurationException e) {
            Activator.getDefault().logError("Error pasing XML input string: " + string, e); //$NON-NLS-1$
            documentElement = null;
        } catch (SAXException e) {
            documentElement = null;
        } catch (IOException e) {
            Activator.getDefault().logError("Error pasing XML input string: " + string, e); //$NON-NLS-1$
            documentElement = null;
        }
    }

    private void initValues() {
        timeStampValue = null;
        timeStampFormat = null;
        logEntriesCount = 0;
        logEntryFound = false;
    }

    private void updatePreviews() {
        if (inputText == null) {
            // early update during construction
            return;
        }
        inputText.setStyleRanges(new StyleRange[] {});
        if (selectedElement == null) {
            return;
        }

        initValues();

        selectedElement.updatePreview();

        if (timeStampValue != null && timeStampFormat != null) {
            try {
                SimpleDateFormat dateFormat = new SimpleDateFormat(timeStampFormat);
                Date date = dateFormat.parse(timeStampValue);
                dateFormat = new SimpleDateFormat(timeStampOutputFormatText.getText().trim());
                timeStampPreviewText.setText(dateFormat.format(date));
            } catch (ParseException e) {
                timeStampPreviewText.setText("*parse exception* [" + timeStampValue + "] <> [" + timeStampFormat + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            } catch (IllegalArgumentException e) {
                timeStampPreviewText.setText("*parse exception* [Illegal Argument]"); //$NON-NLS-1$
            }
        } else {
            timeStampPreviewText.setText("*no matching time stamp*"); //$NON-NLS-1$
        }
    }

    private void openHelpShell(String url) {
        if (helpBrowser != null && !helpBrowser.isDisposed()) {
            helpBrowser.getShell().setActive();
            if (!helpBrowser.getUrl().equals(url)) {
                helpBrowser.setUrl(url);
            }
            return;
        }
        final Shell helpShell = new Shell(getShell(), SWT.SHELL_TRIM);
        helpShell.setLayout(new FillLayout());
        helpBrowser = new Browser(helpShell, SWT.NONE);
        helpBrowser.addTitleListener(new TitleListener() {
            @Override
            public void changed(TitleEvent event) {
                helpShell.setText(event.title);
            }
        });
        helpBrowser.setBounds(0, 0, 600, 400);
        helpShell.pack();
        helpShell.open();
        helpBrowser.setUrl(url);
    }

    private class UpdateListener implements ModifyListener, SelectionListener {

        @Override
        public void modifyText(ModifyEvent e) {
            validate();
            updatePreviews();
        }

        @Override
        public void widgetDefaultSelected(SelectionEvent e) {
            validate();
            updatePreviews();
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            validate();
            updatePreviews();
        }

    }

    private class ElementNode {
        private final InputElement inputElement;
        private final Group group;
        private List<Attribute> attributes = new ArrayList<Attribute>();
        private List<ElementNode> childElements = new ArrayList<ElementNode>();
        private Text elementNameText;
        private Composite tagComposite;
        private Combo tagCombo;
        private Label tagLabel;
        private Text tagText;
        private Combo actionCombo;
        private Label previewLabel;
        private Text previewText;
        private Button logEntryButton;
        private Label fillerLabel;
        private Composite addAttributeComposite;
        private Button addAttributeButton;
        private Label addAttributeLabel;

        public ElementNode(Composite parent, InputElement inputElement) {
            this.inputElement = inputElement;

            group = new Group(parent, SWT.NONE);
            GridLayout gl = new GridLayout(2, false);
            gl.marginHeight = 0;
            group.setLayout(gl);
            group.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
            group.setText(getName(inputElement));

            Label label = new Label(group, SWT.NULL);
            label.setText(Messages.CustomXmlParserInputWizardPage_elementName);
            label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));

            elementNameText = new Text(group, SWT.BORDER | SWT.SINGLE);
            GridData gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
            gd.widthHint = 0;
            elementNameText.setLayoutData(gd);
            elementNameText.addModifyListener(new ModifyListener() {
                @Override
                public void modifyText(ModifyEvent e) {
                    ElementNode.this.inputElement.elementName = elementNameText.getText().trim();
                    group.setText(getName(ElementNode.this.inputElement));
                }
            });
            elementNameText.setText(inputElement.elementName);
            elementNameText.addModifyListener(updateListener);

            if (inputElement.parentElement != null) {
                previewLabel = new Label(group, SWT.NULL);
                previewLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
                previewLabel.setText(Messages.CustomXmlParserInputWizardPage_preview);

                previewText = new Text(group, SWT.BORDER | SWT.SINGLE | SWT.READ_ONLY);
                gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
                gd.widthHint = 0;
                previewText.setLayoutData(gd);
                previewText.setText(Messages.CustomXmlParserInputWizardPage_noMatchingElement);
                previewText.setBackground(COLOR_WIDGET_BACKGROUND);

                logEntryButton = new Button(group, SWT.CHECK);
                logEntryButton.setText(Messages.CustomXmlParserInputWizardPage_logEntry);
                logEntryButton.setSelection(inputElement.logEntry);
                logEntryButton.addSelectionListener(new SelectionListener() {
                    @Override
                    public void widgetDefaultSelected(SelectionEvent e) {
                    }

                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        InputElement parentElem = ElementNode.this.inputElement.parentElement;
                        while (parentElem != null) {
                            parentElem.logEntry = false;
                            parentElem = parentElem.parentElement;
                        }
                    }
                });
                logEntryButton.addSelectionListener(updateListener);

                tagComposite = new Composite(group, SWT.FILL);
                GridLayout tagLayout = new GridLayout(4, false);
                tagLayout.marginWidth = 0;
                tagLayout.marginHeight = 0;
                tagComposite.setLayout(tagLayout);
                tagComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

                tagCombo = new Combo(tagComposite, SWT.DROP_DOWN | SWT.READ_ONLY);
                tagCombo.setItems(new String[] { CustomXmlTraceDefinition.TAG_IGNORE, CustomTraceDefinition.TAG_TIMESTAMP,
                        CustomTraceDefinition.TAG_MESSAGE, CustomTraceDefinition.TAG_OTHER });
                tagCombo.setVisibleItemCount(tagCombo.getItemCount());
                tagCombo.addSelectionListener(new SelectionListener() {
                    @Override
                    public void widgetDefaultSelected(SelectionEvent e) {
                    }

                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        tagText.removeModifyListener(updateListener);
                        switch (tagCombo.getSelectionIndex()) {
                        case 0: // Ignore
                            tagLabel.setVisible(false);
                            tagText.setVisible(false);
                            actionCombo.setVisible(false);
                            break;
                        case 1: // Time Stamp
                            tagLabel.setText(Messages.CustomXmlParserInputWizardPage_format);
                            tagLabel.setVisible(true);
                            tagText.setVisible(true);
                            tagText.addModifyListener(updateListener);
                            actionCombo.setVisible(true);
                            break;
                        case 2: // Message
                            tagLabel.setVisible(false);
                            tagText.setVisible(false);
                            actionCombo.setVisible(true);
                            break;
                        case 3: // Other
                            tagLabel.setText(Messages.CustomXmlParserInputWizardPage_tagName);
                            tagLabel.setVisible(true);
                            if (tagText.getText().trim().length() == 0) {
                                tagText.setText(elementNameText.getText().trim());
                            }
                            tagText.setVisible(true);
                            tagText.addModifyListener(updateListener);
                            actionCombo.setVisible(true);
                            break;
                        default:
                            break;
                        }
                        tagComposite.layout();
                        validate();
                        updatePreviews();
                    }
                });

                tagLabel = new Label(tagComposite, SWT.NULL);
                tagLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));

                tagText = new Text(tagComposite, SWT.BORDER | SWT.SINGLE);
                gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
                gd.widthHint = 0;
                tagText.setLayoutData(gd);

                actionCombo = new Combo(tagComposite, SWT.DROP_DOWN | SWT.READ_ONLY);
                actionCombo.setItems(new String[] { Messages.CustomXmlParserInputWizardPage_set, Messages.CustomXmlParserInputWizardPage_append,
                        Messages.CustomXmlParserInputWizardPage_appendWith });
                actionCombo.select(inputElement.inputAction);
                actionCombo.addSelectionListener(updateListener);

                if (inputElement.inputName.equals(CustomXmlTraceDefinition.TAG_IGNORE)) {
                    tagCombo.select(0);
                    tagLabel.setVisible(false);
                    tagText.setVisible(false);
                    actionCombo.setVisible(false);
                } else if (inputElement.inputName.equals(CustomTraceDefinition.TAG_TIMESTAMP)) {
                    tagCombo.select(1);
                    tagLabel.setText(Messages.CustomXmlParserInputWizardPage_format);
                    tagText.setText(inputElement.inputFormat);
                    tagText.addModifyListener(updateListener);
                } else if (inputElement.inputName.equals(CustomTraceDefinition.TAG_MESSAGE)) {
                    tagCombo.select(2);
                    tagLabel.setVisible(false);
                    tagText.setVisible(false);
                } else {
                    tagCombo.select(3);
                    tagLabel.setText(Messages.CustomXmlParserInputWizardPage_tagName);
                    tagText.setText(inputElement.inputName);
                    tagText.addModifyListener(updateListener);
                }
            }

            if (inputElement.attributes != null) {
                for (InputAttribute inputAttribute : inputElement.attributes) {
                    Attribute attribute = new Attribute(group, this, inputAttribute, attributes.size() + 1);
                    attributes.add(attribute);
                }
            }

            createAddButton();
        }

        private void updatePreview() {
            Element element = getPreviewElement(inputElement);
            if (inputElement.parentElement != null) { // no preview text for
                                                      // document element
                previewText.setText(Messages.CustomXmlParserInputWizardPage_noMatchingElement);
                if (element != null) {
                    previewText.setText(CustomXmlTrace.parseElement(element, new StringBuffer()).toString());
                    if (logEntryButton.getSelection()) {
                        if (!logEntryFound) {
                            logEntryFound = true;
                            logEntriesCount++;
                        } else {
                            logEntryButton.setSelection(false); // remove nested
                                                                // log entry
                        }
                    }
                    if (tagCombo.getText().equals(CustomTraceDefinition.TAG_TIMESTAMP) && logEntriesCount <= 1) {
                        String value = previewText.getText().trim();
                        if (value.length() != 0) {
                            if (actionCombo.getSelectionIndex() == CustomTraceDefinition.ACTION_SET) {
                                timeStampValue = value;
                                timeStampFormat = tagText.getText().trim();
                            } else if (actionCombo.getSelectionIndex() == CustomTraceDefinition.ACTION_APPEND) {
                                if (timeStampValue != null) {
                                    timeStampValue += value;
                                    timeStampFormat += tagText.getText().trim();
                                } else {
                                    timeStampValue = value;
                                    timeStampFormat = tagText.getText().trim();
                                }
                            } else if (actionCombo.getSelectionIndex() == CustomTraceDefinition.ACTION_APPEND_WITH_SEPARATOR) {
                                if (timeStampValue != null) {
                                    timeStampValue += " | " + value; //$NON-NLS-1$
                                    timeStampFormat += " | " + tagText.getText().trim(); //$NON-NLS-1$
                                } else {
                                    timeStampValue = value;
                                    timeStampFormat = tagText.getText().trim();
                                }
                            }
                        }
                    }
                }
            }
            for (Attribute attribute : attributes) {
                if (element != null) {
                    String value = element.getAttribute(attribute.attributeNameText.getText().trim());
                    if (value.length() != 0) {
                        attribute.previewText.setText(value);
                        if (attribute.tagCombo.getText().equals(CustomTraceDefinition.TAG_TIMESTAMP) && logEntriesCount <= 1) {
                            if (attribute.actionCombo.getSelectionIndex() == CustomTraceDefinition.ACTION_SET) {
                                timeStampValue = value;
                                timeStampFormat = attribute.tagText.getText().trim();
                            } else if (attribute.actionCombo.getSelectionIndex() == CustomTraceDefinition.ACTION_APPEND) {
                                if (timeStampValue != null) {
                                    timeStampValue += value;
                                    timeStampFormat += attribute.tagText.getText().trim();
                                } else {
                                    timeStampValue = value;
                                    timeStampFormat = attribute.tagText.getText().trim();
                                }
                            } else if (attribute.actionCombo.getSelectionIndex() == CustomTraceDefinition.ACTION_APPEND_WITH_SEPARATOR) {
                                if (timeStampValue != null) {
                                    timeStampValue += " | " + value; //$NON-NLS-1$
                                    timeStampFormat += " | " + attribute.tagText.getText().trim(); //$NON-NLS-1$
                                } else {
                                    timeStampValue = value;
                                    timeStampFormat = attribute.tagText.getText().trim();
                                }
                            }
                        }
                    } else {
                        attribute.previewText.setText(Messages.CustomXmlParserInputWizardPage_noMatchingAttribute);
                    }
                } else {
                    attribute.previewText.setText(Messages.CustomXmlParserInputWizardPage_noMatchingElement);
                }
            }
            for (ElementNode child : childElements) {
                child.updatePreview();
            }
            if (logEntryButton != null && logEntryButton.getSelection()) {
                logEntryFound = false;
            }
        }

        private void createAddButton() {
            fillerLabel = new Label(group, SWT.NONE);

            addAttributeComposite = new Composite(group, SWT.NONE);
            addAttributeComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
            GridLayout addAttributeLayout = new GridLayout(2, false);
            addAttributeLayout.marginHeight = 0;
            addAttributeLayout.marginWidth = 0;
            addAttributeComposite.setLayout(addAttributeLayout);

            addAttributeButton = new Button(addAttributeComposite, SWT.PUSH);
            addAttributeButton.setImage(ADD_IMAGE);
            addAttributeButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_addAttribute);
            addAttributeButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    removeAddButton();
                    String attributeName = getAttributeNameSuggestion(inputElement);
                    InputAttribute inputAttribute = new InputAttribute(attributeName, attributeName, 0, ""); //$NON-NLS-1$
                    attributes.add(new Attribute(group, ElementNode.this, inputAttribute, attributes.size() + 1));
                    createAddButton();
                    elementContainer.layout();
                    elementScrolledComposite.setMinSize(elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x,
                            elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).y - 1);
                    group.getParent().layout();
                    validate();
                    updatePreviews();
                }
            });

            addAttributeLabel = new Label(addAttributeComposite, SWT.NULL);
            addAttributeLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
            addAttributeLabel.setText(Messages.CustomXmlParserInputWizardPage_newAttibute);
        }

        private void removeAddButton() {
            fillerLabel.dispose();
            addAttributeComposite.dispose();
        }

        private void removeAttribute(int attributeNumber) {
            int nb = attributeNumber;
            if (--nb < attributes.size()) {
                attributes.remove(nb).dispose();
                for (int i = nb; i < attributes.size(); i++) {
                    attributes.get(i).setAttributeNumber(i + 1);
                }
                elementContainer.layout();
                elementScrolledComposite.setMinSize(elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).x,
                        elementContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT).y - 1);
                group.getParent().layout();
            }
        }

        private void dispose() {
            group.dispose();
        }

        private void extractInputs() {
            inputElement.elementName = elementNameText.getText().trim();
            if (inputElement.parentElement != null) {
                inputElement.logEntry = logEntryButton.getSelection();
                if (tagCombo.getText().equals(CustomTraceDefinition.TAG_OTHER)) {
                    inputElement.inputName = tagText.getText().trim();
                } else {
                    inputElement.inputName = tagCombo.getText();
                    if (tagCombo.getText().equals(CustomTraceDefinition.TAG_TIMESTAMP)) {
                        inputElement.inputFormat = tagText.getText().trim();
                    }
                }
                inputElement.inputAction = actionCombo.getSelectionIndex();
            }
            inputElement.attributes = new ArrayList<InputAttribute>(attributes.size());
            for (int i = 0; i < attributes.size(); i++) {
                Attribute attribute = attributes.get(i);
                InputAttribute inputAttribute = new InputAttribute();
                inputAttribute.attributeName = attribute.attributeNameText.getText().trim();
                if (attribute.tagCombo.getText().equals(CustomTraceDefinition.TAG_OTHER)) {
                    inputAttribute.inputName = attribute.tagText.getText().trim();
                } else {
                    inputAttribute.inputName = attribute.tagCombo.getText();
                    if (attribute.tagCombo.getText().equals(CustomTraceDefinition.TAG_TIMESTAMP)) {
                        inputAttribute.inputFormat = attribute.tagText.getText().trim();
                    }
                }
                inputAttribute.inputAction = attribute.actionCombo.getSelectionIndex();
                inputElement.addAttribute(inputAttribute);
            }
        }
    }

    private class Attribute {
        private ElementNode element;
        private int attributeNumber;

        // children of parent (must be disposed)
        private Composite labelComposite;
        private Composite attributeComposite;
        private Label filler;
        private Composite tagComposite;

        // children of labelComposite
        private Label attributeLabel;

        // children of attributeComposite
        private Text attributeNameText;
        private Text previewText;

        // children of tagComposite
        private Combo tagCombo;
        private Label tagLabel;
        private Text tagText;
        private Combo actionCombo;

        public Attribute(Composite parent, ElementNode element, InputAttribute inputAttribute, int attributeNumber) {
            this.element = element;
            this.attributeNumber = attributeNumber;

            labelComposite = new Composite(parent, SWT.FILL);
            GridLayout labelLayout = new GridLayout(2, false);
            labelLayout.marginWidth = 0;
            labelLayout.marginHeight = 0;
            labelComposite.setLayout(labelLayout);
            labelComposite.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));

            Button deleteButton = new Button(labelComposite, SWT.PUSH);
            deleteButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
            deleteButton.setImage(DELETE_IMAGE);
            deleteButton.setToolTipText(Messages.CustomXmlParserInputWizardPage_removeAttribute);
            deleteButton.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    Attribute.this.element.removeAttribute(Attribute.this.attributeNumber);
                    validate();
                    updatePreviews();
                }
            });

            attributeLabel = new Label(labelComposite, SWT.NULL);
            attributeLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
            attributeLabel.setText(Messages.CustomXmlParserInputWizardPage_attibute);

            attributeComposite = new Composite(parent, SWT.FILL);
            GridLayout attributeLayout = new GridLayout(4, false);
            attributeLayout.marginWidth = 0;
            attributeLayout.marginHeight = 0;
            attributeComposite.setLayout(attributeLayout);
            attributeComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

            Label nameLabel = new Label(attributeComposite, SWT.NONE);
            nameLabel.setText(Messages.CustomXmlParserInputWizardPage_name);

            attributeNameText = new Text(attributeComposite, SWT.BORDER | SWT.SINGLE);
            attributeNameText.setLayoutData(new GridData(120, SWT.DEFAULT));
            attributeNameText.setText(inputAttribute.attributeName);
            attributeNameText.addModifyListener(updateListener);

            Label previewLabel = new Label(attributeComposite, SWT.NONE);
            previewLabel.setText(Messages.CustomXmlParserInputWizardPage_preview);

            previewText = new Text(attributeComposite, SWT.BORDER | SWT.SINGLE | SWT.READ_ONLY);
            GridData gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
            gd.widthHint = 0;
            previewText.setLayoutData(gd);
            previewText.setText(Messages.CustomXmlParserInputWizardPage_noMatch);
            previewText.setBackground(COLOR_WIDGET_BACKGROUND);

            filler = new Label(parent, SWT.NULL);

            tagComposite = new Composite(parent, SWT.FILL);
            GridLayout tagLayout = new GridLayout(4, false);
            tagLayout.marginWidth = 0;
            tagLayout.marginHeight = 0;
            tagComposite.setLayout(tagLayout);
            tagComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

            tagCombo = new Combo(tagComposite, SWT.DROP_DOWN | SWT.READ_ONLY);
            tagCombo.setItems(new String[] { CustomTraceDefinition.TAG_TIMESTAMP, CustomTraceDefinition.TAG_MESSAGE,
                    CustomTraceDefinition.TAG_OTHER });
            tagCombo.select(2); // Other
            tagCombo.addSelectionListener(new SelectionListener() {
                @Override
                public void widgetDefaultSelected(SelectionEvent e) {
                }

                @Override
                public void widgetSelected(SelectionEvent e) {
                    tagText.removeModifyListener(updateListener);
                    switch (tagCombo.getSelectionIndex()) {
                    case 0: // Time Stamp
                        tagLabel.setText(Messages.CustomXmlParserInputWizardPage_format);
                        tagLabel.setVisible(true);
                        tagText.setVisible(true);
                        tagText.addModifyListener(updateListener);
                        break;
                    case 1: // Message
                        tagLabel.setVisible(false);
                        tagText.setVisible(false);
                        break;
                    case 2: // Other
                        tagLabel.setText(Messages.CustomXmlParserInputWizardPage_tagName);
                        tagLabel.setVisible(true);
                        if (tagText.getText().trim().length() == 0) {
                            tagText.setText(attributeNameText.getText().trim());
                        }
                        tagText.setVisible(true);
                        tagText.addModifyListener(updateListener);
                        break;
                    default:
                        break;
                    }
                    tagComposite.layout();
                    validate();
                    updatePreviews();
                }
            });

            tagLabel = new Label(tagComposite, SWT.NULL);
            tagLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));

            tagText = new Text(tagComposite, SWT.BORDER | SWT.SINGLE);
            gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
            gd.widthHint = 0;
            tagText.setLayoutData(gd);
            tagText.setText(attributeNameText.getText());

            actionCombo = new Combo(tagComposite, SWT.DROP_DOWN | SWT.READ_ONLY);
            actionCombo.setItems(new String[] { Messages.CustomXmlParserInputWizardPage_set, Messages.CustomXmlParserInputWizardPage_append,
                    Messages.CustomXmlParserInputWizardPage_appendWith });
            actionCombo.select(inputAttribute.inputAction);
            actionCombo.addSelectionListener(updateListener);

            if (inputAttribute.inputName.equals(CustomTraceDefinition.TAG_TIMESTAMP)) {
                tagCombo.select(0);
                tagLabel.setText(Messages.CustomXmlParserInputWizardPage_format);
                tagText.setText(inputAttribute.inputFormat);
                tagText.addModifyListener(updateListener);
            } else if (inputAttribute.inputName.equals(CustomTraceDefinition.TAG_MESSAGE)) {
                tagCombo.select(1);
                tagLabel.setVisible(false);
                tagText.setVisible(false);
            } else {
                tagCombo.select(2);
                tagLabel.setText(Messages.CustomXmlParserInputWizardPage_tagName);
                tagText.setText(inputAttribute.inputName);
                tagText.addModifyListener(updateListener);
            }
        }

        private void dispose() {
            labelComposite.dispose();
            attributeComposite.dispose();
            filler.dispose();
            tagComposite.dispose();
        }

        private void setAttributeNumber(int attributeNumber) {
            this.attributeNumber = attributeNumber;
            labelComposite.layout();
        }
    }

    private Element getPreviewElement(InputElement inputElement) {
        InputElement currentElement = inputElement;
        Element element = documentElement;
        if (element != null) {
            if (!documentElement.getNodeName().equals(definition.rootInputElement.elementName)) {
                return null;
            }
            ArrayList<String> elementNames = new ArrayList<String>();
            while (currentElement != null) {
                elementNames.add(currentElement.elementName);
                currentElement = currentElement.parentElement;
            }
            for (int i = elementNames.size() - 1; --i >= 0;) {
                NodeList childList = element.getChildNodes();
                element = null;
                for (int j = 0; j < childList.getLength(); j++) {
                    Node child = childList.item(j);
                    if (child instanceof Element && child.getNodeName().equals(elementNames.get(i))) {
                        element = (Element) child;
                        break;
                    }
                }
                if (element == null) {
                    break;
                }
            }
            if (element != null) {
                return element;
            }
        }
        return null;
    }

    private String getChildNameSuggestion(InputElement inputElement) {
        if (inputElement == null) {
            if (documentElement != null) {
                return documentElement.getNodeName();
            }
        } else {
            Element element = getPreviewElement(inputElement);
            if (element != null) {
                NodeList childNodes = element.getChildNodes();
                for (int i = 0; i < childNodes.getLength(); i++) {
                    Node node = childNodes.item(i);
                    if (node instanceof Element) {
                        boolean unused = true;
                        if (inputElement.childElements != null) {
                            for (InputElement child : inputElement.childElements) {
                                if (child.elementName.equals(node.getNodeName())) {
                                    unused = false;
                                    break;
                                }
                            }
                        }
                        if (unused) {
                            return node.getNodeName();
                        }
                    }
                }
            }
        }
        return ""; //$NON-NLS-1$
    }

    private String getAttributeNameSuggestion(InputElement inputElement) {
        Element element = getPreviewElement(inputElement);
        if (element != null) {
            NamedNodeMap attributeMap = element.getAttributes();
            for (int i = 0; i < attributeMap.getLength(); i++) {
                Node node = attributeMap.item(i);
                boolean unused = true;
                if (inputElement.attributes != null) {
                    for (InputAttribute attribute : inputElement.attributes) {
                        if (attribute.attributeName.equals(node.getNodeName())) {
                            unused = false;
                            break;
                        }
                    }
                }
                if (unused) {
                    return node.getNodeName();
                }
            }
        }
        return ""; //$NON-NLS-1$
    }

    private void validate() {
        definition.definitionName = logtypeText.getText().trim();
        definition.timeStampOutputFormat = timeStampOutputFormatText.getText().trim();

        if (selectedElement != null) {
            selectedElement.extractInputs();
            treeViewer.refresh();
        }

        StringBuffer errors = new StringBuffer();

        if (definition.definitionName.length() == 0) {
            errors.append(Messages.CustomXmlParserInputWizardPage_emptyLogTypeError);
            logtypeText.setBackground(COLOR_LIGHT_RED);
        } else {
            logtypeText.setBackground(COLOR_TEXT_BACKGROUND);
            for (CustomXmlTraceDefinition def : CustomXmlTraceDefinition.loadAll()) {
                if (definition.definitionName.equals(def.definitionName) &&
                        (editDefinitionName == null || !editDefinitionName.equals(definition.definitionName))) {
                    errors.append(Messages.CustomXmlParserInputWizardPage_duplicatelogTypeError);
                    logtypeText.setBackground(COLOR_LIGHT_RED);
                    break;
                }
            }
        }

        if (definition.rootInputElement == null) {
            errors.append(Messages.CustomXmlParserInputWizardPage_noDocumentError);
        }

        if (definition.rootInputElement != null) {
            logEntryFound = false;
            timeStampFound = false;

            errors.append(validateElement(definition.rootInputElement));

            if ((definition.rootInputElement.attributes != null && definition.rootInputElement.attributes.size() != 0)
                    || (definition.rootInputElement.childElements != null && definition.rootInputElement.childElements.size() != 0)
                    || errors.length() == 0) {
                if (!logEntryFound) {
                    errors.append(Messages.CustomXmlParserInputWizardPage_missingLogEntryError);
                }

                if (timeStampFound) {
                    if (timeStampOutputFormatText.getText().trim().length() == 0) {
                        errors.append(Messages.CustomXmlParserInputWizardPage_missingTimestampFmtError);
                        timeStampOutputFormatText.setBackground(COLOR_LIGHT_RED);
                    } else {
                        try {
                            new SimpleDateFormat(timeStampOutputFormatText.getText().trim());
                            timeStampOutputFormatText.setBackground(COLOR_TEXT_BACKGROUND);
                        } catch (IllegalArgumentException e) {
                            errors.append(Messages.CustomXmlParserInputWizardPage_invalidTimestampFmtError);
                            timeStampOutputFormatText.setBackground(COLOR_LIGHT_RED);
                        }
                    }
                } else {
                    timeStampPreviewText.setText(Messages.CustomXmlParserInputWizardPage_notimestamporAttributeError);
                }
            }
        } else {
            timeStampPreviewText.setText(Messages.CustomXmlParserInputWizardPage_notimestamporAttributeError);
        }

        if (errors.length() == 0) {
            setDescription(defaultDescription);
            setPageComplete(true);
        } else {
            setDescription(errors.toString());
            setPageComplete(false);
        }
    }

    /**
     * Clean up the specified XML element.
     *
     * @param inputElement
     *            The element to clean up
     * @return The validated element
     */
    public StringBuffer validateElement(InputElement inputElement) {
        StringBuffer errors = new StringBuffer();
        ElementNode elementNode = null;
        if (selectedElement != null && selectedElement.inputElement.equals(inputElement)) {
            elementNode = selectedElement;
        }
        if (inputElement == definition.rootInputElement) {
            if (inputElement.elementName.length() == 0) {
                errors.append(Messages.CustomXmlParserInputWizardPage_missingDocumentElementError);
                if (elementNode != null) {
                    elementNode.elementNameText.setBackground(COLOR_LIGHT_RED);
                }
            } else {
                if (elementNode != null) {
                    elementNode.elementNameText.setBackground(COLOR_TEXT_BACKGROUND);
                }
            }
        }
        if (inputElement != definition.rootInputElement) {
            if (inputElement.logEntry) {
                logEntryFound = true;
            }
            if (inputElement.inputName.equals(CustomTraceDefinition.TAG_TIMESTAMP)) {
                timeStampFound = true;
                if (inputElement.inputFormat.length() == 0) {
                    errors.append(Messages.CustomXmlParserInputWizardPage_timestampFormatPrompt
                            + " (" + Messages.CustomXmlParserInputWizardPage_timestampElementPrompt + " " + getName(inputElement) + "). "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                    if (elementNode != null) {
                        elementNode.tagText.setBackground(COLOR_LIGHT_RED);
                    }
                } else {
                    try {
                        new SimpleDateFormat(inputElement.inputFormat);
                        if (elementNode != null) {
                            elementNode.tagText.setBackground(COLOR_TEXT_BACKGROUND);
                        }
                    } catch (IllegalArgumentException e) {
                        errors.append(Messages.CustomXmlParserInputWizardPage_invalidTimestampFmtError
                                + " (" + Messages.CustomXmlParserInputWizardPage_timestampElementPrompt + " " + getName(inputElement) + "). "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                        if (elementNode != null) {
                            elementNode.tagText.setBackground(COLOR_LIGHT_RED);
                        }
                    }
                }
            } else if (inputElement.inputName.length() == 0) {
                errors.append(Messages.CustomXmlParserInputWizardPage_missingInputElementNameError);
                if (elementNode != null) {
                    elementNode.tagText.setBackground(COLOR_LIGHT_RED);
                }
            } else {
                if (elementNode != null) {
                    elementNode.tagText.setBackground(COLOR_TEXT_BACKGROUND);
                }
            }
        }
        if (inputElement.attributes != null) {
            if (elementNode != null) {
                for (Attribute attribute : elementNode.attributes) {
                    attribute.attributeNameText.setBackground(COLOR_TEXT_BACKGROUND);
                }
            }
            for (int i = 0; i < inputElement.attributes.size(); i++) {
                InputAttribute attribute = inputElement.attributes.get(i);
                boolean duplicate = false;
                for (int j = i + 1; j < inputElement.attributes.size(); j++) {
                    InputAttribute otherAttribute = inputElement.attributes.get(j);
                    if (otherAttribute.attributeName.equals(attribute.attributeName)) {
                        duplicate = true;
                        if (elementNode != null) {
                            elementNode.attributes.get(j).attributeNameText.setBackground(COLOR_LIGHT_RED);
                        }
                    }
                }
                if (attribute.attributeName.length() == 0) {
                    errors.append(Messages.CustomXmlParserInputWizardPage_missingAttribute
                            + " (" + Messages.CustomXmlParserInputWizardPage_attributePrompt + " " + getName(inputElement) + ": ?). "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                    if (elementNode != null) {
                        elementNode.attributes.get(i).attributeNameText.setBackground(COLOR_LIGHT_RED);
                    }
                } else if (duplicate) {
                    errors.append(Messages.CustomXmlParserInputWizardPage_duplicateAttributeError
                            + " (" + Messages.CustomXmlParserInputWizardPage_attributePrompt + " " + getName(attribute, inputElement) + "). "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                    if (elementNode != null) {
                        elementNode.attributes.get(i).attributeNameText.setBackground(COLOR_LIGHT_RED);
                    }
                }
                if (attribute.inputName.equals(CustomTraceDefinition.TAG_TIMESTAMP)) {
                    timeStampFound = true;
                    if (attribute.inputFormat.length() == 0) {
                        errors.append(Messages.CustomXmlParserInputWizardPage_missingTimestampInFmtError
                                + " (" + Messages.CustomXmlParserInputWizardPage_attributePrompt + " " + getName(attribute, inputElement) + "). "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                        if (elementNode != null) {
                            elementNode.attributes.get(i).tagText.setBackground(COLOR_LIGHT_RED);
                        }
                    } else {
                        try {
                            new SimpleDateFormat(attribute.inputFormat);
                            if (elementNode != null) {
                                elementNode.attributes.get(i).tagText.setBackground(COLOR_TEXT_BACKGROUND);
                            }
                        } catch (IllegalArgumentException e) {
                            errors.append(Messages.CustomXmlParserInputWizardPage_invalidTimestampInFmtError
                                    + " (" + Messages.CustomXmlParserInputWizardPage_attributePrompt + " " + getName(attribute, inputElement) + "). "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                            if (elementNode != null) {
                                elementNode.attributes.get(i).tagText.setBackground(COLOR_LIGHT_RED);
                            }
                        }
                    }
                } else if (attribute.inputName.length() == 0) {
                    errors.append(Messages.CustomXmlParserInputWizardPage_missingDataGroupNameError
                            + " (" + Messages.CustomXmlParserInputWizardPage_attributePrompt + " " + getName(attribute, inputElement) + "). "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                    if (elementNode != null) {
                        elementNode.attributes.get(i).tagText.setBackground(COLOR_LIGHT_RED);
                    }
                } else {
                    if (elementNode != null) {
                        elementNode.attributes.get(i).tagText.setBackground(COLOR_TEXT_BACKGROUND);
                    }
                }
            }
        }
        if (inputElement.childElements != null) {
            for (InputElement child : inputElement.childElements) {
                ElementNode childElementNode = null;
                if (selectedElement != null && selectedElement.inputElement.equals(child)) {
                    childElementNode = selectedElement;
                }
                if (childElementNode != null) {
                    childElementNode.elementNameText.setBackground(COLOR_TEXT_BACKGROUND);
                }
            }
            for (int i = 0; i < inputElement.childElements.size(); i++) {
                InputElement child = inputElement.childElements.get(i);
                ElementNode childElementNode = null;
                if (selectedElement != null && selectedElement.inputElement.equals(child)) {
                    childElementNode = selectedElement;
                }
                if (child.elementName.length() == 0) {
                    errors.append(Messages.CustomXmlParserInputWizardPage_missingElementNameError
                            + " (" + Messages.CustomXmlParserInputWizardPage_attributePrompt + " " + getName(child) + "). "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                    if (childElementNode != null) {
                        childElementNode.elementNameText.setBackground(COLOR_LIGHT_RED);
                    }
                } else {
                    boolean duplicate = false;
                    for (int j = i + 1; j < inputElement.childElements.size(); j++) {
                        InputElement otherChild = inputElement.childElements.get(j);
                        if (otherChild.elementName.equals(child.elementName)) {
                            duplicate = true;
                            ElementNode otherChildElementNode = null;
                            if (selectedElement != null && selectedElement.inputElement.equals(otherChild)) {
                                otherChildElementNode = selectedElement;
                            }
                            if (otherChildElementNode != null) {
                                otherChildElementNode.elementNameText.setBackground(COLOR_LIGHT_RED);
                            }
                        }
                    }
                    if (duplicate) {
                        errors.append(Messages.CustomXmlParserInputWizardPage_duplicateElementNameError
                                + " (" + Messages.CustomXmlParserInputWizardPage_attributePrompt + " " + getName(child) + "). "); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                        if (childElementNode != null) {
                            childElementNode.elementNameText.setBackground(COLOR_LIGHT_RED);
                        }
                    }
                }

                errors.append(validateElement(child));
            }
        }
        return errors;
    }

    /**
     * Get the trace definition.
     *
     * @return The trace definition
     */
    public CustomXmlTraceDefinition getDefinition() {
        return definition;
    }

    /**
     * Get the raw text input.
     *
     * @return The raw text input.
     */
    public char[] getInputText() {
        return inputText.getText().toCharArray();
    }
}
