/* sqEmbedded.c

	// based on sqMacMinimal.c
	// conversion done by Paul D. Fernhout http://www.kurtz-fernhout.com

	This file includes the minimal support code to build an embedded Squeak system.
	Many primitives are "stubbed-out", meaning that if they are invoked from the
	image they will return a "primitive failed" error. Among the stubbed out primitives
	are those that support sound input and output, MIDI ports, networking,
	joystick, and file directory operations. The basic file read/write operations are
	also stubbed out. The image is read from an array in memory.
	I/O is done through serial primitives.

  The only parts you should need to change on your platform are those related to
	console operations (see the serial IO).

*** Linking ***

	To build a Macintosh VM using this file, link together:

		interp.c		-- automatically generated interpreter file
		sqFilePrims.c	-- file primitives (stubbed out)
		sqEmbedded.c	-- this file
		sqMiscPrims.c	-- automatically generated primitives
		ImageFileInMemory.c -- the image file (prepared from altered mini.image).
*/

#include "sq.h"

#define STUBBED_OUT { success(false); }

extern unsigned char ImageFileInMemory[];

/*** Variables -- Imported from Virtual Machine ***/
extern unsigned char *memory;

/*** Variables -- image and path names ***/
#define IMAGE_NAME_SIZE 300
char imageName[IMAGE_NAME_SIZE + 1];  /* full path to image */

#define SHORTIMAGE_NAME_SIZE 100
char shortImageName[SHORTIMAGE_NAME_SIZE + 1];  /* just the image file name */

#define VMPATH_SIZE 300
char vmPath[VMPATH_SIZE + 1];  /* full path to interpreter's directory */

/*** Functions ***/
char * GetAttributeString(int id);
int  HandleEvents(void);

/*** VM Home Directory Path ***/

int vmPathSize(void) {
	return strlen(vmPath);
}

int vmPathGetLength(int sqVMPathIndex, int length) {
	char *stVMPath = (char *) sqVMPathIndex;
	int count, i;

	count = strlen(vmPath);
	count = (length < count) ? length : count;

	/* copy the file name into the Squeak string */
	for (i = 0; i < count; i++) {
		stVMPath[i] = vmPath[i];
	}
	return count;
}

/*** Mac-related Functions ***/

int HandleEvents(void) {

	/*EventRecord		theEvent;
	int				ok;

	SystemTask();
	ok = GetNextEvent(everyEvent, &theEvent);
	if (ok) {
		switch (theEvent.what) {
			case mouseDown:
				HandleMouseDown(&theEvent);
				return false;
			break;

			case mouseUp:
				recordModifierButtons(&theEvent);
				return false;
			break;

			case keyDown:
			case autoKey:
				if ((theEvent.modifiers & cmdKey) != 0) {
					AdjustMenus();
					HandleMenu(MenuKey(theEvent.message & charCodeMask));
				}
				recordModifierButtons(&theEvent);
				recordKeystroke(&theEvent);
			break;

			case updateEvt:
				BeginUpdate(stWindow);
				fullDisplayUpdate();  /* this makes VM call ioShowDisplay *//*
				EndUpdate(stWindow);
			break;

			case activateEvt:
				if (theEvent.modifiers & activeFlag) {
					windowActive = true;
				} else {
					GetMouse(&savedMousePosition);
					windowActive = false;
				}
				InvalRect(&stWindow->portRect);
			break;
		}
	}
	return ok; */
	return 0;
}


/*** I/O Primitives ***/

int ioBeep(void) {
	// pdf -- SysBeep(1000);
	printf("\nBeep\n");
	return 0;
}

int ioExit(void) {
	exit(0);
}

int ioForceDisplayUpdate(void) {
	/* do nothing on a Mac */
}

int ioFormPrint(int bitsAddr, int width, int height, int depth, double hScale, double vScale, int landscapeFlag) {
	/* experimental: print a form with the given bitmap, width, height, and depth at
	   the given horizontal and vertical scales in the given orientation */

	success(false);  /* stubbed out */
}

int ioGetButtonState(void) {
	ioProcessEvents();  /* process all pending events */
	return 0;
}

int lastkey = -1;


int ioGetKeystroke(void) {
//	int keystate;
//	int temp;

//	ioProcessEvents();  /* process all pending events */
//	if (lastkey >= 0) 
//		{
//		temp = lastkey;
//		lastkey = -1;
//		return temp;
//		}
//	if (!_kbhit())
//		return -1;  /* keystroke buffer is empty */
//	return _getche();
	return -1;  /* keystroke buffer is empty */
}

