/****************************/
/*			MAIN.C			*/
/****************************/

/*	PangeaVR - 3D accelerated plug-in for viewing panoramas.

	©2005 Pangea Software, Inc.  All rights reserved.
	Pangea Software, PangeaVR and various logos are trademarks of Pangea Software, Inc.
	
	Written by Brian Greenstone,   brian@pangeasoft.net	
	
	This program and source code is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
	
	IMPORTANT:  The purpose in releasing this source code is to encourage others
				To port this program to other platforms, and to make improvments to it.
				However, to prevent things from getting out of control with lots of
				different versions of PangeaVR floating around the internet,
				Pangea Software, Inc. prohibits the public distribution of any versions
				of PangeaVR without prior approval from Pangea Software, Inc.
				
				Should you make any improvements to this source code and wish to
				have those changes rolled into an official release then you'll
				need to contact Pangea Software to have your modifications adapted.				
*/

/****************************/
/*    EXTERNALS             */
/****************************/

#ifdef TARGET_OS_MAC
extern	short	gPrefsFolderVRefNum;					// MacOS directory info to get to the Preferences folder
extern	long	gPrefsFolderDirID;
extern	GWorldPtr			gTempImageGWorld;
#endif

extern	Ptr				gTempImageBuffer;
extern	Rect			gTempImageRect;


/****************************/
/*    PROTOTYPES            */
/****************************/

static void	ParseInputParameters(int instance, int16 argc, char* argn[], char* argv[]);



#pragma export on
NPError NP_Initialize(NPNetscapeFuncs* browserFuncs);
NPError NP_GetEntryPoints(NPPluginFuncs* pluginFuncs);
void NP_Shutdown(void);
#ifdef TARGET_OS_MAC
// For compatibility with CFM browsers.
int main(NPNetscapeFuncs *browserFuncs, NPPluginFuncs *pluginFuncs, NPP_ShutdownProcPtr *shutdown);
#endif
#pragma export off



/****************************/
/*    CONSTANTS             */
/****************************/


#define	DEFAULT_CONTROLLER_BAR_SCALE	200.0					// base scale factor for the controller bar

#define	DOWNLOAD_BUFFER_SIZE			50000					// determines how often the loading thermometer bar is updated.  It's updated every n bytes downloaded.


/*********************/
/*    VARIABLES      */
/*********************/

NPN_GetURLProcPtr			NPN_GetURL;
NPN_PostURLProcPtr			NPN_PostURL;
NPN_RequestReadProcPtr 		NPN_RequestRead;
NPN_NewStreamProcPtr 		NPN_NewStream;
NPN_WriteProcPtr			NPN_Write;
NPN_DestroyStreamProcPtr 	NPN_DestroyStream;
NPN_StatusProcPtr			NPN_Status;
NPN_UserAgentProcPtr 		NPN_UserAgent;
NPN_MemAllocProcPtr 		NPN_MemAlloc;
NPN_MemFreeProcPtr			NPN_MemFree;
NPN_MemFlushProcPtr 		NPN_MemFlush;
NPN_ReloadPluginsProcPtr 	NPN_ReloadPlugins;
NPN_GetURLNotifyProcPtr		NPN_GetURLNotify;
NPN_PostURLNotifyProcPtr 	NPN_PostURLNotify;
NPN_GetValueProcPtr 		NPN_GetValue;
NPN_SetValueProcPtr 		NPN_SetValue;
NPN_InvalidateRectProcPtr 	NPN_InvalidateRect;
NPN_InvalidateRegionProcPtr NPN_InvalidateRegion;
NPN_ForceRedrawProcPtr 		NPN_ForceRedraw;



			/* ARRAY OF INSTANCES */
			//
			// Each time the browser opens up a new reference to this plug-in NPP_New() gets called.
			// When this happens, we need to create a new instance that will hold all of the variables
			// associated with this instance / window.
			//
			
MyInstanceType	gMyInstances[MAX_INSTANCES];

char	gTheDataFilePath[2048];


/***************** NP:  INITIALIZE ************************/
//
// This code is called by the browser when it is launched and
// scans the plug-ins directory to initialize them.
//
// For Mac, this is the mach-o entry point.  If CFM then calls main().
//

NPError NP_Initialize(NPNetscapeFuncs* browserFuncs)
{
int				i;

		/* GET POINTERS TO BROWSER'S GECKO FUNCTIONS */
		//
		// This are functions that the plug-in can call to make the
		// browser do things.  
		//
			
    NPN_GetURL			= browserFuncs->geturl;
    NPN_PostURL			= browserFuncs->posturl;
    NPN_RequestRead		= browserFuncs->requestread;
    NPN_NewStream		= browserFuncs->newstream;
    NPN_Write			= browserFuncs->write;
    NPN_DestroyStream	= browserFuncs->destroystream;
    NPN_Status			= browserFuncs->status;
    NPN_UserAgent		= browserFuncs->uagent;
    NPN_MemAlloc		= browserFuncs->memalloc;
    NPN_MemFree			= browserFuncs->memfree;
    NPN_MemFlush		= browserFuncs->memflush;
    NPN_ReloadPlugins	= browserFuncs->reloadplugins;
    NPN_GetURLNotify	= browserFuncs->geturlnotify;
    NPN_PostURLNotify	= browserFuncs->posturlnotify;
    NPN_GetValue		= browserFuncs->getvalue;
    NPN_SetValue		= browserFuncs->setvalue;
    NPN_InvalidateRect	= browserFuncs->invalidaterect;
    NPN_InvalidateRegion = browserFuncs->invalidateregion;
    NPN_ForceRedraw		= browserFuncs->forceredraw;

			/* CLEAR THE INSTANCES LIST */
			
	for (i = 0; i < MAX_INSTANCES; i++)
		gMyInstances[i].isUsed = false;



			/* PLATFORM SPECIFIC INIT */
			
#ifdef TARGET_OS_MAC
//    browser = browserFuncs;
	return(DoPlatformSpecificInit_Mac());
#else
	return(DoPlatformSpecificInit_Windows());
#endif		

}


