/* $Revision: 1.4 $ */
/***************************************************************************
 ** ibrowse.c
 **
 ** This mex file is the entry point to control internet browsers, mainly
 ** for the MATLAB helpdesk. Currently, it directly supports the Internet Explorer 
 ** (V 3.0) and Netscape Navigator (V 3.0). If we find another browser registered
 ** to "handle" the ".html" extension, we just launch the browser passing the URL
 ** as a command line argument.
 **
 *****************************************************************************/



/*******************************************************************************
 ** READ THIS IF YOU"RE GOING TO MODIFY THIS FILE !!!!
 ** Most functions in here return TRUE if they fail. It is consistent within 
 ** this file, and the spec requires the mexfunction to return a 1 if something
 ** goes wrong. If you need to modify this file, please follow this "convention"
 **
 ********************************************************************************/


extern "C"
{
#define bool BOOL
#include <windows.h>
#include "mex.h"
}

#define		BROWSER_NOBROWSER		0
#define		BROWSER_IE				1
#define		BROWSER_NAVIGATOR		2
#define		BROWSER_OTHER			3


/* ********************************************************
 * Watcom is too stupid to have defined these standard GUIDS, I 
 * have to do it here.
 *
 * ******************************************************** */

#ifdef __WATCOMC__
// IID_IDispatch = {00020400-0000-0000-C000-000000000046};
const IID __cdecl IID_IDispatch = 
{ 0x00020400, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } };

// GUID_NULL = {0};
const IID __cdecl GUID_NULL = {0};
#endif





BOOL GetIeClassID( CLSID  * cidIe);
BOOL CreateObject(LPDISPATCH *hDispatch);
BOOL InvokeNavigate(LPDISPATCH lpDispatch, BSTR bstrURL);
int getBrowser(CHAR **szAppName);
BOOL ieload( CHAR *szURL );
void ExitFcn(void);


LPDISPATCH lpDispatch = NULL;


extern "C" {
void mexFunction(int nlhs,  mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    // For now use static strings
    char *szURL=NULL , *szAppName=NULL, *szCommand = NULL;
	const char szBeginCommand[] = "nnload('";
	const char szEndCommand[] = "');"; 
    int bSuccess = 1, iURL=0;    

    // Validate Input
    if((nrhs != 1) || (!mxIsChar(prhs[0])))
        mexErrMsgTxt("Input argument must be one string");

	// Extract the URL from the input argument
	iURL = mxGetN(prhs[0])+1;
	szURL = (char *)mxCalloc(iURL, sizeof(char));
    mxGetString(prhs[0], szURL, iURL);

	switch(getBrowser(&szAppName))
	{
		case BROWSER_IE:
			if(TRUE == ieload(szURL))
				bSuccess = 1;
			else
				bSuccess = 0;
			break;

		case BROWSER_NAVIGATOR:
			szCommand = (char *)mxCalloc(strlen(szBeginCommand)+strlen(szURL)+strlen(szEndCommand)+1, sizeof(char));
			wsprintf(szCommand, "%s%s%s", szBeginCommand, szURL, szEndCommand);
			mexEvalString(szCommand);
			bSuccess = 0;
			break;

		case BROWSER_OTHER:
			szCommand = (char *)mxCalloc(strlen(szAppName)+strlen(szURL)+2, sizeof(char));
			wsprintf(szCommand, "%s %s", szAppName, szURL);
			WinExec(szCommand, SW_SHOWNORMAL);
			bSuccess = 0;
			break;

		case BROWSER_NOBROWSER:
			mexPrintf("Sorry, no browser is registered to handle the '.html' extension.\n");
			break;
			// Add error message here
			
	}

	if(szAppName)
		mxFree(szAppName);

	if(szURL)
		mxFree(szURL);

	if(szCommand)
		mxFree(szCommand);

	plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL);
	*(mxGetPr(plhs[0])) = (double)bSuccess;

	
}
} // extern "C"



