/*******************************************************************************
 * Copyright (c) 2015, 2016 Ericsson
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Alexandre Montplaisir - Initial API and implementation
 *   Matthew Khouzam - Initial API and implementation
 *******************************************************************************/

package org.eclipse.tracecompass.tmf.ui.viewers.table;

import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;

import java.util.Collections;
import java.util.Comparator;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.deferred.DeferredContentProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.tracecompass.tmf.ui.viewers.TmfViewer;

/**
 * Generic {@link TableViewer} wrapper with most standard features enabled.
 * <p>
 * It provides the following features: <br>
 * - Sortable columns <br>
 * - Movable columns <br>
 * - Resizable columns <br>
 * - Tracking last clicked columns
 * <p>
 * The user of this class should add columns to the table by using the
 * {@link #createColumn(String, ColumnLabelProvider, Comparator)} method, and
 * set the content provider and input on the supplied {@link TableViewer}.
 *
 * @since 1.1
 */
public class TmfSimpleTableViewer extends TmfViewer {

    /**
     * Viewer comparator that ignores the element label strings and uses the
     * given comparator to compare the elements directly.
     */
    private static class ElementComparator<T> extends ViewerComparator {

        private Comparator<T> elementComparator;

        public ElementComparator(Comparator<T> comparator) {
            elementComparator = comparator;
        }

        @SuppressWarnings("unchecked")
        @Override
        public int compare(Viewer viewer, Object e1, Object e2) {
            return elementComparator.compare((T) e1, (T) e2);
        }
    }

    /**
     * Comparator that compares the text of a column given by the label
     * provider, using the text's String ordering.
     */
    private static class ColumnLabelComparator implements Comparator<Object> {
        private final ILabelProvider fLabelProvider;

        public ColumnLabelComparator(ILabelProvider labelProvider) {
            fLabelProvider = labelProvider;
        }

        @Override
        public int compare(Object o1, Object o2) {
            String s1 = nullToEmptyString(fLabelProvider.getText(o1));
            String s2 = nullToEmptyString(fLabelProvider.getText(o2));
            return s1.compareTo(s2);
        }
    }

    private final class MouseColumnListener extends MouseAdapter {
        @Override
        public void mouseDown(MouseEvent e) {
            ViewerCell cell = fTableViewer.getCell(new Point(e.x, e.y));
            fSelectedColumn = (cell != null) ? cell.getColumnIndex() : -1;
        }
    }

    private final class ColumnSorter<T> extends SelectionAdapter {
        private final @NonNull TableColumn fColumn;
        private final @NonNull Comparator<T> fComparator;

        private ColumnSorter(@NonNull TableColumn column, @NonNull Comparator<T> comparator) {
            fColumn = column;
            fComparator = comparator;
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            Table table = fTableViewer.getTable();
            TableColumn prevSortcolumn = table.getSortColumn();
            if (prevSortcolumn == fColumn) {
                flipSortDirection();
            }
            table.setSortDirection(fDirection);
            table.setSortColumn(fColumn);
            Comparator<T> comparator;
            if (fDirection == SWT.DOWN) {
                comparator = fComparator;
            } else {
                comparator = checkNotNull(Collections.reverseOrder(fComparator));
            }
            IContentProvider contentProvider = fTableViewer.getContentProvider();
            if (contentProvider instanceof DeferredContentProvider) {
                DeferredContentProvider deferredContentProvider = (DeferredContentProvider) contentProvider;
                deferredContentProvider.setSortOrder(comparator);
            } else if (contentProvider instanceof ISortingLazyContentProvider) {
                ISortingLazyContentProvider sortingLazyContentProvider = (ISortingLazyContentProvider) contentProvider;
                sortingLazyContentProvider.setSortOrder(comparator);
            } else {
                fTableViewer.setComparator(new ElementComparator<>(comparator));
            }
        }
    }

