/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 * 
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 * 
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

#include <Types.h>
#include <Timer.h>
#include <Files.h>
#include <Errors.h>
#include <Folders.h>
#include <Events.h>
#include <Processes.h>
#include <TextUtils.h>
#include <MixedMode.h>
#include <LowMem.h>

#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stat.h>
#include <stdarg.h>
#include <unix.h>

#include "MacErrorHandling.h"

#include "primpl.h"
#include "prgc.h"
#include "MacMemAllocator.h"

enum {
	kGarbageCollectionEmergencyFundSize = 16 * 1024,
	kGarbageCollectionMemTightFundSize	= 48 * 1024
};

enum {
	uppExitToShellProcInfo 				= kPascalStackBased,
	uppStackSpaceProcInfo				= kRegisterBased 
										  | RESULT_SIZE(SIZE_CODE(sizeof(long)))
										  | REGISTER_RESULT_LOCATION(kRegisterD0)
		 								  | REGISTER_ROUTINE_PARAMETER(1, kRegisterD1, SIZE_CODE(sizeof(UInt16)))
};


#define UNIMPLEMENTED_ROUTINE			\
	DebugStr("\pNot Implemented Yet");	\
	return 0;

//
// Local routines
//
void PStrFromCStr(const char *, Str255);
unsigned char GarbageCollectorCacheFlusher(PRUint32 size);

extern PRThread *gPrimaryThread;

pascal void ExitToShellPatch(void);

UniversalProcPtr	gExitToShellPatchCallThru = NULL;
#if GENERATINGCFM
RoutineDescriptor 	gExitToShellPatchRD = BUILD_ROUTINE_DESCRIPTOR(uppExitToShellProcInfo, &ExitToShellPatch);
#else
#define gExitToShellPatchRD ExitToShellPatch
#endif

UniversalProcPtr	gStackSpacePatchCallThru = NULL;
#if GENERATINGCFM
pascal long StackSpacePatch(UInt16);
RoutineDescriptor 	StackSpacePatchRD = BUILD_ROUTINE_DESCRIPTOR(uppStackSpaceProcInfo, &StackSpacePatch);
#else
pascal long StackSpacePatch();
asm pascal long StackSpacePatchGlue();
#endif


//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark CREATING MACINTOSH THREAD STACKS


#if !GENERATINGCFM

asm long pascal StackSpacePatchCallThruGlue(long theAddress)
{
	move.l	4(sp), a0
	jsr		(a0)
	move.l	(sp)+, a0
	add		#0x8, sp 
	move.l	d0, -(sp)
	jmp		(a0)
}

asm pascal long StackSpacePatchGlue()
{

	//	Check out LocalA5.  If it is zero, then
	//	it is our first time through, and we should
	//	store away our A5.  If not, then we are
	//	a real time manager callback, so we should
	//	store away A5, set up our local a5, jsr
	//	to our callback, and then restore the
	//	previous A5.
	
	lea			LocalA5, a0
	move.l		(a0), d0
	cmpi.l		#0, d0
	bne			DoStackSpace
	
	move.l		a5, (a0)
	rts

DoStackSpace:
	
	//	Save A5, restore our local A5
	
	move.l		a5, -(sp)
	move.l		d0, a5 
	
	//	Jump to our C routine
	
	clr.l		-(sp)
	jsr 		StackSpacePatch
	move.l		(sp)+, d0
	
	//	Restore the previous A5
	
	move.l		(sp)+, a5

	rts

LocalA5:

	dc.l		0
	
}

#endif

#if GENERATINGCFM
pascal long StackSpacePatch(UInt16 trapNo)
#else
pascal long StackSpacePatch()
#endif
{
	char		tos;
	PRThread	*thisThread;
	
	thisThread = PR_CurrentThread();
	
	//	If we are the primary thread, then call through to the
	//	good ol' fashion stack space implementation.  Otherwise,
	//	compute it by hand.
	if ((thisThread == gPrimaryThread) || 	
		(&tos < thisThread->stack->stackBottom) || 
		(&tos > thisThread->stack->stackTop)) {
#if GENERATINGCFM
		return CallOSTrapUniversalProc(gStackSpacePatchCallThru, uppStackSpaceProcInfo, trapNo);
#else
		return StackSpacePatchCallThruGlue((long)gStackSpacePatchCallThru);
#endif
	}
	else {
		return &tos - thisThread->stack->stackBottom;
	}
}