int ioPeekKeystroke(void) {
//	if (lastkey >= 0) return lastkey;
//	if (!_kbhit())
		return -1;  /* keystroke buffer is empty */
//	return lastkey = _getche();
}

int ioMicroMSecs(void) {
	/* millisecond clock based on microsecond timer (about 60 times slower than clock()!!) */
	/* Note: This function and ioMSecs() both return a time in milliseconds. The difference
	   is that ioMicroMSecs() is called only when precise millisecond resolution is essential,
	   and thus it can use a more expensive timer than ioMSecs, which is called frequently.
	   However, later VM optimizations reduced the frequency of calls to ioMSecs to the point
	   where clock performance became less critical, and we also started to want millisecond-
	   resolution timers for real time applications such as music. Thus, on the Mac, we've
	   opted to use the microsecond clock for both ioMSecs() and ioMicroMSecs(). */
	time_t ltime;
	time( &ltime );
	return ltime * 1000;
}

int ioMSecs(void) {
	/* return a time in milliseconds for use in Delays and Time millisecondClockValue */
	/* Note: This was once a macro based on clock(); it now uses the microsecond clock for
	   greater resolution. See the comment in ioMicroMSecs(). */
	time_t ltime;
	time( &ltime );
	return ltime * 1000;
}

int ioMousePoint(void) {
	return 0;  /* x is high 16 bits; y is low 16 bits */
}

int ioProcessEvents(void) {
	/* Process Macintosh events, checking for the interrupt key. Return
	   true if the interrupt key was pressed. */
	int maxPollsPerSec = 30;
	static clock_t nextPollTick = 0;

	if (clock() > nextPollTick) {
		/* time to process events! */
		while (HandleEvents()) {
			/* process all pending events */
		}

		/* wait a while before trying again */
		nextPollTick = clock() + (CLOCKS_PER_SEC / maxPollsPerSec);
	}
	return 0;
}

int ioRelinquishProcessorForMicroseconds(int microSeconds) {
	/* This operation is platform dependent. On the Mac, it simply calls
	 * HandleEvents(), which gives other applications a chance to run.
	 */

	while (HandleEvents()) {
		/* process all pending events */
	}
	return microSeconds;
}

int ioScreenSize(void) {
	int w = 1000, h = 1000;
	return (w << 16) | (h & 0xFFFF);  /* w is high 16 bits; h is low 16 bits */
}

int ioSeconds(void) {
	time_t ltime;
	time( &ltime );
	return ltime;
}

int ioSetCursor(int cursorBitsIndex, int offsetX, int offsetY) {
	/* Old version; forward to new version. */
	// ioSetCursorWithMask(cursorBitsIndex, nil, offsetX, offsetY);
}

int ioSetCursorWithMask(int cursorBitsIndex, int cursorMaskIndex, int offsetX, int offsetY) {
	/* Set the 16x16 cursor bitmap. If cursorMaskIndex is nil, then make the mask the same as
	   the cursor bitmap. If not, then mask and cursor bits combined determine how cursor is
	   displayed:
			mask	cursor	effect
			 0		  0		transparent (underlying pixel shows through)
			 1		  1		opaque black
			 1		  0		opaque white
			 0		  1		invert the underlying pixel
	*/
}

int ioSetFullScreen(int fullScreen) {
}

int ioShowDisplay(
	int dispBitsIndex, int width, int height, int depth,
	int affectedL, int affectedR, int affectedT, int affectedB) {
}

/*** Image File Naming ***/

int imageNameSize(void) {
	return strlen(imageName);
}

int imageNameGetLength(int sqImageNameIndex, int length) {
	char *sqImageName = (char *) sqImageNameIndex;
	int count, i;

	count = strlen(imageName);
	count = (length < count) ? length : count;

	/* copy the file name into the Squeak string */
	for (i = 0; i < count; i++) {
		sqImageName[i] = imageName[i];
	}
	return count;
}

