/*******************************************************************************
 * Copyright (c) 2012 Per Kr. Soreide.
 * 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:
 *     Per Kr. Soreide - initial API and implementation
 *******************************************************************************/
package bndtools.release.ui;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;

import org.eclipse.jface.layout.TreeColumnLayout;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ComboBoxCellEditor;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
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.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.Widget;

import aQute.bnd.differ.Baseline;
import aQute.bnd.differ.Baseline.Info;
import aQute.bnd.version.Version;
import bndtools.release.Activator;
import bndtools.release.api.ReleaseOption;
import bndtools.release.nl.Messages;

public class BundleTree extends Composite {

    public final static String VERSION_STRING = "(\\d+)(\\.(\\d+)(\\.(\\d+)(\\.([-_\\da-zA-Z]+))?)?)?"; //$NON-NLS-1$
    public final static Pattern VERSION = Pattern.compile(VERSION_STRING);

    protected SashForm sashForm;
    protected Button showAll;
    protected Combo options;

    protected TreeViewer infoViewer;
    protected Composite infoViewerComposite;

    protected TreeViewer bundleTreeViewer;
    protected Composite bundleTreeViewerComposite;

    protected TreeContentProvider bundleTreeViewerProvider = new TreeContentProvider();
    protected InfoContentProvider infoTreeViewerProvider = new InfoContentProvider();

    private Map<Object, Version> initialSuggested;

    public BundleTree(Composite composite) {
        this(composite, SWT.NONE);
    }

    public BundleTree(Composite composite, int style) {
        super(composite, style);
        createControl();
    }

    public void createControl() {

        setLayout(createGridLayout());
        setLayoutData(createFillGridData());

        sashForm = new SashForm(this, SWT.VERTICAL);
        sashForm.setLayout(createGridLayout());
        sashForm.setLayoutData(createFillGridData());

        createInfoViewer(sashForm);

        Composite composite = new Composite(sashForm, SWT.NONE);
        composite.setLayout(createGridLayout());
        composite.setLayoutData(createFillGridData());

        createBundleTreeViewer(composite);

        createButtons(composite);

        sashForm.setWeights(new int[] {
            30, 70
        });
    }

