/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.internal;

import java.util.Hashtable;

import org.eclipse.jface.util.ListenerList;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.ui.INullSelectionListener;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.ISelectionService;
import org.eclipse.ui.IWorkbenchPart;

/**
 * Abstract selection service.
 */
public abstract class AbstractSelectionService implements ISelectionService {

    /** 
     * The list of selection listeners (not per-part).
     */
    private ListenerList listeners = new ListenerList();

    /** 
     * The list of post selection listeners (not per-part).
     */
    private ListenerList postListeners = new ListenerList();

    /**
     * The currently active part.
     */
    private IWorkbenchPart activePart;

    /**
     * The active part's selection provider, remembered in case the part 
     * replaces its selection provider after we hooked a listener.
     */
    private ISelectionProvider activeProvider;

    /**
     * Map from part id (String) to per-part tracker (AbstractPartSelectionTracker).
     */
    private Hashtable perPartTrackers;

    /**
     * The JFace selection listener to hook on the active part's selection provider.
     */
    private ISelectionChangedListener selListener = new ISelectionChangedListener() {
        public void selectionChanged(SelectionChangedEvent event) {
            fireSelection(activePart, event.getSelection());
        }
    };

    /**
     * The JFace post selection listener to hook on the active part's selection provider.
     */
    private ISelectionChangedListener postSelListener = new ISelectionChangedListener() {
        public void selectionChanged(SelectionChangedEvent event) {
            firePostSelection(activePart, event.getSelection());
        }
    };

    /**
     * Creates a new SelectionService.
     */
    protected AbstractSelectionService() {
    }

    /* (non-Javadoc)
     * Method declared on ISelectionService.
     */
    public void addSelectionListener(ISelectionListener l) {
        listeners.add(l);
    }

    /* (non-Javadoc)
     * Method declared on ISelectionService.
     */
    public void addSelectionListener(String partId, ISelectionListener listener) {
        getPerPartTracker(partId).addSelectionListener(listener);
    }

    /* (non-Javadoc)
     * Method declared on ISelectionService.
     */
    public void addPostSelectionListener(ISelectionListener l) {
        postListeners.add(l);
    }

    /* (non-Javadoc)
     * Method declared on ISelectionService.
     */
    public void addPostSelectionListener(String partId,
            ISelectionListener listener) {
        getPerPartTracker(partId).addPostSelectionListener(listener);
    }

    /* (non-Javadoc)
     * Method declared on ISelectionService.
     */
    public void removeSelectionListener(ISelectionListener l) {
        listeners.remove(l);
    }

    /*
     * (non-Javadoc)
     * Method declared on ISelectionListener.
     */
    public void removePostSelectionListener(String partId,
            ISelectionListener listener) {
        getPerPartTracker(partId).removePostSelectionListener(listener);
    }

    /* (non-Javadoc)
     * Method declared on ISelectionService.
     */
    public void removePostSelectionListener(ISelectionListener l) {
        postListeners.remove(l);
    }

    /*
     * (non-Javadoc)
     * Method declared on ISelectionListener.
     */
    public void removeSelectionListener(String partId,
            ISelectionListener listener) {
        getPerPartTracker(partId).removeSelectionListener(listener);
    }

    /**
     * Fires a selection event to the given listeners.
     * 
     * @param part the part or <code>null</code> if no active part
     * @param sel the selection or <code>null</code> if no active selection
     */
    protected void fireSelection(final IWorkbenchPart part, final ISelection sel) {
        Object[] array = listeners.getListeners();
        for (int i = 0; i < array.length; i++) {
            final ISelectionListener l = (ISelectionListener) array[i];
            if ((part != null && sel != null)
                    || l instanceof INullSelectionListener) {
                
                try {
                    l.selectionChanged(part, sel);
                } catch (Exception e) {
                    WorkbenchPlugin.log(e);
                }
            }
        }
    }

    /**
     * Fires a selection event to the given listeners.
     * 
     * @param part the part or <code>null</code> if no active part
     * @param sel the selection or <code>null</code> if no active selection
     */
    protected void firePostSelection(final IWorkbenchPart part,
            final ISelection sel) {
        Object[] array = postListeners.getListeners();
        for (int i = 0; i < array.length; i++) {
            final ISelectionListener l = (ISelectionListener) array[i];
            if ((part != null && sel != null)
                    || l instanceof INullSelectionListener) {
                
                try {
                    l.selectionChanged(part, sel);
                } catch (Exception e) {
                    WorkbenchPlugin.log(e);
                }
            }
        }
    }

    /**
     * Returns the per-part selection tracker for the given part id.
     * 
     * @param partId part identifier
     * @return per-part selection tracker
     */
    protected AbstractPartSelectionTracker getPerPartTracker(String partId) {
        if (perPartTrackers == null) {
            perPartTrackers = new Hashtable(4);
        }
        AbstractPartSelectionTracker tracker = (AbstractPartSelectionTracker) perPartTrackers
                .get(partId);
        if (tracker == null) {
            tracker = createPartTracker(partId);
            perPartTrackers.put(partId, tracker);
        }
        return tracker;
    }

