/*******************************************************************************
 * Copyright (c) 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.presentations.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.core.runtime.Plugin;
import org.eclipse.jface.util.Assert;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.preferences.IDynamicPropertyMap;
import org.eclipse.ui.internal.preferences.PreferenceStoreAdapter;
import org.eclipse.ui.internal.preferences.PreferencesAdapter;
import org.eclipse.ui.internal.preferences.PropertyMapAdapter;
import org.eclipse.ui.internal.preferences.ThemeManagerAdapter;
import org.eclipse.ui.internal.presentations.defaultpresentation.DefaultPartList;
import org.eclipse.ui.internal.util.PrefUtil;
import org.eclipse.ui.presentations.IPartMenu;
import org.eclipse.ui.presentations.IPresentablePart;
import org.eclipse.ui.presentations.IPresentationSerializer;
import org.eclipse.ui.presentations.IStackPresentationSite;
import org.eclipse.ui.presentations.StackDropResult;
import org.eclipse.ui.presentations.StackPresentation;

/**
 * @since 3.0
 */
public final class TabbedStackPresentation extends StackPresentation {

    private PresentablePartFolder folder;
    private ISystemMenu systemMenu;
    private ISystemMenu partList;
    private PreferenceStoreAdapter apiPreferences = new PreferenceStoreAdapter(PrefUtil
            .getAPIPreferenceStore());
    private ThemeManagerAdapter themePreferences = new ThemeManagerAdapter(
            PlatformUI.getWorkbench().getThemeManager());
    
    private TabOrder tabs;

    private TabDragHandler dragBehavior;

    private boolean initializing = true;
    private int ignoreSelectionChanges = 0;
    
    private TabFolderListener tabFolderListener = new TabFolderListener() {
        public void handleEvent(TabFolderEvent e) {
            switch (e.type) {
             	case TabFolderEvent.EVENT_MINIMIZE: {
             	    getSite().setState(IStackPresentationSite.STATE_MINIMIZED);
             		break;
             	}
             	case TabFolderEvent.EVENT_MAXIMIZE: {
             	    getSite().setState(IStackPresentationSite.STATE_MAXIMIZED);
             		break;
             	}
             	case TabFolderEvent.EVENT_RESTORE: {
             	    getSite().setState(IStackPresentationSite.STATE_RESTORED);
             		break;
             	}
             	case TabFolderEvent.EVENT_CLOSE: {
                    IPresentablePart part = folder.getPartForTab(e.tab);

             		if (part != null) {
             		    getSite().close(new IPresentablePart[] { part });
             		}
             		break;
             	}
             	case TabFolderEvent.EVENT_SHOW_LIST: {
             	    showPartList();
             	    break;
             	}
             	case TabFolderEvent.EVENT_GIVE_FOCUS_TO_PART: { 
                    IPresentablePart part = getSite().getSelectedPart();
             		if (part != null) {
             		    part.setFocus();
             		}
             		break;
             	}
             	case TabFolderEvent.EVENT_PANE_MENU: {
             	    TabbedStackPresentation.this.showPaneMenu(folder.getPartForTab(e.tab), new Point(e.x, e.y)); 	    
             	    break;
             	}
             	case TabFolderEvent.EVENT_DRAG_START: {
             	    AbstractTabItem beingDragged = e.tab;
             	    Point initialLocation = new Point(e.x, e.y);
             	    
                    if (beingDragged == null) {
                        getSite().dragStart(initialLocation, false);
                    } else {
                        IPresentablePart part = folder.getPartForTab(beingDragged);
                        
                        try {
                            dragStart = folder.indexOf(part);
                            getSite().dragStart(part, initialLocation, false);                    
                        } finally {
                            dragStart = -1;
                        }
                    }
                    break;
             	}
             	case TabFolderEvent.EVENT_TAB_SELECTED: {
                    if (ignoreSelectionChanges > 0) {
                        return;
                    }
                    
                    IPresentablePart part = folder.getPartForTab(e.tab);
                    
                    if (part != null) {
                        getSite().selectPart(part);
                    }
             	    break;
             	}
             	case TabFolderEvent.EVENT_SYSTEM_MENU: {
                    IPresentablePart part = folder.getPartForTab(e.tab);
                    
                    if (part == null) {
                        part = getSite().getSelectedPart();
                    }
                    
                    if (part != null) {
                        showSystemMenu(new Point(e.x, e.y), part);
                    }
             	    break;
             	}
             	
            }
        }
    };

    private int dragStart = -1;
    private Map prefs = new HashMap();
    