    private void createInfoViewer(Composite container) {

        infoViewerComposite = new Composite(container, SWT.NONE);
        infoViewerComposite.setLayoutData(createFillGridData());

        TreeColumnLayout layout = new TreeColumnLayout();
        infoViewerComposite.setLayout(layout);

        infoViewer = new TreeViewer(infoViewerComposite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.FULL_SELECTION);
        infoViewer.setUseHashlookup(true);
        infoViewer.getTree()
            .setHeaderVisible(true);

        TreeViewerColumn treeViewerColumn = new TreeViewerColumn(infoViewer, SWT.NONE);
        TreeColumn treeColumn = treeViewerColumn.getColumn();
        layout.setColumnData(treeColumn, new ColumnWeightData(450, 180, true));
        treeColumn.setText(Messages.bundleAndPackageName);
        treeViewerColumn.setLabelProvider(new InfoLabelProvider());

        treeViewerColumn = new TreeViewerColumn(infoViewer, SWT.NONE);
        treeColumn = treeViewerColumn.getColumn();
        layout.setColumnData(treeColumn, new ColumnWeightData(80, 80, true));
        treeColumn.setText(Messages.version2);
        treeViewerColumn.setLabelProvider(new ColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof Baseline) {
                    return ((Baseline) element).getOlderVersion()
                        .getWithoutQualifier()
                        .toString();
                }
                if (element instanceof Info) {
                    return ((Info) element).olderVersion.toString();
                }
                return ""; //$NON-NLS-1$
            }
        });

        treeViewerColumn = new TreeViewerColumn(infoViewer, SWT.NONE);
        treeColumn = treeViewerColumn.getColumn();
        layout.setColumnData(treeColumn, new ColumnWeightData(80, 80, true));
        treeColumn.setText(Messages.newVersion);
        treeViewerColumn.setLabelProvider(new ColumnLabelProvider() {
            @Override
            public String getText(Object element) {
                if (element instanceof Baseline) {
                    return ((Baseline) element).getSuggestedVersion()
                        .toString();
                }
                if (element instanceof Info) {
                    return ((Info) element).suggestedVersion != null ? ((Info) element).suggestedVersion.toString() : ""; //$NON-NLS-1$
                }
                return ""; //$NON-NLS-1$
            }
        });
        treeViewerColumn.setEditingSupport(new InlineComboEditingSupport(infoViewer));

        infoViewer.setContentProvider(infoTreeViewerProvider);
        infoViewer.setAutoExpandLevel(2);

    }

    private void createBundleTreeViewer(Composite container) {

        bundleTreeViewerComposite = new Composite(container, SWT.NONE);
        bundleTreeViewerComposite.setLayoutData(createFillGridData());

        TreeColumnLayout layout = new TreeColumnLayout();
        bundleTreeViewerComposite.setLayout(layout);

        bundleTreeViewer = new TreeViewer(bundleTreeViewerComposite, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.FULL_SELECTION);
        bundleTreeViewer.setUseHashlookup(true);
        bundleTreeViewer.getTree()
            .setHeaderVisible(true);

        TreeViewerColumn treeViewerColumn = new TreeViewerColumn(bundleTreeViewer, SWT.NONE);
        TreeColumn treeColumn = treeViewerColumn.getColumn();
        layout.setColumnData(treeColumn, new ColumnWeightData(100, 340, true));
        treeColumn.setText(Messages.symbNameResources);
        treeViewerColumn.setLabelProvider(new TreeLabelProvider());

        bundleTreeViewer.setContentProvider(bundleTreeViewerProvider);
        bundleTreeViewer.setAutoExpandLevel(3);
    }

    private void createButtons(Composite parent) {

        GridLayout gridLayout = new GridLayout(3, false);
        gridLayout.marginWidth = 0;

        Composite composite = new Composite(parent, SWT.NONE);
        composite.setLayout(gridLayout);

        GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
        gridData.grabExcessHorizontalSpace = true;
        composite.setLayoutData(gridData);

        showAll = new Button(composite, SWT.CHECK);
        showAll.setText(Messages.showAllPackages);
        showAll.setFont(getFont());
        showAll.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent event) {
                Widget widget = event.widget;
                if (widget == showAll) {

                    bundleTreeViewerProvider.setShowAll(!bundleTreeViewerProvider.isShowAll());
                    infoTreeViewerProvider.setShowAll(!infoTreeViewerProvider.isShowAll());

                    if (bundleTreeViewer.getInput() != null || infoViewer.getInput() != null) {
                        bundleTreeViewer.setSelection(null, false);
                        infoViewer.setSelection(null, false);

                        boolean showInfoView = showInfoView(bundleTreeViewer.getInput());
                        if (showInfoView) {
                            sashForm.setMaximizedControl(null);
                        } else {
                            sashForm.setMaximizedControl(bundleTreeViewerComposite);
                        }
                        bundleTreeViewer.refresh();
                        infoViewer.refresh();
                        sashForm.redraw();
                    }
                }
            }
        });

        gridData = new GridData(SWT.END, SWT.CENTER, true, true);

        Label label = new Label(composite, SWT.NONE);
        label.setText(Messages.releaseOption);
        label.setLayoutData(gridData);

        options = new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
        options.setItems(new String[] {
            Messages.updateVersionsAndRelease, Messages.updateVersions, Messages.release
        });
        options.add(Messages.comboSelectText, 0);
        options.select(0);

    }

    public void setInput(Object input) {

        boolean showInfoView = showInfoView(input);
        if (showInfoView) {
            sashForm.setMaximizedControl(null);
        } else {
            sashForm.setMaximizedControl(bundleTreeViewerComposite);
        }

        if (input == null) {
            bundleTreeViewer.setInput(Collections.emptyList());
            infoViewer.setInput(Collections.emptyList());
        } else {
            bundleTreeViewer.getTree()
                .setRedraw(false);
            bundleTreeViewer.setSelection(null, false);
            bundleTreeViewer.setInput(input);
            bundleTreeViewer.getTree()
                .setRedraw(true);
            infoViewer.getTree()
                .setRedraw(false);
            infoViewer.setSelection(null, false);
            infoViewer.setInput(input);
            infoViewer.getTree()
                .setRedraw(true);
        }
        sashForm.redraw();
    }

    private static boolean showInfoView(Object input) {
        if (input instanceof List && ((List<?>) input).size() > 0) {
            Object obj = ((List<?>) input).get(0);
            if (obj instanceof Baseline) {
                return true;
            }
        }
        return false;
    }

    protected Version getInitialSuggestedVersion(Object obj) {
        if (initialSuggested == null) {
            initialSuggested = new HashMap<Object, Version>();
        }

        Version version = initialSuggested.get(obj);
        if (version != null) {
            return version;
        }

        if (obj instanceof Info) {
            version = ((Info) obj).suggestedVersion;
        } else {
            version = ((Baseline) obj).getSuggestedVersion();
        }
        initialSuggested.put(obj, version);
        return version;
    }

    class InlineComboEditingSupport extends EditingSupport {

        protected ComboBoxCellEditor editor;

        public InlineComboEditingSupport(ColumnViewer viewer) {
            super(viewer);
            this.editor = new ComboBoxCellEditor((Composite) viewer.getControl(), new String[] {});

            Control control = editor.getControl();
            ((CCombo) control).addSelectionListener(new SelectionListener() {
                @Override
                public void widgetSelected(SelectionEvent e) {
                    editor.deactivate();
                }

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

        @Override
        protected boolean canEdit(Object element) {
            if (element instanceof Baseline) {
                return true;
            }
            return false;
        }

        @Override
        protected CellEditor getCellEditor(Object element) {

            Set<String> versions = new TreeSet<String>();
            if (element instanceof Baseline) {
                Baseline info = (Baseline) element;
                versions.add(getInitialSuggestedVersion(info).toString());
                if (getInitialSuggestedVersion(info).compareTo(Version.ONE) < 0) {
                    versions.add(Version.ONE.toString());
                }
            }
            if (element instanceof Info) {
                Info info = (Info) element;
                versions.add(getInitialSuggestedVersion(info).toString());
                if (getInitialSuggestedVersion(info).compareTo(Version.ONE) < 0) {
                    versions.add(Version.ONE.toString());
                }
            }

            editor.setItems(versions.toArray(new String[0]));
            return editor;
        }

        @Override
        protected Object getValue(Object element) {
            return null;
            // Not needed
        }

        @Override
        protected void setValue(Object element, Object value) {
            // Not needed
        }

        @Override
        protected void initializeCellEditorValue(CellEditor cellEditor, ViewerCell cell) {

            String selectedVersion = ""; //$NON-NLS-1$
            if (cell.getElement() instanceof Baseline) {
                selectedVersion = ((Baseline) cell.getElement()).getSuggestedVersion()
                    .toString();
            } else if (cell.getElement() instanceof Info) {
                selectedVersion = ((Info) cell.getElement()).suggestedVersion.toString();
            }

            String[] items = ((ComboBoxCellEditor) cellEditor).getItems();
            int idx = -1;
            for (int i = 0; i < items.length; i++) {
                if (items[i].equals(selectedVersion)) {
                    idx = i;
                    break;
                }
            }
            if (idx > -1)
                cellEditor.setValue(idx);
            cell.setText(selectedVersion);
        }

        @Override
        protected void saveCellEditorValue(CellEditor cellEditor, ViewerCell cell) {
            int idx = ((Integer) cellEditor.getValue()).intValue();
            String[] items = ((ComboBoxCellEditor) cellEditor).getItems();

            String selectedVersion;
            if (idx > -1) {
                selectedVersion = items[idx];
            } else {
                selectedVersion = ((CCombo) cellEditor.getControl()).getText();
            }

            Version version;
            try {
                version = Version.parseVersion(selectedVersion);
            } catch (IllegalArgumentException e) {
                Activator.message(String.format(Messages.versionInvalid, selectedVersion));
                return;
            }

            cell.setText(selectedVersion);

            if (cell.getElement() instanceof Baseline) {
                ((Baseline) cell.getElement()).setSuggestedVersion(version);
            } else if (cell.getElement() instanceof Info) {
                ((Info) cell.getElement()).suggestedVersion = version;
            }
        }
    }

    public ReleaseOption getReleaseOption() {
        return ReleaseOption.parse(options.getText());
    }

    private static GridLayout createGridLayout() {
        GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = 1;
        gridLayout.horizontalSpacing = 0;
        gridLayout.verticalSpacing = 0;
        gridLayout.marginWidth = 0;
        gridLayout.marginHeight = 0;
        return gridLayout;
    }

    private static GridData createFillGridData() {
        return new GridData(GridData.FILL, GridData.FILL, true, true);
    }
}