/******************* NP:  GET ENTRY POINTS **********************/
//
// Lets the browser know where our plug-ins callback functions are.
//

NPError NP_GetEntryPoints(NPPluginFuncs* pluginFuncs)
{
    pluginFuncs->version		= 11;
    pluginFuncs->size			= sizeof(pluginFuncs);
    pluginFuncs->newp			= NPP_New;
    pluginFuncs->destroy		= NPP_Destroy;
    pluginFuncs->setwindow		= NPP_SetWindow;
    pluginFuncs->newstream		= NPP_NewStream;
    pluginFuncs->destroystream	= NPP_DestroyStream;
    pluginFuncs->asfile			= NPP_StreamAsFile;
    pluginFuncs->writeready		= (NPP_WriteReadyProcPtr)NPP_WriteReady;
    pluginFuncs->write			= (NPP_WriteProcPtr) NPP_Write;
    pluginFuncs->print			= NPP_Print;
    pluginFuncs->event			= NPP_HandleEvent;
    pluginFuncs->urlnotify		= NPP_URLNotify;
    pluginFuncs->getvalue		= (NPP_GetValueProcPtr) NPP_GetValue;
    pluginFuncs->setvalue		= (NPP_SetValueProcPtr) NPP_SetValue;
    
    return NPERR_NO_ERROR;
}


/******************* NP:  SHUTDOWN ***********************/
//
//  When the last instance of a plug-in is deleted, the plug-in code is unloaded from memory.
//  Plug-ins consume no resources (other than disk space) when not loaded. 
//

void NP_Shutdown(void)
{

#ifdef TARGET_OS_MAC
	DoPlatformSpecificShutdown_Mac();
#else
	DoPlatformSpecificShutdown_Windows();
#endif		

	printf("NP_Shutdown...\n\n\n");

}


#pragma mark -


/********************* NPP: NEW **************************/
//
// Called by the browser to create a new instance of the plug-in.
//
// Multiple instances of the same plug-in can exist:
//		(a) if there are multiple embedded objects on a single page, or
//		(b) if several browser windows are open and each displays the same datatype. 
//
//	INPUT:	pluginType	=	Represents the MIME type of this instance of the plug-in.
//			instance	=	An NPP object created by the browser.
//							You can store the instance-specific private data in its pdata field (instance->pdata).
//			mode		=	Identifies the display mode in which the plug-in was invoked:
//							NP_EMBED :  instance was created by an EMBED and shares the browser window with other content.
//							NP_FULL  :  the instance was created by a separate file and is the primary content in the window.
//			argc		=	parameters from the EMBED tag that called theplug-in, # of parameters in argn/argv.
//							The arguments in the EMBED tag are name-value pairs made up of the attribute name
//							(for example, ALIGN) and its value (for example, top).
//							The argn array contains the attribute names; the argv array contains the attributevalues.
//


NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc, char* argn[], char* argv[], NPSavedData* saved)
{
#pragma unused (pluginType, mode, saved)

int		i, j;
short	browserType;

			/**************************************************/
			/* DETERMINE WHICH BROWSER IS RUNNING THE PLUG-IN */
			/**************************************************/
			
	const char *browserName = NPN_UserAgent(instance);
	printf("Browser = ");
	printf(browserName);
	printf("\n");
		
	if (strstr(browserName, "Safari"))
	{
		browserType = BROWSER_SAFARI;	
//		printf("Browser:  Safari\n");
	}
	else
	if (strstr(browserName, "Firefox"))
	{
		browserType = BROWSER_FIREFOX;	
//		printf("Browser:  Firefox\n");
	}
	else
	if (strstr(browserName, "Netscape"))
	{
		browserType = BROWSER_NETSCAPE;	
//		printf("Browser:  NETSCAPE\n");
	}
	else
	{
		browserType = BROWSER_UNKNOWN;	
//		printf("Browser:  Other\n");
	}



			/* SEE IF WE HAVE A FREE INSTANCE AVAILABLE */
			
	for (i = 0; i < MAX_INSTANCES; i++)
	{
		if (!gMyInstances[i].isUsed)							// is this instance slot free?
		{
			printf("Got Instance Slot #%d\n", i);
		
			instance->pdata = (void *)i;								// keep the index in the NPP instance	

			gMyInstances[i].isUsed			= true;						// mark it as used
			
			gMyInstances[i].instance		= instance;					// remember the NPP instance
			
#ifdef TARGET_OS_MAC
			gMyInstances[i].grafPtr			= nil;
			gMyInstances[i].drawContext		= nil;
#endif			
			
			gMyInstances[i].userString[0]	= 0;
			gMyInstances[i].userStringAlpha = 0.0;						// start faded out
			gMyInstances[i].fontSize		= 32;						// default font size
			gMyInstances[i].fontLoaded		= false;
			gMyInstances[i].thumbnailFilename[0] = 0x00;
			gMyInstances[i].thumbnailLoaded	= false;
			gMyInstances[i].thumbnailPixels = nil;
			gMyInstances[i].thumbnailTexture = 0xfeedface;
			gMyInstances[i].thumbnailScale = 1.0;
			gMyInstances[i].streamURL[0]	= 0x00;
			
			gMyInstances[i].srcfileQueued	= false;
			gMyInstances[i].drawLoadingBar	= true;
			gMyInstances[i].drawDecodingBar = false;
			gMyInstances[i].bytesDownloaded = 0;
			gMyInstances[i].srcFileSize		= 0;
			gMyInstances[i].decodeStep		= -1;
			
			gMyInstances[i].cubeRotDX		=							// init rotation
			gMyInstances[i].cubeRotDY		= 
			gMyInstances[i].cubeRotX		=
			gMyInstances[i].cubeRotY		= 0;
			
			gMyInstances[i].autoPan			= false;
			
			gMyInstances[i].minRotX			= -90; 
			gMyInstances[i].maxRotX			= 90;
			
			gMyInstances[i].npWindow		= nil;
			
			gMyInstances[i].paneWidth = gMyInstances[i].paneHeight = 0;
			gMyInstances[i].aspectRatio		= 1.0;
			
			gMyInstances[i].frameCount		= 0;
			gMyInstances[i].doDragFriction	= false;
			
			gMyInstances[i].cubeFade		= 0;
			gMyInstances[i].loadingFade		= 1.0f;
			
			for (j = 0; j < 8; j++)
			{
				gMyInstances[i].textureNames[j]		= 0xfeedface;
				gMyInstances[i].textureBuffers[j]	= nil;
#ifdef TARGET_OS_MAC
				gMyInstances[i].textureGWorlds[j]	= nil;
#endif			
			}
			
			gMyInstances[i].texturesLoaded	= false;
			
			gMyInstances[i].fov = DEFAULT_FOV;
		
			for (j = 0; j < NUM_SPRITES; j++)
			{
				gMyInstances[i].sprites[j].buffer		= nil;
				gMyInstances[i].sprites[j].textureName	= 0xfeedface;
			}
									
									
			gMyInstances[i].hiliteZoomIn		= false;
			gMyInstances[i].hiliteZoomOut		= false;
			gMyInstances[i].hilitePrefsIcon		= false;
			gMyInstances[i].showController		= true;
			gMyInstances[i].minimizeController	= false;
			gMyInstances[i].controllerMoved		= false;
									
			gMyInstances[i].controllerScale		= DEFAULT_CONTROLLER_BAR_SCALE;
			gMyInstances[i].controllerX			= -gMyInstances[i].controllerScale * 2;
			gMyInstances[i].controllerDX		= .1;				// set to anything other than 0.0 since that tells the move func that it's done moving.

			gMyInstances[i].compressTextures	= false;			// gets set to true if the hardware supports it and disableCompression is false
			gMyInstances[i].disableCompression	= true;				// set by HTML input parameter, true = faster loads, false = higher-quality in low VRAM.
							
					
			gMyInstances[i].browserType			= browserType;	
					
					
					/* NOW MODIFY THOSE DEFAULTS BASED ON INPUT PARMS */
					
			ParseInputParameters(i, argc, argn, argv);

															
			return NPERR_NO_ERROR;
		}
	}
	
	
	
	return(NPERR_GENERIC_ERROR);					// if gets here then no free instance slots
}


/******************* PARSE INPUT PARAMETERS ***********************/
//
// Parses the input parameters.
//