    public TabbedStackPresentation(IStackPresentationSite site, AbstractTabFolder widget, ISystemMenu systemMenu) {
        this(site, new PresentablePartFolder(widget), systemMenu);
    }
    
    public TabbedStackPresentation(IStackPresentationSite site, PresentablePartFolder folder, ISystemMenu systemMenu) {
        this(site, folder, new LeftToRightTabOrder(folder), new ReplaceDragHandler(folder.getTabFolder()), systemMenu);
    }
    
    public TabbedStackPresentation(IStackPresentationSite site,
            PresentablePartFolder newFolder, TabOrder tabs, TabDragHandler dragBehavior, ISystemMenu systemMenu) {
        super(site);
        this.systemMenu = systemMenu;
        
        this.folder = newFolder;
        this.tabs = tabs;
        this.dragBehavior = dragBehavior;

        // Add a dispose listener. This will call the presentationDisposed()
        // method when the widget is destroyed.
        folder.getTabFolder().getControl().addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                presentationDisposed();
            }
        });

        folder.getTabFolder().addListener(tabFolderListener);
        
        this.partList = new DefaultPartList(site, newFolder);
    }
    
    /**
     * Restores a presentation from a previously stored state
     * 
     * @param serializer (not null)
     * @param savedState (not null)
     */
    public void restoreState(IPresentationSerializer serializer,
            IMemento savedState) {
        tabs.restoreState(serializer, savedState);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#saveState(org.eclipse.ui.presentations.IPresentationSerializer, org.eclipse.ui.IMemento)
     */
    public void saveState(IPresentationSerializer context, IMemento memento) {
        super.saveState(context, memento);

        tabs.saveState(context, memento);
    }
    
    /**
     * Returns true iff the presentation has been disposed
     * 
     * @return true iff the presentation has been disposed
     */
    private boolean isDisposed() {
        return folder == null || folder.isDisposed();
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#setBounds(org.eclipse.swt.graphics.Rectangle)
     */
    public void setBounds(Rectangle bounds) {
        folder.setBounds(bounds);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#computeMinimumSize()
     */
    public Point computeMinimumSize() {
        return folder.getTabFolder().computeSize(SWT.DEFAULT, SWT.DEFAULT);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.ISizeProvider#computePreferredSize(boolean, int, int, int)
     */
    public int computePreferredSize(boolean width, int availableParallel,
            int availablePerpendicular, int preferredResult) {
        
        if (preferredResult != INFINITE || getSite().getState() == IStackPresentationSite.STATE_MINIMIZED) {
            int minSize = 0;
	        if (width) {
	            int heightHint = availablePerpendicular == INFINITE ? SWT.DEFAULT : availablePerpendicular;
	            minSize = folder.getTabFolder().computeSize(SWT.DEFAULT, heightHint).x;
	        } else {
	            int widthHint = availablePerpendicular == INFINITE ? SWT.DEFAULT : availablePerpendicular;
	            minSize = folder.getTabFolder().computeSize(widthHint, SWT.DEFAULT).y;
	        }
	        
	        if (getSite().getState() == IStackPresentationSite.STATE_MINIMIZED) {
	            return minSize;
	        }
	        
	        return Math.max(minSize, preferredResult);
        }
        
        return INFINITE;
    }
    
    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#showPartList()
     */
    public void showPartList() {
        if (partList != null) {
            final int numberOfParts = folder.getTabFolder().getItemCount();
            if (numberOfParts > 0) {
                partList.show(getControl(), folder.getTabFolder()
                        .getPartListLocation(), getSite().getSelectedPart());
            }
        }
    }
    
    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#dispose()
     */
    public void dispose() {
        // Dispose the tab folder's widgetry
        folder.getTabFolder().getControl().dispose();
    }

    /**
     * Called when the tab folder is disposed.
     */
    private void presentationDisposed() {
        apiPreferences.dispose();
        themePreferences.dispose();
        
        Iterator iter = prefs.values().iterator();
        while(iter.hasNext()) {
            PropertyMapAdapter next = (PropertyMapAdapter)iter.next();
            next.dispose();
        }

//        if (systemMenu != null) {
//            systemMenu.dispose();
//        }
//        
//        if (partList != null) {
//            partList.dispose();
//        }
        systemMenu = null;
        partList = null;
        
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#setActive(int)
     */
    public void setActive(int newState) {
        folder.getTabFolder().setActive(newState);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#setVisible(boolean)
     */
    public void setVisible(boolean isVisible) {
        folder.getTabFolder().getControl().setVisible(isVisible);

        IPresentablePart current = getSite().getSelectedPart();
        if (current != null) {
            current.setVisible(isVisible);
        }

        if (isVisible) {
            folder.layout(true);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#setState(int)
     */
    public void setState(int state) {
        folder.getTabFolder().setState(state);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#getControl()
     */
    public Control getControl() {
        return folder.getTabFolder().getControl();
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#addPart(org.eclipse.ui.presentations.IPresentablePart, java.lang.Object)
     */
    public void addPart(IPresentablePart newPart, Object cookie) {
        ignoreSelectionChanges++;
        try {
	        if (initializing) {
	            tabs.addInitial(newPart);
	        } else {
	            if (cookie == null) {
	                tabs.add(newPart);
	            } else {
	                int insertionPoint = dragBehavior
	                        .getInsertionPosition(cookie);
	
	                tabs.insert(newPart, insertionPoint);
	            }
	        }
        } finally {
            ignoreSelectionChanges--;
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#movePart(org.eclipse.ui.presentations.IPresentablePart, java.lang.Object)
     */
    public void movePart(IPresentablePart toMove, Object cookie) {
        ignoreSelectionChanges++;
        try {
	        int insertionPoint = dragBehavior.getInsertionPosition(cookie);
	        
	        if (insertionPoint == folder.indexOf(toMove)) {
	            return;
	        }
	        
	        tabs.move(toMove, insertionPoint);
        } finally {
            ignoreSelectionChanges--;
        }
    }
    
    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#removePart(org.eclipse.ui.presentations.IPresentablePart)
     */
    public void removePart(IPresentablePart oldPart) {
        ignoreSelectionChanges++;
        try {
            tabs.remove(oldPart);
        } finally {
            ignoreSelectionChanges--;
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#selectPart(org.eclipse.ui.presentations.IPresentablePart)
     */
    public void selectPart(IPresentablePart toSelect) {
        initializing = false;

        tabs.select(toSelect);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#dragOver(org.eclipse.swt.widgets.Control, org.eclipse.swt.graphics.Point)
     */
    public StackDropResult dragOver(Control currentControl, Point location) {
        return dragBehavior.dragOver(currentControl, location, dragStart);
    }

    /**
     * @param part
     * @param point
     */
    public void showSystemMenu() {
        showSystemMenu(folder.getTabFolder().getSystemMenuLocation(), getSite().getSelectedPart());
    }
    
    public void showSystemMenu(Point displayCoordinates, IPresentablePart context) {
        if (context != getSite().getSelectedPart()) {
            getSite().selectPart(context);
        }
        systemMenu.show(getControl(), displayCoordinates, context);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#showPaneMenu()
     */
    public void showPaneMenu() {
        IPresentablePart part = getSite().getSelectedPart();
        
        if (part != null) {
            showPaneMenu(part, folder.getTabFolder().getPaneMenuLocation());
        }
    }
        
    public void showPaneMenu(IPresentablePart part, Point location) {
        Assert.isTrue(!isDisposed());
        
        IPartMenu menu = part.getMenu();

        if (menu != null) {
            menu.showMenu(location);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.presentations.StackPresentation#getTabList(org.eclipse.ui.presentations.IPresentablePart)
     */
    public Control[] getTabList(IPresentablePart part) {
        ArrayList list = new ArrayList();
        if (folder.getTabFolder().getTabPosition() == SWT.BOTTOM) {
            if (part.getControl() != null)
                list.add(part.getControl());
        }

        list.add(folder.getTabFolder().getControl());
        
        if (part.getToolBar() != null) {
            list.add(part.getToolBar());
        }
        
        if (folder.getTabFolder().getTabPosition() == SWT.TOP) {
            if (part.getControl() != null)
                list.add(part.getControl());
        }

        return (Control[]) list.toArray(new Control[list.size()]);
    }

    public void setPartList(ISystemMenu menu) {
        this.partList = menu;
    }
    
    public IDynamicPropertyMap getTheme() {
        return themePreferences;
    }
    
    public IDynamicPropertyMap getApiPreferences() {
        return apiPreferences;
    }
    
    public IDynamicPropertyMap getPluginPreferences(Plugin toQuery) {
        String id = toQuery.getBundle().getSymbolicName();
        IDynamicPropertyMap result = (IDynamicPropertyMap)prefs.get(id);
        
        if (result != null) {
            return result;
        }
        
        result = new PreferencesAdapter(toQuery.getPluginPreferences());
        prefs.put(id, result);
        return result;
    }
}