/*******************************************************************************
 * Copyright (c) 2005, 2016 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.swt.browser;

import java.io.*;
import java.util.*;
import java.util.List;

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.internal.mozilla.*;
import org.eclipse.swt.internal.win32.*;
import org.eclipse.swt.widgets.*;

class MozillaDelegate {
	Browser browser;
	List<Composite> childWindows = new ArrayList<> (9);
	static long /*int*/ MozillaProc;
	static Callback SubclassProc;

	static Boolean IsXULRunner24;
	static final String LIB_XPCOM = "xpcom.dll"; //$NON-NLS-1$
	static final String LIB_XUL = "xul.dll"; //$NON-NLS-1$

MozillaDelegate (Browser browser) {
	super ();
	this.browser = browser;
}

static Browser findBrowser (long /*int*/ handle) {
	Display display = Display.getCurrent ();
	return (Browser)display.findWidget (handle);
}

static String getCacheParentPath () {
	/* Use the character encoding for the default locale */
	TCHAR buffer = new TCHAR (0, OS.MAX_PATH);
	if (OS.SHGetFolderPath (0, OS.CSIDL_LOCAL_APPDATA, 0, OS.SHGFP_TYPE_CURRENT, buffer) == OS.S_OK) {
		return buffer.toString (0, buffer.strlen ()) + Mozilla.SEPARATOR_OS + "eclipse"; //$NON-NLS-1$
	}
	return getProfilePath ();
}

static String[] getJSLibraryNames () {
	return new String[] {"mozjs.dll", "xul.dll"}; //$NON-NLS-1$ //$NON-NLS-2$
}

static String getJSLibraryName_Pre10 () {
	return "js3250.dll"; //$NON-NLS-1$
}

static String getLibraryName (String mozillaPath) {
	/*
	 * The name of the Gecko library to glue to changed between the XULRunner 10 and
	 * 24 releases.  However it's not possible to programmatically know the version
	 * of a XULRunner that's being used before it has been glued.  To determine the
	 * appropriate Gecko library name to return, look for the presence of an "xpcom"
	 * library in the mozilla path, which is present in all supported XULRunner releases
	 * prior to XULRunner 24.  If this library is there then return it, and if it's not
	 * there then assume that XULRunner 24 is being used and return the new library name
	 * instead ("xul").
	 */
	if (IsXULRunner24 == null) { /* IsXULRunner24 not yet initialized */
		IsXULRunner24 = new File (mozillaPath, LIB_XPCOM).exists () ? Boolean.FALSE : Boolean.TRUE;
	}
	return IsXULRunner24.booleanValue () ? LIB_XUL : LIB_XPCOM;
}

static String getProfilePath () {
	String baseDir;
	/* Use the character encoding for the default locale */
	TCHAR buffer = new TCHAR (0, OS.MAX_PATH);
	if (OS.SHGetFolderPath (0, OS.CSIDL_APPDATA, 0, OS.SHGFP_TYPE_CURRENT, buffer) == OS.S_OK) {
		baseDir = buffer.toString (0, buffer.strlen ());
	} else {
		baseDir = System.getProperty("user.home"); //$NON-NLS-1$
	}
	return baseDir + Mozilla.SEPARATOR_OS + "Mozilla" + Mozilla.SEPARATOR_OS + "eclipse"; //$NON-NLS-1$ //$NON-NLS-2$
}

static String getSWTInitLibraryName () {
	return "swt-xulrunner"; //$NON-NLS-1$
}

static void loadAdditionalLibraries (String mozillaPath, boolean isGlued) {
}

static char[] mbcsToWcs (String codePage, byte[] buffer) {
	char[] chars = new char[buffer.length];
	int charCount = OS.MultiByteToWideChar (OS.CP_ACP, OS.MB_PRECOMPOSED, buffer, buffer.length, chars, chars.length);
	if (charCount == chars.length) return chars;
	char[] result = new char[charCount];
	System.arraycopy (chars, 0, result, 0, charCount);
	return result;
}

static boolean needsSpinup () {
	return false;
}

static byte[] wcsToMbcs (String codePage, String string, boolean terminate) {
	int byteCount;
	char[] chars = new char[string.length()];
	string.getChars (0, chars.length, chars, 0);
	byte[] bytes = new byte[byteCount = chars.length * 2 + (terminate ? 1 : 0)];
	byteCount = OS.WideCharToMultiByte (OS.CP_ACP, 0, chars, chars.length, bytes, byteCount, null, null);
	if (terminate) {
		byteCount++;
	} else {
		if (bytes.length != byteCount) {
			byte[] result = new byte[byteCount];
			System.arraycopy (bytes, 0, result, 0, byteCount);
			bytes = result;
		}
	}
	return bytes;
}

static long /*int*/ windowProc (long /*int*/ hwnd, long /*int*/ msg, long /*int*/ wParam, long /*int*/ lParam) {
	switch ((int)/*64*/msg) {
		case OS.WM_UPDATEUISTATE:
			/*
			 * In XULRunner 17, calling the default windowProc for WM_UPDATEUISTATE message
			 * terminates the program. Workaround is to prevent the call to default windowProc.
			 */
			return 0;
		case OS.WM_ERASEBKGND:
			RECT rect = new RECT ();
			OS.GetClientRect (hwnd, rect);
			OS.FillRect (wParam, rect, OS.GetSysColorBrush (OS.COLOR_WINDOW));
			break;
	}
	return OS.CallWindowProc (MozillaProc, hwnd, (int)/*64*/msg, wParam, lParam);
}

