/*******************************************************************************
 * 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.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

import org.bndtools.utils.swt.FilterPanelPart;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckboxTableViewer;
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.ICheckStateProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITableColorProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
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.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;

import aQute.bnd.build.ProjectBuilder;
import aQute.libg.glob.Glob;
import bndtools.release.Activator;
import bndtools.release.ProjectDiff;
import bndtools.release.nl.Messages;
import bndtools.release.ui.TableSortingEnabler.IColumnContentProvider;

public class ProjectListControl {

    private final Color COLOR_RELEASE_REQUIRED;
    private final Color COLOR_VERSION_UPDATE_REQUIRED;

    private Table projects;
    private final String[] releaseRepos;
    private CheckboxTableViewer tableViewer;
    private final SelectionListener selectionListener;
    private List<ProjectDiff> projectDiffs;
    private ContentProvider contentProvider;

    private static final String PROJECT_COLUMN = "project";
    private static final String REPOSITORY_COLUMN = "repository";
    private static final String BUNDLES_COLUMN = "bundles";

    // Set column names
    private static final String[] columnNames = new String[] {
        PROJECT_COLUMN, REPOSITORY_COLUMN, BUNDLES_COLUMN
    };

    public ProjectListControl(SelectionListener selectionListener, String[] releaseRepos) {
        this.selectionListener = selectionListener;
        this.releaseRepos = releaseRepos;

        COLOR_VERSION_UPDATE_REQUIRED = new Color(Display.getCurrent(), 247, 200, 200);
        COLOR_RELEASE_REQUIRED = new Color(Display.getCurrent(), 206, 255, 206);
    }

    public void createControl(final Composite parent) {

        createFilter(parent);

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

        // Create the composite
        Composite composite = new Composite(parent, SWT.NONE);
        composite.setLayout(gridLayout);
        composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        createTableLayout(composite);
        createLegend(composite);
    }

    private void createFilter(Composite parent) {

        Composite composite = new Composite(parent, SWT.NONE);
        GridLayout gridLayout = new GridLayout(2, false);
        gridLayout.marginHeight = gridLayout.marginWidth = 0;
        gridLayout.horizontalSpacing = 0;
        gridLayout.verticalSpacing = 0;

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

        FilterPanelPart filterPart = new FilterPanelPart(Activator.getDefault()
            .getScheduler());
        filterPart.createControl(composite, 0, 0);
        filterPart.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent event) {
                String filter = (String) event.getNewValue();
                updatedFilter(filter);
            }
        });

        ToolBar toolbar = new ToolBar(composite, SWT.FLAT);
        ToolItem tiCheckAll = new ToolItem(toolbar, SWT.FLAT);
        tiCheckAll.setImage(Activator.getImageDescriptor("icons/check_all.gif")
            .createImage());
        tiCheckAll.setToolTipText(Messages.checkAll);
        tiCheckAll.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                Object[] objs = contentProvider.getElements(null);
                for (Object obj : objs) {
                    ProjectDiff diff = (ProjectDiff) obj;
                    diff.setRelease(true);
                }
                tableViewer.refresh();
            }
        });

        ToolItem tiUncheckAll = new ToolItem(toolbar, SWT.FLAT);
        tiUncheckAll.setImage(Activator.getImageDescriptor("icons/uncheck_all.gif")
            .createImage());
        tiUncheckAll.setToolTipText(Messages.uncheckAll);
        tiUncheckAll.addSelectionListener(new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                Object[] objs = contentProvider.getElements(null);
                for (Object obj : objs) {
                    ProjectDiff diff = (ProjectDiff) obj;
                    diff.setRelease(false);
                }
                tableViewer.refresh();
            }
        });
    }

    private void updatedFilter(String filterString) {
        String newFilter;
        if (filterString == null || filterString.length() == 0 || filterString.trim()
            .equals("*"))
            newFilter = null;
        else
            newFilter = "*" + filterString.trim() + "*";
        contentProvider.setFilter(newFilter);
        tableViewer.refresh();
    }

    private void createTableLayout(Composite parent) {

        Composite composite = new Composite(parent, SWT.NONE);
        composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        // Add TableColumnLayout
        TableColumnLayout layout = new TableColumnLayout();
        composite.setLayout(layout);

        // Instantiate TableViewer
        projects = new Table(composite, SWT.CHECK | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION);
        projects.setHeaderVisible(true);
        projects.setLinesVisible(true);
        projects.addSelectionListener(new SelectionListener() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                selectionListener.widgetSelected(e);
            }

            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                selectionListener.widgetDefaultSelected(e);
            }
        });
        tableViewer = new CheckboxTableViewer(projects);
        tableViewer.setUseHashlookup(true);

        // Project
        TableViewerColumn tableViewerColumn = new TableViewerColumn(tableViewer, SWT.LEFT);
        TableColumn tableCol = tableViewerColumn.getColumn();
        layout.setColumnData(tableCol, new ColumnWeightData(60, 100, true));
        tableCol.setText(Messages.project1);

        // Repository
        tableViewerColumn = new TableViewerColumn(tableViewer, SWT.LEFT);
        tableCol = tableViewerColumn.getColumn();
        layout.setColumnData(tableCol, new ColumnWeightData(26, 80, true));
        tableCol.setText(Messages.repository);
        tableViewerColumn.setEditingSupport(new InlineComboEditingSupport(tableViewer));

        // Bundles
        tableViewerColumn = new TableViewerColumn(tableViewer, SWT.CENTER);
        tableCol = tableViewerColumn.getColumn();
        layout.setColumnData(tableCol, new ColumnWeightData(14, 35, true));
        tableCol.setText(Messages.bundles);

        contentProvider = new ContentProvider();
        tableViewer.setContentProvider(contentProvider);
        tableViewer.setLabelProvider(new TableLabelProvider());
        tableViewer.setColumnProperties(columnNames);
        tableViewer.setCheckStateProvider(new ICheckStateProvider() {

            @Override
            public boolean isGrayed(Object element) {
                return false;
            }

            @Override
            public boolean isChecked(Object element) {
                ProjectDiff diff = (ProjectDiff) element;
                return diff.isRelease();
            }
        });

        projects.pack();

        TableSortingEnabler.applyTableColumnSorting(tableViewer);
    }

    private Image createSmallIcon(Display display) {
        Point[] iconSizes = display.getIconSizes();
        Point chosen = null;

        if (iconSizes.length == 0) {
            chosen = new Point(16, 16);
        } else {
            chosen = iconSizes[0];
        }

        return new Image(display, chosen.x, chosen.y);
    }

    private Image createSolidIcon(Display display, Color color) {
        Image img = createSmallIcon(display);

        GC gc = new GC(img);
        gc.setBackground(color);
        gc.fillRectangle(img.getBounds());
        gc.dispose();

        return img;
    }

    private void createLegend(Composite parent) {

        final Image updateRequiredImage = createSolidIcon(parent.getDisplay(), COLOR_VERSION_UPDATE_REQUIRED);
        final Image releaseRequiredImage = createSolidIcon(parent.getDisplay(), COLOR_RELEASE_REQUIRED);

        Composite composite = new Composite(parent, SWT.NONE);
        GridLayout gridLayout = new GridLayout(2, false);
        gridLayout.verticalSpacing = 2;
        gridLayout.marginHeight = 2;
        gridLayout.marginWidth = 0;
        gridLayout.horizontalSpacing = 2;
        composite.setLayout(gridLayout);

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

        Label l = new Label(composite, SWT.BORDER);
        l.setImage(updateRequiredImage);
        l.addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(DisposeEvent e) {
                updateRequiredImage.dispose();
            }
        });

        l = new Label(composite, SWT.NONE);
        l.setText(Messages.versionUpdateRequired);

        l = new Label(composite, SWT.BORDER);
        l.setImage(releaseRequiredImage);
        l.addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(DisposeEvent e) {
                releaseRequiredImage.dispose();
            }
        });

        l = new Label(composite, SWT.NONE);
        l.setText(Messages.releaseRequired);
    }

    public void setInput(List<ProjectDiff> projectDiffs) {
        this.projectDiffs = projectDiffs;
        tableViewer.setInput(projectDiffs);
    }

    public Table getTable() {
        return projects;
    }

    public void setSelected(int index) {
        projects.select(index);
    }

    private class ContentProvider implements IStructuredContentProvider, IColumnContentProvider {

        private final AtomicReference<String> filterRef = new AtomicReference<String>();

        public void setFilter(String filter) {
            this.filterRef.set(filter);
        }

        @Override
        public void dispose() {}

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

        @Override
        public Object[] getElements(Object parent) {
            final String filter = filterRef.get();
            if (filter == null || "".equals(filter))
                return projectDiffs.toArray();

            Glob glob = new Glob(filter);
            List<ProjectDiff> filtered = new ArrayList<ProjectDiff>();
            for (ProjectDiff diff : projectDiffs) {
                if (glob.matcher(diff.getProject()
                    .getName())
                    .matches()) {
                    filtered.add(diff);
                }
            }
            return filtered.toArray();
        }

        @Override
        public Comparable<?> getValue(Object element, int columnIndex) {
            ProjectDiff diff = (ProjectDiff) element;
            switch (columnIndex) {
                case 0 :
                    return diff.getProject()
                        .getName();
                case 1 :
                    return diff.getReleaseRepository();
                case 2 :
                    int bundles = -1;
                    try (ProjectBuilder pb = diff.getProject()
                        .getBuilder(null)) {
                        bundles = pb.getSubBuilders()
                            .size();
                    } catch (Exception e) {
                        /* ignore */
                    }
                    return Integer.valueOf(bundles);
                default :
                    return "";
            }
        }
    }

    private class TableLabelProvider extends LabelProvider implements ITableColorProvider, ITableLabelProvider {

        @Override
        public Image getColumnImage(Object element, int columnIndex) {
            return null;
        }

        @Override
        public String getColumnText(Object element, int columnIndex) {
            String text = "";
            ProjectDiff diff = (ProjectDiff) element;
            switch (columnIndex) {
                case 0 :
                    text = diff.getProject()
                        .getName();
                    break;
                case 1 :
                    text = diff.getReleaseRepository();
                    if (text == null) {
                        text = diff.getDefaultReleaseRepository();
                    }
                    break;
                case 2 :
                    int bundles = -1;
                    try (ProjectBuilder pb = diff.getProject()
                        .getBuilder(null)) {
                        bundles = pb.getSubBuilders()
                            .size();
                    } catch (Exception e) {
                        /* ignore */
                    }
                    text = String.valueOf(bundles);
                    break;
                default :
                    break;
            }
            return text;
        }

        @Override
        public Color getBackground(Object element, int columnIndex) {
            ProjectDiff diff = (ProjectDiff) element;
            if (diff.isVersionUpdateRequired()) {
                return COLOR_VERSION_UPDATE_REQUIRED;
            }
            if (diff.isReleaseRequired()) {
                return COLOR_RELEASE_REQUIRED;
            }
            return null;
        }

        @Override
        public Color getForeground(Object element, int columnIndex) {
            return null;
        }
    }

    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) {
            return true;
        }

        @Override
        protected CellEditor getCellEditor(Object element) {
            editor.setItems(releaseRepos);
            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 repository = ""; //$NON-NLS-1$
            repository = ((ProjectDiff) cell.getElement()).getReleaseRepository();
            if (repository == null) {
                repository = ((ProjectDiff) cell.getElement()).getDefaultReleaseRepository();
            }
            String[] items = ((ComboBoxCellEditor) cellEditor).getItems();
            int idx = -1;
            for (int i = 0; i < items.length; i++) {
                if (items[i].equals(repository)) {
                    idx = i;
                    break;
                }
            }
            if (idx > -1)
                cellEditor.setValue(idx);
            cell.setText(repository);
        }

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

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

            cell.setText(repository);
            ((ProjectDiff) cell.getElement()).setReleaseRepository(repository);
        }
    }

    public void dispose() {
        COLOR_RELEASE_REQUIRED.dispose();
        COLOR_VERSION_UPDATE_REQUIRED.dispose();
    }
}