    private static final int DEFAULT_COL_WIDTH = 200;
    private final TableViewer fTableViewer;

    private int fDirection;
    private int fSelectedColumn;

    private MenuManager fTablePopupMenuManager;

    /**
     * Constructor that initializes the parent of the viewer
     *
     * @param table
     *            the {@link TableViewer} to wrap
     */
    public TmfSimpleTableViewer(TableViewer table) {
        super(table.getControl().getParent());
        fTableViewer = table;

        final Table tableControl = fTableViewer.getTable();
        tableControl.setHeaderVisible(true);
        tableControl.setLinesVisible(true);

        fDirection = SWT.DOWN;
        fTableViewer.setUseHashlookup(true);
        fTableViewer.getControl().addMouseListener(new MouseColumnListener());

        fTablePopupMenuManager = new MenuManager();
        fTablePopupMenuManager.setRemoveAllWhenShown(true);

        fTablePopupMenuManager.addMenuListener((final @Nullable IMenuManager manager) -> {
            TableViewer viewer = getTableViewer();
            ISelection selection = viewer.getSelection();
            if (selection instanceof IStructuredSelection) {
                IStructuredSelection sel = (IStructuredSelection) selection;
                if (manager != null) {
                    appendToTablePopupMenu(manager, sel);
                }
            }
        });

        Menu tablePopup = fTablePopupMenuManager.createContextMenu(getTableViewer().getTable());
        getTableViewer().getTable().setMenu(tablePopup);

        tableControl.addDisposeListener((e) -> {
            internalDispose();
        });
    }

    @Override
    public void dispose() {
        if (fTableViewer != null) {
            fTableViewer.getControl().dispose();
        }
    }

    private void internalDispose() {
        if (fTablePopupMenuManager != null) {
            fTablePopupMenuManager.dispose();
        }
        super.dispose();
    }

    /**
     * Method to add commands to the context sensitive menu.
     * @param manager
     *          the menu manager
     * @param sel
     *          the current selection
     * @since 2.0
     */
    protected void appendToTablePopupMenu(@NonNull IMenuManager manager, @NonNull IStructuredSelection sel) {
        // Do nothing
    }

    /**
     * Create a column for the table. The column will have a default width set,
     * and will be resizable, moveable and sortable.
     *
     * @param name
     *            the name of the column
     * @param provider
     *            the label provider of the column
     * @param comparator
     *            the comparator associated with clicking on the column, if it
     *            is null, a string comparator on the label will be used
     * @return the column that was created
     * @since 2.0
     */
    public final <T> TableColumn createColumn(String name, ColumnLabelProvider provider, @Nullable Comparator<T> comparator) {
        TableViewerColumn col = new TableViewerColumn(fTableViewer, SWT.NONE);
        col.setLabelProvider(provider);
        final TableColumn column = col.getColumn();
        column.setWidth(DEFAULT_COL_WIDTH);
        column.setText(name);
        column.setResizable(true);
        column.setMoveable(true);
        if (comparator == null) {
            column.addSelectionListener(new ColumnSorter<>(column, new ColumnLabelComparator(provider)));
        } else {
            column.addSelectionListener(new ColumnSorter<>(column, comparator));
        }
        return column;
    }

    /**
     * Reverse the sort direction
     */
    private void flipSortDirection() {
        if (fDirection == SWT.DOWN) {
            fDirection = SWT.UP;
        } else {
            fDirection = SWT.DOWN;
        }
    }

    @Override
    public final Control getControl() {
        return fTableViewer.getControl();
    }

    /**
     * Gets the wrapped table viewer
     *
     * @return the table viewer
     */
    public final TableViewer getTableViewer() {
        return fTableViewer;
    }

    /**
     * Get the selected column index
     *
     * @return the selected column index or -1
     */
    public final int getColumnIndex() {
        return fSelectedColumn;
    }

    @Override
    public final void refresh() {
        fTableViewer.refresh();
    }
}