    /**
     * Creates a new per-part selection tracker for the given part id.
     * 
     * @param partId part identifier
     * @return per-part selection tracker
     */
    protected abstract AbstractPartSelectionTracker createPartTracker(
            String partId);

    /**
     * Returns the selection.
     */
    public ISelection getSelection() {
        if (activeProvider != null)
            return activeProvider.getSelection();
        else
            return null;
    }

    /*
     * @see ISelectionService#getSelection(String)
     */
    public ISelection getSelection(String partId) {
        return getPerPartTracker(partId).getSelection();
    }

    /**
     * Sets the current-active part (or null if none)
     * 
     * @since 3.1 
     *
     * @param newPart the new active part (or null if none)
     */
    public void setActivePart(IWorkbenchPart newPart) {
        // Optimize.
        if (newPart == activePart)
            return;
        
        ISelectionProvider selectionProvider = null;
        
        if (newPart != null) {
            selectionProvider = newPart.getSite().getSelectionProvider();
            
            if (selectionProvider == null) {
                newPart = null;
            }
        }
        
        if (newPart == activePart)
            return;
        
        if (activePart != null) {
            if (activeProvider != null) {
                activeProvider.removeSelectionChangedListener(selListener);
                if (activeProvider instanceof IPostSelectionProvider)
                    ((IPostSelectionProvider) activeProvider)
                            .removePostSelectionChangedListener(postSelListener);
                else
                    activeProvider
                            .removeSelectionChangedListener(postSelListener);
                activeProvider = null;
            }
            activePart = null;
        }

        activePart = newPart;
        
        if (newPart != null) {
            activeProvider = selectionProvider;
            // Fire an event if there's an active provider
            activeProvider.addSelectionChangedListener(selListener);
            ISelection sel = activeProvider.getSelection();
            fireSelection(newPart, sel);
            if (activeProvider instanceof IPostSelectionProvider)
                ((IPostSelectionProvider) activeProvider)
                        .addPostSelectionChangedListener(postSelListener);
            else
                activeProvider.addSelectionChangedListener(postSelListener);
            firePostSelection(newPart, sel);
        } else {
            fireSelection(null, null);
            firePostSelection(null, null);
        }
    }
    
//    /**
//     * Notifies the listener that a part has been activated.
//     */
//    public void partActivated(IWorkbenchPart newPart) {
//        // Optimize.
//        if (newPart == activePart)
//            return;
//
//        // Unhook selection from the old part.
//        reset();
//
//        // Update active part.
//        activePart = newPart;
//
//        // Hook selection on the new part.
//        if (activePart != null) {
//            activeProvider = activePart.getSite().getSelectionProvider();
//            if (activeProvider != null) {
//                // Fire an event if there's an active provider
//                activeProvider.addSelectionChangedListener(selListener);
//                ISelection sel = activeProvider.getSelection();
//                fireSelection(newPart, sel);
//                if (activeProvider instanceof IPostSelectionProvider)
//                    ((IPostSelectionProvider) activeProvider)
//                            .addPostSelectionChangedListener(postSelListener);
//                else
//                    activeProvider.addSelectionChangedListener(postSelListener);
//                firePostSelection(newPart, sel);
//            } else {
//                //Reset active part. activeProvider may not be null next time this method is called.
//                activePart = null;
//            }
//        }
//        // No need to fire an event if no active provider, since this was done in reset()
//    }
//
//    /**
//     * Notifies the listener that a part has been brought to the front.
//     */
//    public void partBroughtToTop(IWorkbenchPart newPart) {
//        // do nothing, the active part has not changed,
//        // so the selection is unaffected
//    }
//
//    /**
//     * Notifies the listener that a part has been closed
//     */
//    public void partClosed(IWorkbenchPart part) {
//        // Unhook selection from the part.
//        if (part == activePart) {
//            reset();
//        }
//    }
//
//    /**
//     * Notifies the listener that a part has been deactivated.
//     */
//    public void partDeactivated(IWorkbenchPart part) {
//        // Unhook selection from the part.
//        if (part == activePart) {
//            reset();
//        }
//    }
//
//    /**
//     * Notifies the listener that a part has been opened.
//     */
//    public void partOpened(IWorkbenchPart part) {
//        // Wait for activation.
//    }
//
//    /**
//     * Notifies the listener that a part has been opened.
//     */
//    public void partInputChanged(IWorkbenchPart part) {
//        // 36501 - only process if part is active
//        if (activePart == part) {
//            reset();
//            partActivated(part);
//        }
//    }
//
//    /**
//     * Resets the service.  The active part and selection provider are
//     * dereferenced.
//     */
//    public void reset() {
//        if (activePart != null) {
//            fireSelection(null, null);
//            firePostSelection(null, null);
//            if (activeProvider != null) {
//                activeProvider.removeSelectionChangedListener(selListener);
//                if (activeProvider instanceof IPostSelectionProvider)
//                    ((IPostSelectionProvider) activeProvider)
//                            .removePostSelectionChangedListener(postSelListener);
//                else
//                    activeProvider
//                            .removeSelectionChangedListener(postSelListener);
//                activeProvider = null;
//            }
//            activePart = null;
//        }
//    }

}