static void	ParseInputParameters(int instance, int16 argc, char* argn[], char* argv[])
{
int		p;

	printf("# input parameters = %d\n", argc);
	for (p = 0; p < argc; p++)
	{
//		printf("parameter:  ");
//		printf(argn[p]);
//		printf(" = ");
//		printf(argv[p]);
//		printf("\n");
		
					/* CONTROLLER */
					
		if (!strcmp("CONTROLLER", argn[p]) || !strcmp("controller", argn[p]))		 
		{
			if (!strcmp("TRUE", argv[p]) || !strcmp("true", argv[p]))
			{
				gMyInstances[instance].showController = true;
			}
			else
			{
				gMyInstances[instance].showController = false;
			}
			continue;
		}

					/* DISABLE COMPRESSION */
					
		if (!strcmp("COMPRESSION", argn[p]) || !strcmp("compression", argn[p]))		 
		{
			if (!strcmp("TRUE", argv[p]) || !strcmp("true", argv[p]))
			{
				printf("HTML request texture compression\n");
				gMyInstances[instance].disableCompression = false;
			}
			else
			{
				gMyInstances[instance].disableCompression = true;
			}
			continue;
		}


					/* FOV */
					
		if (!strcmp("FOV", argn[p]) || !strcmp("fov", argn[p]))		 
		{
			gMyInstances[instance].fov = atoi(argv[p]);	
			if (gMyInstances[instance].fov < MIN_FOV)
				gMyInstances[instance].fov = MIN_FOV;
			else
			if (gMyInstances[instance].fov > MAX_FOV)
				gMyInstances[instance].fov = MAX_FOV;
			continue;
		}


					/* PAN */
					
		if (!strcmp("PAN", argn[p]) || !strcmp("pan", argn[p]))		 
		{
			gMyInstances[instance].cubeRotY = atof(argv[p]);	
			continue;
		}

					/* TILT */
					
		if (!strcmp("TILT", argn[p]) || !strcmp("tilt", argn[p]))		 
		{
			gMyInstances[instance].cubeRotX = -atof(argv[p]);	
			continue;
		}


					/* MAX TILT */
					
		if (!strcmp("MAXTILT", argn[p]) || !strcmp("maxtilt", argn[p]))		 
		{
			gMyInstances[instance].minRotX = -atof(argv[p]);	
			continue;
		}

					/* MIN TILT */
					
		if (!strcmp("MINTILT", argn[p]) || !strcmp("mintilt", argn[p]))		 
		{
			gMyInstances[instance].maxRotX = -atof(argv[p]);	
			continue;
		}


					/* DRAG FRICTION */
					
		if (!strcmp("DRAGFRICTION", argn[p]) || !strcmp("dragfriction", argn[p]))		 
		{
			if (!strcmp("TRUE", argv[p]) || !strcmp("true", argv[p]))
			{
				gMyInstances[instance].doDragFriction = true;
			}
			else
			{
				gMyInstances[instance].doDragFriction = false;
			}
			continue;
		}
		
					/* TEXT */
					
		if (!strcmp("TEXT", argn[p]) || !strcmp("text", argn[p]))		 
		{
			c2pstrcpy(gMyInstances[instance].userString, argv[p]);
			continue;
		}
		
		
					/* FONTSIZE */
		
		if (!strcmp("FONTSIZE", argn[p]) || !strcmp("fontsize", argn[p]))		 
		{
			gMyInstances[instance].fontSize = atoi(argv[p]);	
			continue;
		}
		
		
					/* AUTO-PAN */
		
		if (!strcmp("AUTOPAN", argn[p]) || !strcmp("autopan", argn[p]))		 
		{
			gMyInstances[instance].cubeRotDY = atof(argv[p]);			// set delta rot
			gMyInstances[instance].autoPan = true;						// set auto-pan flag
			continue;
		}

					/* THUMBNAIL */
		
		if (!strcmp("THUMBNAIL", argn[p]) || !strcmp("thumbnail", argn[p]))		 
		{
		
#ifdef TARGET_OS_MAC													
			if (gMyInstances[instance].browserType != BROWSER_SAFARI)	// calling DownloadURL from here will sometimes freeze up Mozilla for unknown reasons.  Best to just block thumbnails.
				continue;
#endif

			strcpy(gMyInstances[instance].thumbnailFilename, argv[p]);
			continue;
		}

					/* THUMBSCALE */
		
		if (!strcmp("THUMBSCALE", argn[p]) || !strcmp("thumbscale", argn[p]))		 
		{
			gMyInstances[instance].thumbnailScale = atof(argv[p]);			// set thumbnail scale factor
			continue;
		}
		
	}
}



/******************** NPP: DESTROY ***********************/
//
//  A plug-in instance is deleted when a user leaves the instance's page or closes its window.
//
//  You should not perform any graphics operations in NPP_Destroy because the instance window is no longer guaranteed to be valid.
//  Also, be sure to delete any private instance-specific information stored in the plug-in's instance->pdata
//

NPError NPP_Destroy(NPP instance, NPSavedData** save)
{
#pragma unused (save)
int		i,j;

	printf("NPP_Destroy...\n\n");

	i = (int)instance->pdata;							// get our instance index

		/* DISPOSE OF TEXTURES */
	
	for (j = 0; j < 8; j++)
	{
		if (gMyInstances[i].textureNames[j] != 0xfeedface)			// nix texture
			glDeleteTextures(1, &gMyInstances[i].textureNames[j]);

		if (gMyInstances[i].textureGWorlds[j])			// nix gworld
		{
			DisposeGWorld(gMyInstances[i].textureGWorlds[j]);
			gMyInstances[i].textureGWorlds[j] = nil;
		}
		
		if (gMyInstances[i].textureBuffers[j])			// nix buffer
		{
			free(gMyInstances[i].textureBuffers[j]);
			gMyInstances[i].textureBuffers[j] = nil;
		}
		
		
	}


			/* FREE THUMBNAIL IMAGE & TEXTURE */
			
	if (gMyInstances[i].thumbnailTexture  != 0xfeedface)
		glDeleteTextures(1, &gMyInstances[i].thumbnailTexture);
	if (gMyInstances[i].thumbnailPixels)
		free(gMyInstances[i].thumbnailPixels);



			/* FREE LOADING SPRITE */
			
	for (j = 0; j < NUM_SPRITES; j++)
	{
		if (gMyInstances[i].sprites[j].buffer)
			free(gMyInstances[i].sprites[j].buffer);
			
		if (gMyInstances[i].sprites[j].textureName != 0xfeedface)		
			glDeleteTextures(1, &gMyInstances[i].sprites[j].textureName);
	}
	
	

	OGL_DisposeDrawContext(i);						// dispose of the OpenGL draw context


	

	gMyInstances[i].isUsed = false;					// not used anymore


    return NPERR_NO_ERROR;
}