//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark ENVIRONMENT VARIABLES


typedef struct EnvVariable EnvVariable;

struct EnvVariable {
	char 			*variable;
	char			*value;
	EnvVariable		*next;
};

EnvVariable		*gEnvironmentVariables = NULL;

char *_MD_GetEnv(const char *name)
{
	EnvVariable 	*currentVariable = gEnvironmentVariables;

	while (currentVariable) {
		if (!strcmp(currentVariable->variable, name))
			return currentVariable->value;
			
		currentVariable = currentVariable->next;
	}

	return NULL;
}

PR_IMPLEMENT(int) 
_MD_PutEnv(const char *string)
{
	EnvVariable 	*currentVariable = gEnvironmentVariables;
	char			*variableCopy,
				    *value,
					*current;
					
	variableCopy = strdup(string);
	PR_ASSERT(variableCopy != NULL);

	current = variableCopy;
	while (*current != '=')
		current++;

	*current = 0;
	current++;

	value = current;

	while (currentVariable) {
		if (!strcmp(currentVariable->variable, variableCopy))
			break;
		
		currentVariable = currentVariable->next;
	}

	if (currentVariable == NULL) {
		currentVariable = PR_NEW(EnvVariable);
		
		if (currentVariable == NULL) {
			PR_DELETE(variableCopy);
			return PR_FALSE;
		}
		
		currentVariable->variable = strdup(variableCopy);
		currentVariable->value = strdup(value);
		currentVariable->next = gEnvironmentVariables;
		gEnvironmentVariables = currentVariable;
	}
	
	else {
		PR_DELETE(currentVariable->value);
		currentVariable->value = strdup(current);

		/* This is a temporary hack.  Working on a real fix, remove this when done. */
		/* OK, there are two ways to access the  */
		/* library path, getenv() and PR_GetLibraryPath().  Take a look at PR_GetLibraryPath(). */
		/* You'll see that we keep the path in a global which is intialized at startup from */
		/* a call to getenv().  From then on, they have nothing in common. */
		/* We need to keep them in synch.  */
		if (strcmp(currentVariable->variable, "LD_LIBRARY_PATH") == 0)
			PR_SetLibraryPath(currentVariable->value);
	}
	
	PR_DELETE(variableCopy);
	return PR_TRUE;
}



//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark MISCELLANEOUS

PRWord *_MD_HomeGCRegisters(PRThread *t, int isCurrent, int *np)
{
    if (isCurrent) {
	(void) setjmp(t->md.jb);
    }
    *np = sizeof(t->md.jb) / sizeof(PRUint32);
    return (PRWord*) (t->md.jb);
}

void _MD_GetRegisters(PRUint32 *to)
{
  (void) setjmp((void*) to);
}

void _MD_EarlyInit()
{
	Handle		environmentVariables;

#if !defined(MAC_NSPR_STANDALONE)
	// MacintoshInitializeMemory();  Moved to mdmacmem.c: AllocateRawMemory(Size blockSize)
#else
	MacintoshInitializeMemory();
#endif
	MacintoshInitializeTime();
	
	//	Install resource-controlled environment variables.
	
	environmentVariables = GetResource('Envi', 128);
	if (environmentVariables != NULL) {
	
		Size 	resourceSize;
		char	*currentPutEnvString = (char *)*environmentVariables,
				*currentScanChar = currentPutEnvString;
				
		resourceSize = GetHandleSize(environmentVariables);			
		DetachResource(environmentVariables);
		HLock(environmentVariables);
		
		while (resourceSize--) {
		
			if ((*currentScanChar == '\n') || (*currentScanChar == '\r')) {
				*currentScanChar = 0;
				_MD_PutEnv (currentPutEnvString);
				currentPutEnvString = currentScanChar + 1;
			}
		
			currentScanChar++;
		
		}
		
		DisposeHandle(environmentVariables);

	}

#ifdef PR_INTERNAL_LOGGING
	_MD_PutEnv ("NSPR_LOG_MODULES=clock:6,cmon:6,io:6,mon:6,linker:6,cvar:6,sched:6,thread:6");
#endif
	
#if GENERATINGCFM
	gStackSpacePatchCallThru = GetOSTrapAddress(0x0065);
	SetOSTrapAddress((UniversalProcPtr)&StackSpacePatchRD, 0x0065);
	{
		long foo;
		foo = StackSpace();
	}
#else
	gStackSpacePatchCallThru = GetOSTrapAddress(0x0065);
	SetOSTrapAddress((UniversalProcPtr)&StackSpacePatchGlue, 0x0065);
	StackSpace();
#endif

	//	THIS IS VERY IMPORTANT.  Install our ExitToShell patch.  
	//	This allows us to deactivate our Time Mananger task even
	//	if we are not totally gracefully exited.  If this is not
	//	done then we will randomly crash at later times when the
	//	task is called after the app heap is gone.
	
	gExitToShellPatchCallThru = GetToolboxTrapAddress(0x01F4);
	SetToolboxTrapAddress((UniversalProcPtr)&gExitToShellPatchRD, 0x01F4);

}

