/*
	This file contains the low-level support code for OS
	surfaces. Since FXBlt may operate on any of these
	some functions must be provided by the client. It is
	crucial that every client creating surfaces for use
	by Squeak registers the surfaces using the functions
	provided here. Other concrete primitives (such as
	creation or destroy) of surfaces can be handled by
	additional support code, querying the surface handle
	from ioFindSurface but the functions provided below
	are critical for any BitBlt operations.
*/

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/* Default EXPORT macro that does nothing (see comment in sq.h): */
#define EXPORT(returnType) returnType

#include "sqVirtualMachine.h"
#include "sqConfig.h"
#include "sqPlatformSpecific.h"
#include "SurfacePlugin.h"

/* internal plugin support */
#ifdef SQUEAK_BUILTIN_PLUGIN
#undef EXPORT
#define EXPORT(returnType) static returnType
#endif

typedef struct SqueakSurface {
	long handle; /* client supplied handle */
	sqSurfaceDispatch *dispatch;
} SqueakSurface;

static SqueakSurface *surfaceArray = NULL;
static long numSurfaces = 0;
static long maxSurfaces = 0;

#ifdef SQUEAK_BUILTIN_PLUGIN
static const char *moduleName = "SurfacePlugin "__DATE__" (i)";
#else
static const char *moduleName = "SurfacePlugin "__DATE__" (e)";
#endif

static VirtualMachine *interpreterProxy;
#define FAIL { interpreterProxy->primitiveFail(); return 0; }

#pragma export on
/* module initialization/shutdown */
EXPORT(long) setInterpreter(struct VirtualMachine *interpreterProxy);
EXPORT(const char*) getModuleName(void);
EXPORT(long) initialiseModule(void);
EXPORT(long) shutdownModule(void);

/* critical FXBlt entry points */
EXPORT(long) ioGetSurfaceFormat (long surfaceID, long* width, long* height, long* depth, long* isMSB);
EXPORT(long) ioLockSurface (long surfaceID, long *pitch, long x, long y, long w, long h);
EXPORT(long) ioUnlockSurface(long surfaceID, long x, long y, long w, long h);

/* interpreter entry point */
EXPORT(long) ioShowSurface(long surfaceID, long x, long y, long w, long h);

/* client entry points */
EXPORT(long) ioRegisterSurface(long surfaceHandle, sqSurfaceDispatch *fn, long *surfaceID);
EXPORT(long) ioUnregisterSurface(long surfaceID);
EXPORT(long) ioFindSurface(long surfaceID, sqSurfaceDispatch *fn, long *surfaceHandle);
#pragma export off

/* ioGetSurfaceFormat:
	Return information describing the given surface.
	Return true if successful, false otherwise. */
EXPORT(long) ioGetSurfaceFormat (long surfaceID, long* width, long* height, long* depth, long* isMSB)
{
	SqueakSurface *surface;
	if(surfaceID < 0 || surfaceID >= maxSurfaces) FAIL;
	surface = surfaceArray + surfaceID;
	if(surface->dispatch == NULL) FAIL;
	if(!surface->dispatch->getSurfaceFormat) FAIL;
	return surface->dispatch->getSurfaceFormat(surface->handle, width, height, depth, isMSB);
}

/* ioLockSurface:
	Lock the bits of the surface. 
	Return a pointer to the actual surface bits,
	or NULL on failure. */
EXPORT(long) ioLockSurface (long surfaceID, long *pitch, long x, long y, long w, long h)
{
	SqueakSurface *surface;
	if(surfaceID < 0 || surfaceID >= maxSurfaces) FAIL;
	surface = surfaceArray + surfaceID;
	if(surface->dispatch == NULL) FAIL;
	if(!surface->dispatch->lockSurface) FAIL;
	return surface->dispatch->lockSurface(surface->handle, pitch, x, y, w, h);
}

/* ioUnlockSurface:
	Unlock the bits of the surface. 
	The return value is ignored. */
EXPORT(long) ioUnlockSurface(long surfaceID, long x, long y, long w, long h)
{
	SqueakSurface *surface;
	if(surfaceID < 0 || surfaceID >= maxSurfaces) FAIL;
	surface = surfaceArray + surfaceID;
	if(surface->dispatch == NULL) FAIL;
	if(!surface->dispatch->unlockSurface) FAIL;
	return surface->dispatch->unlockSurface(surface->handle, x, y, w, h);
}

/* ioShowSurface:
	Transfer the bits of a surface to the screen. */
