package snippet;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.LocationAdapter;
import org.eclipse.swt.browser.LocationEvent;
import org.eclipse.swt.browser.LocationListener;
import org.eclipse.swt.browser.ProgressEvent;
import org.eclipse.swt.browser.ProgressListener;
import org.eclipse.swt.browser.VisibilityWindowAdapter;
import org.eclipse.swt.browser.WindowEvent;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.browser.BusyIndicator;
import org.eclipse.ui.internal.browser.ContextIds;
import org.eclipse.ui.internal.browser.IBrowserViewerContainer;
import org.eclipse.ui.internal.browser.ImageResource;
import org.eclipse.ui.internal.browser.ToolbarLayout;
import org.eclipse.ui.internal.browser.Trace;
import org.eclipse.ui.internal.browser.WebBrowserPreference;
import org.eclipse.ui.internal.browser.WebBrowserUtil;
import org.txm.rcp.commands.OpenLocalizedWebPage;
import org.txm.rcp.swt.JFXBrowser;

/**
 * A Web browser widget. It extends the Eclipse SWT Browser widget by adding an
 * optional toolbar complete with a URL combo box, history, back & forward, and
 * refresh buttons.
 * <p>
 * Use the style bits to choose which toolbars are available within the browser
 * composite. You can access the embedded SWT Browser directly using the
 * getBrowser() method.
 * </p>
 * <p>
 * Additional capabilities are available when used as the internal Web browser,
 * including status text and progress on the Eclipse window's status line, or
 * moving the toolbar capabilities up into the main toolbar.
 * </p>
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>LOCATION_BAR, BUTTON_BAR</dd>
 * <dt><b>Events:</b></dt>
 * <dd>None</dd>
 * </dl>
 *
 * @since 1.0
 */
public class JFXBrowserViewer extends Composite {
	
	/**
	 * Style parameter (value 1) indicating that the URL and Go button will be
	 * on the local toolbar.
	 */
	public static final int LOCATION_BAR = 1 << 1;
	
	/**
	 * Style parameter (value 2) indicating that the toolbar will be available
	 * on the web browser. This style parameter cannot be used without the
	 * LOCATION_BAR style.
	 */
	public static final int BUTTON_BAR = 1 << 2;
	
	protected static final String PROPERTY_TITLE = "title"; //$NON-NLS-1$
	
	private static final int MAX_HISTORY = 50;
	
	public Clipboard clipboard;
	
	public Combo combo;
	
	protected boolean showToolbar;
	
	protected boolean showURLbar;
	
	protected ToolItem back;
	
	protected ToolItem forward;
	
	protected BusyIndicator busy;
	
	protected boolean loading;
	
	protected static java.util.List<String> history;
	
	protected JFXBrowser browser;
	
	protected JFXBrowserText text;
	
	protected boolean newWindow;
	
	protected IBrowserViewerContainer container;
	
	protected String title;
	
	protected int progressWorked = 0;
	
	protected List<PropertyChangeListener> propertyListeners;
	
	/**
	 * Under development - do not use
	 */
	public static interface ILocationListener {
		
		public void locationChanged(String url);
		
		public void historyChanged(String[] history2);
	}
	
	public ILocationListener locationListener;
	
	/**
	 * Under development - do not use
	 */
	public static interface IBackNextListener {
		
		public void updateBackNextBusy();
	}
	
	public IBackNextListener backNextListener;
	
	/**
	 * Creates a new Web browser given its parent and a style value describing
	 * its behavior and appearance.
	 * <p>
	 * The style value is either one of the style constants defined in the class
	 * header or class <code>SWT</code> which is applicable to instances of
	 * this class, or must be built by <em>bitwise OR</em>'ing together (that
	 * is, using the <code>int</code> "|" operator) two or more of those
	 * <code>SWT</code> style constants. The class description lists the style
	 * constants that are applicable to the class. Style bits are also inherited
	 * from superclasses.
	 * </p>
	 *
	 * @param parent
	 *            a composite control which will be the parent of the new
	 *            instance (cannot be null)
	 * @param style
	 *            the style of control to construct
	 */
	public JFXBrowserViewer(Composite parent, int style) {
		super(parent, SWT.NONE);
		
		if ((style & LOCATION_BAR) != 0)
			showURLbar = true;
		
		if ((style & BUTTON_BAR) != 0)
			showToolbar = true;
		
		GridLayout layout = new GridLayout();
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		layout.horizontalSpacing = 0;
		layout.verticalSpacing = 0;
		layout.numColumns = 1;
		setLayout(layout);
		setLayoutData(new GridData(GridData.FILL_BOTH));
		clipboard = new Clipboard(parent.getDisplay());
		
		if (showToolbar || showURLbar) {
			Composite toolbarComp = new Composite(this, SWT.NONE);
			toolbarComp.setLayout(new ToolbarLayout());
			toolbarComp.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING | GridData.FILL_HORIZONTAL));
			