void _MD_FinalInit()
{
	_MD_InitNetAccess();
}

void PR_InitMemory(void) {
#ifndef NSPR_AS_SHARED_LIB
	//	Needed for Mac browsers without Java.  We donÕt want them calling PR_INIT, since it
	//	brings in all of the thread support.  But we do need to allow them to initialize
	//	the NSPR memory package.
	//	This should go away when all clients of the NSPR want threads AND memory.
	MacintoshInitializeMemory();
#endif
}

//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark TERMINATION

typedef pascal void (* ExitToShellProc)(void);  

//	THIS IS *** VERY *** IMPORTANT... our ExitToShell patch.
//	This allows us to deactivate our Time Mananger task even
//	if we are not totally gracefully exited.  If this is not
//	done then we will randomly crash at later times when the
//	task is called after the app heap is gone.

extern TMTask		gTimeManagerTaskElem;

pascal void ExitToShellPatch(void)
{
	_MD_StopInterrupts();	

	CloseOpenTransport();	

#if GENERATINGCFM
	CallUniversalProc(gExitToShellPatchCallThru, uppExitToShellProcInfo);
#else 
	{
		ExitToShellProc	*exitProc = (ExitToShellProc *)&gExitToShellPatchCallThru;
		(*exitProc)();
	}
#endif

}



//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark STRING OPERATIONS

#if !defined(MAC_NSPR_STANDALONE)

//	PStrFromCStr converts the source C string to a destination
//	pascal string as it copies. The dest string will
//	be truncated to fit into an Str255 if necessary.
//  If the C String pointer is NULL, the pascal string's length is set to zero
//
extern void 
PStrFromCStr(const char* src, Str255 dst)
{
	short 	length  = 0;
	
	// handle case of overlapping strings
	if ( (void*)src == (void*)dst )
	{
		unsigned char*		curdst = &dst[1];
		unsigned char		thisChar;
				
		thisChar = *(const unsigned char*)src++;
		while ( thisChar != '\0' ) 
		{
			unsigned char	nextChar;
			
			// use nextChar so we don't overwrite what we are about to read
			nextChar = *(const unsigned char*)src++;
			*curdst++ = thisChar;
			thisChar = nextChar;
			
			if ( ++length >= 255 )
				break;
		}
	}
	else if ( src != NULL )
	{
		unsigned char*		curdst = &dst[1];
		short 				overflow = 255;		// count down so test it loop is faster
		register char		temp;
	
		// Can't do the K&R C thing of Òwhile (*s++ = *t++)Ó because it will copy trailing zero
		// which might overrun pascal buffer.  Instead we use a temp variable.
		while ( (temp = *src++) != 0 ) 
		{
			*(char*)curdst++ = temp;
				
			if ( --overflow <= 0 )
				break;
		}
		length = 255 - overflow;
	}
	dst[0] = length;
}


void CStrFromPStr(ConstStr255Param pString, char **cString)
{
	// Allocates a cString and copies a Pascal string into it.
	unsigned int	len;
	
	len = pString[0];
	*cString = malloc(len+1);
	
	if (*cString != NULL) {
		strncpy(*cString, (char *)&pString[1], len);
		(*cString)[len] = NULL;
	}
}


void dprintf(const char *format, ...)
{
#if DEBUG
    va_list ap;
	Str255 buffer;
	
	va_start(ap, format);
	buffer[0] = PR_vsnprintf((char *)buffer + 1, sizeof(buffer) - 1, format, ap);
	va_end(ap);
	
	DebugStr(buffer);
#endif /* DEBUG */
}

#else

void debugstr(const char *debuggerMsg)
{
	Str255		pStr;
	
	PStrFromCStr(debuggerMsg, pStr);
	DebugStr(pStr);
}