/******************* NPP: SET WINDOW ***************************/
//
// Called by the browser to set the window into which the plug-in draw.
// The browser calls NPP_SetWindow whenever the drawable changes. 
//
// This gets called a lot!  Whenever the user scrolls a web page or resizes a window.
//

NPError NPP_SetWindow(NPP instance, NPWindow* window)
{
NP_Port		*npPort;
int			i;

	i = (int)instance->pdata;								// get our instance index
	
			/* CHECK FOR NIL'S */
			
	if (window == nil)
	{
		printf("NPWindow == nil\n");
		return NPERR_NO_ERROR;	
	}
	
	
	if (window->window == nil)
	{
		printf("window == nil\n");
		return NPERR_NO_ERROR;	
	}	
	
	
	if (window->x < -1000)				// is this bogus?  For some reason we get wild values sometimes, but they're always bogus, so check for them.
		return NPERR_NO_ERROR;

    gMyInstances[i].npWindow = window;					// remember this
	
		
	
			/******************************/
			/* EXTRACT PORT & WINDOW INFO */
			/******************************/
			
	npPort = window->window;

#ifdef TARGET_OS_MAC
	gMyInstances[i].grafPtr		= npPort->port;	
	gMyInstances[i].macWindow	= GetWindowFromPort(gMyInstances[i].grafPtr);

	if (!IsValidWindowPtr(gMyInstances[i].macWindow))
		printf("IsValidWindowPtr == false\n");
#else
	----- WINDOWS OR LINUX CODE GOES HERE -----
#endif		

	gMyInstances[i].pluginPortX = npPort->portx;
	gMyInstances[i].pluginPortY = npPort->porty;

	gMyInstances[i].paneWidth		= window->width;
	gMyInstances[i].paneHeight		= window->height;
	gMyInstances[i].paneClip.top	= window->clipRect.top;
	gMyInstances[i].paneClip.left	= window->clipRect.left;
	gMyInstances[i].paneClip.bottom	= window->clipRect.bottom;
	gMyInstances[i].paneClip.right	= window->clipRect.right;

#if 0
	printf("window x = %d\n", window->x);
	printf("window y = %d\n", window->y);
	printf("window w = %d\n", window->width);
	printf("window h = %d\n", window->height);

	printf("window clip top    = %d\n", window->clipRect.top);
	printf("window clip bottom = %d\n", window->clipRect.bottom);
	printf("window clip left   = %d\n", window->clipRect.left);
	printf("window clip right  = %d\n", window->clipRect.right);
#endif	

				/* CALC CONTROLLER SCALE */
				//
				// We need to slightly scale the controller bar based on the size of the window.
				// This will do that while making sure it doesn't get too big or too small.
				//
				
	gMyInstances[i].controllerScale = (gMyInstances[i].paneWidth + gMyInstances[i].paneHeight) * .5 * .25f;			// scale controller based on window size
	
	if (gMyInstances[i].controllerScale < (DEFAULT_CONTROLLER_BAR_SCALE * .6))				// don't let it get too small
		gMyInstances[i].controllerScale = DEFAULT_CONTROLLER_BAR_SCALE * .6;
	else
	if (gMyInstances[i].controllerScale > DEFAULT_CONTROLLER_BAR_SCALE)						// don't let it get too big
		gMyInstances[i].controllerScale = DEFAULT_CONTROLLER_BAR_SCALE;
	
	gMyInstances[i].controllerMoved = true;													// if this is a resize on the window then need to redraw controller
	gMyInstances[i].controllerDX = .1;
	
		
		/******************************************/
		/* INIT OUR OPENGL DRAW CONTEXT IF NEEDED */
		/******************************************/
				
	OGL_InitDrawContext(i);


			/* SEE IF A .MOV FILE WAS QUEUED UP FROM EARLIER */
			//
			// In rare cases we get the .mov data before a window has even been created.
			// When that happens, the NPP_StreamAsFile() function just queues up the file so
			// that we can parse it after we get the NPP_NewWindow() call when the OpenGL Draw
			// Context gets created (above).
			//
			
	if (gMyInstances[i].srcfileQueued)
	{
		printf("Parsing the queued file...\n");
		gMyInstances[i].srcfileQueued = false;
		
		if (!gMyInstances[i].doEquirectangular)							// see if we've got a .mov queued or a .jpeg
			ParseQTMovie(i);
		else
			LoadEquirectangularImage(i);
	}

	
	OGL_DrawScene(i);
	
    return NPERR_NO_ERROR;
}
 


#pragma mark -


/*********************** NPP:  NEW STREAM **************************/
//
// Called by the browser to tell us that there is a stream it needs to send us.  
// Probably from the SRC attrbute of the EMBED tag.
//
// Here, we pass back NP_ASFILE to tell the browser that we want the stream written out to a 
// temp file, but we also want the bytes passed in as they come.  We don't currently do anything
// with those streamed bytes, but we use the callback as a way to update the loading bar.
//

NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream* stream, NPBool seekable, uint16* stype)
{
#pragma unused (type, stream, seekable)
int		i;

	i = (int)instance->pdata;											// get our instance index

	gMyInstances[i].srcFileSize = stream->end;							// get the length of the stream (aka file size)
	gMyInstances[i].bytesDownloaded = 0;								// nothing has been downloaded yet

	strcpy(gMyInstances[i].streamURL, stream->url);						// keep a copy of the url
	

	printf("NPP_NewStream was called...\n");
	printf(stream->url);
	printf("\n");
//	printf(type);
//	printf("\n");
	
	
	
				/*******************************/
				/* SEE WHAT KIND OF FILE IT IS */
				/*******************************/
	
					/* IF IT'S A JPEG THEN RENDER A SPHERRE */
					
	if (strstr(stream->url, ".jpeg") || strstr(stream->url, ".JPEG") ||
		strstr(stream->url, ".jpg") || strstr(stream->url, ".JPG"))
	{
		gMyInstances[i].doEquirectangular = true;
	}
	
			/* NOT JPEG, SO ASSUME IT'S A QUICKTIME MOVIE SO USE CUBE */
			
	else
	{
		gMyInstances[i].doEquirectangular = false;
	
	}
	
	
	printf("Stream length = %d\n", gMyInstances[i].srcFileSize);

	*stype = NP_ASFILE;												// tell the browser to save this as a file for us
	
	
			/*********************************************************************/
			/* IF THERE WAS A THUMBNAIL SPECIFIED IN THE INPUT PARAMETS, LOAD IT */
			/*********************************************************************/
			//
			// This attempt may fail if the OpenGL draw context isn't created yet. 
			// If so, we try this again when the draw context is created.
			//
		
	if (gMyInstances[i].thumbnailFilename[0] != 0x00)					// is there a thumbnail file?
	{
		LoadThumbnail(i, stream->url);
	}
	
	
	
    return NPERR_NO_ERROR;
}


/**************** NPP: DESTROY STREAM *********************/
// 
// Called by the browser when all of the data has been sent.
//

NPError NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason)
{
#pragma unused (stream)
int		i;

	i = (int)instance->pdata;								// get our instance index

	if (reason == NPRES_DONE)
		printf("NPP_DestroyStream - Done!\n");
	else
	if (reason == NPRES_USER_BREAK)
		printf("NPP_DestroyStream - user canceled steam!\n");
	else
	if (reason == NPRES_NETWORK_ERR)
		printf("NPP_DestroyStream - network error!\n");


	gMyInstances[i].drawLoadingBar = false;
	gMyInstances[i].drawDecodingBar = true;

    return NPERR_NO_ERROR;
}


/********************* NPP:  WRITE READY *****************************/
//
// After a call to NPP_NewStream and before writing data to the plug-in, the browser calls NPP_WriteReady
// to determine the maximum number of bytes that the plug-in can consume.
// This function allows the browser to send only as much data to the instance as it can handle at one time.
//
// After a call to NPP_NewStream, in which the plug-in requested a normal-mode stream,
// the browser delivers the data in the stream progressively in a series of calls to NPP_WriteReady and NPP_Write.
// The browser calls NPP_WriteReady before each call to NPP_Write.
//
// Remember that in our case, we won't really be using this data, and there really isn't any buffer.  
// We're just telling the browser how often we'll be updating our loading bar.
//

int32 NPP_WriteReady(NPP instance, NPStream* stream)
{
#pragma unused (instance, stream)

    return (DOWNLOAD_BUFFER_SIZE);					// this tells the browser how many bytes to queue up before calling NPP_WRITE
}


/******************** NPP: WRITE ************************/
//
// We're using NP_ASFILE instead of NP_ASFILEONLY so that this function gets called as the
// data is downloaded.  We don't do anything with the data since it's getting written to a file,
// but we update the progress bar via this.
//

int32 NPP_Write(NPP instance, NPStream* stream, int32 offset, int32 len, void* buffer)
{
#pragma unused (stream, offset, len, buffer)

int		i;

	i = (int)instance->pdata;								// get our instance index

	gMyInstances[i].bytesDownloaded += len;

//	printf("# bytes downloaded = %d\n", gMyInstances[i].bytesDownloaded);



				/* DRAW STUFF */
				
	OGL_DrawScene(i);
	
    return(len);											// tell browser that we got all that
}


/********************* NPP: STEAM AS FILE ************************/
//
// Called by the browser when we pass NP_ASFILE in NPP_NewStream.
// This gives us the full path to the temp file that the browser downloaded.
//