			if (showToolbar)
				createToolbar(toolbarComp);
			
			if (showURLbar)
				createLocationBar(toolbarComp);
			
			if (showToolbar | showURLbar) {
				busy = new BusyIndicator(toolbarComp, SWT.NONE);
				busy.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
				busy.addMouseListener(new MouseAdapter() {
					
					@Override
					public void mouseDown(MouseEvent e) {
						setURL(OpenLocalizedWebPage.URL_TEXTOMETRIE);
					}
				});
			}
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, ContextIds.WEB_BROWSER);
		}
		
		// create a new SWT Web browser widget, checking once again to make sure
		// we can use it in this environment
		// if (WebBrowserUtil.canUseInternalWebBrowser())
		try {
			this.browser = new JFXBrowser(this, SWT.NONE);
		}
		catch (SWTError e) {
			if (e.code != SWT.ERROR_NO_HANDLES) {
				WebBrowserUtil.openError(org.eclipse.ui.internal.browser.Messages.errorCouldNotLaunchInternalWebBrowser);
				return;
			}
			text = new JFXBrowserText(this, this, e);
		}
		
		if (showURLbar)
			updateHistory();
		if (showToolbar)
			updateBackNextBusy();
		
		if (browser != null) {
			browser.setLayoutData(new GridData(GridData.FILL_BOTH));
			PlatformUI.getWorkbench().getHelpSystem().setHelp(browser,
					ContextIds.WEB_BROWSER);
		}
		else
			text.getControl().setLayoutData(new GridData(GridData.FILL_BOTH));
		
		addBrowserListeners();
		// listen();
	}
	
	/**
	 * Returns the underlying SWT browser widget.
	 *
	 * @return the underlying browser
	 */
	public JFXBrowser getBrowser() {
		return browser;
	}
	
	/**
	 * Navigate to the home URL.
	 */
	public void home() {
		System.out.println("JFXBrowserViewer.home()");
		// browser.getWebView().setH.set.setText(""); //$NON-NLS-1$
	}
	
	/**
	 * Loads a URL.
	 *
	 * @param url
	 *            the URL to be loaded
	 * @return true if the operation was successful and false otherwise.
	 * @exception IllegalArgumentException
	 *                <ul>
	 *                <li>ERROR_NULL_ARGUMENT - if the url is null</li>
	 *                </ul>
	 * @exception SWTException
	 *                <ul>
	 *                <li>ERROR_THREAD_INVALID_ACCESS when called from the
	 *                wrong thread</li>
	 *                <li>ERROR_WIDGET_DISPOSED when the widget has been
	 *                disposed</li>
	 *                </ul>
	 * @see #getURL()
	 */
	public void setURL(String url) {
		setURL(url, true);
	}
	
	protected void updateBackNextBusy() {
		if (!back.isDisposed()) {
			back.setEnabled(isBackEnabled());
		}
		if (!forward.isDisposed()) {
			forward.setEnabled(isForwardEnabled());
		}
		if (!busy.isDisposed()) {
			busy.setBusy(loading);
		}
		
		if (backNextListener != null)
			backNextListener.updateBackNextBusy();
	}
	
	protected void updateLocation() {
		if (locationListener != null)
			locationListener.historyChanged(null);
		
		if (locationListener != null)
			locationListener.locationChanged(null);
	}
	
	/**
	 *
	 */
	private void addBrowserListeners() {
		if (browser == null) return;
		// respond to ExternalBrowserInstance StatusTextEvents events by
		// updating the status line
		browser.addStatusTextListener(event -> {
			// System.out.println("status: " + event.text); //$NON-NLS-1$
			if (container != null) {
				IStatusLineManager status = container.getActionBars().getStatusLineManager();
				status.setMessage(event.text);
			}
		});
		
		// Add listener for new window creation so that we can instead of
		// opening a separate
		// new window in which the session is lost, we can instead open a new
		// window in a new
		// shell within the browser area thereby maintaining the session.
		browser.addOpenWindowListener(event -> {
			Shell shell2 = new Shell(getShell(), SWT.SHELL_TRIM);
			shell2.setLayout(new FillLayout());
			shell2.setText(org.eclipse.ui.internal.browser.Messages.viewWebBrowserTitle);
			shell2.setImage(getShell().getImage());
			if (event.location != null)
				shell2.setLocation(event.location);
			if (event.size != null)
				shell2.setSize(event.size);
			int style = 0;
			if (showURLbar)
				style += LOCATION_BAR;
			if (showToolbar)
				style += BUTTON_BAR;
			JFXBrowserViewer browser2 = new JFXBrowserViewer(shell2, style);
			browser2.newWindow = true;
			event.browser = null; // TODO should be browser2
		});
		
		browser.addVisibilityWindowListener(new VisibilityWindowAdapter() {
			
			@Override
			public void show(WindowEvent e) {
				Browser browser2 = (Browser) e.widget;
				if (browser2.getParent().getParent() instanceof Shell) {
					Shell shell = (Shell) browser2.getParent().getParent();
					if (e.location != null)
						shell.setLocation(e.location);
					if (e.size != null)
						shell.setSize(shell.computeSize(e.size.x, e.size.y));
					shell.open();
				}
			}
		});
		
		browser.addCloseWindowListener(event -> {
			// if shell is not null, it must be a secondary popup window,
			// else its an editor window
			if (newWindow)
				getShell().dispose();
			else
				container.close();
		});
		
		browser.addProgressListener(new ProgressListener() {
			
			@Override
			public void changed(ProgressEvent event) {
				// System.out.println("progress: " + event.current + ", " + event.total); //$NON-NLS-1$ //$NON-NLS-2$
				if (event.total == 0)
					return;
				
				boolean done = (event.current == event.total);
				
				int percentProgress = event.current * 100 / event.total;
				if (container != null) {
					IProgressMonitor monitor = container.getActionBars()
							.getStatusLineManager().getProgressMonitor();
					if (done) {
						monitor.done();
						progressWorked = 0;
					}
					else if (progressWorked == 0) {
						monitor.beginTask("", event.total); //$NON-NLS-1$
						progressWorked = percentProgress;
					}
					else {
						monitor.worked(event.current - progressWorked);
						progressWorked = event.current;
					}
				}
				
				if (showToolbar) {
					if (!busy.isBusy() && !done)
						loading = true;
					else if (busy.isBusy() && done) // once the progress hits
						// 100 percent, done, set
						// busy to false
						loading = false;
					
					// System.out.println("loading: " + loading); //$NON-NLS-1$
					updateBackNextBusy();
					updateHistory();
				}
			}
			
			@Override
			public void completed(ProgressEvent event) {
				if (container != null) {
					IProgressMonitor monitor = container.getActionBars()
							.getStatusLineManager().getProgressMonitor();
					monitor.done();
				}
				if (showToolbar) {
					loading = false;
					updateBackNextBusy();
					updateHistory();
				}
			}
		});
		
		if (showURLbar) {
			browser.addLocationListener(new LocationAdapter() {
				
				@Override
				public void changed(LocationEvent event) {
					if (!event.top)
						return;
					if (combo != null) {
						if (!"about:blank".equals(event.location)) { //$NON-NLS-1$
							combo.setText(event.location);
							addToHistory(event.location);
							updateHistory();
						}// else
							// combo.setText(""); //$NON-NLS-1$
					}
				}
			});
		}
		
		browser.addTitleListener(event -> {
			String oldTitle = title;
			title = event.title;
			firePropertyChangeEvent(PROPERTY_TITLE, oldTitle, title);
		});
	}
	
	/**
	 * Add a property change listener to this instance.
	 *
	 * @param listener java.beans.PropertyChangeListener
	 */
	public void addPropertyChangeListener(PropertyChangeListener listener) {
		if (propertyListeners == null)
			propertyListeners = new ArrayList<>();
		propertyListeners.add(listener);
	}
	
	/**
	 * Remove a property change listener from this instance.
	 *
	 * @param listener java.beans.PropertyChangeListener
	 */
	public void removePropertyChangeListener(PropertyChangeListener listener) {
		if (propertyListeners != null)
			propertyListeners.remove(listener);
	}
	
	/**
	 * Fire a property change event.
	 */
	protected void firePropertyChangeEvent(String propertyName, Object oldValue, Object newValue) {
		if (propertyListeners == null)
			return;
		
		PropertyChangeEvent event = new PropertyChangeEvent(this, propertyName, oldValue, newValue);
		// Trace.trace("Firing: " + event + " " + oldValue);
		try {
			int size = propertyListeners.size();
			PropertyChangeListener[] pcl = new PropertyChangeListener[size];
			propertyListeners.toArray(pcl);
			
			for (int i = 0; i < size; i++)
				try {
					pcl[i].propertyChange(event);
				}
				catch (Exception e) {
					// ignore
				}
		}
		catch (Exception e) {
			// ignore
		}
	}
	
	/**
	 * Navigate to the next session history item. Convenience method that calls
	 * the underlying SWT browser.
	 *
	 * @return <code>true</code> if the operation was successful and
	 *         <code>false</code> otherwise
	 * @exception SWTException
	 *                <ul>
	 *                <li>ERROR_THREAD_INVALID_ACCESS when called from the
	 *                wrong thread</li>
	 *                <li>ERROR_WIDGET_DISPOSED when the widget has been
	 *                disposed</li>
	 *                </ul>
	 * @see #back
	 */
	public boolean forward() {
		if (browser == null)
			return false;
		return browser.forward();
	}
	
	/**
	 * Navigate to the previous session history item. Convenience method that
	 * calls the underlying SWT browser.
	 *
	 * @return <code>true</code> if the operation was successful and
	 *         <code>false</code> otherwise
	 * @exception SWTException
	 *                <ul>
	 *                <li>ERROR_THREAD_INVALID_ACCESS when called from the
	 *                wrong thread</li>
	 *                <li>ERROR_WIDGET_DISPOSED when the widget has been
	 *                disposed</li>
	 *                </ul>
	 * @see #forward
	 */
	public boolean back() {
		if (browser == null)
			return false;
		return browser.back();
	}
	
	/**
	 * Returns <code>true</code> if the receiver can navigate to the previous
	 * session history item, and <code>false</code> otherwise. Convenience
	 * method that calls the underlying SWT browser.
	 *
	 * @return the receiver's back command enabled state
	 * @exception SWTException
	 *                <ul>
	 *                <li>ERROR_WIDGET_DISPOSED - if the receiver has been
	 *                disposed</li>
	 *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
	 *                thread that created the receiver</li>
	 *                </ul>
	 * @see #back
	 */
	public boolean isBackEnabled() {
		if (browser == null)
			return false;
		return browser.isBackEnabled();
	}
	
	/**
	 * Returns <code>true</code> if the receiver can navigate to the next
	 * session history item, and <code>false</code> otherwise. Convenience
	 * method that calls the underlying SWT browser.
	 *
	 * @return the receiver's forward command enabled state
	 * @exception SWTException
	 *                <ul>
	 *                <li>ERROR_WIDGET_DISPOSED - if the receiver has been
	 *                disposed</li>
	 *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
	 *                thread that created the receiver</li>
	 *                </ul>
	 * @see #forward
	 */
	public boolean isForwardEnabled() {
		if (browser == null)
			return false;
		return browser.isForwardEnabled();
	}
	
	/**
	 * Stop any loading and rendering activity. Convenience method that calls
	 * the underlying SWT browser.
	 *
	 * @exception SWTException
	 *                <ul>
	 *                <li>ERROR_THREAD_INVALID_ACCESS when called from the
	 *                wrong thread</li>
	 *                <li>ERROR_WIDGET_DISPOSED when the widget has been
	 *                disposed</li>
	 *                </ul>
	 */
	public void stop() {
		if (browser != null)
			browser.stop();
	}
	
	/**
	 *
	 */
	private boolean navigate(String url) {
		Trace.trace(Trace.FINER, "Navigate: " + url); //$NON-NLS-1$
		if (url != null && url.equals(getURL())) {
			refresh();
			return true;
		}
		if (browser != null)
			return browser.setUrl(url, null, new String[] { "Cache-Control: no-cache" }); //$NON-NLS-1$
		return text.setUrl(url);
	}
	
	/**
	 * Refresh the current page. Convenience method that calls the underlying
	 * SWT browser.
	 *
	 * @exception SWTException
	 *                <ul>
	 *                <li>ERROR_THREAD_INVALID_ACCESS when called from the
	 *                wrong thread</li>
	 *                <li>ERROR_WIDGET_DISPOSED when the widget has been
	 *                disposed</li>
	 *                </ul>
	 */
	public void refresh() {
		if (browser != null)
			browser.refresh();
		else
			text.refresh();
		try {
			Thread.sleep(50);
		}
		catch (Exception e) {
			// ignore
		}
	}
	
	private void setURL(String url, boolean browse) {
		Trace.trace(Trace.FINEST, "setURL: " + url + " " + browse); //$NON-NLS-1$ //$NON-NLS-2$
		if (url == null) {
			home();
			return;
		}
		
		if (browse)
			navigate(url);
		
		addToHistory(url);
		updateHistory();
	}
	
	protected void addToHistory(String url) {
		if (history == null)
			history = WebBrowserPreference.getInternalWebBrowserHistory();
		int found = -1;
		int size = history.size();
		for (int i = 0; i < size; i++) {
			String s = history.get(i);
			if (s.equals(url)) {
				found = i;
				break;
			}
		}
		
		if (found == -1) {
			if (size >= MAX_HISTORY)
				history.remove(size - 1);
			history.add(0, url);
			WebBrowserPreference.setInternalWebBrowserHistory(history);
		}
		else if (found != 0) {
			history.remove(found);
			history.add(0, url);
			WebBrowserPreference.setInternalWebBrowserHistory(history);
		}
	}
	
	/**
	 *
	 */
	@Override
	public void dispose() {
		super.dispose();
		
		showToolbar = false;
		
		if (busy != null)
			busy.dispose();
		busy = null;
		
		browser = null;
		text = null;
		if (clipboard != null)
			clipboard.dispose();
		clipboard = null;
		
		removeSynchronizationListener();
	}
	
	private ToolBar createLocationBar(Composite parent) {
		combo = new Combo(parent, SWT.DROP_DOWN);
		
		updateHistory();
		
		combo.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
			try {
				if (combo.getSelectionIndex() != -1 && !combo.getListVisible()) {
					setURL(combo.getItem(combo.getSelectionIndex()));
				}
			}
			catch (Exception e1) {
				// ignore
			}
		}));
		combo.addListener(SWT.DefaultSelection, e -> setURL(combo.getText()));
		
		ToolBar toolbar = new ToolBar(parent, SWT.FLAT);
		
		ToolItem go = new ToolItem(toolbar, SWT.NONE);
		go.setImage(ImageResource.getImage(ImageResource.IMG_ELCL_NAV_GO));
		go.setHotImage(ImageResource.getImage(ImageResource.IMG_CLCL_NAV_GO));
		go.setDisabledImage(ImageResource
				.getImage(ImageResource.IMG_DLCL_NAV_GO));
		go.setToolTipText(org.eclipse.ui.internal.browser.Messages.actionWebBrowserGo);
		go.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> setURL(combo.getText())));
		
		return toolbar;
	}
	
	private ToolBar createToolbar(Composite parent) {
		ToolBar toolbar = new ToolBar(parent, SWT.FLAT);
		
		// create back and forward actions
		back = new ToolItem(toolbar, SWT.NONE);
		back.setImage(ImageResource
				.getImage(ImageResource.IMG_ELCL_NAV_BACKWARD));
		back.setHotImage(ImageResource
				.getImage(ImageResource.IMG_CLCL_NAV_BACKWARD));
		back.setDisabledImage(ImageResource
				.getImage(ImageResource.IMG_DLCL_NAV_BACKWARD));
		back.setToolTipText(org.eclipse.ui.internal.browser.Messages.actionWebBrowserBack);
		back.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> back()));
		
		forward = new ToolItem(toolbar, SWT.NONE);
		forward.setImage(ImageResource
				.getImage(ImageResource.IMG_ELCL_NAV_FORWARD));
		forward.setHotImage(ImageResource
				.getImage(ImageResource.IMG_CLCL_NAV_FORWARD));
		forward.setDisabledImage(ImageResource
				.getImage(ImageResource.IMG_DLCL_NAV_FORWARD));
		forward.setToolTipText(org.eclipse.ui.internal.browser.Messages.actionWebBrowserForward);
		forward.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> forward()));
		
		// create refresh, stop, and print actions
		ToolItem stop = new ToolItem(toolbar, SWT.NONE);
		stop.setImage(ImageResource.getImage(ImageResource.IMG_ELCL_NAV_STOP));
		stop.setHotImage(ImageResource
				.getImage(ImageResource.IMG_CLCL_NAV_STOP));
		stop.setDisabledImage(ImageResource
				.getImage(ImageResource.IMG_DLCL_NAV_STOP));
		stop.setToolTipText(org.eclipse.ui.internal.browser.Messages.actionWebBrowserStop);
		stop.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> stop()));
		
		ToolItem refresh = new ToolItem(toolbar, SWT.NONE);
		refresh.setImage(ImageResource
				.getImage(ImageResource.IMG_ELCL_NAV_REFRESH));
		refresh.setHotImage(ImageResource
				.getImage(ImageResource.IMG_CLCL_NAV_REFRESH));
		refresh.setDisabledImage(ImageResource
				.getImage(ImageResource.IMG_DLCL_NAV_REFRESH));
		refresh.setToolTipText(org.eclipse.ui.internal.browser.Messages.actionWebBrowserRefresh);
		refresh.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> refresh()));
		
		return toolbar;
	}
	
	/**
	 * Returns the current URL. Convenience method that calls the underlying SWT
	 * browser.
	 *
	 * @return the current URL or an empty <code>String</code> if there is no
	 *         current URL
	 * @exception SWTException
	 *                <ul>
	 *                <li>ERROR_THREAD_INVALID_ACCESS when called from the
	 *                wrong thread</li>
	 *                <li>ERROR_WIDGET_DISPOSED when the widget has been
	 *                disposed</li>
	 *                </ul>
	 * @see #setURL(String)
	 */
	public String getURL() {
		if (browser != null)
			return browser.getUrl();
		return text.getUrl();
	}
	
	@Override
	public boolean setFocus() {
		if (browser != null) {
			browser.setFocus();
			updateHistory();
			return true;
		}
		return super.setFocus();
	}
	
	/**
	 * Update the history list to the global/shared copy.
	 */
	protected void updateHistory() {
		if (combo == null || combo.isDisposed())
			return;
		
		String temp = combo.getText();
		if (history == null)
			history = WebBrowserPreference.getInternalWebBrowserHistory();
		
		String[] historyList = new String[history.size()];
		history.toArray(historyList);
		combo.setItems(historyList);
		
		combo.setText(temp);
	}
	
	public IBrowserViewerContainer getContainer() {
		return container;
	}
	
	public void setContainer(IBrowserViewerContainer container) {
		if (container == null && this.container != null) {
			IStatusLineManager manager = this.container.getActionBars().getStatusLineManager();
			if (manager != null)
				manager.getProgressMonitor().done();
		}
		this.container = container;
	}
	
	protected File file;
	
	protected long timestamp;
	
	protected Thread fileListenerThread;
	
	protected LocationListener locationListener2;
	
	protected Object syncObject = new Object();
	
	protected void addSynchronizationListener() {
		if (fileListenerThread != null)
			return;
		
		fileListenerThread = new Thread("Browser file synchronization") { //$NON-NLS-1$
			
			@Override
			public void run() {
				while (fileListenerThread != null) {
					try {
						Thread.sleep(2000);
					}
					catch (Exception e) {
						// ignore
					}
					synchronized (syncObject) {
						if (file != null && file.lastModified() != timestamp) {
							timestamp = file.lastModified();
							Display.getDefault().syncExec(() -> refresh());
						}
					}
				}
			}
		};
		fileListenerThread.setDaemon(true);
		fileListenerThread.setPriority(Thread.MIN_PRIORITY);
		
		locationListener2 = new LocationAdapter() {
			
			@Override
			public void changed(LocationEvent event) {
				File temp = getFile(event.location);
				if (temp != null && temp.exists()) {
					synchronized (syncObject) {
						file = temp;
						timestamp = file.lastModified();
					}
				}
				else
					file = null;
			}
		};
		browser.addLocationListener(locationListener2);
		
		File temp = getFile(browser.getUrl());
		if (temp != null && temp.exists()) {
			file = temp;
			timestamp = file.lastModified();
		}
		fileListenerThread.start();
	}
	
	protected static File getFile(String location) {
		if (location == null)
			return null;
		if (location.startsWith("file:/")) //$NON-NLS-1$
			location = location.substring(6);
		
		return new File(location);
	}
	
	protected void removeSynchronizationListener() {
		if (fileListenerThread == null)
			return;
		
		fileListenerThread = null;
		browser.removeLocationListener(locationListener2);
		locationListener2 = null;
	}
}
