/* -*- Mode: C; tab-width: 4; -*- */
/*******************************************************************************
 * Simple LiveConnect Sample Plugin
 * Copyright (c) 1996 Netscape Communications. All rights reserved.
 ******************************************************************************/
/*
 * npshell.c
 *
 * Netscape Client Plugin API
 * - Function that need to be implemented by plugin developers
 *
 * This file defines a "shell" plugin that plugin developers can use
 * as the basis for a real plugin.  This shell just provides empty
 * implementations of all functions that the plugin can implement
 * that will be called by Netscape (the NPP_xxx methods defined in 
 * npapi.h). 
 *
 *******************************************************************************
 * PLUGIN DEVELOPERS:
 *	Implement your plugins here.
 *	A sample text plugin is implemented here.
 *	All the sample plugin does is displays any file with a
 *	".txt" extension in a scrolled text window. It uses Motif.
 ******************************************************************************/

#include <stdio.h>
#include <string.h>
#include "npapi.h"

/*
** Define IMPLEMENT_Simple before including Simple.h to state that we're
** implementing the native methods of this plug-in here, and consequently
** need to access it's protected and private memebers.
*/
#define IMPLEMENT_Simple
#include "Simple.h"

/* Windows Includes */
#ifdef _WINDOWS
#include <windows.h>
#endif /* _WINDOWS */

/*
** Stuff for the NPP_SetWindow method:
*/
#ifdef XP_UNIX
#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#endif /* XP_UNIX */

/*******************************************************************************
 * Instance state information about the plugin.
 *
 * PLUGIN DEVELOPERS:
 *	Use this struct to hold per-instance information that you'll
 *	need in the various functions in this file.
 ******************************************************************************/

typedef struct _PluginInstance
{
	NPWindow*		fWindow;
	uint16			fMode;

/* Windows data members */
#ifdef _WINDOWS
	HWND			fhWnd;
	WNDPROC			fDefaultWindowProc;
#endif /* _WINDOWS */

/* UNIX data members */
#ifdef XP_UNIX
    Window window;
    Display *display;
    uint32 x, y;
    uint32 width, height;
#endif /* XP_UNIX */
} PluginInstance;

void DisplayJavaMessage(NPP instance, char* msg, int len);

/*******************************************************************************
 * Windows-only declarations
 ******************************************************************************/

#ifdef _WINDOWS
LRESULT CALLBACK PluginWindowProc( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);
const char* gInstanceLookupString = "instance->pdata";
#endif

/*******************************************************************************
 * Mac-only declarations
 ******************************************************************************/

#ifdef macintosh
NPBool	StartDraw(NPWindow* window);
void 	EndDraw(NPWindow* window);
void 	DoDraw(PluginInstance* This);
CGrafPort gSavePort;
CGrafPtr gOldPort;
#endif

/*******************************************************************************
 * Unix-only declarations
 ******************************************************************************/

#ifdef XP_UNIX
void Redraw(Widget w, XtPointer closure, XEvent *event);
#endif /* XP_UNIX */

/*******************************************************************************
 * UNIX-only API calls
 ******************************************************************************/

#ifdef XP_UNIX

char*
NPP_GetMIMEDescription(void)
{
	return("application/x-simple-plugin:smp:Simple LiveConnect Sample Plug-in");
}

#define PLUGIN_NAME			"Simple LiveConnect Sample Plug-in"
#define PLUGIN_DESCRIPTION	"Demonstrates a simple LiveConnected plug-in."

NPError
NPP_GetValue(void *future, NPPVariable variable, void *value)
{
    NPError err = NPERR_NO_ERROR;
    if (variable == NPPVpluginNameString)
		*((char **)value) = PLUGIN_NAME;
    else if (variable == NPPVpluginDescriptionString)
		*((char **)value) = PLUGIN_DESCRIPTION;
    else
		err = NPERR_GENERIC_ERROR;

    return err;
}
#endif /* XP_UNIX */

/*******************************************************************************
 * General Plug-in Calls
 ******************************************************************************/