void NPP_StreamAsFile(NPP instance, NPStream* stream, const char* fname)
{
#pragma unused (stream)

OSErr	iErr;
Str255	s;
int		i;

	i = (int)instance->pdata;								// get our instance index

	printf("NPP_StreamAsFile:  ");
	printf(fname);
	printf("\n");

	strcpy(gTheDataFilePath, fname);						// copy path into global 

#ifdef TARGET_OS_MAC

    if (strncmp(fname, "/", 1) == 0)
	{
        // Browser handed us a POSIX path.
        FSRef fref;
		iErr = FSPathMakeRef((const UInt8 *) fname, &fref, NULL);
        if (iErr != noErr)
		{
            printf("NPP_StreamAsFile: FSPathMakeRef failed %d\n", iErr);
            return;
        }
		
        iErr = FSGetCatalogInfo(&fref, kFSCatInfoNone, NULL, NULL, &gMyInstances[i].dataFileSpec, NULL);
        if (iErr != noErr)
		{
            printf("NPP_StreamAsFile: FSGetCatalogInfo failed %d\n", iErr);
            return;
        }
    }
	else			// standard full path
	{
		c2pstrcpy(s, fname);												// convert path to pascal...
		iErr = FSMakeFSSpec( 0, 0, s, &gMyInstances[i].dataFileSpec);		// ... and make an FSSpec for it.
		if (iErr)
		{
			printf("NPP_StreamAsFile: FSMakeFSSpec failed!\n"); 
			printf("%d", iErr);
			printf("\n");
			return;
		}
	}
#endif


				/* SEE IF CAN LOAD NOW */
				//
				// Ok, this is important.  Sometimes, the browser issues the New_Stream call before issuing a SetWindow call.
				// This means that no Draw Context exists yet to load the file into, so we need to handle this case.
				//
				
	if (!gMyInstances[i].drawContext)			// there must be a draw context to load this!
	{
		printf("No Draw Context exists yet, so queuing the file for later...\n");
		gMyInstances[i].srcfileQueued = true;
		return;
	}
				/* A DRAW CONTEXT EXISTS, SO ITS SAFE TO LOAD THE MOVIE NOW */
	else
	{
		if (!gMyInstances[i].doEquirectangular)					// load quicktime movie if we're not doing equirectangular
			ParseQTMovie(i);
		else
			LoadEquirectangularImage(i);						// otherwise, it's an equirectangular image in a JPEG that gets drawn onto a sphere
	}
			
	OGL_DrawScene(i);
}



/***************** NPP:  PRINT **************************/
//
// Called by the browser.  Request a platform-specific print operation for the instance.
//

void NPP_Print(NPP instance, NPPrint* platformPrint)
{
#pragma unused (instance, platformPrint)
}


#pragma mark -


/****************** NPP:  HANDLE EVENT ***********************/
//
// Called by the browser.  Delivers a platform-specific event to the instance.
//
// Here we're getting a Mac OS EventRecord - old style.
//

int16 NPP_HandleEvent(NPP instance, void* event)
{
int			i;
static Boolean		enter = false;						// keep this from being re-entrant since we call GetNextEvent in here.

	if (enter)
		return(1);
		
	enter = true;

	i = (int)instance->pdata;								// get our instance index


#ifdef TARGET_OS_MAC
	HandleBrowserEvent_Mac(i, event);
#else
	HandleBrowserEvent_Windows(i, event);
#endif		


	
	enter = false;
    return 1;
}


/****************** NPP:  URL NOTIFY ********************/

void NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData)
{
#pragma unused (instance, url, reason, notifyData)

}


/****************** NPP:  GET VALUE ***********************/
//
// Called by the browser to query the plug-in for info.
//

NPError NPP_GetValue(void *instance, NPPVariable variable, void *value)
{
#pragma unused (instance, variable, value)

    return NPERR_NO_ERROR;
}

/******************* NPP:  SET VALUE ************************/
//
// Called by the browser to set info.
//

NPError NPP_SetValue(void *instance, NPNVariable variable, void *value)
{
#pragma unused (instance, variable, value)

    return NPERR_NO_ERROR;
}


#pragma mark -




/************************* IS MOUSE IN PLUGIN PANE ******************************/
//
// Uses Mac OS calls to determine if the mouse is currently in the plug-in's GrafPort.
//

Boolean IsMouseInPluginPane(int i, int x, int y)
{

	if (x < 0)
		return(false);
	if (y < 0)
		return(false);
		
	if ((int)x > (int)gMyInstances[i].npWindow->width)
		return(false);
	if ((int)y > (int)gMyInstances[i].npWindow->height)
		return(false);
		

		
	return(true);
}


/************************ CHECK CLICK ON CONTROLLER ICON ***************************/
//
// When a mouse-down event occurs, this checks to see if the click was on
// our controller bar.
//
// INPUT:  x/y = plug-in pane coordinates of the mouse when clicked
//