int getBrowser(CHAR **szAppName)
{
	HKEY hKey = NULL;
	const char szHtmlKey[] = ".html";
	const char szPathCompletion[] = "\\shell\\open\\command";
	char *szKeyEntry=NULL, *szCommandLocation=NULL;
	LONG lSize = 0;
	int i, iReturnVal;

	

	if(ERROR_SUCCESS == RegQueryValue(HKEY_CLASSES_ROOT, szHtmlKey, NULL, &lSize))
	{
		szKeyEntry = (char*)mxCalloc(lSize, sizeof(char));
		szKeyEntry[0] = '\0';
		RegQueryValue(HKEY_CLASSES_ROOT, szHtmlKey, szKeyEntry, &lSize);

		if(0 == strlen(szKeyEntry))
		{
			iReturnVal = (int)BROWSER_NOBROWSER;
			goto EXIT_POINT;
		}
		
		szCommandLocation = (char *)mxCalloc(strlen(szKeyEntry) + strlen(szPathCompletion) + 1, sizeof(char));
		wsprintf(szCommandLocation, "%s%s", szKeyEntry, szPathCompletion);
		

		RegQueryValue(HKEY_CLASSES_ROOT, szCommandLocation, NULL, &lSize);
		*szAppName = (char *)mxCalloc(lSize, sizeof(char));
		RegQueryValue(HKEY_CLASSES_ROOT, szCommandLocation, *szAppName, &lSize);
		
		// Move everything to lower case
		for(i=0; (*szAppName)[i] != '\0'; i++)
			(*szAppName)[i] = tolower((*szAppName)[i]);
		
		// If there isn't an open command, say so.
		if(0 == strlen(*szAppName))
		{
			iReturnVal = (int)BROWSER_NOBROWSER;
			goto EXIT_POINT;
		} 		
		// figure out the browser
		else if(NULL != strstr(*szAppName, "netscape"))
		{
			iReturnVal = (int)BROWSER_NAVIGATOR; 
			goto EXIT_POINT;
		}
		else if(NULL != strstr(*szAppName, "iexplore"))
		{
			iReturnVal = (int)BROWSER_IE;
			goto EXIT_POINT;
		}
		else
		{
			iReturnVal = (int)BROWSER_OTHER;
			goto EXIT_POINT;
		}
	}
	else
	{
		iReturnVal = (int)BROWSER_NOBROWSER;
	}

	EXIT_POINT:
	
	if(NULL != szKeyEntry)
		mxFree(szKeyEntry);

	if(NULL != szCommandLocation)
		mxFree(szCommandLocation);

	return(iReturnVal);


}



BOOL ieload( CHAR *szURL )
{

	WCHAR wszURL[MAX_PATH];
    BSTR  bstrURL = NULL;
    INT iLength = 0;
    BOOL bInitOK = FALSE;
    INT cNumEntries = 0;
    CLSID cidIe = {0};
	char szErrorMessage[MAX_PATH];
	BOOL bWasError = FALSE;

	// Register the AtExit Fcn
	mexAtExit(ExitFcn);

    // Make sure OLE is initialized
  	if(SUCCEEDED(OleInitialize(NULL)))
        bInitOK = TRUE;
    else
    {
		strcpy(szErrorMessage, "Error. Unable to initialize OLE libraries");
		bWasError = TRUE;
		goto CLEAN_UP;
	}

    
    MultiByteToWideChar(CP_ACP, 0, szURL, -1, wszURL, MAX_PATH);
    bstrURL = SysAllocString(wszURL);

    // CoCreate only if lpDispatch == NULL
    if((NULL == lpDispatch) && (TRUE == CreateObject(&lpDispatch)))  // TRUE means something went wrong
	{
		strcpy(szErrorMessage, "Error Creating Internet Explorer Object\n");
		bWasError = TRUE;
		goto CLEAN_UP;
	}


    if(TRUE == InvokeNavigate(lpDispatch, bstrURL))		// TRUE means it didn't work
	{
		// If the invoke failed, it may be that the user closed the explorer. 
		// In that case, we need to CoCreate the object again
		lpDispatch = NULL;
		if(!CreateObject(&lpDispatch))
			InvokeNavigate(lpDispatch, bstrURL);

	}

CLEAN_UP:

	// SysFreeing an NULL BSTR is legal
    SysFreeString(bstrURL);

   	if(bInitOK)
        OleUninitialize();

	if(TRUE == bWasError)
	{
		mexPrintf(szErrorMessage);
		return TRUE;
	}
	else
		return FALSE;

}

BOOL GetIeClassID( CLSID  * cidIe)
{
	CHAR szCLSID[MAX_PATH];
	WCHAR wszCLSID[MAX_PATH];
	LONG length;

	length = (LONG)MAX_PATH;
	if(ERROR_SUCCESS != RegQueryValue(HKEY_CLASSES_ROOT, "InternetExplorer.Application\\CLSID", szCLSID, &length))
	{
		mexPrintf("Error: Internet Explorer not properly registered. Re-installation recommended\n");
		return (TRUE);
	}
	else
	{

		// Turn string into wide char
		MultiByteToWideChar( CP_ACP, 0, szCLSID, -1, wszCLSID, MAX_PATH);

		if(ERROR_SUCCESS == CLSIDFromString(wszCLSID, cidIe))
			return FALSE;
		else
			return TRUE;
	
	}

	

}