/*
** NPP_Initialize is called when your DLL is being loaded to do any
** DLL-specific initialization.
*/
NPError
NPP_Initialize(void)
{
    return NPERR_NO_ERROR;
}

/*
** We'll keep a global execution environment around to make our life
** simpler.
*/
JRIEnv* env;

/*
** NPP_GetJavaClass is called during initialization to ask your plugin
** what its associated Java class is. If you don't have one, just return
** NULL. Otherwise, use the javah-generated "use_" function to both
** initialize your class and return it. If you can't find your class, an
** error will be signalled by "use_" and will cause the Navigator to
** complain to the user.
*/
jref
NPP_GetJavaClass(void)
{
	struct java_lang_Class* myClass;
	env = NPN_GetJavaEnv();
	if (env == NULL)
		return NULL;		/* Java disabled */

    myClass = use_Simple(env);

	if (myClass == NULL) {
		/*
		** If our class doesn't exist (the user hasn't installed it) then
		** don't allow any of the Java stuff to happen.
		*/
		env = NULL;
	}
	return myClass;
}

/*
** NPP_Shutdown is called when your DLL is being unloaded to do any
** DLL-specific shut-down. You should be a good citizen and declare that
** you're not using your java class any more. This allows java to unload
** it, freeing up memory.
*/
void
NPP_Shutdown(void)
{
	if (env)
		unuse_Simple(env);
}

/*
** This function is a utility routine that calls back into Java to print
** messages to the Java Console and to stdout (via the native method,
** native_Simple_printToStdout, defined below).  Sure, it's not a very
** interesting use of Java, but it gets the point across.
*/
void
DisplayJavaMessage(NPP instance, char* msg, int len)
{
	jref str, javaPeer;

	if (!env) {
		/* Java failed to initialize, so do nothing. */
		return;
	}

	if (len == -1) 
		len = strlen(msg);

	/*
    ** Use the JRI (see jri.h) to create a Java string from the input
    ** message:
    */
	str = JRI_NewStringUTF(env, msg, len);

	/*
    ** Use the NPN_GetJavaPeer operation to get the Java instance that
    ** corresponds to our plug-in (an instance of the Simple class):
    */
	javaPeer = NPN_GetJavaPeer(instance);
	
	/*
    ** Finally, call our plug-in's big "feature" -- the 'doit' method,
    ** passing the execution environment, the object, and the java
    ** string:
    */
	Simple_doit(env, javaPeer, str);
}

/*
** NPP_New is called when your plugin is instantiated (i.e. when an EMBED
** tag appears on a page).
*/
NPError 
NPP_New(NPMIMEType pluginType,
	NPP instance,
	uint16 mode,
	int16 argc,
	char* argn[],
	char* argv[],
	NPSavedData* saved)
{
	NPError result = NPERR_NO_ERROR;
	PluginInstance* This;
	char factString[60];

	if (instance == NULL)
		return NPERR_INVALID_INSTANCE_ERROR;
		
	instance->pdata = NPN_MemAlloc(sizeof(PluginInstance));
	
	This = (PluginInstance*) instance->pdata;

	if (This == NULL)
	    return NPERR_OUT_OF_MEMORY_ERROR;

	{
		/* mode is NP_EMBED, NP_FULL, or NP_BACKGROUND (see npapi.h) */
		This->fWindow = NULL;
		This->fMode = mode;

#ifdef XP_UNIX
		This->window = 0;
#endif /* XP_UNIX */
#ifdef _WINDOWS
		This->fhWnd = NULL;
		This->fDefaultWindowProc = NULL;
#endif /* _WIDOWS */

		/* PLUGIN DEVELOPERS:
		 *	Initialize fields of your plugin
		 *	instance data here.  If the NPSavedData is non-
		 *	NULL, you can use that data (returned by you from
		 *	NPP_Destroy to set up the new plugin instance).
		 */
	}

	/* Show off some of that Java functionality: */
	if (env) {
		jint v;

		/*
		** Call the DisplayJavaMessage utility function to cause Java to
		** write to the console and to stdout:
		*/
		DisplayJavaMessage(instance, "Hello world from npsimple!", -1); 

		/*
		** Also test out that fancy factorial method. It's a static
		** method, so we'll need to use the class object in order to call
		** it:
		*/
		v = Simple_fact(env, class_Simple(env), 10);
		sprintf(factString, "my favorite function returned %d\n", v);
		DisplayJavaMessage( instance, factString, -1 );
	}

	return result;
}


