/*--------------------------------------------------------------*
  Copyright (C) 2006-2015 OpenSim Ltd.

  This file is distributed WITHOUT ANY WARRANTY. See the file
  'License' for details on this and other legal matters.
*--------------------------------------------------------------*/

package org.omnetpp.ned.editor.graph;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.gef.ContextMenuProvider;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.dnd.TemplateTransferDropTargetListener;
import org.eclipse.gef.ui.actions.ActionRegistry;
import org.eclipse.gef.ui.parts.AbstractEditPartViewer;
import org.eclipse.gef.ui.parts.ContentOutlinePage;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.util.TransferDropTargetListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.part.IPageSite;
import org.omnetpp.common.Debug;
import org.omnetpp.common.util.ReflectionUtils;
import org.omnetpp.ned.core.NedResourcesPlugin;
import org.omnetpp.ned.editor.NedEditor;
import org.omnetpp.ned.editor.graph.actions.GNedContextMenuProvider;
import org.omnetpp.ned.editor.graph.parts.outline.NedTreeEditPartFactory;
import org.omnetpp.ned.model.notification.INedChangeListener;
import org.omnetpp.ned.model.notification.NedModelEvent;

/**
 * Outline viewer for graphical ned editor
 */
class NedOutlinePage extends ContentOutlinePage implements INedChangeListener, ISelectionListener {
    private final GraphicalNedEditor graphicalNedEditor;
    private boolean linkWithEditor = false;

    public NedOutlinePage(GraphicalNedEditor graphicalNedEditor, EditPartViewer viewer) {
        super(viewer);
        this.graphicalNedEditor = graphicalNedEditor;
    }

    @Override
    public void init(IPageSite pageSite) {
        super.init(pageSite);
        NedResourcesPlugin.getNedResources().addNedModelChangeListener(this);
        getSite().getPage().addSelectionListener(this);
        getSite().getPage().addPostSelectionListener(this);
        // register actions for the editor
        ActionRegistry registry = this.graphicalNedEditor.getActionRegistry();
        IActionBars bars = pageSite.getActionBars();
        String id = ActionFactory.UNDO.getId();
        bars.setGlobalActionHandler(id, registry.getAction(id));
        id = ActionFactory.REDO.getId();
        bars.setGlobalActionHandler(id, registry.getAction(id));
        id = ActionFactory.DELETE.getId();
        bars.setGlobalActionHandler(id, registry.getAction(id));

        id = ActionFactory.CUT.getId();
        bars.setGlobalActionHandler(id, registry.getAction(id));
        id = ActionFactory.COPY.getId();
        bars.setGlobalActionHandler(id, registry.getAction(id));
        id = ActionFactory.PASTE.getId();
        bars.setGlobalActionHandler(id, registry.getAction(id));
        bars.updateActionBars();
    }

    @Override
    public void createControl(Composite parent) {
        // TODO KLUDGE: This block removes the drag source and drop targets so that dragging will work when switch back and forth
        //              between the text and graphical editors. See comment in MultiPageNedEditor in pageChange.
        //              Both the drag source and the drag target are disposed by their corresponding setter methods.
        Method method = ReflectionUtils.getMethod(AbstractEditPartViewer.class, "setDragSource", DragSource.class);
        ReflectionUtils.setAccessible(method);
        ReflectionUtils.invokeMethod(method, getViewer(), (DragSource)null);
        method = ReflectionUtils.getMethod(AbstractEditPartViewer.class, "setDropTarget", DropTarget.class);
        ReflectionUtils.setAccessible(method);
        ReflectionUtils.invokeMethod(method, getViewer(), (DropTarget)null);
        // KLUDGE END

        super.createControl(parent);

        getViewer().setEditDomain(graphicalNedEditor.getEditDomain());
        getViewer().setEditPartFactory(new NedTreeEditPartFactory());
        ContextMenuProvider provider = new GNedContextMenuProvider(getViewer(), graphicalNedEditor.getActionRegistry(), getSite());
        getViewer().setContextMenu(provider);
        getViewer().setKeyHandler(this.graphicalNedEditor.getCommonKeyHandler());
        getViewer().addDropTargetListener((TransferDropTargetListener)
                new TemplateTransferDropTargetListener(getViewer()));
        graphicalNedEditor.getSelectionSynchronizer().addViewer(getViewer());
        setContents(graphicalNedEditor.getModel());
    }

    @Override
    public void dispose() {
        graphicalNedEditor.getSelectionSynchronizer().removeViewer(getViewer());
        getSite().getPage().removeSelectionListener(this);
        getSite().getPage().removePostSelectionListener(this);
        NedResourcesPlugin.getNedResources().removeNedModelChangeListener(this);
        super.dispose();
    }

    public void setContents(Object contents) {
        Debug.println("NedOutLinePage: starting setContents");
        long startTime = System.currentTimeMillis();
        getViewer().setContents(contents);
        Debug.println("NedOutLinePage: setContents: " + (System.currentTimeMillis()-startTime) + "ms");
    }

    private void refresh() {
        Debug.println("NedOutLinePage: starting refresh");
        long startTime = System.currentTimeMillis();
        getViewer().getContents().refresh();
        Debug.println("NedOutLinePage: refresh: " + (System.currentTimeMillis()-startTime) + "ms");
    }

    public void modelChanged(NedModelEvent event) {
        if (Display.getCurrent() != null) {
            refresh();
        }
        else {
            Display.getDefault().asyncExec(new Runnable() {
                public void run() {
                    refresh();
                }
            });
        }
    }

    public void selectionChanged(IWorkbenchPart part, ISelection selection) {
        // we don't care about selection change events if they were not generated by the active nedEditor
        if (getActiveEditor() != part || !(part instanceof NedEditor) || !linkWithEditor)
            return;

        // generally the synchronization is done by the graphicalEditors SelectionSynchronizer
        // but we handle the SelSync with the TextEditor differently. We receive text selections from there
        // and if any selection change event received we get the selection from the active editor
        // turn it to editparts (as text editor selection contains model objects not edit parts)
        // and pass it to the outline view. We disable the general sync to avoid syncing back the
        // selection to the text editor
        // Debug.println("*** NedOutline selecion changed from: "+part);
        if (selection instanceof ITextSelection) {
            ISelection activeEditorSelection = getActiveEditorSelection();
            if (activeEditorSelection instanceof IStructuredSelection) {
                List<Object> partSelList = new ArrayList<Object>();
                for (Object sel : ((IStructuredSelection)activeEditorSelection).toArray()) {
                    Object selPart = getViewer().getEditPartRegistry().get(sel);
                    if (selPart != null)
                        partSelList.add(selPart);
                }
                getViewer().setSelection(new StructuredSelection(partSelList));
            }
        }
    }



    /**
     * Utility method: Returns the active editor, or null.
     */
    protected IEditorPart getActiveEditor() {
        IPageSite site = getSite();
        IWorkbenchPage activePage = site==null ? null : site.getWorkbenchWindow().getActivePage();
        return activePage==null ? null : activePage.getActiveEditor();
    }

    /**
     * Utility method: Return the active editor's selection, or null.
     */
    protected ISelection getActiveEditorSelection() {
        IEditorPart editor = getActiveEditor();
        ISelectionProvider selectionProvider = editor==null ? null : editor.getSite().getSelectionProvider();
        return selectionProvider==null ? null : selectionProvider.getSelection();
    }

}