/*******************************************************************************
 * Copyright (c) 2009, 2015 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:
 *   Francois Chouinard - Initial API and implementation
 *   Bernd Hufmann - Filled with content
 *   Markus Schorn - Bug 448058: Use org.eclipse.remote in favor of RSE
 *******************************************************************************/

package org.eclipse.tracecompass.internal.lttng2.control.ui.views;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.remote.core.IRemoteConnection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.model.ITraceControlComponent;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.model.ITraceControlComponentChangedListener;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.model.impl.TargetNodeComponent;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.model.impl.TraceControlContentProvider;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.model.impl.TraceControlLabelProvider;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.model.impl.TraceControlRoot;
import org.eclipse.tracecompass.tmf.remote.core.proxy.TmfRemoteConnectionFactory;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.progress.UIJob;

/**
 * <p>
 * View implementation for Trace Control.
 * </p>
 *
 * @author Bernd Hufmann
 */
public class ControlView extends ViewPart implements ITraceControlComponentChangedListener {

    // ------------------------------------------------------------------------
    // Constants
    // ------------------------------------------------------------------------

    /**
     * View ID.
     */
    public static final String ID = "org.eclipse.linuxtools.internal.lttng2.ui.views.control"; //$NON-NLS-1$

    private static final String KEY_REMOTE_CONNECTION_NAME = "rc_name_"; //$NON-NLS-1$
    private static final String KEY_REMOTE_PROVIDER = "rc_id_"; //$NON-NLS-1$
    /**
     * The default expand level.
     */
    private static final int DEFAULT_EXPAND_LEVEL = 3;

    // ------------------------------------------------------------------------
    // Attributes
    // ------------------------------------------------------------------------

    /**
     * The tree viewer.
     */
    private TreeViewer fTreeViewer = null;
    /**
     * The trace control root node. This provides access to the whole model.
     */
    private ITraceControlComponent fRoot = null;

    private List<IRemoteConnection> fInitialConnections;

    // ------------------------------------------------------------------------
    // Accessors
    // ------------------------------------------------------------------------

    /**
     * Returns the trace control tree node (model)
     *
     * @return the trace control tree node (model).
     */
    public ITraceControlComponent getTraceControlRoot() {
        return fRoot;
    }

    // ------------------------------------------------------------------------
    // Operations
    // ------------------------------------------------------------------------

    @Override
    public void createPartControl(Composite parent) {
        // Filter that shows all children of a match node
        PatternFilter filter = new PatternFilter() {
            @Override
            protected boolean isLeafMatch(Viewer viewer, Object element) {
                ITraceControlComponent parentElement = (ITraceControlComponent) element;
                while (parentElement != null) {
                    if (super.isLeafMatch(viewer, parentElement)) {
                        return true;
                    }
                    parentElement = parentElement.getParent();
                }
                return false;
            }
        };

        // Create filtered tree
        FilteredTree filteredTree = new FilteredTree(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, filter, true) {
            @Override
            protected void updateToolbar(boolean visible) {
                super.updateToolbar(visible);
                treeViewer.expandToLevel(DEFAULT_EXPAND_LEVEL);
            }
        };

        fTreeViewer = filteredTree.getViewer();
        ColumnViewerToolTipSupport.enableFor(fTreeViewer);

        fTreeViewer.setContentProvider(new TraceControlContentProvider());
        fTreeViewer.setLabelProvider(new TraceControlLabelProvider());

        // Create model root
        fRoot = new TraceControlRoot();
        fRoot.addComponentListener(this);
        if (fInitialConnections != null) {
            for (IRemoteConnection rc : fInitialConnections) {
                TargetNodeComponent node = new TargetNodeComponent(rc.getName(), fRoot, rc);
                fRoot.addChild(node);
            }
            fInitialConnections = null;
        }
        fTreeViewer.setInput(fRoot);

        // Create context menu for the tree viewer
        createContextMenu();

        getSite().setSelectionProvider(fTreeViewer);
    }

    @Override
    public void saveState(IMemento memento) {
        int i = 0;
        for (ITraceControlComponent cmp : fRoot.getChildren()) {
            if (cmp instanceof TargetNodeComponent) {
                IRemoteConnection rc = ((TargetNodeComponent) cmp).getRemoteSystemProxy().getRemoteConnection();
                memento.putString(KEY_REMOTE_PROVIDER + i, rc.getConnectionType().getId());
                memento.putString(KEY_REMOTE_CONNECTION_NAME + i, rc.getName());
                i++;
            }
        }
        super.saveState(memento);
    }


    @Override
    public void init(IViewSite site, IMemento memento) throws PartInitException {
        super.init(site, memento);
        if (memento != null) {
            fInitialConnections = new ArrayList<>();
            for(int i = 0; ; i++) {
                String id = memento.getString(KEY_REMOTE_PROVIDER + i);
                String name = memento.getString(KEY_REMOTE_CONNECTION_NAME + i);
                if ((id == null) || (name == null)) {
                    break;
                }
                IRemoteConnection conn = TmfRemoteConnectionFactory.getRemoteConnection(id, name);
                if (conn != null) {
                    fInitialConnections.add(conn);
                }
            }
        }
    }

    @Override
    public void setFocus() {
        fTreeViewer.getControl().setFocus();
    }

    @Override
    public void componentAdded(ITraceControlComponent parent, ITraceControlComponent component) {
        componentChanged(parent);
    }

    @Override
    public void componentRemoved(ITraceControlComponent parent, ITraceControlComponent component) {
        componentChanged(parent);
    }

    @Override
    public void componentChanged(final ITraceControlComponent component) {
        if (fTreeViewer.getTree().isDisposed()) {
            return;
        }

        UIJob myJob = new UIJob("Refresh") { //$NON-NLS-1$
            @Override
            public IStatus runInUIThread(IProgressMonitor monitor) {
                if (fTreeViewer.getTree().isDisposed()) {
                    return Status.OK_STATUS;
                }

                fTreeViewer.refresh(component);

                // Change selection needed
                final ISelection sel = fTreeViewer.getSelection();
                fTreeViewer.setSelection(null);
                fTreeViewer.setSelection(sel);

                // Show component that was changed
                fTreeViewer.reveal(component);

                return Status.OK_STATUS;
            }
        };
        myJob.setUser(false);
        myJob.setSystem(true);
        myJob.schedule();
    }

    /**
     * Sets the selected component in the tree
     * @param component - component to select
     */
    public void setSelection(ITraceControlComponent component) {
        ITraceControlComponent[] components = new ITraceControlComponent[1];
        components[0] = component;
        setSelection(components);
    }

    /**
     * Sets the selected components in the tree
     * @param components - array of components to select
     */
    public void setSelection(ITraceControlComponent[] components) {
        final StructuredSelection selection = new StructuredSelection(components);
        UIJob myJob = new UIJob("Select") { //$NON-NLS-1$
            @Override
            public IStatus runInUIThread(IProgressMonitor monitor) {
                fTreeViewer.setSelection(selection);
                return Status.OK_STATUS;
            }
        };
        myJob.setUser(false);
        myJob.schedule();
    }

    // ------------------------------------------------------------------------
    // Helper methods
    // ------------------------------------------------------------------------
    /**
     * Creates the context sensitive menu.
     */
    private void createContextMenu() {
        // First we create a menu Manager
        final MenuManager menuManager = new MenuManager();
        final Menu menu = menuManager.createContextMenu(fTreeViewer.getTree());
        // Set the MenuManager
        fTreeViewer.getTree().setMenu(menu);
        getSite().registerContextMenu(menuManager, fTreeViewer);
    }
}