NPError 
NPP_Destroy(NPP instance, NPSavedData** save)
{
	PluginInstance* This;

	if (instance == NULL)
		return NPERR_INVALID_INSTANCE_ERROR;

	This = (PluginInstance*) instance->pdata;

	/* PLUGIN DEVELOPERS:
	 *	If desired, call NP_MemAlloc to create a
	 *	NPSavedDate structure containing any state information
	 *	that you want restored if this plugin instance is later
	 *	recreated.
	 */

	if (This != NULL) {
		NPN_MemFree(instance->pdata);
		instance->pdata = NULL;
	}

	DisplayJavaMessage(instance, "Calling NPP_Destroy.", -1);

	return NPERR_NO_ERROR;
}

NPError 
NPP_SetWindow(NPP instance, NPWindow* window)
{
	NPError result = NPERR_NO_ERROR;
	PluginInstance* This;

	DisplayJavaMessage(instance, "Calling NPP_SetWindow.", -1); 

	if (instance == NULL)
		return NPERR_INVALID_INSTANCE_ERROR;

	This = (PluginInstance*) instance->pdata;

	/*
	 * PLUGIN DEVELOPERS:
	 *	Before setting window to point to the
	 *	new window, you may wish to compare the new window
	 *	info to the previous window (if any) to note window
	 *	size changes, etc.
	 */
	
#ifdef XP_UNIX
	{
		Widget netscape_widget;

		This->window = (Window) window->window;
		This->x = window->x;
		This->y = window->y;
		This->width = window->width;
		This->height = window->height;
		This->display = ((NPSetWindowCallbackStruct *)window->ws_info)->display;
		
	    netscape_widget = XtWindowToWidget(This->display, This->window);
		XtAddEventHandler(netscape_widget, ExposureMask, FALSE, (XtEventHandler)Redraw, This);
		Redraw(netscape_widget, (XtPointer)This, NULL);
	}
#endif /* XP_UNIX */
#ifdef _WINDOWS
	{
		if( This->fWindow != NULL ) // If we already have a window, clean
									// it up before trying to subclass
									// the new window.
		{
			if( (window == NULL) || ( window->window == NULL ) ) {
				// There is now no window to use. get rid of the old
				// one and exit.
				SetWindowLong( This->fhWnd, GWL_WNDPROC, (LONG)This->fDefaultWindowProc);
				This->fDefaultWindowProc = NULL;
				This->fhWnd = NULL;
				return NPERR_NO_ERROR;
			}
	
			else if ( This->fWindow->window == window->window ) {
				// The new window is the same as the old one. Exit now.
				return NPERR_NO_ERROR;
			}
			else {
				// Clean up the old window, so that we can subclass the new
				// one later.
				SetWindowLong( This->fhWnd, GWL_WNDPROC, (LONG)This->fDefaultWindowProc);
				This->fDefaultWindowProc = NULL;
				This->fhWnd = NULL;
			}
		}
		else if( (window == NULL) || ( window->window == NULL ) ) {
			// We can just get out of here if there is no current
			// window and there is no new window to use.
			return NPERR_NO_ERROR;
		}
	
		// At this point, we will subclass
		// window->window so that we can begin drawing and
		// receiving window messages.
		This->fDefaultWindowProc = (WNDPROC)SetWindowLong( (HWND)window->window, GWL_WNDPROC, (LONG)PluginWindowProc);
		This->fhWnd = (HWND) window->window;
		SetProp( This->fhWnd, gInstanceLookupString, (HANDLE)This);
	
		InvalidateRect( This->fhWnd, NULL, TRUE );
		UpdateWindow( This->fhWnd );
	}
#endif /* _WINDOWS */

#ifdef macintosh
	This->fWindow = window;
	if( StartDraw( window ) ) {
		DoDraw(This);
		EndDraw( window );
	}
#endif /* macintosh */

	This->fWindow = window;
	return result;
}