void addWindowSubclass () {
	long /*int*/ hwndChild = OS.GetWindow (browser.handle, OS.GW_CHILD);
	if (SubclassProc == null) {
		SubclassProc = new Callback (MozillaDelegate.class, "windowProc", 4); //$NON-NLS-1$
		MozillaProc = OS.GetWindowLongPtr (hwndChild, OS.GWL_WNDPROC);
	}
	OS.SetWindowLongPtr (hwndChild, OS.GWL_WNDPROC, SubclassProc.getAddress ());
}

int createBaseWindow (nsIBaseWindow baseWindow) {
	return baseWindow.Create ();
}

long /*int*/ getHandle () {
	return browser.handle;
}

Point getNativeSize (int width, int height) {
	return new Point (width, height);
}

long /*int*/ getSiteWindow () {
	/*
	* As of XULRunner 4, XULRunner's printing facilities on Windows destroy
	* the HWND that is returned from here once the print dialog is dismissed
	* (originating bug: https://bugzilla.mozilla.org/show_bug.cgi?id=588735 ).
	* For this scenario it is now expected that the handle that is returned
	* here is a child of the browser handle, not the browser handle itself.
	*
	* The other scenario that requests this handle is the Mozilla.getBrowser()
	* implementation.  This method's GetSiteWindow() invocation is surrounded
	* by boolean flags to help differentiate it from the printing scenario,
	* since Mozilla.getBrowser() does not destroy the handle it receives back.
	*
	* All children that are created here are stored and then destroyed once
	* the current page is left.  This is guard code that should only be needed
	* if Mozilla.getSiteWindow() is ever invoked by a path other than one of
	* the two described above.
	*/
	if (!MozillaVersion.CheckVersion (MozillaVersion.VERSION_XR10) || Mozilla.IsGettingSiteWindow) {
		return getHandle ();
	}

	Composite child = new Composite (browser, SWT.NONE);
	childWindows.add (child);
	return child.handle;
}

void handleFocus () {
}

void handleMouseDown () {
}

boolean hookEnterExit () {
	return true;
}

void init () {
	if (MozillaVersion.CheckVersion (MozillaVersion.VERSION_XR10)) {
		/*
		* In XULRunner versions > 4, sending WM_GETDLGCODE to a WM_KEYDOWN's MSG hwnd answers 0
		* instead of the expected DLGC_WANTALLKEYS.  This causes the default traversal framework
		* perform traversals outside of the Browser when it should not.  Hook a Traverse listener
		* to work around these problems.
		*/
		browser.addListener (SWT.Traverse, new Listener () {
			@Override
			public void handleEvent (Event event) {
				switch (event.detail) {
					case SWT.TRAVERSE_RETURN: {
						/* always veto the traversal */
						event.doit = false;
						break;
					}
					case SWT.TRAVERSE_TAB_NEXT:
					case SWT.TRAVERSE_TAB_PREVIOUS: {
						/* veto the traversal whenever an element in the browser has focus */
						long /*int*/[] result = new long /*int*/[1];
						int rc = XPCOM.NS_GetServiceManager (result);
						if (rc != XPCOM.NS_OK) Mozilla.error (rc);
						if (result[0] == 0) Mozilla.error (XPCOM.NS_NOINTERFACE);
						nsIServiceManager serviceManager = new nsIServiceManager (result[0]);
						result[0] = 0;
						byte[] aContractID = MozillaDelegate.wcsToMbcs (null, XPCOM.NS_FOCUSMANAGER_CONTRACTID, true);
						rc = serviceManager.GetServiceByContractID (aContractID, IIDStore.GetIID (nsIFocusManager.class, MozillaVersion.VERSION_XR10), result);
						serviceManager.Release ();

						if (rc == XPCOM.NS_OK && result[0] != 0) {
							nsIFocusManager focusManager = new nsIFocusManager (result[0]);
							result[0] = 0;
							rc = focusManager.GetFocusedElement (result);
							focusManager.Release ();
							event.doit = result[0] == 0;
							if (rc == XPCOM.NS_OK && result[0] != 0) {
								new nsISupports (result[0]).Release ();
							}
						}
						break;
					}
				}
			}
		});

		/* children created in getSiteHandle() should be destroyed whenever a page is left */
		browser.addLocationListener (new LocationAdapter () {
			@Override
			public void changing (LocationEvent event) {
				Iterator<Composite> it = childWindows.iterator ();
				while (it.hasNext ()) {
					it.next ().dispose ();
				}
				childWindows.clear ();
			}
		});
	}
}

void onDispose (long /*int*/ embedHandle) {
	if (SubclassProc == null) return;
	long /*int*/ hwndChild = OS.GetWindow (browser.handle, OS.GW_CHILD);
	OS.SetWindowLongPtr (hwndChild, OS.GWL_WNDPROC, MozillaProc);
	childWindows = null;
	browser = null;
}

void removeWindowSubclass () {
	long /*int*/ hwndChild = OS.GetWindow (browser.handle, OS.GW_CHILD);
	if (SubclassProc != null) {
		OS.SetWindowLongPtr (hwndChild, OS.GWL_WNDPROC, MozillaProc);
	}
}

boolean sendTraverse () {
	return false;
}

void setSize (long /*int*/ embedHandle, int width, int height) {
}
}