int imageNamePutLength(int sqImageNameIndex, int length) {
	char *sqImageName = (char *) sqImageNameIndex;
	int count, i, ch, j;
	int lastColonIndex = -1;

	count = (IMAGE_NAME_SIZE < length) ? IMAGE_NAME_SIZE : length;

	/* copy the file name into a null-terminated C string */
	for (i = 0; i < count; i++) {
		ch = imageName[i] = sqImageName[i];
		if (ch == ':') {
			lastColonIndex = i;
		}
	}
	imageName[count] = 0;

	/* copy short image name into a null-terminated C string */
	for (i = lastColonIndex + 1, j = 0; i < count; i++, j++) {
		shortImageName[j] = imageName[i];
	}
	shortImageName[j] = 0;

	// SetWindowTitle(shortImageName);
	return count;
}

/*** Clipboard Support ***/

int clipboardReadIntoAt(int count, int byteArrayIndex, int startIndex) {
	return 0;
}

int clipboardSize(void) {
	return 0;
}

int clipboardWriteFromAt(int count, int byteArrayIndex, int startIndex) {
	return 0;
}

/*** Directory ***/

int dir_Delimitor(void) {
	return '/';
}

/*** System Attributes ***/

char * GetAttributeString(int id) {
	/* This is a hook for getting various status strings back from
	   the OS. In particular, it allows Squeak to be passed arguments
	   such as the name of a file to be processed. Command line options
	   are reported this way as well, on platforms that support them.
	*/

	// id #0 should return the full name of VM; for now it just returns its path
	if (id == 0) return vmPath;
	// id #1 should return imageName, but returns empty string in this release to
	// ease the transition (1.3x images otherwise try to read image as a document)
	if (id == 1) return "";  /* will be imageName */
	if (id == 2) return "";

	if (id == 1001) return "Embedded";
	if (id == 1002) return "Version 1";
	if (id == 1003) return "CPU";

	/* attribute undefined by this platform */
	success(false);
	return "";
}

int attributeSize(int id) {
	return strlen(GetAttributeString(id));
}

int getAttributeIntoLength(int id, int byteArrayIndex, int length) {
	char *srcPtr, *dstPtr, *end;
	int charsToMove;

	srcPtr = GetAttributeString(id);
	charsToMove = strlen(srcPtr);
	if (charsToMove > length) {
		charsToMove = length;
	}

	dstPtr = (char *) byteArrayIndex;
	end = srcPtr + charsToMove;
	while (srcPtr < end) {
		*dstPtr++ = *srcPtr++;
	}
	return charsToMove;
}

/*** Image File Operations ***/

long gImagePosition = 0;

void sqImageFileClose(sqImageFile f) {
	// do nothing
}

sqImageFile sqImageFileOpen(char *fileName, char *mode) {
  gImagePosition = 0;
	return 1;
}

int sqImageFilePosition(sqImageFile f) {
	return gImagePosition;
}

int sqImageFileRead(void *ptr, int elementSize, int count, sqImageFile f) {
	long int byteCount = elementSize * count;
	memcpy(ptr, ImageFileInMemory + gImagePosition, byteCount);
	gImagePosition += byteCount;
	return byteCount / elementSize;
}

void sqImageFileSeek(sqImageFile f, int pos) {
	gImagePosition = pos;
}

int sqImageFileWrite(void *ptr, int elementSize, int count, sqImageFile f) {
	long int byteCount = elementSize * count;
	// not for now
	// memcpy(ImageFileInMemory + gImagePosition, ptr, byteCount);
	gImagePosition += byteCount;
	return byteCount / elementSize;
}

/*** Directory Stubs ***/

int dir_Create(char *pathString, int pathStringLength)						STUBBED_OUT
int dir_Lookup(char *pathString, int pathStringLength, int index,
  char *name, int *nameLength, int *creationDate, int *modificationDate,
  int *isDirectory, int *sizeIfFile)										STUBBED_OUT
dir_SetMacFileTypeAndCreator(char *filename, int filenameSize,
  char *fType, char *fCreator)												{/* noop */}

/*** Joystick Stubs ***/

int joystickRead(int stickIndex)											STUBBED_OUT

/*** MIDI Stubs ***/

int sqMIDIClosePort(int portNum)											STUBBED_OUT
int sqMIDIGetClock(void)													STUBBED_OUT
int sqMIDIGetPortCount(void)												STUBBED_OUT
int sqMIDIGetPortDirectionality(int portNum)								STUBBED_OUT
int sqMIDIGetPortName(int portNum, int namePtr, int length)					STUBBED_OUT
int sqMIDIOpenPort(int portNum, int readSemaIndex, int interfaceClockRate)	STUBBED_OUT
int sqMIDIParameter(int whichParameter, int modify, int newValue)			STUBBED_OUT
int sqMIDIPortReadInto(int portNum, int count, int bufferPtr)				STUBBED_OUT
int sqMIDIPortWriteFromAt(int portNum, int count, int bufferPtr, int time)	STUBBED_OUT