NPError 
NPP_NewStream(NPP instance,
	      NPMIMEType type,
	      NPStream *stream, 
	      NPBool seekable,
	      uint16 *stype)
{
	PluginInstance* This;

	if (instance == NULL)
		return NPERR_INVALID_INSTANCE_ERROR;

	This = (PluginInstance*) instance->pdata;

	DisplayJavaMessage(instance, "Calling NPP_NewStream.", -1); 

	return NPERR_NO_ERROR;
}


/* PLUGIN DEVELOPERS:
 *	These next 2 functions are directly relevant in a plug-in which
 *	handles the data in a streaming manner. If you want zero bytes
 *	because no buffer space is YET available, return 0. As long as
 *	the stream has not been written to the plugin, Navigator will
 *	continue trying to send bytes.  If the plugin doesn't want them,
 *	just return some large number from NPP_WriteReady(), and
 *	ignore them in NPP_Write().  For a NP_ASFILE stream, they are
 *	still called but can safely be ignored using this strategy.
 */

int32 STREAMBUFSIZE = 0X0FFFFFFF; /* If we are reading from a file in NPAsFile
								   * mode so we can take any size stream in our
								   * write call (since we ignore it) */

int32 
NPP_WriteReady(NPP instance, NPStream *stream)
{
	PluginInstance* This;
	if (instance != NULL)
		This = (PluginInstance*) instance->pdata;

	DisplayJavaMessage(instance, "Calling NPP_WriteReady.", -1); 

	/* Number of bytes ready to accept in NPP_Write() */
	return STREAMBUFSIZE;
}


int32 
NPP_Write(NPP instance, NPStream *stream, int32 offset, int32 len, void *buffer)
{
	if (instance != NULL)
	{
		PluginInstance* This = (PluginInstance*) instance->pdata;
	}

	DisplayJavaMessage(instance, (char*)buffer, len); 

	return len;		/* The number of bytes accepted */
}


NPError 
NPP_DestroyStream(NPP instance, NPStream *stream, NPError reason)
{
	PluginInstance* This;

	if (instance == NULL)
		return NPERR_INVALID_INSTANCE_ERROR;
	This = (PluginInstance*) instance->pdata;

	DisplayJavaMessage(instance, "Calling NPP_DestroyStream.", -1); 

	return NPERR_NO_ERROR;
}


void 
NPP_StreamAsFile(NPP instance, NPStream *stream, const char* fname)
{
	PluginInstance* This;
	if (instance != NULL)
		This = (PluginInstance*) instance->pdata;

	DisplayJavaMessage(instance, "Calling NPP_StreamAsFile.", -1); 
}


