/*******************************************************************************
 * Copyright (c) 2004, 2009 Tasktop Technologies 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:
 *     Tasktop Technologies - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.commons.ui.dialogs;

import java.util.HashSet;
import java.util.Set;

import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.PopupDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.window.Window;
import org.eclipse.mylyn.internal.commons.ui.Messages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.Shell;

/**
 * Abstract class for an in-place popup dialog that has a cancel button and sends and ok event when the dialog is closed
 * 
 * @author Shawn Minto
 * @since 3.7
 */
public abstract class AbstractInPlaceDialog extends PopupDialog {

	public static final int ID_CLEAR = IDialogConstants.CLIENT_ID + 1;

	protected static final int MARGIN_SIZE = 3;

	private final int side;

	private final Rectangle controlBounds;

	private Control control;

	private final Set<IInPlaceDialogListener> listeners = new HashSet<IInPlaceDialogListener>();

	private final Control openControl;

	DisposeListener disposeListener = new DisposeListener() {

		public void widgetDisposed(DisposeEvent e) {
			dispose();
		}
	};

	public AbstractInPlaceDialog(Shell parent, int side, Control openControl) {
		super(parent, PopupDialog.INFOPOPUP_SHELLSTYLE, false, false, false, false, false, null, null);
		this.side = side;
		this.openControl = openControl;

		Rectangle bounds;
		if (openControl == null || openControl.isDisposed()) {
			bounds = new Rectangle(0, 0, 0, 0);
		} else {
			bounds = openControl.getBounds();
			Point absPosition = openControl.toDisplay(openControl.getLocation());
			bounds.x = absPosition.x - bounds.x;
			bounds.y = absPosition.y - bounds.y;
		}
		this.controlBounds = bounds;
		if (openControl != null) {
			openControl.addDisposeListener(disposeListener);
		}

	}

	@Override
	protected Control createContents(Composite parent) {
		return createDialogArea(parent);
	}

	@Override
	protected Control createDialogArea(Composite parent) {
		Composite composite = new Composite(parent, SWT.NONE);
		GridLayout gl = new GridLayout();
		gl.marginHeight = 0;
		gl.marginWidth = 0;
		gl.horizontalSpacing = 0;
		gl.verticalSpacing = 0;
		gl.numColumns = 1;
		composite.setLayout(gl);
		GridDataFactory.fillDefaults().grab(true, true).applyTo(composite);

		this.control = createControl(composite);

		Composite buttonComposite = new Composite(parent, SWT.NONE);
		gl = new GridLayout();
		gl.marginHeight = 0;
		gl.marginWidth = 0;
		gl.marginBottom = MARGIN_SIZE;
		gl.marginRight = MARGIN_SIZE;
		gl.horizontalSpacing = 10;
		gl.verticalSpacing = 0;
		gl.numColumns = 1;
		gl.makeColumnsEqualWidth = false;
		buttonComposite.setLayout(gl);
		GridDataFactory.fillDefaults().grab(true, false).align(SWT.END, SWT.CENTER).applyTo(buttonComposite);
		createButtons(buttonComposite);
		Dialog.applyDialogFont(parent);

		parent.pack();

		setBackgroundColor(parent);

		return parent;
	}