char *strdup(const char *source)
{
	char 	*newAllocation;
	size_t	stringLength;

#ifdef DEBUG
	PR_ASSERT(source);
#endif
	
	stringLength = strlen(source) + 1;
	
	newAllocation = (char *)PR_MALLOC(stringLength);
	if (newAllocation == NULL)
		return NULL;
	BlockMoveData(source, newAllocation, stringLength);
	return newAllocation;
}

//	PStrFromCStr converts the source C string to a destination
//	pascal string as it copies. The dest string will
//	be truncated to fit into an Str255 if necessary.
//  If the C String pointer is NULL, the pascal string's length is set to zero
//
void PStrFromCStr(const char* src, Str255 dst)
{
	short 	length  = 0;
	
	// handle case of overlapping strings
	if ( (void*)src == (void*)dst )
	{
		unsigned char*		curdst = &dst[1];
		unsigned char		thisChar;
				
		thisChar = *(const unsigned char*)src++;
		while ( thisChar != '\0' ) 
		{
			unsigned char	nextChar;
			
			// use nextChar so we don't overwrite what we are about to read
			nextChar = *(const unsigned char*)src++;
			*curdst++ = thisChar;
			thisChar = nextChar;
			
			if ( ++length >= 255 )
				break;
		}
	}
	else if ( src != NULL )
	{
		unsigned char*		curdst = &dst[1];
		short 				overflow = 255;		// count down so test it loop is faster
		register char		temp;
	
		// Can't do the K&R C thing of Òwhile (*s++ = *t++)Ó because it will copy trailing zero
		// which might overrun pascal buffer.  Instead we use a temp variable.
		while ( (temp = *src++) != 0 ) 
		{
			*(char*)curdst++ = temp;
				
			if ( --overflow <= 0 )
				break;
		}
		length = 255 - overflow;
	}
	dst[0] = length;
}


void CStrFromPStr(ConstStr255Param pString, char **cString)
{
	// Allocates a cString and copies a Pascal string into it.
	unsigned int	len;
	
	len = pString[0];
	*cString = PR_MALLOC(len+1);
	
	if (*cString != NULL) {
		strncpy(*cString, (char *)&pString[1], len);
		(*cString)[len] = NULL;
	}
}


size_t strlen(const char *source)
{
	size_t currentLength = 0;
	
	if (source == NULL)
		return currentLength;
			
	while (*source++ != '\0')
		currentLength++;
		
	return currentLength;
}

int strcmpcore(const char *str1, const char *str2, int caseSensitive)
{
	char 	currentChar1, currentChar2;

	while (1) {
	
		currentChar1 = *str1;
		currentChar2 = *str2;
		
		if (!caseSensitive) {
			
			if ((currentChar1 >= 'a') && (currentChar1 <= 'z'))
				currentChar1 += ('A' - 'a');
		
			if ((currentChar2 >= 'a') && (currentChar2 <= 'z'))
				currentChar2 += ('A' - 'a');
		
		}
	
		if (currentChar1 == '\0')
			break;
	
		if (currentChar1 != currentChar2)
			return currentChar1 - currentChar2;
			
		str1++;
		str2++;
	
	}
	
	return currentChar1 - currentChar2;
}

int strcmp(const char *str1, const char *str2)
{
	return strcmpcore(str1, str2, true);
}

int strcasecmp(const char *str1, const char *str2)
{
	return strcmpcore(str1, str2, false);
}