void 
NPP_Print(NPP instance, NPPrint* printInfo)
{
	DisplayJavaMessage(instance, "Calling NPP_Print.", -1); 

	if(printInfo == NULL)
		return;

	if (instance != NULL) {
		PluginInstance* This = (PluginInstance*) instance->pdata;
	
		if (printInfo->mode == NP_FULL) {
		    /*
		     * PLUGIN DEVELOPERS:
		     *	If your plugin would like to take over
		     *	printing completely when it is in full-screen mode,
		     *	set printInfo->pluginPrinted to TRUE and print your
		     *	plugin as you see fit.  If your plugin wants Netscape
		     *	to handle printing in this case, set
		     *	printInfo->pluginPrinted to FALSE (the default) and
		     *	do nothing.  If you do want to handle printing
		     *	yourself, printOne is true if the print button
		     *	(as opposed to the print menu) was clicked.
		     *	On the Macintosh, platformPrint is a THPrint; on
		     *	Windows, platformPrint is a structure
		     *	(defined in npapi.h) containing the printer name, port,
		     *	etc.
		     */

			void* platformPrint =
				printInfo->print.fullPrint.platformPrint;
			NPBool printOne =
				printInfo->print.fullPrint.printOne;
			
			/* Do the default*/
			printInfo->print.fullPrint.pluginPrinted = FALSE;
		}
		else {	/* If not fullscreen, we must be embedded */
		    /*
		     * PLUGIN DEVELOPERS:
		     *	If your plugin is embedded, or is full-screen
		     *	but you returned false in pluginPrinted above, NPP_Print
		     *	will be called with mode == NP_EMBED.  The NPWindow
		     *	in the printInfo gives the location and dimensions of
		     *	the embedded plugin on the printed page.  On the
		     *	Macintosh, platformPrint is the printer port; on
		     *	Windows, platformPrint is the handle to the printing
		     *	device context.
		     */

			NPWindow* printWindow =
				&(printInfo->print.embedPrint.window);
			void* platformPrint =
				printInfo->print.embedPrint.platformPrint;
		}
	}
}

/*******************************************************************************
 * Define the Java native methods
 ******************************************************************************/

/* public native printToStdout(Ljava/lang/String;)V */
JRI_PUBLIC_API(void)
native_Simple_printToStdout(JRIEnv* env, struct Simple* self,
							struct java_lang_String * s)
{
    const char* chars = JRI_GetStringUTFChars(env, s);
    printf(chars);		/* cross-platform UI! */
}

/*******************************************************************************
// NPP_URLNotify:
// Notifies the instance of the completion of a URL request. 
// 
// NPP_URLNotify is called when Netscape completes a NPN_GetURLNotify or
// NPN_PostURLNotify request, to inform the plug-in that the request,
// identified by url, has completed for the reason specified by reason. The most
// common reason code is NPRES_DONE, indicating simply that the request
// completed normally. Other possible reason codes are NPRES_USER_BREAK,
// indicating that the request was halted due to a user action (for example,
// clicking the "Stop" button), and NPRES_NETWORK_ERR, indicating that the
// request could not be completed (for example, because the URL could not be
// found). The complete list of reason codes is found in npapi.h. 
// 
// The parameter notifyData is the same plug-in-private value passed as an
// argument to the corresponding NPN_GetURLNotify or NPN_PostURLNotify
// call, and can be used by your plug-in to uniquely identify the request. 
 ******************************************************************************/

void
NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData)
{
}

/*******************************************************************************
// NPP_HandleEvent:
// Mac-only, but stub must be present for Windows
// Delivers a platform-specific event to the instance. 
// 
// On the Macintosh, event is a pointer to a standard Macintosh EventRecord.
// All standard event types are passed to the instance as appropriate. In general,
// return TRUE if you handle the event and FALSE if you ignore the event. 
 ******************************************************************************/

#ifndef XP_UNIX

int16
NPP_HandleEvent(NPP instance, void* event)
{
	int16 eventHandled = FALSE;
	
#ifdef macintosh
	PluginInstance* This = (PluginInstance*) instance->pdata;
	EventRecord* ev = (EventRecord*) event;
	
	if (instance == NULL)
		return eventHandled;
		
	if (This != NULL && event != NULL)
	{
		switch (ev->what)
		{
			//
			// Draw ourselves on update events
			//
			case updateEvt:
				if( StartDraw( This->fWindow ) ) {
					DoDraw(This);
					EndDraw( This->fWindow );
				}
				eventHandled = true;
				break;
				
			default:
				break;
		}
			
	}
#endif /* macintosh */

	return eventHandled;
}

#endif /* ndef XP_UNIX */

/*******************************************************************************
 * UNIX-only methods
 ******************************************************************************/