	private void setBackgroundColor(Composite parent) {
		parent.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_WHITE));
		for (Control control : parent.getChildren()) {
			if (control instanceof Composite) {
				setBackgroundColor((Composite) control);
			} else {
				control.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_WHITE));
			}
		}
	}

	protected abstract Control createControl(Composite composite);

	protected void createButtons(Composite composite) {
		createButton(composite, ID_CLEAR, Messages.DateSelectionDialog_Clear, true);
	}

	protected void dispose() {
		setReturnCode(Window.CANCEL);
		close();
	}

	protected Control createButton(Composite composite, final int returnCode, String text, final boolean shouldClose) {
		Button button = new Button(composite, SWT.NONE);
		button.setText(text);
		button.addSelectionListener(new SelectionListener() {

			public void widgetSelected(SelectionEvent e) {
				setReturnCode(returnCode);
				if (shouldClose) {
					close();
				} else {
					notifyButtonPressed(false);
				}
			}

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}
		});

		if (composite.getLayout() instanceof GridLayout) {
			((GridLayout) composite.getLayout()).numColumns = composite.getChildren().length;
		}
		GridDataFactory.fillDefaults().applyTo(button);
		return button;
	}

	@Override
	public int open() {
		int result = super.open();
		getControl().setFocus();
		setReturnCode(-1);
		return result;
	}

	@Override
	public boolean close() {
		if (getReturnCode() == -1) {
			setReturnCode(Window.OK);
		}
		notifyButtonPressed(true);
		if (openControl != null && !openControl.isDisposed()) {
			openControl.removeDisposeListener(disposeListener);
		}
		return super.close();
	}

	public void addEventListener(IInPlaceDialogListener listener) {
		listeners.add(listener);
	}

	public void removeCloseListener(IInPlaceDialogListener listener) {
		listeners.remove(listener);
	}

	private void notifyButtonPressed(boolean isClosing) {
		final InPlaceDialogEvent event = new InPlaceDialogEvent(getReturnCode(), isClosing);
		for (final IInPlaceDialogListener listener : listeners) {
			SafeRunnable.run(new ISafeRunnable() {
				public void run() throws Exception {
					listener.buttonPressed(event);
				}

				public void handleException(Throwable exception) {
					// ignore					
				}
			});
		}
	}

	@Override
	protected void initializeBounds() {
//		Rectangle monitorBounds = PlatformUI.getWorkbench()
//				.getActiveWorkbenchWindow()
//				.getShell()
//				.getMonitor()
//				.getClientArea();
		Rectangle monitorBounds = getShell().getMonitor().getClientArea();
		Rectangle bounds = getShell().getBounds();
		int x = 0;
		int y = 0;

		switch (side) {
		case SWT.TOP:
			x = controlBounds.x;
			y = controlBounds.y + controlBounds.height;
			if (x + bounds.width > monitorBounds.x + monitorBounds.width) {
				x = (controlBounds.x + controlBounds.width) - bounds.width;
			}
			break;
		case SWT.BOTTOM:
			x = controlBounds.x;
			y = controlBounds.y - bounds.height;
			if (x + bounds.width > monitorBounds.x + monitorBounds.width) {
				x = (controlBounds.x + controlBounds.width) - bounds.width;
			}
			break;
		case SWT.RIGHT:
			x = (controlBounds.x + controlBounds.width) - bounds.width;
			y = controlBounds.y + controlBounds.height;
			break;
		case SWT.LEFT:
			x = controlBounds.x;
			y = controlBounds.y + controlBounds.height;
			break;
		}
		getShell().setBounds(x, y, bounds.width, bounds.height);
	}

	@Override
	protected void adjustBounds() {
		Point tipSize = getShell().getSize();
		Point location = getShell().getLocation();

		Rectangle bounds;
		Point rightBounds = new Point(tipSize.x + location.x, tipSize.y + location.y);

		Monitor[] ms = getShell().getDisplay().getMonitors();

		if (ms.length > 1) {
			bounds = controlBounds;
			Point p = new Point(location.x, location.y);

			Rectangle tmp;
			for (Monitor element : ms) {
				tmp = element.getBounds();
				if (tmp.contains(p)) {
					bounds = tmp;
					break;
				}
			}

		} else {
			bounds = getControl().getDisplay().getBounds();
		}

		if (!(bounds.contains(location) && bounds.contains(rightBounds))) {
			if (rightBounds.x > bounds.x + bounds.width) {
				location.x -= rightBounds.x - (bounds.x + bounds.width);
			}

			if (rightBounds.y > bounds.y + bounds.height) {
				location.y -= rightBounds.y - (bounds.y + bounds.height);
			}

			if (location.x < bounds.x) {
				location.x = bounds.x;
			}

			if (location.y < bounds.y) {
				location.y = bounds.y;
			}
		}

		getShell().setLocation(location);
	}

	public Control getControl() {
		return control;
	}
}
