package org.gudy.azureus2.ui.swt.shells;

import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.util.*;
import org.gudy.azureus2.ui.swt.Utils;
import org.gudy.azureus2.ui.swt.mainwindow.TorrentOpener;

import com.aelitis.azureus.ui.UIFunctionsUserPrompter;
import com.aelitis.azureus.ui.common.RememberedDecisionsManager;
import com.aelitis.azureus.ui.swt.UISkinnableManagerSWT;
import com.aelitis.azureus.ui.swt.UISkinnableSWTListener;

/**
 * A messagebox that allows you config the button
 * 
 * @todo When key is pressed, cancel auto close timer
 */
public class MessageBoxShell
	implements UIFunctionsUserPrompter
{
	private final static String REGEX_URLHTML = "<A HREF=\"(.+?)\">(.+?)</A>";

	private final static int MIN_SIZE_X = 300;

	private final static int MIN_SIZE_Y = 120;

	private final static int MAX_SIZE_X = 500;

	private static final int MIN_BUTTON_SIZE = 70;

	private static int numOpen = 0;

	private static ArrayList skinnableListeners = new ArrayList();

	private Shell parent;

	private final String title;

	private final String text;

	private final String[] buttons;

	private final int defaultOption;

	private String rememberID;

	private String rememberText;

	private boolean rememberByDefault;

	private int autoCloseInMS;

	private String html;

	private String url;

	private boolean autoClosed = false;

	private Object[] relatedObjects;

	private Image imgLeft;

	public static void addSkinnableListener(UISkinnableSWTListener l) {
		skinnableListeners.add(l);
	}

	public static void removeSkinnableListener(UISkinnableSWTListener l) {
		skinnableListeners.remove(l);
	}

	public static int open(final Shell parent, final String title,
			final String text, final String[] buttons, final int defaultOption) {
		return open(parent, title, text, buttons, defaultOption, null, false, -1);
	}

	public static int open(final Shell parent, final String title,
			final String text, final String[] buttons, final int defaultOption,
			final String rememberID, final boolean bRememberByDefault,
			final int autoCloseInMS) {
		return open(parent, title, text, buttons, defaultOption, rememberID,
				MessageText.getString("MessageBoxWindow.rememberdecision"),
				bRememberByDefault, autoCloseInMS);
	}

	public static int open(final Shell parent, final String title,
			final String text, final String[] buttons, final int defaultOption,
			final String rememberID, final String rememberText,
			final boolean bRememberByDefault, final int autoCloseInMS) {

		MessageBoxShell messageBoxShell = new MessageBoxShell(parent, title, text,
				buttons, defaultOption, rememberID, rememberText, bRememberByDefault,
				autoCloseInMS);
		return messageBoxShell.open();
	}

	public static boolean isOpen() {
		return numOpen > 0;
	}

	public MessageBoxShell(final Shell parent, final String title,
			final String text, final String[] buttons, final int defaultOption,
			final String rememberID, final String rememberText,
			final boolean bRememberByDefault, final int autoCloseInMS) {

		this.parent = parent;
		this.title = title;
		this.text = text;
		this.buttons = buttons;
		this.defaultOption = defaultOption;
		this.rememberID = rememberID;
		this.rememberText = rememberText;
		this.rememberByDefault = bRememberByDefault;
		this.autoCloseInMS = autoCloseInMS;
	}

	/**
	 * @param shellForChildren
	 * @param string
	 * @param string2
	 * @param strings
	 */
	public MessageBoxShell(final Shell parent, final String title,
			final String text, final String[] buttons, final int defaultOption) {
		this(parent, title, text, buttons, defaultOption, null, null, false, -1);
	}

	public int open() {
		if (rememberID != null) {
			int rememberedDecision = RememberedDecisionsManager.getRememberedDecision(rememberID);
			if (rememberedDecision >= 0) {
				return rememberedDecision;
			}
		}

		numOpen++;

		final int[] result = new int[1];
		result[0] = -1;

		Utils.execSWTThread(new AERunnable() {
			public void runSupport() {
				result[0] = _open();
			}
		}, false);

		numOpen--;
		return result[0];
	}

	private int _open() {
		final int[] result = {
			-1
		};

		if (parent == null || parent.isDisposed()) {
			parent = Utils.findAnyShell();
			if (parent == null || parent.isDisposed()) {
				return result[0];
			}
		}

		MouseTrackAdapter mouseAdapter = null;
		Display display = parent.getDisplay();

		final Shell shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.RESIZE
				| SWT.APPLICATION_MODAL);
		shell.setText(title);
		GridLayout gridLayout = new GridLayout();
		shell.setLayout(gridLayout);
		Utils.setShellIcon(shell);

		UISkinnableSWTListener[] listeners = UISkinnableManagerSWT.getInstance().getSkinnableListeners(
				MessageBoxShell.class.toString());
		for (int i = 0; i < listeners.length; i++) {
			listeners[i].skinBeforeComponents(shell, this, relatedObjects);
		}

		FormData formData;
		GridData gridData;
		
		Composite textComposite = shell;
		if (imgLeft != null) {
			textComposite = new Composite(shell, SWT.NONE);
			textComposite.setLayout(new GridLayout(2, false));
			textComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
			Label lblImage = new Label(textComposite, SWT.NONE);
			lblImage.setImage(imgLeft);
			lblImage.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING));
		}

		Control linkControl;
		try {
			Link linkLabel = new Link(textComposite, SWT.WRAP);

			linkControl = linkLabel;

			linkLabel.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
			linkLabel.setText(text.replaceAll(" &", " &&"));
			linkLabel.addSelectionListener(new SelectionAdapter() {
				public void widgetSelected(SelectionEvent e) {
					if (e.text.endsWith(".torrent"))
						TorrentOpener.openTorrent(e.text);
					else
						Utils.launch(e.text);
				}
			});

			Matcher matcher = Pattern.compile(REGEX_URLHTML, Pattern.CASE_INSENSITIVE).matcher(
					text);
			String tooltip = null;
			while (matcher.find()) {
				if (tooltip == null)
					tooltip = "";
				else
					tooltip += "\n";
				tooltip += matcher.group(2) + ": " + matcher.group(1);
			}
			linkLabel.setToolTipText(tooltip);
		} catch (Throwable t) {
			// 3.0
			Label linkLabel = new Label(textComposite, SWT.WRAP);
			linkControl = linkLabel;

			String urlText = Pattern.compile(REGEX_URLHTML, Pattern.CASE_INSENSITIVE).matcher(
					text).replaceAll("$2 ($1)");

			linkLabel.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
			linkLabel.setText(urlText);
		}

		if ((html != null && html.length() > 0)
				|| (url != null && url.length() > 0)) {
			try {
				final Browser browser = new Browser(shell, SWT.NONE);
				if (url != null && url.length() > 0) {
					browser.setUrl(url);
				} else {
					browser.setText(html);
				}
				GridData gd = new GridData(GridData.FILL_BOTH);
				gd.heightHint = 200;
				browser.setLayoutData(gd);
				browser.addProgressListener(new ProgressListener() {
					public void completed(ProgressEvent event) {
						browser.addLocationListener(new LocationListener() {
							public void changing(LocationEvent event) {
								event.doit = false;
							}

							public void changed(LocationEvent event) {
							}
						});
						browser.addOpenWindowListener(new OpenWindowListener() {
							public void open(WindowEvent event) {
								event.required = true;
							}
						});
					}

					public void changed(ProgressEvent event) {
					}
				});
			} catch (Exception e) {
				Debug.out(e);
				if (html != null) {
					Text text = new Text(shell, SWT.BORDER | SWT.READ_ONLY | SWT.WRAP);
					text.setText(html);
					GridData gd = new GridData(GridData.FILL_BOTH);
					gd.heightHint = 200;
					text.setLayoutData(gd);
				}
			}

			gridData = new GridData(GridData.FILL_HORIZONTAL);
			linkControl.setLayoutData(gridData);
		} else {
			gridData = new GridData(GridData.FILL_BOTH);
			linkControl.setLayoutData(gridData);
		}

		Label lblPadding = new Label(shell, SWT.NONE);
		lblPadding.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		// Closing in..
		if (autoCloseInMS > 0) {
			final Label lblCloseIn = new Label(shell, SWT.WRAP);
			lblCloseIn.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
			long endOn = SystemTime.getCurrentTime() + autoCloseInMS;
			lblCloseIn.setData("CloseOn", new Long(endOn));
			SimpleTimer.addPeriodicEvent("autoclose", 500, new TimerEventPerformer() {
				public void perform(TimerEvent event) {
					if (shell.isDisposed()) {
						event.cancel();
						return;
					}
					Utils.execSWTThread(new AERunnable() {
						public void runSupport() {
							if (!shell.isDisposed()) {
								boolean bDelayPaused = lblCloseIn.getData("DelayPaused") != null;
								if (bDelayPaused) {
									return;
								}

								long endOn = ((Long) lblCloseIn.getData("CloseOn")).longValue();
								if (SystemTime.getCurrentTime() > endOn) {
									result[0] = defaultOption;
									autoClosed = true;
									shell.dispose();
								} else {
									String sText = "";

									if (lblCloseIn == null || lblCloseIn.isDisposed())
										return;

									if (!bDelayPaused) {
										long delaySecs = (endOn - SystemTime.getCurrentTime()) / 1000;
										sText = MessageText.getString("popup.closing.in",
												new String[] {
													String.valueOf(delaySecs)
												});
									}

									lblCloseIn.setText(sText);
								}
							}
						};
					});
				}
			});

			SimpleTimer.addPeriodicEvent("OverPopup", 100, new TimerEventPerformer() {
				boolean wasOver = true;

				long lEnterOn = 0;

				public void perform(final TimerEvent event) {
					if (shell.isDisposed()) {
						event.cancel();
						return;
					}
					Utils.execSWTThread(new AERunnable() {
						public void runSupport() {
							if (shell.isDisposed()) {
								event.cancel();
								return;
							}
							boolean isOver = shell.getBounds().contains(
									shell.getDisplay().getCursorLocation());
							if (isOver != wasOver) {
								wasOver = isOver;
								if (isOver) {
									lblCloseIn.setData("DelayPaused", "");
									lEnterOn = SystemTime.getCurrentTime();
									lblCloseIn.setText("");
								} else {
									lblCloseIn.setData("DelayPaused", null);
									if (lEnterOn > 0) {
										long diff = SystemTime.getCurrentTime() - lEnterOn;
										long endOn = ((Long) lblCloseIn.getData("CloseOn")).longValue()
												+ diff;
										lblCloseIn.setData("CloseOn", new Long(endOn));
									}
								}
							}
						}
					});
				}
			});
		}

		// Remember Me
		Button checkRemember = null;
		if (rememberID != null) {
			checkRemember = new Button(shell, SWT.CHECK);
			checkRemember.setText(rememberText);
			checkRemember.setSelection(rememberByDefault);

			checkRemember.addDisposeListener(new DisposeListener() {
				public void widgetDisposed(DisposeEvent e) {
					Button checkRemember = (Button) e.widget;
					if (rememberID != null && checkRemember != null
							&& checkRemember.getSelection()) {
						RememberedDecisionsManager.setRemembered(rememberID, result[0]);
					}
				}
			});
		}

		// Buttons

		Composite cButtons = new Composite(shell, SWT.NONE);
		FormLayout layout = new FormLayout();

		cButtons.setLayout(layout);
		gridData = new GridData(GridData.HORIZONTAL_ALIGN_CENTER);
		cButtons.setLayoutData(gridData);

		Control lastButton = null;

		Listener buttonListener = new Listener() {

			public void handleEvent(Event event) {
				result[0] = ((Integer) event.widget.getData()).intValue();
				shell.dispose();
			}

		};

		int buttonWidth = 0;
		Button[] swtButtons = new Button[buttons.length];
		for (int i = 0; i < buttons.length; i++) {
			Button button = new Button(cButtons, SWT.PUSH);
			swtButtons[i] = button;
			button.setData(new Integer(i));
			button.setText(buttons[i]);
			button.addListener(SWT.Selection, buttonListener);

			formData = new FormData();
			if (lastButton != null) {
				formData.left = new FormAttachment(lastButton, 5);
			}

			button.setLayoutData(formData);

			Point size = button.computeSize(SWT.DEFAULT, SWT.DEFAULT);
			if (size.x > buttonWidth) {
				buttonWidth = size.x;
			}

			if (i == defaultOption) {
				button.setFocus();
				shell.setDefaultButton(button);
			}

			lastButton = button;
		}

		if (buttonWidth > 0) {
			if (buttonWidth < MIN_BUTTON_SIZE) {
				buttonWidth = MIN_BUTTON_SIZE;
			}
			for (int i = 0; i < buttons.length; i++) {
				Point size = swtButtons[i].computeSize(buttonWidth, SWT.DEFAULT);
				swtButtons[i].setSize(size);
				formData = (FormData) swtButtons[i].getLayoutData();
				formData.width = buttonWidth;
			}
		}

		shell.addTraverseListener(new TraverseListener() {
			public void keyTraversed(TraverseEvent event) {
				if (event.detail == SWT.TRAVERSE_ESCAPE) {
					shell.dispose();
				}
			}
		});
		
		Listener filterListener = new Listener() {
			public void handleEvent(Event event) {
				if (event.detail == SWT.TRAVERSE_ARROW_NEXT) {
					event.detail = SWT.TRAVERSE_TAB_NEXT;
					event.doit = true;
				} else if (event.detail == SWT.TRAVERSE_ARROW_PREVIOUS) {
					event.detail = SWT.TRAVERSE_TAB_PREVIOUS;						
					event.doit = true;
				}
			}
		};
		display.addFilter(SWT.Traverse, filterListener);
		
		if (mouseAdapter != null) {
			addMouseTrackListener(shell, mouseAdapter);
		}

		shell.pack();
		Point size = shell.getSize();
		if (size.x < MIN_SIZE_X) {
			size.x = MIN_SIZE_X;
			shell.setSize(size);
		} else if (size.x > MAX_SIZE_X) {
			size = shell.computeSize(MAX_SIZE_X, SWT.DEFAULT);
			shell.setSize(size);
		}

		if (size.y < MIN_SIZE_Y) {
			size.y = MIN_SIZE_Y;
			shell.setSize(size);
		}

		Utils.centerWindowRelativeTo(shell, parent);

		for (int i = 0; i < listeners.length; i++) {
			listeners[i].skinAfterComponents(shell, this, relatedObjects);
		}

		shell.open();

		while (!shell.isDisposed()) {
			if (!display.readAndDispatch()) {
				display.sleep();
			}
		}
		
		if (display != null && !display.isDisposed()) {
			display.removeFilter(SWT.Traverse, filterListener);
		}

		return result[0];
	}

	/**
	 * Adds mousetracklistener to composite and all it's children
	 * 
	 * @param parent Composite to start at
	 * @param listener Listener to add
	 */
	private void addMouseTrackListener(Composite parent,
			MouseTrackListener listener) {
		if (parent == null || listener == null || parent.isDisposed())
			return;

		parent.addMouseTrackListener(listener);
		Control[] children = parent.getChildren();
		for (int i = 0; i < children.length; i++) {
			Control control = children[i];
			if (control instanceof Composite)
				addMouseTrackListener((Composite) control, listener);
			else
				control.addMouseTrackListener(listener);
		}
	}

	public String getHtml() {
		return html;
	}

	public void setHtml(String html) {
		this.html = html;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	/**
	 * @return the rememberID
	 */
	public String getRememberID() {
		return rememberID;
	}

	/**
	 * @param rememberID the rememberID to set
	 */
	public void setRememberID(String rememberID, boolean rememberByDefault) {
		this.rememberID = rememberID;
		this.rememberByDefault = rememberByDefault;
	}

	/**
	 * @return the rememberText
	 */
	public String getRememberText() {
		return rememberText;
	}

	/**
	 * @param rememberText the rememberText to set
	 */
	public void setRememberText(String rememberText) {
		this.rememberText = rememberText;
	}

	/**
	 * @return the autoCloseInMS
	 */
	public int getAutoCloseInMS() {
		return autoCloseInMS;
	}

	/**
	 * @param autoCloseInMS the autoCloseInMS to set
	 */
	public void setAutoCloseInMS(int autoCloseInMS) {
		this.autoCloseInMS = autoCloseInMS;
	}

	/**
	 * @return the autoClosed
	 */
	public boolean isAutoClosed() {
		return autoClosed;
	}
	
	// @see com.aelitis.azureus.ui.UIFunctionsUserPrompter#setRelatedObject(java.lang.Object)
	public void setRelatedObject(Object relatedObject) {
		this.relatedObjects = new Object[] { relatedObject };
	}

	// @see com.aelitis.azureus.ui.UIFunctionsUserPrompter#setRelatedObjects(java.lang.Object[])
	public void setRelatedObjects(Object[] relatedObjects) {
		this.relatedObjects = relatedObjects;
	}
	
	public Object[] getRelatedObjects() {
		return relatedObjects;
	}
	
	public void setLeftImage(Image imgLeft) {
		this.imgLeft = imgLeft;
	}
	
	/**
	 * Replaces Image on left with icon
	 * 
	 * @param icon SWT.ICON_ERROR, ICON_INFORMATION, ICON_QUESTION, ICON_WARNING, ICON_WORKING
	 *
	 * @since 3.0.1.7
	 */
	public void setLeftImage(int icon) {
		setLeftImage(Display.getDefault().getSystemImage(icon));
	}

	public static void main(String[] args) {
		Display display = Display.getDefault();
		Shell shell = new Shell(display, SWT.SHELL_TRIM);
		shell.open();

		MessageBoxShell messageBoxShell = new MessageBoxShell(
				shell,
				"Title",
				"Test\n"
						+ "THis is a very long line that tests whether the box gets really wide which is something we don't want.\n"
						+ "A <A HREF=\"Link\">link</A> for <A HREF=\"http://moo.com\">you</a>",
				new String[] {
					"Okay",
					"Cancyyyyyy",
					"Maybe"
				}, 1, "test2",
				MessageText.getString("MessageBoxWindow.nomoreprompting"), false, 15000);

		messageBoxShell.setHtml("<b>Moo</b> goes the cow<p><hr>");
		System.out.println(messageBoxShell.open());
	}
}