#if GENERATING68K
asm void *memset(void *target, int pattern, size_t length)										// Legal asm qualifier
{
				MOVEA.L		4(SP),A0			// target -> A0
				MOVE.W		10(SP),D0			// pattern -> D0, length -> D1
				MOVE.L		12(SP),D1
				CMPI.L		#30,D1
				BLT			end
				
				// Fill D0 with the pattern
				MOVEQ		#0,D2				// Clear D2, weÕll use it as scratch
				MOVE.B		D0,D2				// Fill the bottom byte
				LSL.W		#8,D0				//  
				OR.W		D0,D2
				MOVE.L		D2,D0
				SWAP		D2
				OR.L		D2,D0
				
				// Are we odd aligned?
				MOVE.L		A0,D2				// Copy target address into scratch
				LSR.B		#1,D2				// Sets C bit
				BCC.S		checkAlign2			// If even, check for 16-byte alignment
				MOVE.B		D0,(A0)+			// Take care of odd byte
				SUBQ.L		#1,D1				// Update length
				
				// Are we odd 16-byte word alligned?
checkAlign2:	LSR.B		#1,D2				// Still set from last check
				BCC			totallyAligned
				MOVE.W		D0,(A0)+
				SUBQ.L		#2,D1
			
totallyAligned:	MOVE.L		D1,D2
				LSR.L		#4,D2
				SUBQ.L		#1,D2
copyHunk:		MOVE.L		D0,(A0)+
				MOVE.L		D0,(A0)+
				MOVE.L		D0,(A0)+
				MOVE.L		D0,(A0)+
				SUBQ.L		#1,D2
				BCC			copyHunk
				ANDI.W		#15,D1				// Check done?
				BRA			end
dribble:		MOVE.B		D0,(A0)+
end:			DBF			D1,dribble
				MOVE.L		4(SP),D0			// Return the target
				RTS								
}
#endif

void *memcpy(void *to, const void *from, size_t size)
{
	if (size != 0) {
#if DEBUG
		if ((UInt32)to < 0x1000)
			DebugStr("\pmemcpy has illegal to argument");
		if ((UInt32)from < 0x1000)
			DebugStr("\pmemcpy has illegal from argument");
#endif
		BlockMoveData(from, to, size);
	}
	return to;
}

void dprintf(const char *format, ...)
{
    va_list ap;
	char	*buffer;
	
	va_start(ap, format);
	buffer = (char *)PR_vsmprintf(format, ap);
	va_end(ap);
	
	debugstr(buffer);
	PR_DELETE(buffer);
}

void
exit(int result)
{
#pragma unused (result)

		ExitToShell();
}

void abort(void)
{
	exit(-1);
}

#endif

//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark FLUSHING THE GARBAGE COLLECTOR

#if !defined(MAC_NSPR_STANDALONE)

unsigned char GarbageCollectorCacheFlusher(PRUint32)
{

    PRIntn is;

	UInt32		oldPriority;

	//	If java wasn't completely initialized, then bail 
	//	harmlessly.
	
	if (PR_GetGCInfo()->lock == NULL)
		return false;

#if DEBUG
	if (_MD_GET_INTSOFF() == 1)
		DebugStr("\pGarbageCollectorCacheFlusher at interrupt time!");
#endif

	//	The synchronization here is very tricky.  We really
	//	don't want any other threads to run while we are 
	//	cleaning up the gc heap... they could call malloc,
	//	and then we would be in trouble in a big way.  So,
	//	we jack up our priority and that of the finalizer
	//	so that we won't yield to other threads.
	//	dkc 5/17/96

	oldPriority = PR_GetThreadPriority(PR_GetCurrentThread());
	_PR_INTSOFF(is);
	_PR_SetThreadPriority(PR_GetCurrentThread(), (PRThreadPriority)30);
	_PR_INTSON(is);

	//	Garbage collect twice.  This will finalize any
	//	dangling AWT resources (images, components), and
	//	then free up their GC space, too.
	//	dkc 2/15/96
	//  interrupts must be on during PR_GC
	
	PR_GC();
	
	//	By setting the finalizer priority to 31, then we 
	//	ensure it will run before us.  When it finishes
	//	its list of finalizations, it returns to us
	//	for the second garbage collection.
	
	PR_Yield();

	PR_GC();
	
	//	Restore our old priorities.
	
	_PR_INTSOFF(is);
	_PR_SetThreadPriority(PR_GetCurrentThread(), (PRThreadPriority)oldPriority);
	_PR_INTSON(is);

	return false;
}

#endif

//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark MISCELLANEOUS-HACKS


//
//		***** HACK  FIX THESE ****
//
extern long _MD_GetOSName(char *buf, long count)
{
	long	len;
	
	len = PR_snprintf(buf, count, "Mac OS");
	
	return 0;
}

extern long _MD_GetOSVersion(char *buf, long count)
{
	long	len;
	
	len = PR_snprintf(buf, count, "7.5");

	return 0;
}

extern long _MD_GetArchitecture(char *buf, long count)
{
	long	len;
	
#if defined(GENERATINGPOWERPC) && GENERATINGPOWERPC	
	len = PR_snprintf(buf, count, "PowerPC");
#else
	len = PR_snprintf(buf, count, "Motorola68k");
#endif

	return 0;
}