#ifdef XP_UNIX
void
Redraw(Widget w, XtPointer closure, XEvent *event)
{
	PluginInstance* This = (PluginInstance*)closure;
	GC gc;
	XGCValues gcv;
	const char* text = "Hello World";

	XtVaGetValues(w, XtNbackground, &gcv.background,
				  XtNforeground, &gcv.foreground, 0);
	gc = XCreateGC(This->display, This->window, 
				   GCForeground|GCBackground, &gcv);
	XDrawRectangle(This->display, This->window, gc, 
				   0, 0, This->width-1, This->height-1);
	XDrawString(This->display, This->window, gc, 
				This->width/2 - 100, This->height/2,
				text, strlen(text));
}
#endif /* XP_UNIX */

/*******************************************************************************
 * Windows-only methods
 ******************************************************************************/

#ifdef _WINDOWS
LRESULT CALLBACK PluginWindowProc( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
	PluginInstance* This = (PluginInstance*) GetProp(hWnd, gInstanceLookupString);

	switch( Msg ) {
		case WM_PAINT:
		{
			PAINTSTRUCT paintStruct;
			HDC hdc;

			hdc = BeginPaint( hWnd, &paintStruct );
			TextOut(hdc, 0, 0, "Hello, World!", 15);

			EndPaint( hWnd, &paintStruct );
			break;
		}
		default:
		{
			This->fDefaultWindowProc( hWnd, Msg, wParam, lParam);
		}
	}
	return 0;
}
#endif /* _WINDOWS */

/*******************************************************************************
 * Mac-only methods
 ******************************************************************************/

#ifdef macintosh

NPBool
StartDraw(NPWindow* window)
{
	NP_Port* port;
	Rect clipRect;
	RGBColor  col;
	
	if (window == NULL)
		return FALSE;
	port = (NP_Port*) window->window;
	if (window->clipRect.left < window->clipRect.right)
	{
	// Preserve the old port
		GetPort((GrafPtr*)&gOldPort);
		SetPort((GrafPtr)port->port);
	// Preserve the old drawing environment
		gSavePort.portRect = port->port->portRect;
		gSavePort.txFont = port->port->txFont;
		gSavePort.txFace = port->port->txFace;
		gSavePort.txMode = port->port->txMode;
		gSavePort.rgbFgColor = port->port->rgbFgColor;
		gSavePort.rgbBkColor = port->port->rgbBkColor;
		GetClip(gSavePort.clipRgn);
	// Setup our drawing environment
		clipRect.top = window->clipRect.top + port->porty;
		clipRect.left = window->clipRect.left + port->portx;
		clipRect.bottom = window->clipRect.bottom + port->porty;
		clipRect.right = window->clipRect.right + port->portx;
		SetOrigin(port->portx,port->porty);
		ClipRect(&clipRect);
		clipRect.top = clipRect.left = 0;
		TextSize(12);
		TextFont(geneva);
		TextMode(srcCopy);
		col.red = col.green = col.blue = 0;
		RGBForeColor(&col);
		col.red = col.green = col.blue = 65000;
		RGBBackColor(&col);
		return TRUE;
	}
	else
		return FALSE;
}

void
EndDraw(NPWindow* window)
{
	CGrafPtr myPort;
	NP_Port* port = (NP_Port*) window->window;
	SetOrigin(gSavePort.portRect.left, gSavePort.portRect.top);
	SetClip(gSavePort.clipRgn);
	GetPort((GrafPtr*)&myPort);
	myPort->txFont = gSavePort.txFont;
	myPort->txFace = gSavePort.txFace;
	myPort->txMode = gSavePort.txMode;
	RGBForeColor(&gSavePort.rgbFgColor);
	RGBBackColor(&gSavePort.rgbBkColor);
	SetPort((GrafPtr)gOldPort);
}

void
DoDraw(PluginInstance* This)
{
	Rect drawRect;
	drawRect.top = 0;
	drawRect.left = 0;
	drawRect.bottom = drawRect.top + This->fWindow->height;
	drawRect.right = drawRect.left + This->fWindow->width;
	EraseRect( &drawRect );
	MoveTo( 2, 12 );
	DrawString("\pHello, World!");
}
#endif /* macintosh */

/******************************************************************************/