EXPORT(long) ioShowSurface(long surfaceID, long x, long y, long w, long h)
{
	SqueakSurface *surface;
	if(surfaceID < 0 || surfaceID >= maxSurfaces) FAIL;
	surface = surfaceArray + surfaceID;
	if(surface->dispatch == NULL) FAIL;
	if(!surface->dispatch->showSurface) FAIL;
	return surface->dispatch->showSurface(surface->handle, x, y, w, h);
}

/* ioRegisterSurface:
	Register a new surface with the given handle and
	the set of surface functions. The new ID is returned
	in surfaceID. Returns true if successful, false 
	otherwise. */
EXPORT(long) ioRegisterSurface(long surfaceHandle, sqSurfaceDispatch *fn, long *surfaceID)
{
	long index;

	if(!fn) return 0;
	if(fn->majorVersion != 1 && fn->minorVersion != 0) return 0;
	if(numSurfaces == maxSurfaces) {
		maxSurfaces = maxSurfaces * 2 + 10;
		surfaceArray = realloc(surfaceArray, sizeof(SqueakSurface) * maxSurfaces);
		for(index = numSurfaces; index < maxSurfaces; index++){
			surfaceArray[index].handle = 0;
			surfaceArray[index].dispatch = NULL;
		}
		index = numSurfaces;
	} else {
		for(index = 0; index < maxSurfaces; index++)
			if(surfaceArray[index].dispatch == NULL)
				break;
	}
	if(index >= maxSurfaces) return 0; /* should not happen */
	surfaceArray[index].handle = surfaceHandle;
	surfaceArray[index].dispatch = fn;
	*surfaceID = index;
	numSurfaces++;
	return 1;
}

/* ioUnregisterSurface:
	Unregister the surface with the given handle.
	Returns true if successful, false otherwise. */
EXPORT(long) ioUnregisterSurface(long surfaceID)
{
	SqueakSurface *surface;
	if(surfaceID < 0 || surfaceID >= maxSurfaces) return 0;
	surface = surfaceArray + surfaceID;
	if(surface->dispatch == NULL) return 0;
	surface->handle = 0;
	surface->dispatch = NULL;
	numSurfaces--;
	return 1;
}

/* ioFindSurface:
	Find the surface with the given ID, and, optionally,
	the given set of surface functions. The registered handle
	is returned in surfaceHandle. Return true if successful
	(e.g., the surface has been found), false otherwise. */
EXPORT(long) ioFindSurface(long surfaceID, sqSurfaceDispatch *fn, long *surfaceHandle)
{
	SqueakSurface *surface;
	if(surfaceID < 0 || surfaceID >= maxSurfaces) return 0;
	surface = surfaceArray + surfaceID;
	if(surface->dispatch == NULL) return 0;
	if(fn && fn != surface->dispatch) return 0;
	*surfaceHandle = surface->handle;
	return 1;
}

EXPORT(long) setInterpreter(struct VirtualMachine* anInterpreter) {
    long ok;

	interpreterProxy = anInterpreter;
	ok = interpreterProxy->majorVersion() == VM_PROXY_MAJOR;
	if (ok == 0) {
		return 0;
	}
	ok = interpreterProxy->minorVersion() >= VM_PROXY_MINOR;
	return ok;
}
EXPORT(const char*) getModuleName(void) {
	return moduleName;
}

EXPORT(long) initialiseModule() {
	surfaceArray = NULL;
	numSurfaces = 0;
	maxSurfaces = 0;
	return 1;
}

EXPORT(long) shutdownModule() {
	/* This module can only be shut down if no surfaces are registered */
	if(numSurfaces != 0) return 0;
	free(surfaceArray);
	return 1;
}

#ifdef SQUEAK_BUILTIN_PLUGIN
void* SurfacePlugin_exports[][3] = {
  {"SurfacePlugin", "setInterpreter", (void*)setInterpreter},
  {"SurfacePlugin", "getModuleName", (void*)getModuleName},
  {"SurfacePlugin", "initialiseModule", (void*)initialiseModule},
  {"SurfacePlugin", "shutdownModule", (void*)shutdownModule},
  {"SurfacePlugin", "ioGetSurfaceFormat", (void*)ioGetSurfaceFormat},
  {"SurfacePlugin", "ioLockSurface", (void*)ioLockSurface},
  {"SurfacePlugin", "ioUnlockSurface", (void*)ioUnlockSurface},
  {"SurfacePlugin", "ioShowSurface", (void*)ioShowSurface},
  {"SurfacePlugin", "ioRegisterSurface", (void*)ioRegisterSurface},
  {"SurfacePlugin", "ioUnregisterSurface", (void*)ioUnregisterSurface},
  {"SurfacePlugin", "ioFindSurface", (void*)ioFindSurface},
  {NULL, NULL, NULL}
};
#endif /* ifdef SQ_BUILTIN_PLUGIN */