/*** Networking Stubs ***/

int sqNetworkInit(int resolverSemaIndex)									STUBBED_OUT
void sqNetworkShutdown(void)												STUBBED_OUT
void sqResolverAbort(void)													STUBBED_OUT
void sqResolverAddrLookupResult(char *nameForAddress, int nameSize)			STUBBED_OUT
int sqResolverAddrLookupResultSize(void)									STUBBED_OUT
int sqResolverError(void)													STUBBED_OUT
int sqResolverLocalAddress(void)											STUBBED_OUT
int sqResolverNameLookupResult(void)										STUBBED_OUT
void sqResolverStartAddrLookup(int address)									STUBBED_OUT
void sqResolverStartNameLookup(char *hostName, int nameSize)				STUBBED_OUT
int sqResolverStatus(void)													STUBBED_OUT
void sqSocketAbortConnection(SocketPtr s)									STUBBED_OUT
void sqSocketCloseConnection(SocketPtr s)									STUBBED_OUT
int sqSocketConnectionStatus(SocketPtr s)									STUBBED_OUT
void sqSocketConnectToPort(SocketPtr s, int addr, int port)					STUBBED_OUT
void sqSocketCreateNetTypeSocketTypeRecvBytesSendBytesSemaID(
  SocketPtr s, int netType, int socketType,
  int recvBufSize, int sendBufSize, int semaIndex)							STUBBED_OUT
void sqSocketDestroy(SocketPtr s)											STUBBED_OUT
int sqSocketError(SocketPtr s)												STUBBED_OUT
void sqSocketListenOnPort(SocketPtr s, int port)							STUBBED_OUT
int sqSocketLocalAddress(SocketPtr s)										STUBBED_OUT
int sqSocketLocalPort(SocketPtr s)											STUBBED_OUT
int sqSocketReceiveDataAvailable(SocketPtr s)								STUBBED_OUT
int sqSocketReceiveDataBufCount(SocketPtr s, int buf, int bufSize)			STUBBED_OUT
int sqSocketRemoteAddress(SocketPtr s)										STUBBED_OUT
int sqSocketRemotePort(SocketPtr s)											STUBBED_OUT
int sqSocketSendDataBufCount(SocketPtr s, int buf, int bufSize)				STUBBED_OUT
int sqSocketSendDone(SocketPtr s)											STUBBED_OUT

/*** Profiling Stubs ***/

int clearProfile(void)														STUBBED_OUT
int dumpProfile(void)														STUBBED_OUT
int startProfiling(void)													STUBBED_OUT
int stopProfiling(void)														STUBBED_OUT

/*** Serial Port Functions Stubs ***/

int serialPortClose(int portNum) 
	{
	return 0;
	}

int serialPortOpen(int portNum, int baudRate, int stopBitsType,
		int parityType, int dataBits, int inFlowCtrl, int outFlowCtrl,
		int xOnChar, int xOffChar) 
	{
	return 0;
	}

int serialPortReadInto(int portNum, int count, int bufferPtr)	
	{
	if (!_kbhit()) return 0;
	*(char*)bufferPtr = (char)_getch();
	return 1;
	}

int serialPortWriteFrom(int portNum, int count, int bufferPtr)
	{
	char output[1024];
	if (count > 1023) count = 1023;
	memcpy(output, (char*)bufferPtr, count);
	output[count] = '\0';
  printf("%s", output);
	return count;
	}

/*** Sound Output Stubs ***/

int snd_AvailableSpace(void)												STUBBED_OUT
int snd_PlaySamplesFromAtLength(
  int frameCount, int arrayIndex, int startIndex)							STUBBED_OUT
int snd_InsertSamplesFromLeadTime(
  int frameCount, int srcBufPtr, int samplesOfLeadTime)						STUBBED_OUT
int snd_PlaySilence(void)													STUBBED_OUT
int snd_Start(int frameCount, int samplesPerSec, int stereo, int semaIndex)	STUBBED_OUT
int snd_Stop(void)															STUBBED_OUT

/*** Sound Input Stubs ***/