BOOL CreateObject(LPDISPATCH *hDispatch)
{
    CLSID cidIe = {0};
	WCHAR wszVisible[MAX_PATH];
	BSTR bstrVisible;
	DISPPARAMS dp;
	EXCEPINFO ei = {0};
	VARIANTARG vVisible;
	DISPID didVisible;
	UINT uiErr = 0;

	
	if(GetIeClassID(&cidIe))
		return TRUE;

	if(S_OK != CoCreateInstance(cidIe, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (LPVOID *)hDispatch))
		return TRUE;
						
						
	// Make the object visible
	MultiByteToWideChar(CP_ACP, 0, "Visible", -1, wszVisible, MAX_PATH);
	bstrVisible = SysAllocString(wszVisible);

	(*hDispatch)->GetIDsOfNames(IID_NULL, &bstrVisible, 1, LOCALE_SYSTEM_DEFAULT, &didVisible);
	SysFreeString(bstrVisible);

	VariantInit(&vVisible);
	vVisible.vt = VT_BOOL;

	#ifdef __WATCOMC__
    vVisible.bool = -1;	
	#else
    vVisible.boolVal = -1;
	#endif


	dp.rgvarg = &vVisible;
	dp.cArgs = 1;
	dp.rgdispidNamedArgs = NULL;
	dp.cNamedArgs = 0;

	(*hDispatch)->Invoke(didVisible, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dp, NULL, &ei, &uiErr);
	
	return FALSE;
}



BOOL InvokeNavigate(LPDISPATCH lpDispatch, BSTR bstrURL)
{
	WCHAR wszNavigate[MAX_PATH];
	BSTR bstrNavigate;
	UINT uiErr = 0;
	DISPPARAMS dp;
	EXCEPINFO ei = {0};
	VARIANTARG vURL;
	DISPID didNavigateID, didHWND;

	VARIANTARG vHWND;
	WCHAR wszHWND[MAX_PATH];
	BSTR bstrHWND;
	DISPPARAMS dpNull = {NULL, NULL, 0, 0};

	HWND hIeWnd = NULL;

	
	// We need to find if the window is iconic. Unfortunately, there is no
	// property for this. On the other hand, there's a HWND property that I 
	// can get and use the starndard API with. So, let's do that
	MultiByteToWideChar(CP_ACP, 0, "HWND", -1, wszHWND, MAX_PATH);
	bstrHWND = SysAllocString(wszHWND);
	lpDispatch->GetIDsOfNames(IID_NULL, &bstrHWND, 1, LOCALE_SYSTEM_DEFAULT, &didHWND);
	SysFreeString(bstrHWND);

	VariantInit(&vHWND);
	vHWND.vt = VT_EMPTY;
	if(S_OK != lpDispatch->Invoke(didHWND, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dpNull, &vHWND, &ei, &uiErr))
		return TRUE;

	hIeWnd = (HWND)vHWND.lVal;
	// If the window is iconic, or it's in the background, this will make sure it's visible and move it to the front.
	if (!IsWindowVisible(hIeWnd))
		ShowWindow(hIeWnd, SW_SHOW);
		
	BringWindowToTop(hIeWnd);


	// Get the Navigate method ID
	MultiByteToWideChar(CP_ACP, 0, "Navigate", -1, wszNavigate, MAX_PATH);
	bstrNavigate = SysAllocString(wszNavigate);

	if(S_OK != lpDispatch->GetIDsOfNames(IID_NULL, &bstrNavigate, 1, LOCALE_SYSTEM_DEFAULT, &didNavigateID))
	{
		SysFreeString(bstrNavigate);
		return TRUE;
	}
	SysFreeString(bstrNavigate);

	VariantInit(&vURL);
	vURL.vt = VT_BSTR;
	vURL.bstrVal = bstrURL;

	dp.rgvarg = &vURL;
	dp.cArgs = 1;
	
	dp.rgdispidNamedArgs = NULL;
	dp.cNamedArgs = 0;

	if(S_OK != lpDispatch->Invoke(didNavigateID, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dp, NULL, &ei, &uiErr))
	{
		// Something went wrong. The dispatch interface may not be valid, so indicate this by bassing back a TRUE
		return TRUE;
	}

	return FALSE;
}

void ExitFcn(void)
{
	if(lpDispatch)
	{
		lpDispatch->Release();
		lpDispatch = NULL;
	}

}