Boolean CheckClickOnControllerIcon(int i, float x, float y)
{
float	buttonTop, buttonBottom;

	if (!gMyInstances[i].showController)						// is controller hidden?
		return(false);

				/************************************/
				/* GET MOUSE COORDS INSIDE OUR PANE */
				/************************************/
				//
				// According to the Gecko specs, 
				//			"The current port is set up so that its origin matches the top-left corner of the plug-in.
				//			A plug-in does not need to set up the current port for mouse coordinate translation."
				//
				// However... this is not true in Firefox!  Firefox's port is set to the window's upper/left coord, so there's
				// no way to get the correct coords!  
				//
					

	x /= gMyInstances[i].controllerScale;			// scale into sprite's world
	y = gMyInstances[i].paneHeight - y;				// flip y to be offset from bottom of pane
	y /= gMyInstances[i].controllerScale; 

//	printf("mouse x/y = %f / %f\n", x, y);	//--------
	

	buttonTop		= 0.21;
	buttonBottom	= 0.02;


				/************************/
				/* CLICK ON UN-MINIMIZE */
				/************************/
				
	if (gMyInstances[i].minimizeController)
	{
		if ((x > .0) && (x < .2) && (y < buttonTop) && (y > buttonBottom))
		{
			gMyInstances[i].controllerDX = .1;						// set this to anything other than 0.0!
			gMyInstances[i].minimizeController = false;
			gMyInstances[i].controllerMoved = true;
			return(true);
		}
	}
	else											// not minimized, so check buttons
	{

					/*************************/
					/* CLICK ON ZOOM-IN ICON */
					/*************************/
					
		if ((x > 0.03) && (x < 0.217) && (y < buttonTop) && (y > buttonBottom))
		{
			gMyInstances[i].hiliteZoomIn = true;

			do
			{
				gMyInstances[i].fov	-= gMyInstances[i].oneOverFPS * FOV_ZOOM_SPEED;
				if (gMyInstances[i].fov < MIN_FOV)
					gMyInstances[i].fov = MIN_FOV;
				OGL_DrawScene(i);
			}while(Button());

			gMyInstances[i].hiliteZoomIn = false;
			OGL_DrawScene(i);
			return(true);
		}

					/*************************/
					/* CLICK ON ZOOM-OUT ICON */
					/*************************/
			
		if ((x > 0.26) && (x < 0.45) && (y < buttonTop) && (y > buttonBottom))
		{
			gMyInstances[i].hiliteZoomOut = true;

			do
			{
				gMyInstances[i].fov	+= gMyInstances[i].oneOverFPS * FOV_ZOOM_SPEED;
				if (gMyInstances[i].fov > MAX_FOV)
					gMyInstances[i].fov = MAX_FOV;
				OGL_DrawScene(i);
			}while(Button());

			gMyInstances[i].hiliteZoomOut = false;
			OGL_DrawScene(i);
			return(true);
		}


					/************************/
					/* CLICK ON PANGEA LOGO */
					/************************/
		else
		if ((x > .52) && (x < .79) && (y < buttonTop) && (y > buttonBottom))
		{
			LaunchURL("\phttp://www.pangeasoft.net");
			return(true);
		}

					/***********************/
					/* CLICK ON EARTH ICON */
					/***********************/
		else
		if ((x > .82) && (x < 1.0) && (y < buttonTop) && (y > buttonBottom))
		{
			LaunchURL("\phttp://www.pangeasoft.net/pano/plugin/vrdirectory.html");
			return(true);
		}


					/***********************/
					/* CLICK ON PREFS ICON */
					/***********************/
		else
		if ((x > 1.1) && (x < 1.27) && (y < buttonTop) && (y > buttonBottom))
		{
			DoPrefsDialog(i);
			return(true);
		}

					/********************************/
					/* CLICK ON HIDECONTROLLER ICON */
					/********************************/
		else
		if ((x > 1.35) && (x < 1.53) && (y < buttonTop) && (y > buttonBottom))
		{
			gMyInstances[i].controllerDX = 500;							// give that little forward momentum
			gMyInstances[i].minimizeController = true;
			gMyInstances[i].controllerMoved = true;
			return(true);
		}
	}

	return(false);
}


#pragma mark -

/************************ LAUNCH URL *****************************/
//
// Called to make OS X load up the given URL in the default browser.
//

OSStatus LaunchURL(ConstStr255Param urlStr)
{
#ifdef TARGET_OS_MAC
OSStatus err;
ICInstance inst;
long startSel;
long endSel;


    err = ICStart(&inst, 'xxxx');     
    if (err == noErr)
    {
	    startSel = 0;
	    endSel = urlStr[0];
	    err = ICLaunchURL(inst, "\p", (char *) &urlStr[1], urlStr[0], &startSel, &endSel);
		if (err)
			printf("LaunchURL: ICLaunchURL failed %d\n", err);
		
        ICStop(inst);
    }
	else
		printf("LaunchURL: ICStart failed %d\n", err);
		
    return (err);
	
#else
	----- WINDOWS OR LINUX CODE GOES HERE -----
#endif		
}


/************************** DOWNLOAD URL **********************************/
//
// This downloads the file indicated in the urlString.  It puts it into the handle pointed to by data.
//

int DownloadURL(const char *urlString, Handle	data)
{
#ifdef TARGET_OS_MAC
OSErr	iErr;

			/* DOWNLOAD DATA */

#if 0

	URLReference		urlRef;
	
	iErr = URLNewReference (urlString, &urlRef);
	if (iErr)
		printf("DownloadURL: URLNewReference() failed\n");

	iErr = URLDownload( urlRef,					// pass the URL ref
						nil,						// download to handle, not FSSpec
						data,						// to this handle
						0, 							// no flags
						nil,						// no callback eventProc
						nil);						// no userContex
						
	URLDisposeReference(urlRef);

#else

	iErr = URLSimpleDownload(
						urlString,					// pass the URL string
						nil,						// download to handle, not FSSpec
						data,						// to this handle
						0, 							// no flags
						nil,						// no callback eventProc
						nil);						// no userContex
#endif	
											
	if (iErr != noErr)
		printf("DownloadURL: URLSimpleDownload failed!  %d\n", iErr);
						
	return(iErr);

#else
	----- WINDOWS OR LINUX CODE GOES HERE -----
#endif		
}