int snd_SetRecordLevel(int level)											STUBBED_OUT
int snd_StartRecording(int desiredSamplesPerSec, int stereo, int semaIndex)	STUBBED_OUT
int snd_StopRecording(void)													STUBBED_OUT
double snd_GetRecordingSampleRate(void)										STUBBED_OUT
int snd_RecordSamplesIntoAtLength(
  int buf, int startSliceIndex, int bufferSizeInBytes)						STUBBED_OUT

/*** Sound Synthesis Primitives Stubs ***/

int primFMSoundmixSampleCountintostartingAtleftVolrightVol(void)			STUBBED_OUT
int primLoopedSampledSoundmixSampleCountintostartingAtleftVolrightVol(void)	STUBBED_OUT
int primPluckedSoundmixSampleCountintostartingAtleftVolrightVol(void)		STUBBED_OUT
int primReverbSoundapplyReverbTostartingAtcount(void)						STUBBED_OUT
int primSampledSoundmixSampleCountintostartingAtleftVolrightVol(void)		STUBBED_OUT

/*** Old Sound Synthesis Primitives Stubs ***/

int primFMSoundmixSampleCountintostartingAtpan(void)						STUBBED_OUT
int primPluckedSoundmixSampleCountintostartingAtpan(void)					STUBBED_OUT
int primSampledSoundmixSampleCountintostartingAtpan(void)					STUBBED_OUT
int primWaveTableSoundmixSampleCountintostartingAtpan(void)					STUBBED_OUT

/*** Experimental Asynchronous File I/O ***/

int asyncFileClose(AsyncFile *f)											STUBBED_OUT
int asyncFileOpen(
  AsyncFile *f, int fileNamePtr, int fileNameSize,
  int writeFlag, int semaIndex)												STUBBED_OUT
int asyncFileRecordSize()													STUBBED_OUT
int asyncFileReadResult(AsyncFile *f, int bufferPtr, int bufferSize)		STUBBED_OUT
int asyncFileReadStart(AsyncFile *f, int fPosition, int count)				STUBBED_OUT
int asyncFileWriteResult(AsyncFile *f)										STUBBED_OUT
int asyncFileWriteStart(
  AsyncFile *f, int fPosition, int bufferPtr, int bufferSize)				STUBBED_OUT

/*** Main ***/

void main(void) {
	sqImageFile f;
	int reservedMemory, availableMemory;

	/* check the interpreter's size assumptions for basic data types */
	if (sizeof(int) != 4) {
		error("This C compiler's integers are not 32 bits.");
	}
	if (sizeof(double) != 8) {
		error("This C compiler's floats are not 64 bits.");
	}
	if (sizeof(time_t) != 4) {
		error("This C compiler's time_t's are not 32 bits.");
	}

	sqFileInit();

	imageName[0] = shortImageName[0] = vmPath[0] = 0;
	strcpy(imageName, "squeak.image");
	strcpy(shortImageName, "squeak.image");

	/* compute the desired memory allocation */
	reservedMemory = 150000;
	availableMemory = 1000000 - reservedMemory;
	/******
	  Note: This is platform-specific. On the Mac, the user specifies the desired
	    memory partition for each application using the Finder's Get Info command.
	    MaxBlock() returns the amount of memory in the partition minus space for
	    the code segment and other resources. On other platforms, the desired heap
	    size would be specified in other ways (e.g, via a command line argument).
	    The maximum size of the object heap is fixed at at startup. If you run low
	    on space, you must save the image and restart with more memory.

	  Note: Some memory must be reserved for Mac toolbox calls, sound buffers, etc.
	    A 30K reserve is too little. 40K allows Squeal to run but crashes if the
	    console is opened. 50K allows the console to be opened (with and w/o the
	    profiler). I added another 30K to provide for sound buffers and reliability.
	    (Note: Later discovered that sound output failed if SoundManager was not
	    preloaded unless there is about 100K reserved. Added 30K to that.)
	******/

	/* uncomment the following to open the C transcript window for debugging: */
	//printf("Move this window, then hit CR\n"); getchar();

	/* read the image file and allocate memory for Squeak heap */
	f = sqImageFileOpen(imageName, "rb");
	if (f == NULL) {
		/* give a Mac-specific error message if image file is not found */
		printf("Could not open the Squeak image file from memory image.\n");
		printf("This could be from a programming error or a corrupt EXE.\n");
		printf("Press the return key to exit.\n");
		getchar();
		printf("Aborting...\n");
		ioExit();
	}
	readImageFromFileHeapSize(f, availableMemory);
	sqImageFileClose(f);

	/* run Squeak */
	interpret();
}
